From 7f47553ee67798c78f96bbd6ce799b017e29c87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 11 Oct 2024 13:54:32 +0200 Subject: [PATCH 1/2] Support binary and text modules --- index.js | 32 ++++++++++++++++ lib/constants.js | 4 +- test.js | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 96507c5..255478c 100644 --- a/index.js +++ b/index.js @@ -529,6 +529,10 @@ function extensionForType (type) { return '.bundle' case constants.types.ADDON: return '.bare' + case constants.types.BINARY: + return '.bin' + case constants.types.TEXT: + return '.txt' default: return null } @@ -548,6 +552,10 @@ function typeForAttributes (attributes) { return constants.types.BUNDLE case 'addon': return constants.types.ADDON + case 'binary': + return constants.types.BINARY + case 'text': + return constants.types.TEXT default: return 0 } @@ -778,6 +786,30 @@ Module._extensions['.bundle'] = function (module, source, referrer) { } } +Module._extensions['.bin'] = function (module, source, referrer) { + const protocol = module._protocol + + module._type = constants.types.BINARY + + if (source === null) source = protocol.read(module._url) + + if (typeof source === 'string') source = Buffer.from(source) + + module._exports = source +} + +Module._extensions['.txt'] = function (module, source, referrer) { + const protocol = module._protocol + + module._type = constants.types.TEXT + + if (source === null) source = protocol.read(module._url) + + if (typeof source !== 'string') source = Buffer.coerce(source).toString() + + module._exports = source +} + Module._protocol = new Protocol({ postresolve (url) { switch (url.protocol) { diff --git a/lib/constants.js b/lib/constants.js index 1d08eaf..2e90afe 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -10,6 +10,8 @@ module.exports = { MODULE: 2, JSON: 3, BUNDLE: 4, - ADDON: 5 + ADDON: 5, + BINARY: 6, + TEXT: 7 } } diff --git a/test.js b/test.js index 267c6e3..cffeb84 100644 --- a/test.js +++ b/test.js @@ -2903,6 +2903,102 @@ test('load .bundle with asset import, resolutions map pointing outside .bundle', t.is(Module.load(new URL(root + '/app.bundle'), bundle.toBuffer(), { protocol }).exports, isWindows ? 'c:\\bar.txt' : '/bar.txt') }) +test('load .js with .bin require', (t) => { + t.teardown(onteardown) + + const protocol = new Module.Protocol({ + exists (url) { + return url.href === root + '/foo.bin' + }, + + read (url) { + if (url.href === root + '/index.js') { + return 'module.exports = require(\'./foo.bin\')' + } + + if (url.href === root + '/foo.bin') { + return Buffer.from('hello world') + } + + t.fail() + } + }) + + t.alike(Module.load(new URL(root + '/index.js'), { protocol }).exports, Buffer.from('hello world')) +}) + +test('load .js with .txt require', (t) => { + t.teardown(onteardown) + + const protocol = new Module.Protocol({ + exists (url) { + return url.href === root + '/foo.txt' + }, + + read (url) { + if (url.href === root + '/index.js') { + return 'module.exports = require(\'./foo.txt\')' + } + + if (url.href === root + '/foo.txt') { + return 'hello world' + } + + t.fail() + } + }) + + t.is(Module.load(new URL(root + '/index.js'), { protocol }).exports, 'hello world') +}) + +test('load .js with .bin require, asserted type', (t) => { + t.teardown(onteardown) + + const protocol = new Module.Protocol({ + exists (url) { + return url.href === root + '/asset' + }, + + read (url) { + if (url.href === root + '/index.js') { + return 'module.exports = require(\'./asset\', { with: { type: \'binary\' } })' + } + + if (url.href === root + '/asset') { + return Buffer.from('hello world') + } + + t.fail() + } + }) + + t.alike(Module.load(new URL(root + '/index.js'), { protocol }).exports, Buffer.from('hello world')) +}) + +test('load .js with .txt require, asserted type', (t) => { + t.teardown(onteardown) + + const protocol = new Module.Protocol({ + exists (url) { + return url.href === root + '/asset' + }, + + read (url) { + if (url.href === root + '/index.js') { + return 'module.exports = require(\'./asset\', { with: { type: \'text\' } })' + } + + if (url.href === root + '/asset') { + return 'hello world' + } + + t.fail() + } + }) + + t.is(Module.load(new URL(root + '/index.js'), { protocol }).exports, 'hello world') +}) + function onteardown () { // TODO Provide a public API for clearing the cache. Module._cache = Object.create(null) From 16568ec69400ac40c6eb28c4ee2cc50eb0f47d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 11 Oct 2024 14:25:58 +0200 Subject: [PATCH 2/2] Adjust resolution extensions based on asserted type --- README.md | 5 ++++- index.js | 49 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a9b3321..f9aaeeb 100644 --- a/README.md +++ b/README.md @@ -212,11 +212,14 @@ Options include: { isImport = false, referrer = null, + type, + extensions, protocol, imports, resolutions, builtins, - conditions + conditions, + attributes } ``` diff --git a/index.js b/index.js index 255478c..c6b21f8 100644 --- a/index.js +++ b/index.js @@ -252,7 +252,7 @@ const Module = module.exports = exports = class Module { throw errors.MODULE_NOT_FOUND(msg) } - const url = this.resolve(href, referrer._url, { isImport: true, referrer }) + const url = this.resolve(href, referrer._url, { isImport: true, referrer, attributes }) const module = this.load(url, { isImport: true, isDynamicImport, referrer, attributes }) @@ -392,10 +392,10 @@ const Module = module.exports = exports = class Module { module._builtins = builtins module._conditions = conditions - let extension = extensionForType(type) || path.extname(url.pathname) + let extension = canonicalExtensionForType(type) || path.extname(url.pathname) if (extension in self._extensions === false) { - if (defaultType) extension = extensionForType(defaultType) || '.js' + if (defaultType) extension = canonicalExtensionForType(defaultType) || '.js' else extension = '.js' } @@ -422,6 +422,9 @@ const Module = module.exports = exports = class Module { isImport = false, referrer = null, + attributes, + type = typeForAttributes(attributes), + extensions = extensionsForType(type), protocol = referrer ? referrer._protocol : self._protocol, imports = referrer ? referrer._imports : null, resolutions = referrer ? referrer._resolutions : null, @@ -439,18 +442,11 @@ const Module = module.exports = exports = class Module { conditions: isImport ? ['import', ...conditions] : ['require', ...conditions], imports, resolutions, + extensions, builtins: builtins ? Object.keys(builtins) : [], engines: { ...Bare.versions - }, - extensions: [ - '.js', - '.cjs', - '.mjs', - '.json', - '.bare', - '.node' - ] + } }, readPackage)) { switch (resolution.protocol) { case 'builtin:': return resolution @@ -517,7 +513,28 @@ const Module = module.exports = exports = class Module { } } -function extensionForType (type) { +function extensionsForType (type) { + switch (type) { + case constants.types.SCRIPT: + return ['.js', '.cjs'] + case constants.types.MODULE: + return ['.js', '.mjs'] + case constants.types.JSON: + return ['.json'] + case constants.types.BUNDLE: + return ['.json'] + case constants.types.ADDON: + return ['.bare', '.node'] + case constants.types.BINARY: + return ['.bin'] + case constants.types.TEXT: + return ['.txt'] + default: + return ['.js', '.cjs', '.mjs', '.json', '.bare', '.node'] + } +} + +function canonicalExtensionForType (type) { switch (type) { case constants.types.SCRIPT: return '.cjs' @@ -619,9 +636,11 @@ const createRequire = exports.createRequire = function createRequire (parentURL, return require function require (specifier, opts = {}) { - const resolved = self.resolve(specifier, referrer._url, { referrer }) + const attributes = opts && opts.with + + const resolved = self.resolve(specifier, referrer._url, { referrer, attributes }) - const module = self.load(resolved, { referrer, attributes: opts.with }) + const module = self.load(resolved, { referrer, attributes }) return module._exports }