summaryrefslogtreecommitdiff
path: root/lib/internal/modules
diff options
context:
space:
mode:
authorBradley Farias <bradley.meck@gmail.com>2019-07-09 16:03:07 -0500
committerRuben Bridgewater <ruben@bridgewater.de>2019-09-03 14:14:03 +0200
commita7c8322a54bac6bb9f6a7b14b9927059621e2224 (patch)
tree77dd697e39d0eb8827b14064731e1beef65a15b8 /lib/internal/modules
parent6ff803d97c8849571bb95f46ba6520150b78ccd3 (diff)
downloadnode-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.js22
-rw-r--r--lib/internal/modules/esm/loader.js7
-rw-r--r--lib/internal/modules/esm/translators.js86
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;
}