node/lib/internal/bootstrap/pre_execution.js
guybedford b1094dbe19
esm: phase two of new esm implementation
This PR updates the current `--experimental-modules` implementation
based on the work of the modules team  and reflects Phase 2 of our
new modules plan.

The largest differences from the current implementation include

* `packge.type` which can be either `module` or `commonjs`
  - `type: "commonjs"`:
    - `.js` is parsed as commonjs
    - default for entry point without an extension is commonjs
  - `type: "module"`:
    - `.js` is parsed as esm
    - does not support loading JSON or Native Module by default
    - default for entry point without an extension is esm
* `--entry-type=[mode]`
  - allows you set the type on entry point.
* A new file extension `.cjs`.
  - this is specifically to support importing commonjs in the
    `module` mode.
  - this is only in the esm loader, the commonjs loader remains
    untouched, but the extension will work in the old loader if you use
    the full file path.
* `--es-module-specifier-resolution=[type]`
  - options are `explicit` (default) and `node`
  - by default our loader will not allow for optional extensions in
    the import, the path for a module must include the extension if
    there is one
  - by default our loader will not allow for importing directories that
    have an index file
  - developers can use `--es-module-specifier-resolution=node` to
    enable the commonjs specifier resolution algorithm
  - This is not a “feature” but rather an implementation for
    experimentation. It is expected to change before the flag is
    removed
* `--experimental-json-loader`
  - the only way to import json when `"type": "module"`
  - when enable all `import 'thing.json'` will go through the
    experimental loader independent of mode
  - based on https://github.com/whatwg/html/issues/4315
* You can use `package.main` to set an entry point for a module
  - the file extensions used in main will be resolved based on the
    `type` of the module

Refs: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
Refs: https://github.com/GeoffreyBooth/node-import-file-specifier-resolution-proposal
Refs: https://github.com/nodejs/modules/pull/180
Refs: https://github.com/nodejs/ecmascript-modules/pull/6
Refs: https://github.com/nodejs/ecmascript-modules/pull/12
Refs: https://github.com/nodejs/ecmascript-modules/pull/28
Refs: https://github.com/nodejs/modules/issues/255
Refs: https://github.com/whatwg/html/issues/4315
Refs: https://github.com/w3c/webcomponents/issues/770
Co-authored-by: Myles Borins <MylesBorins@google.com>
Co-authored-by: John-David Dalton <john.david.dalton@gmail.com>
Co-authored-by: Evan Plaice <evanplaice@gmail.com>
Co-authored-by: Geoffrey Booth <webmaster@geoffreybooth.com>
Co-authored-by: Michaël Zasso <targos@protonmail.com>

PR-URL: https://github.com/nodejs/node/pull/26745
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Ben Coe <bencoe@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
2019-03-27 15:52:11 -04:00

366 lines
11 KiB
JavaScript

'use strict';
const { getOptionValue } = require('internal/options');
const { Buffer } = require('buffer');
function prepareMainThreadExecution() {
// Patch the process object with legacy properties and normalizations
patchProcessObject();
setupTraceCategoryState();
setupWarningHandler();
// Resolve the coverage directory to an absolute path, and
// overwrite process.env so that the original path gets passed
// to child processes even when they switch cwd.
if (process.env.NODE_V8_COVERAGE) {
process.env.NODE_V8_COVERAGE =
setupCoverageHooks(process.env.NODE_V8_COVERAGE);
}
setupDebugEnv();
// Only main thread receives signals.
setupSignalHandlers();
// Process initial diagnostic reporting configuration, if present.
initializeReport();
initializeReportSignalHandlers(); // Main-thread-only.
// If the process is spawned with env NODE_CHANNEL_FD, it's probably
// spawned by our child_process module, then initialize IPC.
// This attaches some internal event listeners and creates:
// process.send(), process.channel, process.connected,
// process.disconnect().
setupChildProcessIpcChannel();
// Load policy from disk and parse it.
initializePolicy();
// If this is a worker in cluster mode, start up the communication
// channel. This needs to be done before any user code gets executed
// (including preload modules).
initializeClusterIPC();
initializeDeprecations();
initializeFrozenIntrinsics();
initializeESMLoader();
loadPreloadModules();
}
function patchProcessObject() {
Object.defineProperty(process, 'argv0', {
enumerable: true,
configurable: false,
value: process.argv[0]
});
process.argv[0] = process.execPath;
// TODO(joyeecheung): most of these should be deprecated and removed,
// execpt some that we need to be able to mutate during run time.
addReadOnlyProcessAlias('_eval', '--eval');
addReadOnlyProcessAlias('_print_eval', '--print');
addReadOnlyProcessAlias('_syntax_check_only', '--check');
addReadOnlyProcessAlias('_forceRepl', '--interactive');
addReadOnlyProcessAlias('_preload_modules', '--require');
addReadOnlyProcessAlias('noDeprecation', '--no-deprecation');
addReadOnlyProcessAlias('noProcessWarnings', '--no-warnings');
addReadOnlyProcessAlias('traceProcessWarnings', '--trace-warnings');
addReadOnlyProcessAlias('throwDeprecation', '--throw-deprecation');
addReadOnlyProcessAlias('profProcess', '--prof-process');
addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation');
addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false);
addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false);
}
function addReadOnlyProcessAlias(name, option, enumerable = true) {
const value = getOptionValue(option);
if (value) {
Object.defineProperty(process, name, {
writable: false,
configurable: true,
enumerable,
value
});
}
}
function setupWarningHandler() {
const {
onWarning
} = require('internal/process/warning');
if (!getOptionValue('--no-warnings') &&
process.env.NODE_NO_WARNINGS !== '1') {
process.on('warning', onWarning);
}
}
// Setup User-facing NODE_V8_COVERAGE environment variable that writes
// ScriptCoverage to a specified file.
function setupCoverageHooks(dir) {
const originalReallyExit = process.reallyExit;
const cwd = require('internal/process/execution').tryGetCwd();
const { resolve } = require('path');
const coverageDirectory = resolve(cwd, dir);
const {
writeCoverage,
setCoverageDirectory
} = require('internal/profiler');
setCoverageDirectory(coverageDirectory);
process.on('exit', writeCoverage);
process.reallyExit = (code) => {
writeCoverage();
originalReallyExit(code);
};
return coverageDirectory;
}
function initializeReport() {
if (!getOptionValue('--experimental-report')) {
return;
}
const { report } = require('internal/process/report');
const { emitExperimentalWarning } = require('internal/util');
Object.defineProperty(process, 'report', {
enumerable: false,
configurable: true,
get() {
emitExperimentalWarning('report');
return report;
}
});
}
function setupDebugEnv() {
require('internal/util/debuglog').initializeDebugEnv(process.env.NODE_DEBUG);
if (getOptionValue('--expose-internals')) {
require('internal/bootstrap/loaders').NativeModule.exposeInternals();
}
}
function setupSignalHandlers() {
const {
createSignalHandlers
} = require('internal/process/main_thread_only');
const {
startListeningIfSignal,
stopListeningIfSignal
} = createSignalHandlers();
process.on('newListener', startListeningIfSignal);
process.on('removeListener', stopListeningIfSignal);
}
// This has to be called after both initializeReport() and
// setupSignalHandlers() are called
function initializeReportSignalHandlers() {
if (!getOptionValue('--experimental-report')) {
return;
}
const { addSignalHandler } = require('internal/process/report');
addSignalHandler();
}
function setupTraceCategoryState() {
const { isTraceCategoryEnabled } = internalBinding('trace_events');
const { toggleTraceCategoryState } = require('internal/process/per_thread');
toggleTraceCategoryState(isTraceCategoryEnabled('node.async_hooks'));
}
// In general deprecations are intialized wherever the APIs are implemented,
// this is used to deprecate APIs implemented in C++ where the deprecation
// utitlities are not easily accessible.
function initializeDeprecations() {
const { deprecate } = require('internal/util');
const pendingDeprecation = getOptionValue('--pending-deprecation');
// DEP0103: access to `process.binding('util').isX` type checkers
// TODO(addaleax): Turn into a full runtime deprecation.
const utilBinding = internalBinding('util');
const types = require('internal/util/types');
for (const name of [
'isArrayBuffer',
'isArrayBufferView',
'isAsyncFunction',
'isDataView',
'isDate',
'isExternal',
'isMap',
'isMapIterator',
'isNativeError',
'isPromise',
'isRegExp',
'isSet',
'isSetIterator',
'isTypedArray',
'isUint8Array',
'isAnyArrayBuffer'
]) {
utilBinding[name] = pendingDeprecation ?
deprecate(types[name],
'Accessing native typechecking bindings of Node ' +
'directly is deprecated. ' +
`Please use \`util.types.${name}\` instead.`,
'DEP0103') :
types[name];
}
// TODO(joyeecheung): this is a legacy property exposed to process.
// Now that we use the config binding to carry this information, remove
// it from the process. We may consider exposing it properly in
// process.features.
const { noBrowserGlobals } = internalBinding('config');
if (noBrowserGlobals) {
Object.defineProperty(process, '_noBrowserGlobals', {
writable: false,
enumerable: true,
configurable: true,
value: noBrowserGlobals
});
}
if (pendingDeprecation) {
process.binding = deprecate(process.binding,
'process.binding() is deprecated. ' +
'Please use public APIs instead.', 'DEP0111');
}
// Create global.process and global.Buffer as getters so that we have a
// deprecation path for these in ES Modules.
// See https://github.com/nodejs/node/pull/26334.
let _process = process;
Object.defineProperty(global, 'process', {
get() {
return _process;
},
set(value) {
_process = value;
},
enumerable: false,
configurable: true
});
let _Buffer = Buffer;
Object.defineProperty(global, 'Buffer', {
get() {
return _Buffer;
},
set(value) {
_Buffer = value;
},
enumerable: false,
configurable: true
});
}
function setupChildProcessIpcChannel() {
if (process.env.NODE_CHANNEL_FD) {
const assert = require('internal/assert');
const fd = parseInt(process.env.NODE_CHANNEL_FD, 10);
assert(fd >= 0);
// Make sure it's not accidentally inherited by child processes.
delete process.env.NODE_CHANNEL_FD;
require('child_process')._forkChild(fd);
assert(process.send);
}
}
function initializeClusterIPC() {
if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
const cluster = require('cluster');
cluster._setupWorker();
// Make sure it's not accidentally inherited by child processes.
delete process.env.NODE_UNIQUE_ID;
}
}
function initializePolicy() {
const experimentalPolicy = getOptionValue('--experimental-policy');
if (experimentalPolicy) {
process.emitWarning('Policies are experimental.',
'ExperimentalWarning');
const { pathToFileURL, URL } = require('url');
// URL here as it is slightly different parsing
// no bare specifiers for now
let manifestURL;
if (require('path').isAbsolute(experimentalPolicy)) {
manifestURL = new URL(`file:///${experimentalPolicy}`);
} else {
const cwdURL = pathToFileURL(process.cwd());
cwdURL.pathname += '/';
manifestURL = new URL(experimentalPolicy, cwdURL);
}
const fs = require('fs');
const src = fs.readFileSync(manifestURL, 'utf8');
require('internal/process/policy')
.setup(src, manifestURL.href);
}
}
function initializeESMLoader() {
const experimentalModules = getOptionValue('--experimental-modules');
const experimentalVMModules = getOptionValue('--experimental-vm-modules');
if (experimentalModules || experimentalVMModules) {
if (experimentalModules) {
process.emitWarning(
'The ESM module loader is experimental.',
'ExperimentalWarning', undefined);
}
const {
setImportModuleDynamicallyCallback,
setInitializeImportMetaObjectCallback
} = internalBinding('module_wrap');
const esm = require('internal/process/esm_loader');
// Setup per-isolate callbacks that locate data or callbacks that we keep
// track of for different ESM modules.
setInitializeImportMetaObjectCallback(esm.initializeImportMetaObject);
setImportModuleDynamicallyCallback(esm.importModuleDynamicallyCallback);
const userLoader = getOptionValue('--loader');
// If --loader is specified, create a loader with user hooks. Otherwise
// create the default loader.
if (userLoader) {
const { emitExperimentalWarning } = require('internal/util');
emitExperimentalWarning('--loader');
}
esm.initializeLoader(process.cwd(), userLoader);
}
}
function initializeFrozenIntrinsics() {
if (getOptionValue('--frozen-intrinsics')) {
process.emitWarning('The --frozen-intrinsics flag is experimental',
'ExperimentalWarning');
require('internal/freeze_intrinsics')();
}
}
function loadPreloadModules() {
// For user code, we preload modules if `-r` is passed
const preloadModules = getOptionValue('--require');
if (preloadModules) {
const {
_preloadModules
} = require('internal/modules/cjs/loader');
_preloadModules(preloadModules);
}
}
module.exports = {
patchProcessObject,
setupCoverageHooks,
setupWarningHandler,
setupDebugEnv,
prepareMainThreadExecution,
initializeDeprecations,
initializeESMLoader,
initializeFrozenIntrinsics,
loadPreloadModules,
setupTraceCategoryState,
initializeReport
};