node-esm-mock 0.1.0 → 0.2.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/README.md +4 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.js +61 -77
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# esm-mock
|
|
1
|
+
# node-esm-mock
|
|
2
2
|
|
|
3
3
|
Mock ES modules in Node.js using `registerHooks` (Node >=22.7.0).
|
|
4
4
|
|
|
@@ -7,13 +7,13 @@ No loader flags, no `--experimental-loader`.
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install esm-mock
|
|
10
|
+
npm install node-esm-mock
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import { mock } from 'esm-mock';
|
|
16
|
+
import { mock } from 'node-esm-mock';
|
|
17
17
|
import assert from 'node:assert';
|
|
18
18
|
import sinon from 'sinon';
|
|
19
19
|
|
|
@@ -71,7 +71,7 @@ assert.ok(Worker instanceof worker_threads.Worker);
|
|
|
71
71
|
|
|
72
72
|
## How it works
|
|
73
73
|
|
|
74
|
-
`esm-mock` registers a [`registerHooks`](https://nodejs.org/api/module.html#moduleregisterhooksoptions) instance at module scope that intercepts `resolve` and `load` for any module URL registered via `add()`. When a mocked module is requested, resolution short-circuits to a synthetic `mock-facade:` URL and `load` serves auto-generated ES module source that re-exports the replacement values.
|
|
74
|
+
`node-esm-mock` registers a [`registerHooks`](https://nodejs.org/api/module.html#moduleregisterhooksoptions) instance at module scope that intercepts `resolve` and `load` for any module URL registered via `add()`. When a mocked module is requested, resolution short-circuits to a synthetic `mock-facade:` URL and `load` serves auto-generated ES module source that re-exports the replacement values.
|
|
75
75
|
|
|
76
76
|
Each `mock(mocks).for(specifier)` call:
|
|
77
77
|
1. Registers each mock entry via the internal `add()` function.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare function mock(
|
|
3
|
-
for<T = any>(specifier: string): Promise<T>;
|
|
1
|
+
export declare const mocksFor: Map<string, Map<string, any>>;
|
|
2
|
+
export declare function mock(modules?: Record<string, any>): {
|
|
3
|
+
for<T = any>(specifier: string, keep?: boolean): Promise<T>;
|
|
4
4
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,94 +1,78 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { registerHooks } from 'node:module';
|
|
2
|
-
const mockedModuleExports = new Map();
|
|
3
|
-
let mainImportURL = import.meta.url;
|
|
4
11
|
registerHooks({
|
|
5
|
-
resolve(specifier, context,
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
resolve(specifier, context, nextResolver) {
|
|
13
|
+
const resolved = nextResolver(specifier, context);
|
|
14
|
+
const mocksForSpecifier = mocksFor.get(specifier);
|
|
15
|
+
if (mocksForSpecifier) {
|
|
16
|
+
mocksFor.set(resolved.url, mocksForSpecifier);
|
|
17
|
+
}
|
|
18
|
+
if (context.parentURL) {
|
|
19
|
+
const mocksForParentURL = mocksFor.get(context.parentURL);
|
|
20
|
+
if (mocksForParentURL) {
|
|
21
|
+
const scope = context.parentURL.split('?')[1];
|
|
22
|
+
const resolvedUrl = `${resolved.url}?${scope}`;
|
|
23
|
+
if (mocksForParentURL.has(resolvedUrl)) {
|
|
24
|
+
return {
|
|
25
|
+
url: resolvedUrl, format: 'mocked', importAttributes: {
|
|
26
|
+
parentURL: context.parentURL,
|
|
27
|
+
}, shortCircuit: true
|
|
28
|
+
};
|
|
29
|
+
}
|
|
13
30
|
}
|
|
14
31
|
}
|
|
15
|
-
return
|
|
16
|
-
}, load(url, context,
|
|
17
|
-
if (
|
|
18
|
-
const
|
|
19
|
-
return {
|
|
20
|
-
shortCircuit: true, source: generateModule(encodedTargetURL), format: 'module',
|
|
21
|
-
};
|
|
32
|
+
return resolved;
|
|
33
|
+
}, load(url, context, nextLoader) {
|
|
34
|
+
if (context.format === 'mocked') {
|
|
35
|
+
const source = generateModule(context.importAttributes.parentURL, url);
|
|
36
|
+
return { source, format: 'module', shortCircuit: true };
|
|
22
37
|
}
|
|
23
|
-
return
|
|
38
|
+
return nextLoader(url, context);
|
|
24
39
|
}
|
|
25
40
|
});
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
export const mocksFor = new Map();
|
|
42
|
+
function generateModule(parent, url) {
|
|
28
43
|
const body = [
|
|
29
|
-
`import {
|
|
30
|
-
|
|
31
|
-
'let mapping = {__proto__: null};',
|
|
32
|
-
`const mock = mockedModules.get(${JSON.stringify(encodedTargetURL)});`,
|
|
44
|
+
`import {mocksFor} from ${JSON.stringify(import.meta.url)};`,
|
|
45
|
+
`const exports = mocksFor.get(${JSON.stringify(parent)});`,
|
|
33
46
|
];
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
for (var k in mapping) {
|
|
42
|
-
mapping[k] = mock.namespace[k];
|
|
43
|
-
}
|
|
44
|
-
});`);
|
|
45
|
-
return body.join('\n');
|
|
46
|
-
}
|
|
47
|
-
export const mockedModules = new Map();
|
|
48
|
-
function add(resolved, replacementProperties) {
|
|
49
|
-
const exportNames = Object.keys(replacementProperties);
|
|
50
|
-
const namespace = { __proto__: null };
|
|
51
|
-
const listeners = [];
|
|
52
|
-
for (const name of exportNames) {
|
|
53
|
-
let currentValueForPropertyName = replacementProperties[name];
|
|
54
|
-
Object.defineProperty(namespace, name, {
|
|
55
|
-
// @ts-ignore
|
|
56
|
-
__proto__: null,
|
|
57
|
-
enumerable: true,
|
|
58
|
-
get() {
|
|
59
|
-
return currentValueForPropertyName;
|
|
60
|
-
}, set(v) {
|
|
61
|
-
currentValueForPropertyName = v;
|
|
62
|
-
for (const fn of listeners) {
|
|
63
|
-
try {
|
|
64
|
-
fn(name);
|
|
65
|
-
}
|
|
66
|
-
catch (_a) {
|
|
67
|
-
/* noop */
|
|
68
|
-
}
|
|
47
|
+
if (parent) {
|
|
48
|
+
const mocksForParent = mocksFor.get(parent);
|
|
49
|
+
if (mocksForParent) {
|
|
50
|
+
const exports = mocksForParent.get(url);
|
|
51
|
+
if (exports) {
|
|
52
|
+
for (const key in exports) {
|
|
53
|
+
body.push(`export const ${key} = exports.get(${JSON.stringify(url)})[${JSON.stringify(key)}];`);
|
|
69
54
|
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
72
57
|
}
|
|
73
|
-
|
|
74
|
-
namespace, listeners,
|
|
75
|
-
});
|
|
76
|
-
mockedModuleExports.set(resolved, exportNames);
|
|
77
|
-
return namespace;
|
|
58
|
+
return body.join('\n');
|
|
78
59
|
}
|
|
79
|
-
|
|
80
|
-
|
|
60
|
+
let version = 0;
|
|
61
|
+
export function mock(modules = {}) {
|
|
81
62
|
return {
|
|
82
|
-
for(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
63
|
+
for(specifier_1) {
|
|
64
|
+
return __awaiter(this, arguments, void 0, function* (specifier, keep = false) {
|
|
65
|
+
try {
|
|
66
|
+
const scope = (version++).toString() + +new Date();
|
|
67
|
+
specifier = `${specifier}?${scope}`;
|
|
68
|
+
mocksFor.set(specifier, new Map(Object.entries(modules).map(([k, v]) => [`${k}?${scope}`, v])));
|
|
69
|
+
return yield import(specifier);
|
|
86
70
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
71
|
+
finally {
|
|
72
|
+
if (!keep)
|
|
73
|
+
mocksFor.delete(specifier);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
92
76
|
}
|
|
93
77
|
};
|
|
94
78
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-esm-mock",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Mock ES modules in Node.js using registerHooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
"registerHooks"
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/prograpedia/node-esm-mock"
|
|
29
|
+
},
|
|
30
|
+
"author": "Cristian Torres <cris.torres15@gmail.com>",
|
|
26
31
|
"engines": {
|
|
27
32
|
"node": ">=22.7.0"
|
|
28
33
|
},
|
|
@@ -30,13 +35,15 @@
|
|
|
30
35
|
"typescript": "^5.5.3"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
38
|
+
"@types/chai": "^5.2.3",
|
|
33
39
|
"@types/node": "^25.9.3",
|
|
34
40
|
"@types/sinon": "^21.0.1",
|
|
41
|
+
"chai": "^6.2.2",
|
|
35
42
|
"sinon": "^22.0.0",
|
|
36
43
|
"ts-node": "^10.9.2"
|
|
37
44
|
},
|
|
38
45
|
"scripts": {
|
|
39
46
|
"build": "tsc",
|
|
40
|
-
"test": "node --
|
|
47
|
+
"test": "node --import ./utils/register.ts --no-warnings --test"
|
|
41
48
|
}
|
|
42
49
|
}
|