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

Commit 2408085

Browse files
committed
esm: add experimental .json support to loader
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
1 parent ffa64db commit 2408085

File tree

10 files changed

+109
-2
lines changed

10 files changed

+109
-2
lines changed

lib/internal/modules/esm/default_resolve.js

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
1010
const { ERR_INVALID_PACKAGE_CONFIG,
1111
ERR_TYPE_MISMATCH,
1212
ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
13+
const experimentalJsonModules = getOptionValue('--experimental-json-modules');
1314
const { resolve: moduleWrapResolve } = internalBinding('module_wrap');
1415
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
1516
const asyncESM = require('internal/process/esm_loader');
@@ -34,6 +35,16 @@ const legacyExtensionFormatMap = {
3435
'.node': 'commonjs'
3536
};
3637

38+
if (experimentalJsonModules) {
39+
// This is a total hack
40+
Object.assign(extensionFormatMap, {
41+
'.json': 'json'
42+
});
43+
Object.assign(legacyExtensionFormatMap, {
44+
'.json': 'json'
45+
});
46+
}
47+
3748
function readPackageConfig(path, parentURL) {
3849
const existing = pjsonCache.get(path);
3950
if (existing !== undefined)

lib/internal/modules/esm/translators.js

+36-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
const { NativeModule } = require('internal/bootstrap/loaders');
44
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
55
const {
6-
stripShebang
6+
stripShebang,
7+
stripBOM
78
} = require('internal/modules/cjs/helpers');
89
const CJSModule = require('internal/modules/cjs/loader');
910
const internalURLModule = require('internal/url');
@@ -13,14 +14,15 @@ const fs = require('fs');
1314
const {
1415
SafeMap,
1516
} = primordials;
16-
const { URL } = require('url');
17+
const { fileURLToPath, URL } = require('url');
1718
const { debuglog, promisify } = require('util');
1819
const esmLoader = require('internal/process/esm_loader');
1920
const {
2021
ERR_UNKNOWN_BUILTIN_MODULE
2122
} = require('internal/errors').codes;
2223
const readFileAsync = promisify(fs.readFile);
2324
const StringReplace = Function.call.bind(String.prototype.replace);
25+
const JsonParse = JSON.parse;
2426

2527
const debug = debuglog('esm');
2628

@@ -99,3 +101,35 @@ translators.set('builtin', async function(url) {
99101
reflect.exports.default.set(module.exports);
100102
});
101103
});
104+
105+
// Strategy for loading a JSON file
106+
translators.set('json', async (url) => {
107+
debug(`Translating JSONModule ${url}`);
108+
debug(`Loading JSONModule ${url}`);
109+
const pathname = fileURLToPath(url);
110+
const modulePath = isWindows ?
111+
StringReplace(pathname, winSepRegEx, '\\') : pathname;
112+
let module = CJSModule._cache[modulePath];
113+
if (module && module.loaded) {
114+
const exports = module.exports;
115+
return createDynamicModule(['default'], url, (reflect) => {
116+
reflect.exports.default.set(exports);
117+
});
118+
}
119+
const content = await readFileAsync(pathname, 'utf-8');
120+
try {
121+
const exports = JsonParse(stripBOM(content));
122+
module = {
123+
exports,
124+
loaded: true
125+
};
126+
} catch (err) {
127+
err.message = pathname + ': ' + err.message;
128+
throw err;
129+
}
130+
CJSModule._cache[modulePath] = module;
131+
return createDynamicModule(['default'], url, (reflect) => {
132+
debug(`Parsing JSONModule ${url}`);
133+
reflect.exports.default.set(module.exports);
134+
});
135+
});

src/node_options.cc

+4
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ DebugOptionsParser::DebugOptionsParser() {
214214
}
215215

216216
EnvironmentOptionsParser::EnvironmentOptionsParser() {
217+
AddOption("--experimental-json-modules",
218+
"experimental JSON interop support for the ES Module loader",
219+
&EnvironmentOptions::experimental_json_modules,
220+
kAllowedInEnvironment);
217221
AddOption("--experimental-modules",
218222
"experimental ES Module support and caching modules",
219223
&EnvironmentOptions::experimental_modules,

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class DebugOptions : public Options {
9191
class EnvironmentOptions : public Options {
9292
public:
9393
bool abort_on_uncaught_exception = false;
94+
bool experimental_json_modules = false;
9495
bool experimental_modules = false;
9596
std::string es_module_specifier_resolution = "explicit";
9697
std::string module_type;
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Flags: --experimental-modules --experimental-json-modules
2+
/* eslint-disable node-core/required-modules */
3+
import '../common/index.mjs';
4+
5+
import { strictEqual, deepStrictEqual } from 'assert';
6+
7+
import { createRequireFromPath as createRequire } from 'module';
8+
import { fileURLToPath as fromURL } from 'url';
9+
10+
import mod from '../fixtures/es-modules/json-cache/mod.cjs';
11+
import another from '../fixtures/es-modules/json-cache/another.cjs';
12+
import test from '../fixtures/es-modules/json-cache/test.json';
13+
14+
const require = createRequire(fromURL(import.meta.url));
15+
16+
const modCjs = require('../fixtures/es-modules/json-cache/mod.cjs');
17+
const anotherCjs = require('../fixtures/es-modules/json-cache/another.cjs');
18+
const testCjs = require('../fixtures/es-modules/json-cache/test.json');
19+
20+
strictEqual(mod.one, 1);
21+
strictEqual(another.one, 'zalgo');
22+
strictEqual(test.one, 'it comes');
23+
24+
deepStrictEqual(mod, modCjs);
25+
deepStrictEqual(another, anotherCjs);
26+
deepStrictEqual(test, testCjs);

test/es-module/test-esm-json.mjs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Flags: --experimental-modules --experimental-json-modules
2+
/* eslint-disable node-core/required-modules */
3+
4+
import '../common/index.mjs';
5+
import { strictEqual } from 'assert';
6+
7+
import secret from '../fixtures/experimental.json';
8+
9+
strictEqual(secret.ofLife, 42);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const test = require('./test.json');
2+
3+
module.exports = {
4+
...test
5+
};
6+
7+
test.one = 'it comes';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const test = require('./test.json');
2+
3+
module.exports = {
4+
...test
5+
};
6+
7+
test.one = 'zalgo';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"one": 1,
3+
"two": 2,
4+
"three": 3
5+
}

test/fixtures/experimental.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"ofLife": 42
3+
}

0 commit comments

Comments
 (0)