@contrast/esm-hooks 2.4.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/common.mjs +36 -55
- package/lib/get-file-type.mjs +23 -29
- package/lib/hooks.mjs +107 -84
- package/lib/index.mjs +3 -3
- package/lib/loader-agent.mjs +23 -7
- package/package.json +3 -3
package/lib/common.mjs
CHANGED
|
@@ -12,51 +12,17 @@
|
|
|
12
12
|
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
// @ts-check
|
|
16
16
|
import { readdir } from 'node:fs/promises';
|
|
17
17
|
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
19
|
|
|
19
20
|
const REDIRECTS_PATH = './redirects';
|
|
20
21
|
|
|
21
22
|
export const mappings = await makeMappings();
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
// the load() hook receives an URL. When the URL is converted into an URL object,
|
|
27
|
-
// the pathname property will be '/C:/Users/.../redirects' on windows. And that
|
|
28
|
-
// doesn't start with a drive letter, so when fsp.readdir() gets it, node decides
|
|
29
|
-
// to add a drive letter: 'C:/C:/Users/.../redirects'. So we have to strip off the
|
|
30
|
-
// the leading / so the drive letter is recognized and another isn't added. it seems
|
|
31
|
-
// like a node bug (or possibly windows bug) but i don't have time to research the
|
|
32
|
-
// standards to figure out what URL.pathname should be in a windows file: context.
|
|
33
|
-
//
|
|
34
|
-
// maybe this should be in the load() hook?
|
|
35
|
-
//
|
|
36
|
-
// should
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
* @param {string} path
|
|
40
|
-
* @returns {string}
|
|
41
|
-
*/
|
|
42
|
-
export function fixPath(p) {
|
|
43
|
-
if (p.match(/^\/[A-Z]:/)) {
|
|
44
|
-
p = p.slice(1);
|
|
45
|
-
}
|
|
46
|
-
return p;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
import { getFileType } from './get-file-type.mjs';
|
|
50
|
-
export { getFileType };
|
|
51
|
-
|
|
52
|
-
async function makeMappings() {
|
|
53
|
-
/**
|
|
54
|
-
* @typedef { 'builtin' | 'commonjs' | 'module' } TargetType
|
|
55
|
-
* @typedef {string} RequireSpecifier
|
|
56
|
-
*/
|
|
57
|
-
/**
|
|
58
|
-
* @type {Record<RequireSpecifier, {url: URL, target: TargetType}>}
|
|
59
|
-
*/
|
|
24
|
+
export async function makeMappings() {
|
|
25
|
+
/** @type {Record<string, string>} */
|
|
60
26
|
const mappings = Object.create(null);
|
|
61
27
|
|
|
62
28
|
// the name of the directory is the format of the target being loaded. so "cjs"
|
|
@@ -80,12 +46,15 @@ async function makeMappings() {
|
|
|
80
46
|
|
|
81
47
|
// get an absolute path because reading the redirect file is going to be executed
|
|
82
48
|
// in another context.
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
49
|
+
const redirectsPath = path.resolve(
|
|
50
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
51
|
+
REDIRECTS_PATH,
|
|
52
|
+
dir,
|
|
53
|
+
);
|
|
86
54
|
|
|
87
|
-
await recursiveReaddir(
|
|
55
|
+
await recursiveReaddir(redirectsPath);
|
|
88
56
|
|
|
57
|
+
/** @param {string} dirpath */
|
|
89
58
|
// eslint-disable-next-line no-inner-declarations
|
|
90
59
|
async function recursiveReaddir(dirpath) {
|
|
91
60
|
const dirents = await readdir(dirpath, { withFileTypes: true });
|
|
@@ -101,13 +70,15 @@ async function makeMappings() {
|
|
|
101
70
|
if (!dirent.name.endsWith('.mjs')) {
|
|
102
71
|
continue;
|
|
103
72
|
}
|
|
104
|
-
// it a file that ends with .mjs, so it's a redirect file.
|
|
105
|
-
const redirectURL =
|
|
73
|
+
// it's a file that ends with .mjs, so it's a redirect file.
|
|
74
|
+
const redirectURL = pathToFileURL(path.join(dirpath, dirent.name));
|
|
106
75
|
|
|
107
|
-
// all redirects point to .mjs files
|
|
108
|
-
// the
|
|
109
|
-
//
|
|
110
|
-
// e.g.,
|
|
76
|
+
// all redirects point to .mjs files.
|
|
77
|
+
// the 'csi-flag' query specifies the type of file that will be loaded
|
|
78
|
+
// by the redirecting .mjs file:
|
|
79
|
+
// e.g.,
|
|
80
|
+
// 'node-fetch': 'file://.../redirects/esm/node-fetch.mjs?csi-flag=module`
|
|
81
|
+
// 'fs/promises': 'file://.../redirects/builtin/fs/promises.mjs?csi-flag=builtin`,
|
|
111
82
|
//
|
|
112
83
|
// there is a separate builtin directory because putting a colon in a filename doesn't work on windows. so
|
|
113
84
|
// that's why the mapping below adds the `node:` prefix.
|
|
@@ -115,13 +86,26 @@ async function makeMappings() {
|
|
|
115
86
|
if (pathStack.length) {
|
|
116
87
|
name = `${pathStack.join('/')}/${name}`;
|
|
117
88
|
}
|
|
89
|
+
|
|
90
|
+
// set flag to module or commonjs. i'm not sure this is needed but am keeping it
|
|
91
|
+
// in place until we have to implement esm-native module rewrites/wrapping. this
|
|
92
|
+
// is the point at which resolve() communicates to load().
|
|
93
|
+
//
|
|
94
|
+
// builtin's are probably the most likely to be loaded, so they're first.
|
|
95
|
+
// some tweaks might be needed when we start to patch esm-native modules
|
|
96
|
+
// in esm-native-code:
|
|
97
|
+
// https://nodejs.org/docs/latest-v20.x/api/esm.html#builtin-modules
|
|
98
|
+
// https://nodejs.org/docs/latest-v20.x/api/module.html#modulesyncbuiltinesmexports
|
|
118
99
|
if (dir === 'builtin') {
|
|
119
|
-
|
|
120
|
-
mappings[
|
|
100
|
+
redirectURL.searchParams.set('csi-flag', 'builtin');
|
|
101
|
+
mappings[name] = redirectURL.href;
|
|
102
|
+
mappings[`node:${name}`] = redirectURL.href;
|
|
121
103
|
} else if (dir === 'cjs') {
|
|
122
|
-
|
|
104
|
+
redirectURL.searchParams.set('csi-flag', 'commonjs');
|
|
105
|
+
mappings[name] = redirectURL.href;
|
|
123
106
|
} else if (dir === 'esm') {
|
|
124
|
-
|
|
107
|
+
redirectURL.searchParams.set('csi-flag', 'module');
|
|
108
|
+
mappings[name] = redirectURL.href;
|
|
125
109
|
} else {
|
|
126
110
|
throw new Error(`target type ${dir} not yet implemented`);
|
|
127
111
|
}
|
|
@@ -131,6 +115,3 @@ async function makeMappings() {
|
|
|
131
115
|
|
|
132
116
|
return mappings;
|
|
133
117
|
}
|
|
134
|
-
|
|
135
|
-
// useful for tests
|
|
136
|
-
export { makeMappings };
|
package/lib/get-file-type.mjs
CHANGED
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
-
import
|
|
16
|
-
import path from 'path';
|
|
17
|
-
import M from 'module';
|
|
18
|
-
|
|
19
|
-
import {
|
|
15
|
+
import { readFile } from 'node:fs/promises';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import M from 'node:module';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { findPackageJson } from '@contrast/find-package-json';
|
|
20
20
|
|
|
21
21
|
const isBuiltin = M.isBuiltin || function(pathname) {
|
|
22
22
|
if (pathname.startsWith('node:')) {
|
|
@@ -25,30 +25,26 @@ const isBuiltin = M.isBuiltin || function(pathname) {
|
|
|
25
25
|
return M.builtinModules.includes(pathname);
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
/** @typedef { 'builtin' | 'commonjs' | 'module' } Format */
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
|
-
*
|
|
30
|
-
* @param {
|
|
31
|
-
* @returns {
|
|
31
|
+
* @param {string | URL} filename
|
|
32
|
+
* @param {import('@contrast/find-package-json').Options["stopAt"]=} stopAt
|
|
33
|
+
* @returns {Promise<Format | null>}
|
|
32
34
|
*/
|
|
33
|
-
export function
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
export async function getFileType(filename, stopAt) {
|
|
36
|
+
try {
|
|
37
|
+
filename = fileURLToPath(filename);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// already a path or node: url
|
|
36
40
|
}
|
|
37
|
-
return p;
|
|
38
|
-
}
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
let pathname = filename.pathname || filename;
|
|
42
|
-
// presumes 16.17+
|
|
43
|
-
if (isBuiltin(pathname)) {
|
|
42
|
+
if (isBuiltin(filename)) {
|
|
44
43
|
return 'builtin';
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// if the file extension specifies the type, there's no need to do extra
|
|
50
|
-
// IO.
|
|
51
|
-
const ext = path.extname(pathname);
|
|
46
|
+
// if the file extension specifies the type, there's no need to do extra IO.
|
|
47
|
+
const ext = path.extname(filename);
|
|
52
48
|
if (ext === '.mjs') {
|
|
53
49
|
return 'module';
|
|
54
50
|
} else if (ext === '.cjs') {
|
|
@@ -58,14 +54,12 @@ export function getFileType(filename) {
|
|
|
58
54
|
// Node assumes `commonjs` if `type` is not set in package.json
|
|
59
55
|
let parentType = 'commonjs';
|
|
60
56
|
try {
|
|
61
|
-
|
|
62
|
-
// root of the application is?
|
|
63
|
-
const pkg = findPackageJsonSync({ cwd: fixPath(path.dirname(pathname)) });
|
|
57
|
+
const pkg = await findPackageJson({ cwd: filename, stopAt });
|
|
64
58
|
if (pkg) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
parentType =
|
|
59
|
+
const json = await readFile(pkg, 'utf8');
|
|
60
|
+
const { type } = JSON.parse(json);
|
|
61
|
+
if (type) {
|
|
62
|
+
parentType = type;
|
|
69
63
|
}
|
|
70
64
|
}
|
|
71
65
|
} catch (err) {
|
package/lib/hooks.mjs
CHANGED
|
@@ -13,29 +13,41 @@
|
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { readFile as rf } from 'node:fs/promises';
|
|
17
16
|
import * as process from 'node:process';
|
|
18
|
-
import {
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
|
+
import { mappings } from './common.mjs';
|
|
19
|
+
import { getFileType } from './get-file-type.mjs';
|
|
19
20
|
import { default as initLoaderAgent } from './loader-agent.mjs';
|
|
20
21
|
const [major, minor] = process.versions.node.split('.').map(it => +it);
|
|
21
22
|
const isLT16_12 = major < 16 || (major === 16 && minor < 12);
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} ResolveResult
|
|
26
|
+
* @prop {string=} format A hint to the load hook (it might be ignored) 'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
|
|
27
|
+
* @prop {Object=} importAssertions The import assertions to use when caching the module (optional; if excluded the input will be used) (before v21)
|
|
28
|
+
* @prop {Object=} importAttributes The import attributes to use when caching the module (optional; if excluded the input will be used) (after v21)
|
|
29
|
+
* @prop {boolean=} shortCircuit A signal that this hook intends to terminate the chain of `resolve` hooks. Default: `false`
|
|
30
|
+
* @prop {string} url The absolute URL to which this input resolves
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {Object} LoadResult
|
|
35
|
+
* @prop {string} format
|
|
36
|
+
* @prop {boolean=} shortCircuit A signal that this hook intends to terminate the chain of resolve hooks. Default: `false`
|
|
37
|
+
* @prop {string | ArrayBuffer | TypedArray} source The source for Node.js to evaluate
|
|
38
|
+
*/
|
|
24
39
|
|
|
25
40
|
/**
|
|
26
41
|
* Agent instance with minimum footprint that handles functionality related esm module loading.
|
|
27
42
|
* - We handles redirects to force require.
|
|
28
43
|
* - Module rewriting via exported load hook
|
|
44
|
+
* @type {import('./loader-agent.mjs').LoaderAgent=}
|
|
29
45
|
*/
|
|
30
46
|
let loaderAgent;
|
|
31
47
|
|
|
32
48
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* port: import('node:worker_threads').MessagePort,
|
|
36
|
-
* appInfo: import('@contrast/common').AppInfo,
|
|
37
|
-
* agentVersion: string,
|
|
38
|
-
* }} data
|
|
49
|
+
* Module.register callback
|
|
50
|
+
* @param {import('./loader-agent.mjs').InitData} data
|
|
39
51
|
*/
|
|
40
52
|
async function initialize(data = {}) {
|
|
41
53
|
loaderAgent = initLoaderAgent(data);
|
|
@@ -43,116 +55,127 @@ async function initialize(data = {}) {
|
|
|
43
55
|
await loaderAgent.install();
|
|
44
56
|
}
|
|
45
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} specifier
|
|
60
|
+
* @param {Object} context
|
|
61
|
+
* @param {string[]} context.conditions Export conditions of the relevant package.json
|
|
62
|
+
* @param {Object=} context.importAssertions An object whose key-value pairs represent the assertions for the module to import (before v21)
|
|
63
|
+
* @param {Object=} context.importAttributes An object whose key-value pairs represent the attributes for the module to import (after v21)
|
|
64
|
+
* @param {string=} context.parentURL The module importing this one, or undefined if this is the Node.js entry point
|
|
65
|
+
* @param {(specifier, context) => Promise<ResolveResult>} nextResolve The subsequent resolve hook in the chain, or the Node.js default resolve hook after the last user-supplied resolve hook
|
|
66
|
+
* @returns {Promise<ResolveResult>}
|
|
67
|
+
*/
|
|
46
68
|
async function resolve(specifier, context, nextResolve) {
|
|
47
69
|
loaderAgent?.esmHooks?.debugEsmResolve?.(specifier);
|
|
48
70
|
|
|
71
|
+
if (!loaderAgent?.enable) {
|
|
72
|
+
return protectedNextResolve(specifier, context, nextResolve);
|
|
73
|
+
}
|
|
74
|
+
|
|
49
75
|
let isFlaggedToPatch = false;
|
|
50
76
|
if (context.parentURL) {
|
|
51
|
-
isFlaggedToPatch = context.parentURL.
|
|
77
|
+
isFlaggedToPatch = new URL(context.parentURL).searchParams.has('csi-flag');
|
|
52
78
|
}
|
|
53
79
|
|
|
54
|
-
if (
|
|
55
|
-
// eslint-disable-next-line prefer-const
|
|
56
|
-
let { url, format, target } = mappings[specifier];
|
|
57
|
-
// set flag to module or commonjs. i'm not sure this is needed but am keeping it
|
|
58
|
-
// in place until we have to implement esm-native module rewrites/wrapping. this
|
|
59
|
-
// is the point at which resolve() communicates to load().
|
|
60
|
-
//
|
|
61
|
-
// builtin's are probably the most likely to be loaded, so they're first.
|
|
62
|
-
// some tweaks might be needed when we start to patch esm-native modules
|
|
63
|
-
// in esm-native-code:
|
|
64
|
-
// https://nodejs.org/docs/latest-v20.x/api/esm.html#builtin-modules
|
|
65
|
-
// https://nodejs.org/docs/latest-v20.x/api/module.html#modulesyncbuiltinesmexports
|
|
66
|
-
if (target === 'builtin') {
|
|
67
|
-
url = `${url.href}?csi-flag=m`;
|
|
68
|
-
format = 'module';
|
|
69
|
-
} else if (target === 'commonjs') {
|
|
70
|
-
url = `${url.href}?csi-flag=c`;
|
|
71
|
-
format = getFileType(url) || format || 'commonjs';
|
|
72
|
-
} else if (target === 'module') {
|
|
73
|
-
url = `${url.href}?csi-flag=m`;
|
|
74
|
-
format = getFileType(url) || format || 'module';
|
|
75
|
-
} else {
|
|
76
|
-
throw new Error(`unexpected target ${target} for ${specifier}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
80
|
+
if (!isFlaggedToPatch && specifier in mappings) {
|
|
79
81
|
return {
|
|
80
|
-
url,
|
|
81
|
-
format,
|
|
82
|
+
url: mappings[specifier],
|
|
83
|
+
format: 'module',
|
|
82
84
|
shortCircuit: true,
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
return protectedNextResolve(
|
|
88
|
+
return protectedNextResolve(specifier, context, nextResolve);
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} url The URL returned by the resolve chain
|
|
93
|
+
* @param {Object} context
|
|
94
|
+
* @param {string[]} context.conditions Export conditions of the relevant package.json
|
|
95
|
+
* @param {string=} context.format The format optionally supplied by the resolve hook chain
|
|
96
|
+
* @param {Object=} context.importAssertions (before v21)
|
|
97
|
+
* @param {Object=} context.importAttributes (after v21)
|
|
98
|
+
* @param {(url, context) => Promise<LoadResult>} nextLoad The subsequent load hook in the chain, or the Node.js default load hook after the last user-supplied load hook
|
|
99
|
+
* @returns {Promise<LoadResult>}
|
|
100
|
+
*/
|
|
89
101
|
async function load(url, context, nextLoad) {
|
|
90
102
|
loaderAgent?.esmHooks?.debugEsmLoad?.(url);
|
|
91
103
|
|
|
92
104
|
const urlObject = new URL(url);
|
|
105
|
+
const type = await getFileType(url, loaderAgent?.appInfo.app_dir);
|
|
93
106
|
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return
|
|
98
|
-
source,
|
|
99
|
-
format: 'module',
|
|
100
|
-
shortCircuit: true,
|
|
101
|
-
};
|
|
107
|
+
// if it's not a builtin or a flagged file, it needs to be rewritten.
|
|
108
|
+
// if it's not an es module it will be rewritten by the require hooks.
|
|
109
|
+
if (!loaderAgent?.enable || urlObject.searchParams.has('csi-flag') || type !== 'module') {
|
|
110
|
+
return nextLoad(url, context);
|
|
102
111
|
}
|
|
103
112
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
const filename = fileURLToPath(url);
|
|
114
|
+
|
|
115
|
+
// Handles the event that other hooks redirect an import to a `.node` addon.
|
|
116
|
+
if (filename.endsWith('.node')) {
|
|
107
117
|
return {
|
|
108
|
-
source: `
|
|
118
|
+
source: `module.exports = require("${filename}");`,
|
|
109
119
|
format: 'commonjs',
|
|
110
120
|
shortCircuit: true,
|
|
111
121
|
};
|
|
112
122
|
}
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (urlObject.pathname.match(/(.js|.mjs|.cjs)$/)) {
|
|
117
|
-
// if it's not a module it will be rewritten by the require hooks.
|
|
118
|
-
const type = getFileType(urlObject);
|
|
119
|
-
if (type !== 'module') {
|
|
120
|
-
return nextLoad(url, context);
|
|
121
|
-
}
|
|
124
|
+
if (loaderAgent.config.agent.node.rewrite.cache.enable) {
|
|
125
|
+
const cached = await loaderAgent.rewriter.cache.find(filename);
|
|
122
126
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
let result;
|
|
126
|
-
|
|
127
|
-
if (loaderAgent?.enable) {
|
|
128
|
-
const rewriteOptions = {
|
|
129
|
-
filename,
|
|
130
|
-
isModule: type === 'module',
|
|
131
|
-
inject: true,
|
|
132
|
-
wrap: type !== 'module', // cannot wrap modules
|
|
133
|
-
};
|
|
134
|
-
result = await loaderAgent.rewriter.rewrite(source, rewriteOptions);
|
|
135
|
-
|
|
136
|
-
if (process.env.CSI_EXPOSE_CORE) {
|
|
137
|
-
// only do this in testing scenarios (todo: compose this functionality into test agents instead)
|
|
138
|
-
loaderAgent.esmHooks.portClient.sendStatus('rewriting file during ESM load hook', rewriteOptions);
|
|
139
|
-
}
|
|
127
|
+
if (cached) {
|
|
128
|
+
return nextLoad(pathToFileURL(cached).href, context);
|
|
140
129
|
}
|
|
130
|
+
}
|
|
141
131
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
132
|
+
/** @type {import('@contrast/rewriter').RewriteOpts} */
|
|
133
|
+
const rewriteOptions = {
|
|
134
|
+
filename,
|
|
135
|
+
isModule: true,
|
|
136
|
+
inject: true,
|
|
137
|
+
wrap: false, // cannot wrap modules
|
|
138
|
+
trim: false,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const { source } = await nextLoad(url, context);
|
|
142
|
+
const rewriteResult = await loaderAgent.rewriter.rewrite(
|
|
143
|
+
source.toString(), // might be a Buffer
|
|
144
|
+
rewriteOptions
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (process.env.CSI_EXPOSE_CORE) {
|
|
148
|
+
// only do this in testing scenarios (todo: compose this functionality into test agents instead)
|
|
149
|
+
loaderAgent.esmHooks.portClient.sendStatus('rewriting file during ESM load hook', rewriteOptions);
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
if (loaderAgent.config.agent.node.rewrite.cache.enable) {
|
|
153
|
+
loaderAgent.rewriter.cache.write(filename, rewriteResult);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
source: rewriteResult.code,
|
|
158
|
+
format: 'module',
|
|
159
|
+
shortCircuit: true,
|
|
160
|
+
};
|
|
151
161
|
}
|
|
152
162
|
|
|
153
|
-
|
|
163
|
+
/**
|
|
164
|
+
* from https://github.com/iambumblehead/esmock/blob/main/src/esmockLoader.js#L89
|
|
165
|
+
*
|
|
166
|
+
* new versions of node: when multiple loaders are used and context
|
|
167
|
+
* is passed to nextResolve, the process crashes in a recursive call
|
|
168
|
+
* see: /esmock/issues/#48
|
|
169
|
+
*
|
|
170
|
+
* old versions of node: if context.parentURL is defined, and context
|
|
171
|
+
* is not passed to nextResolve, the tests fail
|
|
172
|
+
*
|
|
173
|
+
* later versions of node v16 include 'node-addons'
|
|
174
|
+
* @type {typeof resolve}
|
|
175
|
+
*/
|
|
176
|
+
async function protectedNextResolve(specifier, context, nextResolve) {
|
|
154
177
|
if (context.parentURL) {
|
|
155
|
-
if (context.conditions.at(-1) === 'node-addons' || context.importAssertions || isLT16_12) {
|
|
178
|
+
if (context.conditions.at(-1) === 'node-addons' || context.importAssertions || context.importAttributes || isLT16_12) {
|
|
156
179
|
return nextResolve(specifier, context);
|
|
157
180
|
}
|
|
158
181
|
}
|
package/lib/index.mjs
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
import Module from 'node:module';
|
|
17
17
|
import { MessageChannel } from 'node:worker_threads';
|
|
18
18
|
import { default as debugMethods } from './debug-methods.mjs';
|
|
19
|
-
import { initialize, load, resolve } from './hooks.mjs';
|
|
20
19
|
import { default as portClient } from './post-message/main-client.mjs';
|
|
21
20
|
|
|
22
21
|
export default function init(core) {
|
|
@@ -26,7 +25,6 @@ export default function init(core) {
|
|
|
26
25
|
} = new MessageChannel();
|
|
27
26
|
|
|
28
27
|
const esmHooks = core.esmHooks = {
|
|
29
|
-
hooks: { resolve, load }, // remove when all LTS versions support Module.register
|
|
30
28
|
getActiveModes() {
|
|
31
29
|
const modes = [];
|
|
32
30
|
if (core.config.getEffectiveValue('assess.enable')) {
|
|
@@ -53,12 +51,14 @@ export default function init(core) {
|
|
|
53
51
|
// Instantiate loader agent via register (if available) or manually.
|
|
54
52
|
// we can simplify when all LTS versions support register
|
|
55
53
|
if (Module.register) {
|
|
56
|
-
await Module.register(
|
|
54
|
+
await Module.register('./hooks.mjs', import.meta.url, {
|
|
57
55
|
data,
|
|
58
56
|
transferList: [workerPort],
|
|
59
57
|
});
|
|
60
58
|
} else {
|
|
59
|
+
const { initialize, resolve, load } = await import('./hooks.mjs');
|
|
61
60
|
await initialize(data);
|
|
61
|
+
esmHooks.hooks = { resolve, load };
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// these have side-effects, so compose during installation phase
|
package/lib/loader-agent.mjs
CHANGED
|
@@ -19,18 +19,34 @@ import { default as debugMethods } from './debug-methods.mjs';
|
|
|
19
19
|
import { default as portClient } from './post-message/loader-client.mjs';
|
|
20
20
|
const require = Module.createRequire(import.meta.url);
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} InitData
|
|
24
|
+
* @prop {import('@contrast/rewriter').Mode[]} modes
|
|
25
|
+
* @prop {import('node:worker_threads').MessagePort} port
|
|
26
|
+
* @prop {import('@contrast/common').AppInfo} appInfo
|
|
27
|
+
* @prop {string} agentVersion
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} LoaderAgent
|
|
32
|
+
* @prop {import('@contrast/common').Messages} messages
|
|
33
|
+
* @prop {import('@contrast/common').AppInfo} appInfo
|
|
34
|
+
* @prop {string} agentVersion
|
|
35
|
+
* @prop {boolean} enable
|
|
36
|
+
* @prop {Record<never, never>} esmHooks
|
|
37
|
+
* @prop {import('@contrast/config').Config} config
|
|
38
|
+
* @prop {import('@contrast/logger').Logger} logger
|
|
39
|
+
* @prop {import('@contrast/rewriter').Rewriter} rewriter
|
|
40
|
+
*/
|
|
41
|
+
|
|
23
42
|
const ERROR_MESSAGE = 'An error prevented the Contrast agent from initializing in the loader thread.';
|
|
24
43
|
|
|
25
44
|
/**
|
|
26
|
-
* @param {
|
|
27
|
-
*
|
|
28
|
-
* port: import('node:worker_threads').MessagePort,
|
|
29
|
-
* appInfo: import('@contrast/common').AppInfo,
|
|
30
|
-
* agentVersion: string,
|
|
31
|
-
* }} data
|
|
45
|
+
* @param {InitData} data
|
|
46
|
+
* @returns {LoaderAgent}
|
|
32
47
|
*/
|
|
33
48
|
export default function init({ appInfo, agentVersion, port, modes }) {
|
|
49
|
+
/** @type {LoaderAgent} */
|
|
34
50
|
const core = {
|
|
35
51
|
appInfo,
|
|
36
52
|
agentVersion,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/esm-hooks",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Support for loading and instrumenting ECMAScript modules",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"test": "../scripts/test.sh"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@contrast/common": "1.
|
|
23
|
-
"@contrast/core": "1.
|
|
22
|
+
"@contrast/common": "1.20.0",
|
|
23
|
+
"@contrast/core": "1.31.0",
|
|
24
24
|
"@contrast/find-package-json": "^1.0.0"
|
|
25
25
|
}
|
|
26
26
|
}
|