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 committed Mar 8, 2019
1 parent 0237465 commit 2233250
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 3 deletions.
2 changes: 1 addition & 1 deletion doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ module.exports = 'cjs';

// esm.mjs
import { createRequireFromPath as createRequire } from 'module';
import { fileURLToPath as fromPath } from 'url';
import { fileURLToPath as fromUrl } from 'url';

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

Expand Down
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,12 +14,13 @@ 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 readFileAsync = promisify(fs.readFile);
const StringReplace = Function.call.bind(String.prototype.replace);
const JsonParse = JSON.parse;

const debug = debuglog('esm');

Expand Down Expand Up @@ -94,3 +96,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
Expand Up @@ -212,6 +212,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 @@ -85,6 +85,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 module_type;
std::string experimental_policy;
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 2233250

Please sign in to comment.