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

Commit

Permalink
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
  • Loading branch information
MylesBorins authored and nodejs-ci committed Mar 23, 2019
1 parent ea06ef2 commit 4fb92d7
Show file tree
Hide file tree
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
Expand Up @@ -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');
Expand All @@ -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)
Expand Down
38 changes: 36 additions & 2 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');

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

// Strategy for loading a JSON file
translators.set('json', async function jsonStrategy(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
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
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 4fb92d7

Please sign in to comment.