diff --git a/lib/cmd-build.js b/lib/cmd-build.js index 003a16e..2abff85 100644 --- a/lib/cmd-build.js +++ b/lib/cmd-build.js @@ -4,7 +4,9 @@ const fs = require('fs') const path = require('path') const { pipeline } = require('stream/promises') -const { FilemapWithOutput } = require('./core-filemap-with-output') +const { ListItem } = require('./core-data-structures') +const { Filemap } = require('./core-filemap-build') +const { Urlmap } = require('./core-urlmap') const { ProcessingFactory } = require('./core-processing-factory') const { ProcessingWithCache } = require('./core-processing-with-cache') const { copy, mkdir } = require('./util-fs') @@ -42,10 +44,12 @@ async function build (options, logger) { const visited = new Set() const plugins = loadCustomPlugins(settings.wave?.plugins, packageScope).concat(corePlugins) - const filemap = new FilemapWithOutput({ packageScope, wd }) + const staticDirs = ListItem.from(publicDirectory) + const filemap = new Filemap({ packageScope, staticDirs, wd }) + const urlmap = new Urlmap({ packageScope, staticDirs }) const cmd = 'build' - const factoryOptions = { plugins, filemap, cmd, env, settings, logger } + const factoryOptions = { plugins, filemap, urlmap, cmd, env, settings, logger } if (options.cache) factoryOptions.product = ProcessingWithCache const processingFactory = new ProcessingFactory(factoryOptions) @@ -56,7 +60,7 @@ async function build (options, logger) { const cwd = process.cwd() for (const input of options._) { - const filepath = path.resolve(cwd, input) + const filepath = './' + path.relative(wd, path.resolve(cwd, input)) const processing = processingFactory.resolve(filepath, fakeReferer) enqueue(processing) } @@ -68,9 +72,9 @@ async function build (options, logger) { function enqueue (processing) { if (processing == null) return - const publicUrl = processing.publicUrl() - if (visited.has(publicUrl)) return - visited.add(publicUrl) + const id = processing.id + if (visited.has(id)) return + visited.add(id) q.push(processing) } diff --git a/lib/cmd-serve.js b/lib/cmd-serve.js index 5751c6c..30f0247 100644 --- a/lib/cmd-serve.js +++ b/lib/cmd-serve.js @@ -3,12 +3,12 @@ const chokidar = require('chokidar') const fs = require('fs') const path = require('path') -const { finished, pipeline } = require('stream') -const { FilemapWithAliases } = require('./core-filemap-with-aliases') +const { Filemap } = require('./core-filemap-serve') +const { ListItem } = require('./core-data-structures') const { ProcessingFactory } = require('./core-processing-factory') const { ProcessingWithCache } = require('./core-processing-with-cache') -const { UrlmapWithAliases } = require('./core-urlmap-with-aliases') +const { Urlmap } = require('./core-urlmap') const { abc } = require('./util-hash') const { broadcast, createSocket } = require('./util-socket') const { contentType } = require('./util-mime-types') @@ -18,6 +18,7 @@ const { debounce } = require('./util-scheduler') const { isFile } = require('./util-fs') const { loadCustomPlugins, loadPackageSettings } = require('./core-settings') const { nodeModules } = require('./core-resolve') +const { pipe } = require('./util-stream') const { scope } = require('./util-scope') const corePlugins = [ @@ -37,7 +38,7 @@ async function serve (options, logger) { const packageScope = await scope(wd) ?? wd const settings = await loadPackageSettings(packageScope) const publicDirectory = path.join(packageScope, 'public') - const innerPublicDirectory = path.resolve(__dirname, '../public') + const internalDirectory = path.resolve(__dirname, '../public') logger.debug('environment variables %o', env) logger.debug('package settings %o', settings) @@ -52,14 +53,15 @@ async function serve (options, logger) { const modulesDirs = nodeModules(path.dirname(packageScope)) if (modulesDirs != null) { for (const directory of modulesDirs) { - const alias = `/~nm${abc(aliases.size)}` + const alias = `~nm${abc(aliases.size)}` aliases.set(alias, directory) } } const plugins = loadCustomPlugins(settings.wave?.plugins, packageScope).concat(corePlugins) - const filemap = new FilemapWithAliases({ aliases, packageScope, wd }) - const urlmap = new UrlmapWithAliases({ aliases }); urlmap.cache = cache + const staticDirs = ListItem.from(publicDirectory) + const filemap = new Filemap({ aliases, packageScope, staticDirs, wd }) + const urlmap = new Urlmap({ aliases, packageScope, staticDirs }); urlmap.cache = cache const cmd = 'serve' const factoryOptions = { plugins, filemap, urlmap, cmd, env, settings, logger } @@ -70,8 +72,7 @@ async function serve (options, logger) { async function middleware (req, res) { const startTime = Date.now() - let url = new URL(req.url, `http://localhost:${port}/`).pathname - if (url.endsWith('/')) url += 'index.html' + const url = new URL(req.url, `http://localhost:${port}/`).pathname if (req.method !== 'GET') { res.writeHead(405, { allow: 'GET' }) @@ -79,6 +80,29 @@ async function serve (options, logger) { return } + if (url.startsWith('/-internal-/')) { + const internalFile = path.join(internalDirectory, url.substring(12)) + if (await isFile(internalFile)) { + res.writeHead(200, { + 'content-type': contentType(internalFile), + 'server-timing': [ + 'internalFile', + `routing;dur=${Date.now() - startTime}` + ].join(', ') + }) + + pipe(fs.createReadStream(internalFile), res, err => { + if (err != null) handleRequestError(err, res) + }) + } else { + // nothing to serve + res.writeHead(404, 'not found') + res.end() + } + + return + } + // serve project files and apply transformation if necessary const processing = processingFactory.resolve('.' + url, fakeReferer) // treat urls as relative paths if (processing != null) { @@ -90,67 +114,13 @@ async function serve (options, logger) { ].join(', ') }) - const streams = await processing.transform() - if (streams.length > 1) { - pipeline( - ...streams, - err => { - if (err != null) handleRequestError(err, res) - } - ).pipe(res) - } else { - finished( - streams[0].pipe(res), - err => { - if (err != null) handleRequestError(err, res) - } - ) - } - return - } - - // serve files from "public" directory - const publicFile = path.join(publicDirectory, url) - if (await isFile(publicFile)) { - res.writeHead(200, { - 'content-type': contentType(publicFile), - 'server-timing': [ - 'publicFile', - `routing;dur=${Date.now() - startTime}` - ].join(', ') + pipe(await processing.transform(), res, err => { + if (err != null) handleRequestError(err, res) }) - finished( - fs.createReadStream(publicFile).pipe(res), - err => { - if (err != null) handleRequestError(err, res) - } - ) return } - // serve development helpers from internal "public" directory - if (url.startsWith('/~/')) { - const innerFile = url.replace('/~', innerPublicDirectory) - if (await isFile(innerFile)) { - res.writeHead(200, { - 'content-type': contentType(innerFile), - 'server-timing': [ - 'internalFile', - `routing;dur=${Date.now() - startTime}` - ].join(', ') - }) - - finished( - fs.createReadStream(innerFile).pipe(res), - err => { - if (err != null) handleRequestError(err, res) - } - ) - return - } - } - // nothing to serve res.writeHead(404, 'not found') res.end() diff --git a/lib/core-filemap-build.js b/lib/core-filemap-build.js new file mode 100644 index 0000000..4ab5b5b --- /dev/null +++ b/lib/core-filemap-build.js @@ -0,0 +1,44 @@ +'use strict' + +const path = require('path') + +const { IFilemap } = require('./core-filemap-i') +const { extname } = require('./util-path') +const { hash } = require('./util-hash') + +class Filemap extends IFilemap { + constructor (options) { + super(options) + + this.packageScope = options.packageScope + this.staticDirs = options.staticDirs + this.wd = options.wd + } + + map (abspath, referer) { + super.map(abspath, referer) + + if (this.staticDirs != null) { + for (const directory of this.staticDirs) { + if (abspath.startsWith(directory)) return '/' + path.relative(directory, abspath) + } + } + + const ext = extname(abspath) + const genericName = hash(path.relative(this.packageScope, abspath)) + + switch (ext) { + case '.html': + return '/' + path.relative(this.wd, abspath) + case '.css': + case '.js': + return '/' + path.join('static', ext.substring(1), genericName + ext) + default: + return '/' + path.join('static', 'media', genericName + ext) + } + } +} + +module.exports = { + Filemap +} diff --git a/lib/core-filemap-i.js b/lib/core-filemap-i.js new file mode 100644 index 0000000..c76f4a2 --- /dev/null +++ b/lib/core-filemap-i.js @@ -0,0 +1,14 @@ +'use strict' + +const assert = require('assert') + +class IFilemap { + map (abspath, referer, options) { + assert(typeof abspath === 'string') + return null + } +} + +module.exports = { + IFilemap +} diff --git a/lib/core-filemap-serve.js b/lib/core-filemap-serve.js new file mode 100644 index 0000000..cd748b5 --- /dev/null +++ b/lib/core-filemap-serve.js @@ -0,0 +1,68 @@ +'use strict' + +const path = require('path') + +const { IFilemap } = require('./core-filemap-i') +const { hash } = require('./util-hash') +const { foldLevels } = require('./util-path') + +const workdir = Symbol('workdir') + +class Filemap extends IFilemap { + constructor (options) { + super(options) + + this.aliases = options.aliases + this.packageScope = options.packageScope + this.staticDirs = options.staticDirs + this.wd = options.wd + + this[workdir] = path.relative(this.packageScope, this.wd) + } + + map (abspath, referer) { + super.map(abspath, referer) + + if (this.staticDirs != null) { + for (const directory of this.staticDirs) { + if (abspath.startsWith(directory)) return '/' + path.relative(directory, abspath) + } + } + + const relative = path.relative(this.packageScope, abspath) + + if (relative.startsWith(this[workdir])) { + return '/' + path.relative(this[workdir], relative) + } + + if (relative.startsWith('../')) { + if (relative.includes('node_modules')) { + const base = abspath.substring(0, abspath.indexOf('node_modules') + 'node_modules'.length) + for (const [alias, directory] of this.aliases) { + if (base === directory) return '/' + path.join(alias, abspath.substring(base.length + 1)) + } + } + + return null + } + + const right = foldLevels(path.relative(this[workdir], relative)) + const middle = relative.substring(0, relative.lastIndexOf(right)) + + const alias = `~${hash(middle)}` // todo a readable solution + if (!this.aliases.has(alias)) { + const base = path.join(this.packageScope, middle.substring(0, middle.length - path.sep.length)) + this.aliases.set(alias, base) + } + + return '/' + path.join(alias, right) + } + + output (relative) { + return relative + } +} + +module.exports = { + Filemap +} diff --git a/lib/core-filemap-with-aliases.js b/lib/core-filemap-with-aliases.js deleted file mode 100644 index 95cf3b5..0000000 --- a/lib/core-filemap-with-aliases.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict' - -const assert = require('assert') -const path = require('path') - -const { Filemap } = require('./core-filemap') -const { hash } = require('./util-hash') -const { foldLevels } = require('./util-path') - -class FilemapWithAliases extends Filemap { - constructor (options) { - super(options) - - assert(options.aliases != null) - this.aliases = options.aliases - assert(typeof options.packageScope === 'string') - this.packageScope = options.packageScope - } - - map (abspath, referer, options) { - assert(typeof abspath === 'string') - - if (abspath.startsWith(this.wd)) { - return super.map(abspath, referer, options) - } - - if (abspath.startsWith(this.packageScope)) { - const middleRight = path.relative(this.packageScope, abspath) - const right = foldLevels(path.relative(this.wd, abspath)) - const middle = middleRight.substring(0, middleRight.lastIndexOf(right)) - - const alias = `/~${hash(middle)}` // todo a readable solution - if (!this.aliases.has(alias)) { - const base = path.join(this.packageScope, middle.substring(0, middle.length - path.sep.length)) - this.aliases.set(alias, base) - } - - return path.join(alias, right) - } - - if (abspath.includes('node_modules')) { - const base = abspath.substring(0, abspath.indexOf('node_modules') + 'node_modules'.length) - for (const [alias, directory] of this.aliases) { - if (base === directory) return path.join(alias, abspath.substring(base.length + 1)) - } - } - - return null - } -} - -module.exports = { FilemapWithAliases } diff --git a/lib/core-filemap-with-output.js b/lib/core-filemap-with-output.js deleted file mode 100644 index b60a149..0000000 --- a/lib/core-filemap-with-output.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' - -const assert = require('assert') -const path = require('path') - -const { Filemap } = require('./core-filemap') -const { hash } = require('./util-hash') - -class FilemapWithOutput extends Filemap { - constructor (options) { - super(options) - - assert(typeof options.packageScope === 'string') - this.packageScope = options.packageScope - } - - map (abspath, referer, options) { - assert(typeof abspath === 'string') - - const ext = options?.extension ?? path.extname(abspath) - const genericName = hash(path.relative(this.packageScope, abspath)) - - switch (ext) { - case '.html': - return super.map(abspath, referer, options) - case '.css': - case '.js': - return path.join('/static', ext.substring(1), genericName + ext) - default: - return path.join('/static/media', genericName + ext) - } - } -} - -module.exports = { - FilemapWithOutput -} diff --git a/lib/core-filemap.js b/lib/core-filemap.js deleted file mode 100644 index 75a6845..0000000 --- a/lib/core-filemap.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict' - -const assert = require('assert') -const path = require('path') - -const { extname } = require('./util-path') - -class Filemap { - constructor (options) { - assert(options.wd != null) - this.wd = options.wd - } - - map (abspath, referer, options) { - assert(typeof abspath === 'string') - - const customExtension = options?.extension - if (customExtension != null && !abspath.endsWith(customExtension)) { - abspath = path.join( - path.dirname(abspath), - path.basename(abspath, extname(abspath, true)) + customExtension - ) - } - - if (referer != null) { - const directory = path.dirname(referer) - if (abspath.startsWith(directory)) return './' + path.relative(directory, abspath) - } - - return '/' + path.relative(this.wd, abspath) - } -} - -module.exports = { - Filemap -} diff --git a/lib/core-processing-factory.js b/lib/core-processing-factory.js index 05fb78f..4e6462e 100644 --- a/lib/core-processing-factory.js +++ b/lib/core-processing-factory.js @@ -4,22 +4,35 @@ const assert = require('assert') const { inspect } = require('util') const { ILogger } = require('./util-logger-i') +const { IUrlmap } = require('./core-urlmap-i') const { Processing } = require('./core-processing') -const { Urlmap } = require('./core-urlmap') const { extname } = require('./util-path') const customInspect = inspect.custom ?? 'inspect' class ProcessingFactory { + static validatePlugin (plugin) { + assert(typeof plugin.type === 'string' && plugin.type.length > 0) + if (!Array.isArray(plugin.extensions)) throw new Error(`plugin "${plugin.type}": "extensions" should be an array of file extensions that can be used as input`) + if (typeof plugin.for !== 'string') throw new Error(`plugin "${plugin.type}": "for" should be an extension for an output file`) + if (typeof plugin.transform !== 'function') throw new Error(`plugin "${plugin.type}": "transform" should be a function`) + + if (plugin.type !== 'identity') { + assert(plugin.extensions.every(ext => ext !== '')) + assert(plugin.for !== '') + } + } + constructor (options) { // mandatory for resolve - assert(Array.isArray(options.plugins)) // properly validate plugins + assert(Array.isArray(options.plugins)) + for (const plugin of options.plugins) ProcessingFactory.validatePlugin(plugin) this.plugins = options.plugins this.product = options.product ?? Processing // required for processing this.filemap = options.filemap - this.urlmap = options.urlmap ?? new Urlmap() + this.urlmap = options.urlmap ?? new IUrlmap() this.cmd = options.cmd this.env = options.env this.settings = options.settings @@ -31,6 +44,8 @@ class ProcessingFactory { assert(typeof url === 'string') assert(typeof referer === 'string') + if (url.endsWith('/')) url += 'index.html' + const ext = extname(url) ?? extname(referer) const matchingPlugins = this.plugins.filter(plugin => plugin.for === ext || plugin.for === '') @@ -41,7 +56,11 @@ class ProcessingFactory { const abspath = this.urlmap.map(url, referer) if (abspath == null) continue + // first plugin that may apply custom transformation, + // for example ".md" → ".html" const plugins = [plugin] + // other plugins that can be applied after, + // should have a matching extension among "extensions". for (let k = n + 1; k < matchingPlugins.length; ++k) { const nextPlugin = matchingPlugins[k] if (nextPlugin.extensions.includes(ext)) plugins.push(nextPlugin) @@ -51,12 +70,12 @@ class ProcessingFactory { const processingOptions = { url, abspath, plugins, filemap, cmd, env, settings } const processing = new Product(processingOptions, this) - this.logger.debug({ url, referer, abspath }, 'url resolved') + this.logger.debug('resolve completed %o', { url, referer, abspath }) return processing } - this.logger.debug({ url, referer }, 'url not resolved') + this.logger.debug('resolve errored %o', { url, referer }) return null } diff --git a/lib/core-processing.js b/lib/core-processing.js index 4df535e..ce86ea9 100644 --- a/lib/core-processing.js +++ b/lib/core-processing.js @@ -1,6 +1,7 @@ 'use strict' const fs = require('fs') +const path = require('path') const { inspect } = require('util') const { pipeline } = require('stream/promises') @@ -9,6 +10,7 @@ const { createBufferStream, createTransformStream } = require('./util-stream') const { extname } = require('./util-path') const customInspect = inspect.custom ?? 'inspect' +const internal = /^\/-internal-\// const protocol = /^[a-z]{2,6}:\/\//i const context = Symbol('context') @@ -33,9 +35,25 @@ class Processing { this[factory] = processingFactory } - publicUrl () { - const options = { extension: extname(this.url) } - return this.filemap.map(this.abspath, null, options) + get id () { + return this.publicUrl() + } + + publicUrl (referer) { + const ext = extname(this.url) ?? extname(this.abspath) + const request = !this.abspath.endsWith(ext) + ? path.join( + path.dirname(this.abspath), + path.basename(this.abspath, extname(this.abspath, true)) + ext + ) + : this.abspath + + const url = this.filemap.map(request, null) + if (url.endsWith('index.html') && referer != null) { + return url.substring(0, url.length - 'index.html'.length) + } + + return url } resolve (url, options) { @@ -49,23 +67,26 @@ class Processing { } requestDependency (url) { + if (internal.test(url)) throw new Error('requestDependency does not handle internal files') if (protocol.test(url)) throw new Error('requestDependency does not support urls') const dependency = this.resolve(url, { captureDependency: false }) - if (dependency) return null + if (dependency == null) return null const bufferStream = createBufferStream() - return pipeline(...dependency.transform(), bufferStream) + return dependency.transform() + .then(streams => pipeline(...streams, bufferStream)) .then(() => bufferStream.buffer.toString('utf8')) } resolveDependency (url) { + if (internal.test(url)) return url if (protocol.test(url)) return url const dependency = this.resolve(url, { captureDependency: true }) if (dependency == null) console.log('can not resolve', url) - return dependency?.publicUrl() ?? url + return dependency?.publicUrl(this.abspath) ?? url } async transform () { diff --git a/lib/core-resolve.js b/lib/core-resolve.js index a455a04..9e66a9f 100644 --- a/lib/core-resolve.js +++ b/lib/core-resolve.js @@ -111,5 +111,7 @@ function resolveUrl (url, referer, options) { module.exports = { nodeModules, + resolveAsFile, + resolveAsDirectory, resolveUrl } diff --git a/lib/core-urlmap-i.js b/lib/core-urlmap-i.js new file mode 100644 index 0000000..999ab41 --- /dev/null +++ b/lib/core-urlmap-i.js @@ -0,0 +1,15 @@ +'use strict' + +const assert = require('assert') + +class IUrlmap { + map (url, referer) { + assert(typeof url === 'string') + assert(typeof referer === 'string') + return null + } +} + +module.exports = { + IUrlmap +} diff --git a/lib/core-urlmap-with-aliases.js b/lib/core-urlmap-with-aliases.js deleted file mode 100644 index 2104837..0000000 --- a/lib/core-urlmap-with-aliases.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict' - -const assert = require('assert') -const path = require('path') - -const { Urlmap } = require('./core-urlmap') -const { resolveUrl } = require('./core-resolve') - -const isAlias = /\/~[a-z]+/i - -class UrlmapWithAliases extends Urlmap { - constructor (options) { - super(options) - - assert(options.aliases != null) - this.aliases = options.aliases - } - - map (url, referer) { - assert(typeof url === 'string') - assert(typeof referer === 'string') - - if (isAlias.test(url)) { - const offset = url.startsWith('./') ? 1 : 0 - const alias = url.substring(offset, url.indexOf('/', offset + 1)) - - if (this.aliases.has(alias)) { - url = path.join(this.aliases.get(alias), url.substring(offset + alias.length + 1)) - - const options = { extensions: this.extensions } - return resolveUrl(url, referer, options) - } - } - - return super.map(url, referer) - } -} - -module.exports = { - UrlmapWithAliases -} diff --git a/lib/core-urlmap.js b/lib/core-urlmap.js index 60703f9..27b5c6e 100644 --- a/lib/core-urlmap.js +++ b/lib/core-urlmap.js @@ -1,21 +1,72 @@ 'use strict' -const assert = require('assert') +const path = require('path') +const { IUrlmap } = require('./core-urlmap-i') const { ListItem } = require('./core-data-structures') +const { isFileSync } = require('./util-fs') const { resolveUrl } = require('./core-resolve') -class Urlmap { - constructor () { +class Urlmap extends IUrlmap { + static isAliasPath (url) { + return /^(?:\.\/)?~[a-z0-9]+/i.test(url) + } + + static isRootPath (url) { + return ( + url.startsWith('./~/') || + url.startsWith('~/') + ) + } + + constructor (options) { + super(options) + + this.aliases = options.aliases this.cache = null this.extensions = ListItem.from('') + this.packageScope = options.packageScope + this.staticDirs = options.staticDirs } map (url, referer) { - assert(typeof url === 'string') - assert(typeof referer === 'string') + super.map(url, referer) + + // resolved aliases or package path will start from "/~", + // thus turn it into "./~" to properly resolve it + if (url.startsWith('/~')) url = '.' + url const options = { cache: this.cache, extensions: this.extensions } + + if (url.startsWith('/')) { + const abspath = resolveUrl(url, referer, options) + if (abspath != null) return abspath + + if (this.staticDirs != null) { + for (const directory of this.staticDirs) { + const staticPath = path.join(directory, url) + if (isFileSync(staticPath)) return staticPath + } + } + + return null + } + + if (Urlmap.isAliasPath(url)) { + const offset = url.indexOf('~') + const alias = url.substring(offset, url.indexOf('/', offset)) + + if (this.aliases.has(alias)) { + url = path.join(this.aliases.get(alias), url.substring(offset + alias.length + 1)) + } + } + + if (Urlmap.isRootPath(url)) { + const offset = url.indexOf('~/') + const base = url.substring(offset + 2) + url = path.join(this.packageScope, base) + } + return resolveUrl(url, referer, options) } } diff --git a/lib/util-stream.js b/lib/util-stream.js index 80e9c8b..72a9ba1 100644 --- a/lib/util-stream.js +++ b/lib/util-stream.js @@ -1,7 +1,7 @@ 'use strict' const assert = require('assert') -const { PassThrough, Transform } = require('stream') +const { PassThrough, Transform, finished, pipeline } = require('stream') function createBufferStream () { const bufferStream = new Transform({ @@ -39,16 +39,20 @@ function createTransformStream (tf, context) { }) } -function pipe (...streams) { - if (streams.length === 0) return null - - let cursor = streams[0] - for (let i = 1; i < streams.length; i++) { - const stream = streams[i] - cursor = cursor.pipe(stream) +function pipe (streams, destination, callback) { + if (!Array.isArray(streams)) { + finished( + streams.pipe(destination), + callback + ) + } else if (streams.length === 1) { + finished( + streams[0].pipe(destination), + callback + ) + } else { + pipeline(...streams, callback).pipe(destination) } - - return streams[0] } function tee (output) { diff --git a/package.json b/package.json index 01b4b10..379c952 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "under-the-wave", - "version": "1.3.5", + "version": "1.3.6", "bin": { "under-the-wave": "./lib/bin.js", "wave": "./lib/bin.js" diff --git a/plugins/p-markdown.js b/plugins/p-markdown.js index 82f2460..e9c9d29 100644 --- a/plugins/p-markdown.js +++ b/plugins/p-markdown.js @@ -21,13 +21,18 @@ function transformMarkdownPlugin (string, context, done) { return } - context.request(meta.layout) - .then(buffer => { - const template = buffer.toString('utf8') - // keep in mind that matching html plugins will be applied one more time after, - // thus some plugins may be applied twice - return posthtml(markdownPlugin(meta.__content)).process(template) - }) + const file = context.request(meta.layout) + if (file == null) { + done(new Error('request failed for "' + meta.layout + '"')) + return + } + + file.then(buffer => { + const template = buffer.toString('utf8') + // keep in mind that matching html plugins will be applied one more time after, + // thus some plugins may be applied twice + return posthtml(markdownPlugin(meta.__content)).process(template) + }) .then(result => done(null, result.html), done) } diff --git a/plugins/p-style.js b/plugins/p-style.js index 793fae7..ca70a2b 100644 --- a/plugins/p-style.js +++ b/plugins/p-style.js @@ -6,8 +6,9 @@ const updateAssetPathStylePlugin = require('./t-style-update-asset-path') module.exports = { extensions: ['.css'], - type: 'css', - transform: transformStyleCorePlugin + for: '.css', + transform: transformStyleCorePlugin, + type: 'style' } function transformStyleCorePlugin (string, context, done) { diff --git a/plugins/t-markup-inject-bridge.js b/plugins/t-markup-inject-bridge.js index 8d2f180..2c2cf01 100644 --- a/plugins/t-markup-inject-bridge.js +++ b/plugins/t-markup-inject-bridge.js @@ -1,6 +1,6 @@ 'use strict' -const bridgeUrl = '/~/bridge.js' +const bridgeUrl = '/-internal-/bridge.js' const indent = /^[\n\s]+$/ const scriptNode = { diff --git a/tests/core-filemap-with-aliases.test.js b/tests/core-filemap-with-aliases.test.js deleted file mode 100644 index a0fd91f..0000000 --- a/tests/core-filemap-with-aliases.test.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict' - -const assert = require('uvu/assert') -const { test } = require('uvu') - -const { FilemapWithAliases } = require('../lib/core-filemap-with-aliases') - -const packageScope = '/www' -const wd = '/www/pages' - -test('FilemapWithAliases.map', () => { - const aliases = new Map() - const filemap = new FilemapWithAliases({ aliases, packageScope, wd }) - - assert.is(filemap.map('/www/lib/abc.js'), '/~hldurme/abc.js') - assert.is(filemap.map('/www/lib/utils.js'), '/~hldurme/utils.js') - assert.is(filemap.map('/www/static/css/tachyons.css'), '/~jdrquyi/css/tachyons.css') -}) - -test.run() diff --git a/tests/core-filemap.test.js b/tests/core-filemap.test.js deleted file mode 100644 index 688199e..0000000 --- a/tests/core-filemap.test.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -const assert = require('uvu/assert') -const { test } = require('uvu') - -const { Filemap } = require('../lib/core-filemap') - -test('IFilemap.map', () => { - const filemap = new Filemap({ wd: '/www' }) - // map file to url - assert.is(filemap.map('/www/index.html'), '/index.html') - // map file to relative url - assert.is(filemap.map('/www/static/js/app.js', '/www/index.html'), './static/js/app.js') -}) - -test.run() diff --git a/tests/core-processing-factory.test.js b/tests/core-processing-factory.test.js index ab68509..649bb3f 100644 --- a/tests/core-processing-factory.test.js +++ b/tests/core-processing-factory.test.js @@ -4,16 +4,27 @@ const assert = require('uvu/assert') const path = require('path') const { test } = require('uvu') +const { ListItem } = require('../lib/core-data-structures') const { ProcessingFactory } = require('../lib/core-processing-factory') const { Processing } = require('../lib/core-processing') +const { resolveAsFile } = require('../lib/core-resolve') const projectRoot = path.resolve(__dirname, 'fixture') const referer = path.join(projectRoot, 'index.html') +const urlmap = { + extensions: ListItem.from(''), + map (url, referer) { + const abspath = path.join(path.dirname(referer), url) + const options = { extensions: this.extensions } + if (resolveAsFile(abspath, options)) return abspath + return null + } +} + test('ProcessingFactory.create returns processing instance', () => { const plugins = [require('../plugins/p-identity')] - const processingFactory = new ProcessingFactory({ plugins }) - processingFactory.cmd = 'test' + const processingFactory = new ProcessingFactory({ plugins, urlmap }) const processing = processingFactory.resolve('./static/hello.js', referer) @@ -25,8 +36,7 @@ test('ProcessingFactory.create returns processing instance', () => { test('ProcessingFactory.create maps ".html" file to ".md"', () => { const plugin = require('../plugins/p-markdown') - const processingFactory = new ProcessingFactory({ plugins: [plugin] }) - processingFactory.cmd = 'test' + const processingFactory = new ProcessingFactory({ plugins: [plugin], urlmap }) assert.not.ok(plugin.extensions.includes(plugin.for)) assert.instance(processingFactory.resolve('./readme.html', referer), Processing) diff --git a/tests/core-urlmap-with-aliases.test.js b/tests/core-urlmap-with-aliases.test.js deleted file mode 100644 index 9d09c03..0000000 --- a/tests/core-urlmap-with-aliases.test.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const assert = require('uvu/assert') -const path = require('path') -const { test } = require('uvu') - -const { UrlmapWithAliases } = require('../lib/core-urlmap-with-aliases') - -const projectRoot = path.resolve(__dirname, 'fixture') -const referer = path.join(projectRoot, '_') -const output = path.join(projectRoot, 'index.html') - -test('Urlmap.map "./x" path', () => { - const aliases = new Map() - const urlmap = new UrlmapWithAliases({ aliases }) - - aliases.set('/~w', projectRoot) - assert.is(urlmap.map('/~w/index.html', referer), output) -}) - -test.run() diff --git a/tests/core-urlmap.test.js b/tests/core-urlmap.test.js index 1eea474..af5d713 100644 --- a/tests/core-urlmap.test.js +++ b/tests/core-urlmap.test.js @@ -4,20 +4,58 @@ const assert = require('uvu/assert') const path = require('path') const { test } = require('uvu') +const { ListItem } = require('../lib/core-data-structures') const { Urlmap } = require('../lib/core-urlmap') -const projectRoot = path.resolve(__dirname, 'fixture') -const referer = path.join(projectRoot, '_') -const output = path.join(projectRoot, 'index.html') +const packageScope = path.resolve(__dirname, 'fixture') +const referer = path.join(packageScope, '_') + +const output = path.join(packageScope, 'index.html') test('Urlmap.map "./x" path', () => { - const urlmap = new Urlmap() + const aliases = new Map() + const urlmap = new Urlmap({ aliases, packageScope }) assert.is(urlmap.map('./index.html', referer), output) }) +test('Urlmap.map "/x" path', () => { + const aliases = new Map() + const urlmap = new Urlmap({ aliases, packageScope }) + + assert.is(urlmap.map(output, referer), output) +}) + +test('Urlmap.map "~/x" path', () => { + const aliases = new Map() + const urlmap = new Urlmap({ aliases, packageScope }) + + assert.is(urlmap.map('./~/index.html', referer), output) + assert.is(urlmap.map('~/index.html', referer), output) +}) + +test('Urlmap.map "~st/x" path', () => { + const aliasPath = path.join(packageScope, 'static') + const aliases = new Map([['~st', aliasPath]]) + const output = path.join(packageScope, 'static/hello.js') + const urlmap = new Urlmap({ aliases, packageScope }) + + assert.is(urlmap.map('./~st/hello.js', referer), output) + assert.is(urlmap.map('~st/hello.js', referer), output) +}) + +test('Urlmap.map → public file', () => { + const aliases = new Map() + const staticDirs = ListItem.from(path.join(packageScope, 'public')) + const output = path.join(packageScope, 'public/robots.txt') + const urlmap = new Urlmap({ aliases, packageScope, staticDirs }) + + assert.is(urlmap.map('/robots.txt', referer), output) +}) + test('Urlmap.map → "null" for not existing file', () => { - const urlmap = new Urlmap() + const aliases = new Map() + const urlmap = new Urlmap({ aliases, packageScope }) assert.is(urlmap.map('./unknown.html', referer), null) }) diff --git a/tests/t-markup-inject-bridge.test.js b/tests/t-markup-inject-bridge.test.js index 08d25b3..ae0934b 100644 --- a/tests/t-markup-inject-bridge.test.js +++ b/tests/t-markup-inject-bridge.test.js @@ -21,7 +21,7 @@ test('inject bridge script into head', async () => {
- + @@ -45,7 +45,7 @@ test('inject bridge script into minified head', async () => { const expected = ` - + `