Skip to content
This repository has been archived by the owner on Apr 16, 2020. It is now read-only.

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
esm: add experimental .json support to loader
Browse files Browse the repository at this point in the history
With the new flag `--experimental-json-modules` it is now possible
to import .json files. It piggy backs on the current cjs loader
implementation, so it only exports a default. This is a bit of a
hack, and it should potentially have it's own loader, especially
if we change the cjs loader at all.

The behavior for .json in the cjs loader matches the current
planned behavior if json modules were to be standardized, specifically
that a .json module only exports a default.

Refs: nodejs/modules#255
Refs: whatwg/html#4315
Refs: WICG/webcomponents#770
MylesBorins authored and nodejs-ci committed Mar 21, 2019

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
1 parent 298a94e commit 866fffa
Showing 10 changed files with 109 additions and 2 deletions.
11 changes: 11 additions & 0 deletions lib/internal/modules/esm/default_resolve.js
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const { ERR_INVALID_PACKAGE_CONFIG,
ERR_TYPE_MISMATCH,
ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
const experimentalJsonModules = getOptionValue('--experimental-json-modules');
const { resolve: moduleWrapResolve } = internalBinding('module_wrap');
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
const asyncESM = require('internal/process/esm_loader');
@@ -34,6 +35,16 @@ const legacyExtensionFormatMap = {
'.node': 'commonjs'
};

if (experimentalJsonModules) {
// This is a total hack
Object.assign(extensionFormatMap, {
'.json': 'json'
});
Object.assign(legacyExtensionFormatMap, {
'.json': 'json'
});
}

function readPackageConfig(path, parentURL) {
const existing = pjsonCache.get(path);
if (existing !== undefined)
38 changes: 36 additions & 2 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@
const { NativeModule } = require('internal/bootstrap/loaders');
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
const {
stripShebang
stripShebang,
stripBOM
} = require('internal/modules/cjs/helpers');
const CJSModule = require('internal/modules/cjs/loader');
const internalURLModule = require('internal/url');
@@ -13,14 +14,15 @@ const fs = require('fs');
const {
SafeMap,
} = primordials;
const { URL } = require('url');
const { fileURLToPath, URL } = require('url');
const { debuglog, promisify } = require('util');
const esmLoader = require('internal/process/esm_loader');
const {
ERR_UNKNOWN_BUILTIN_MODULE
} = require('internal/errors').codes;
const readFileAsync = promisify(fs.readFile);
const StringReplace = Function.call.bind(String.prototype.replace);
const JsonParse = JSON.parse;

const debug = debuglog('esm');

@@ -99,3 +101,35 @@ translators.set('builtin', async function(url) {
reflect.exports.default.set(module.exports);
});
});

// Strategy for loading a JSON file
translators.set('json', async (url) => {
debug(`Translating JSONModule ${url}`);
debug(`Loading JSONModule ${url}`);
const pathname = fileURLToPath(url);
const modulePath = isWindows ?
StringReplace(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 content = await readFileAsync(pathname, 'utf-8');
try {
const exports = JsonParse(stripBOM(content));
module = {
exports,
loaded: true
};
} catch (err) {
err.message = pathname + ': ' + err.message;
throw err;
}
CJSModule._cache[modulePath] = module;
return createDynamicModule(['default'], url, (reflect) => {
debug(`Parsing JSONModule ${url}`);
reflect.exports.default.set(module.exports);
});
});
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
@@ -214,6 +214,10 @@ DebugOptionsParser::DebugOptionsParser() {
}

EnvironmentOptionsParser::EnvironmentOptionsParser() {
AddOption("--experimental-json-modules",
"experimental JSON interop support for the ES Module loader",
&EnvironmentOptions::experimental_json_modules,
kAllowedInEnvironment);
AddOption("--experimental-modules",
"experimental ES Module support and caching modules",
&EnvironmentOptions::experimental_modules,
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
@@ -91,6 +91,7 @@ class DebugOptions : public Options {
class EnvironmentOptions : public Options {
public:
bool abort_on_uncaught_exception = false;
bool experimental_json_modules = false;
bool experimental_modules = false;
std::string es_module_specifier_resolution = "explicit";
std::string module_type;
26 changes: 26 additions & 0 deletions test/es-module/test-esm-json-cache.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Flags: --experimental-modules --experimental-json-modules
/* eslint-disable node-core/required-modules */
import '../common/index.mjs';

import { strictEqual, deepStrictEqual } from 'assert';

import { createRequireFromPath as createRequire } from 'module';
import { fileURLToPath as fromURL } from 'url';

import mod from '../fixtures/es-modules/json-cache/mod.cjs';
import another from '../fixtures/es-modules/json-cache/another.cjs';
import test from '../fixtures/es-modules/json-cache/test.json';

const require = createRequire(fromURL(import.meta.url));

const modCjs = require('../fixtures/es-modules/json-cache/mod.cjs');
const anotherCjs = require('../fixtures/es-modules/json-cache/another.cjs');
const testCjs = require('../fixtures/es-modules/json-cache/test.json');

strictEqual(mod.one, 1);
strictEqual(another.one, 'zalgo');
strictEqual(test.one, 'it comes');

deepStrictEqual(mod, modCjs);
deepStrictEqual(another, anotherCjs);
deepStrictEqual(test, testCjs);
9 changes: 9 additions & 0 deletions test/es-module/test-esm-json.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Flags: --experimental-modules --experimental-json-modules
/* eslint-disable node-core/required-modules */

import '../common/index.mjs';
import { strictEqual } from 'assert';

import secret from '../fixtures/experimental.json';

strictEqual(secret.ofLife, 42);
7 changes: 7 additions & 0 deletions test/fixtures/es-modules/json-cache/another.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const test = require('./test.json');

module.exports = {
...test
};

test.one = 'it comes';
7 changes: 7 additions & 0 deletions test/fixtures/es-modules/json-cache/mod.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const test = require('./test.json');

module.exports = {
...test
};

test.one = 'zalgo';
5 changes: 5 additions & 0 deletions test/fixtures/es-modules/json-cache/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"one": 1,
"two": 2,
"three": 3
}
3 changes: 3 additions & 0 deletions test/fixtures/experimental.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"ofLife": 42
}

0 comments on commit 866fffa

Please sign in to comment.