diff options
author | Bradley Farias <bradley.meck@gmail.com> | 2019-07-09 16:03:07 -0500 |
---|---|---|
committer | Ruben Bridgewater <ruben@bridgewater.de> | 2019-09-03 14:14:03 +0200 |
commit | a7c8322a54bac6bb9f6a7b14b9927059621e2224 (patch) | |
tree | 77dd697e39d0eb8827b14064731e1beef65a15b8 /lib/internal/modules | |
parent | 6ff803d97c8849571bb95f46ba6520150b78ccd3 (diff) | |
download | node-new-a7c8322a54bac6bb9f6a7b14b9927059621e2224.tar.gz |
esm: support loading data URLs
Co-Authored-By: Jan Olaf Krems <jan.krems@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/28614
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Diffstat (limited to 'lib/internal/modules')
-rw-r--r-- | lib/internal/modules/esm/default_resolve.js | 22 | ||||
-rw-r--r-- | lib/internal/modules/esm/loader.js | 7 | ||||
-rw-r--r-- | lib/internal/modules/esm/translators.js | 86 |
3 files changed, 87 insertions, 28 deletions
diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js index 46e7b2415a..580419deac 100644 --- a/lib/internal/modules/esm/default_resolve.js +++ b/lib/internal/modules/esm/default_resolve.js @@ -12,7 +12,7 @@ const typeFlag = getOptionValue('--input-type'); const experimentalWasmModules = getOptionValue('--experimental-wasm-modules'); const { resolve: moduleWrapResolve, getPackageType } = internalBinding('module_wrap'); -const { pathToFileURL, fileURLToPath } = require('internal/url'); +const { URL, pathToFileURL, fileURLToPath } = require('internal/url'); const { ERR_INPUT_TYPE_NOT_ALLOWED, ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; @@ -45,12 +45,32 @@ if (experimentalWasmModules) extensionFormatMap['.wasm'] = legacyExtensionFormatMap['.wasm'] = 'wasm'; function resolve(specifier, parentURL) { + try { + const parsed = new URL(specifier); + if (parsed.protocol === 'data:') { + const [ , mime ] = /^([^/]+\/[^;,]+)(;base64)?,/.exec(parsed.pathname) || [ null, null, null ]; + const format = ({ + '__proto__': null, + 'text/javascript': 'module', + 'application/json': 'json', + 'application/wasm': experimentalWasmModules ? 'wasm' : null + })[mime] || null; + return { + url: specifier, + format + }; + } + } catch {} if (NativeModule.canBeRequiredByUsers(specifier)) { return { url: specifier, format: 'builtin' }; } + if (parentURL && parentURL.startsWith('data:')) { + // This is gonna blow up, we want the error + new URL(specifier, parentURL); + } const isMain = parentURL === undefined; if (isMain) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index bffefa884e..09109d3c71 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -102,9 +102,12 @@ class Loader { } } - if (format !== 'dynamic' && !url.startsWith('file:')) + if (format !== 'dynamic' && + !url.startsWith('file:') && + !url.startsWith('data:') + ) throw new ERR_INVALID_RETURN_PROPERTY( - 'file: url', 'loader resolve', 'url', url + 'file: or data: url', 'loader resolve', 'url', url ); return { url, format }; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index d693b79448..9cb0781ab9 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -9,6 +9,8 @@ const { StringPrototype } = primordials; +const { Buffer } = require('buffer'); + const { stripShebang, stripBOM, @@ -24,6 +26,8 @@ const { debuglog } = require('internal/util/debuglog'); const { promisify } = require('internal/util'); const esmLoader = require('internal/process/esm_loader'); const { + ERR_INVALID_URL, + ERR_INVALID_URL_SCHEME, ERR_UNKNOWN_BUILTIN_MODULE } = require('internal/errors').codes; const readFileAsync = promisify(fs.readFile); @@ -34,6 +38,31 @@ const debug = debuglog('esm'); const translators = new SafeMap(); exports.translators = translators; +const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(;base64)?,([\s\S]*)$/; +function getSource(url) { + const parsed = new URL(url); + if (parsed.protocol === 'file:') { + return readFileAsync(parsed); + } else if (parsed.protocol === 'data:') { + const match = DATA_URL_PATTERN.exec(parsed.pathname); + if (!match) { + throw new ERR_INVALID_URL(url); + } + const [ , base64, body ] = match; + return Buffer.from(body, base64 ? 'base64' : 'utf8'); + } else { + throw new ERR_INVALID_URL_SCHEME(['file', 'data']); + } +} + +function errPath(url) { + const parsed = new URL(url); + if (parsed.protocol === 'file:') { + return fileURLToPath(parsed); + } + return url; +} + function initializeImportMeta(meta, { url }) { meta.url = url; } @@ -45,7 +74,7 @@ async function importModuleDynamically(specifier, { url }) { // Strategy for loading a standard JavaScript module translators.set('module', async function moduleStrategy(url) { - const source = `${await readFileAsync(new URL(url))}`; + const source = `${await getSource(url)}`; debug(`Translating StandardModule ${url}`); const { ModuleWrap, callbackMap } = internalBinding('module_wrap'); const module = new ModuleWrap(stripShebang(source), url); @@ -112,26 +141,32 @@ translators.set('builtin', async function builtinStrategy(url) { translators.set('json', async function jsonStrategy(url) { debug(`Translating JSONModule ${url}`); debug(`Loading JSONModule ${url}`); - const pathname = fileURLToPath(url); - const modulePath = isWindows ? - StringPrototype.replace(pathname, winSepRegEx, '\\') : pathname; - let module = CJSModule._cache[modulePath]; - if (module && module.loaded) { - const exports = module.exports; - return createDynamicModule([], ['default'], url, (reflect) => { - reflect.exports.default.set(exports); - }); + const pathname = url.startsWith('file:') ? fileURLToPath(url) : null; + let modulePath; + let module; + if (pathname) { + modulePath = isWindows ? + StringPrototype.replace(pathname, winSepRegEx, '\\') : pathname; + module = CJSModule._cache[modulePath]; + if (module && module.loaded) { + const exports = module.exports; + return createDynamicModule([], ['default'], url, (reflect) => { + reflect.exports.default.set(exports); + }); + } } - const content = await readFileAsync(pathname, 'utf-8'); - // A require call could have been called on the same file during loading and - // that resolves synchronously. To make sure we always return the identical - // export, we have to check again if the module already exists or not. - module = CJSModule._cache[modulePath]; - if (module && module.loaded) { - const exports = module.exports; - return createDynamicModule(['default'], url, (reflect) => { - reflect.exports.default.set(exports); - }); + const content = `${await getSource(url)}`; + if (pathname) { + // A require call could have been called on the same file during loading and + // that resolves synchronously. To make sure we always return the identical + // export, we have to check again if the module already exists or not. + module = CJSModule._cache[modulePath]; + if (module && module.loaded) { + const exports = module.exports; + return createDynamicModule(['default'], url, (reflect) => { + reflect.exports.default.set(exports); + }); + } } try { const exports = JsonParse(stripBOM(content)); @@ -144,10 +179,12 @@ translators.set('json', async function jsonStrategy(url) { // parse error instead of just manipulating the original error message. // That would allow to add further properties and maybe additional // debugging information. - err.message = pathname + ': ' + err.message; + err.message = errPath(url) + ': ' + err.message; throw err; } - CJSModule._cache[modulePath] = module; + if (pathname) { + CJSModule._cache[modulePath] = module; + } return createDynamicModule([], ['default'], url, (reflect) => { debug(`Parsing JSONModule ${url}`); reflect.exports.default.set(module.exports); @@ -156,14 +193,13 @@ translators.set('json', async function jsonStrategy(url) { // Strategy for loading a wasm module translators.set('wasm', async function(url) { - const pathname = fileURLToPath(url); - const buffer = await readFileAsync(pathname); + const buffer = await getSource(url); debug(`Translating WASMModule ${url}`); let compiled; try { compiled = await WebAssembly.compile(buffer); } catch (err) { - err.message = pathname + ': ' + err.message; + err.message = errPath(url) + ': ' + err.message; throw err; } |