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 6, 2019
1 parent 49a38bb commit 842ce61
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 3 deletions.
13 changes: 12 additions & 1 deletion 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 @@ -29,11 +30,20 @@ const legacyExtensionFormatMap = {
'__proto__': null,
'.cjs': 'commonjs',
'.js': 'commonjs',
'.json': 'commonjs',
'.mjs': 'module',
'.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 Expand Up @@ -103,6 +113,7 @@ function getModuleFormat(url, isMain, parentURL) {
return format;
}


function resolve(specifier, parentURL) {
if (NativeModule.canBeRequiredByUsers(specifier)) {
return {
Expand Down
24 changes: 22 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,21 @@ 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 content = await readFileAsync(pathname, 'utf-8');
return createDynamicModule(['default'], url, (reflect) => {
debug(`Parsing JSONModule ${url}`);
try {
const exports = JsonParse(stripBOM(content));
reflect.exports.default.set(exports);
} catch (err) {
err.message = pathname + ': ' + err.message;
throw err;
}
});
});
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,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
12 changes: 12 additions & 0 deletions test/es-module/test-esm-json.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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';
import * as namespace from '../fixtures/experimental.json';

strictEqual(secret.ofLife, 42);
strictEqual(namespace.ofLife, undefined);
strictEqual(namespace.default.ofLife, 42);
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 842ce61

Please sign in to comment.