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

Commit a30dfda

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 ea59221 commit a30dfda

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,12 +14,13 @@ 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

2021
const readFileAsync = promisify(fs.readFile);
2122
const StringReplace = Function.call.bind(String.prototype.replace);
23+
const JsonParse = JSON.parse;
2224

2325
const debug = debuglog('esm');
2426

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

src/node_options.cc

+4
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ DebugOptionsParser::DebugOptionsParser() {
212212
}
213213

214214
EnvironmentOptionsParser::EnvironmentOptionsParser() {
215+
AddOption("--experimental-json-modules",
216+
"experimental JSON interop support for the ES Module loader",
217+
&EnvironmentOptions::experimental_json_modules,
218+
kAllowedInEnvironment);
215219
AddOption("--experimental-modules",
216220
"experimental ES Module support and caching modules",
217221
&EnvironmentOptions::experimental_modules,

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class DebugOptions : public Options {
8585
class EnvironmentOptions : public Options {
8686
public:
8787
bool abort_on_uncaught_exception = false;
88+
bool experimental_json_modules = false;
8889
bool experimental_modules = false;
8990
std::string es_module_specifier_resolution = "explicit";
9091
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)