• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { spawnPromisified } from '../common/index.mjs';
2import * as fixtures from '../common/fixtures.mjs';
3import assert from 'node:assert';
4import { execPath } from 'node:process';
5import { describe, it } from 'node:test';
6
7describe('Loader hooks throwing errors', { concurrency: true }, () => {
8  it('throws on nonexistent modules', async () => {
9    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
10      '--no-warnings',
11      '--experimental-loader',
12      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
13      '--input-type=module',
14      '--eval',
15      'import "nonexistent/file.mjs"',
16    ]);
17
18    assert.match(stderr, /ERR_MODULE_NOT_FOUND/);
19    assert.strictEqual(stdout, '');
20    assert.strictEqual(code, 1);
21    assert.strictEqual(signal, null);
22  });
23
24  it('throws on unknown extensions', async () => {
25    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
26      '--no-warnings',
27      '--experimental-loader',
28      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
29      '--input-type=module',
30      '--eval',
31      `import ${JSON.stringify(fixtures.fileURL('/es-modules/file.unknown'))}`,
32    ]);
33
34    assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
35    assert.strictEqual(stdout, '');
36    assert.strictEqual(code, 1);
37    assert.strictEqual(signal, null);
38  });
39
40  it('throws on invalid return values', async () => {
41    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
42      '--no-warnings',
43      '--experimental-loader',
44      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
45      '--input-type=module',
46      '--eval',
47      'import "esmHook/badReturnVal.mjs"',
48    ]);
49
50    assert.match(stderr, /ERR_INVALID_RETURN_VALUE/);
51    assert.strictEqual(stdout, '');
52    assert.strictEqual(code, 1);
53    assert.strictEqual(signal, null);
54  });
55
56  it('throws on boolean false', async () => {
57    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
58      '--no-warnings',
59      '--experimental-loader',
60      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
61      '--input-type=module',
62      '--eval',
63      'import "esmHook/format.false"',
64    ]);
65
66    assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/);
67    assert.strictEqual(stdout, '');
68    assert.strictEqual(code, 1);
69    assert.strictEqual(signal, null);
70  });
71  it('throws on boolean true', async () => {
72    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
73      '--no-warnings',
74      '--experimental-loader',
75      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
76      '--input-type=module',
77      '--eval',
78      'import "esmHook/format.true"',
79    ]);
80
81    assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/);
82    assert.strictEqual(stdout, '');
83    assert.strictEqual(code, 1);
84    assert.strictEqual(signal, null);
85  });
86
87  it('throws on invalid returned object', async () => {
88    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
89      '--no-warnings',
90      '--experimental-loader',
91      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
92      '--input-type=module',
93      '--eval',
94      'import "esmHook/badReturnFormatVal.mjs"',
95    ]);
96
97    assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/);
98    assert.strictEqual(stdout, '');
99    assert.strictEqual(code, 1);
100    assert.strictEqual(signal, null);
101  });
102
103  it('throws on unsupported format', async () => {
104    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
105      '--no-warnings',
106      '--experimental-loader',
107      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
108      '--input-type=module',
109      '--eval',
110      'import "esmHook/unsupportedReturnFormatVal.mjs"',
111    ]);
112
113    assert.match(stderr, /ERR_UNKNOWN_MODULE_FORMAT/);
114    assert.strictEqual(stdout, '');
115    assert.strictEqual(code, 1);
116    assert.strictEqual(signal, null);
117  });
118
119  it('throws on invalid format property type', async () => {
120    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
121      '--no-warnings',
122      '--experimental-loader',
123      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
124      '--input-type=module',
125      '--eval',
126      'import "esmHook/badReturnSourceVal.mjs"',
127    ]);
128
129    assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/);
130    assert.strictEqual(stdout, '');
131    assert.strictEqual(code, 1);
132    assert.strictEqual(signal, null);
133  });
134
135  it('rejects dynamic imports for all of the error cases checked above', async () => {
136    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
137      '--no-warnings',
138      '--experimental-loader',
139      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
140      '--input-type=module',
141      '--eval',
142      `import assert from 'node:assert';
143      await Promise.allSettled([
144        import('nonexistent/file.mjs'),
145        import(${JSON.stringify(fixtures.fileURL('/es-modules/file.unknown'))}),
146        import('esmHook/badReturnVal.mjs'),
147        import('esmHook/format.false'),
148        import('esmHook/format.true'),
149        import('esmHook/badReturnFormatVal.mjs'),
150        import('esmHook/unsupportedReturnFormatVal.mjs'),
151        import('esmHook/badReturnSourceVal.mjs'),
152      ]).then((results) => {
153        assert.strictEqual(results.every((result) => result.status === 'rejected'), true);
154      })`,
155    ]);
156
157    assert.strictEqual(stderr, '');
158    assert.strictEqual(stdout, '');
159    assert.strictEqual(code, 0);
160    assert.strictEqual(signal, null);
161  });
162});
163
164describe('Loader hooks parsing modules', { concurrency: true }, () => {
165  it('can parse .js files as ESM', async () => {
166    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
167      '--no-warnings',
168      '--experimental-loader',
169      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
170      '--input-type=module',
171      '--eval',
172      `import assert from 'node:assert';
173      await import(${JSON.stringify(fixtures.fileURL('/es-module-loaders/js-as-esm.js'))})
174      .then((parsedModule) => {
175        assert.strictEqual(typeof parsedModule, 'object');
176        assert.strictEqual(parsedModule.namedExport, 'named-export');
177      })`,
178    ]);
179
180    assert.strictEqual(stderr, '');
181    assert.strictEqual(stdout, '');
182    assert.strictEqual(code, 0);
183    assert.strictEqual(signal, null);
184  });
185
186  it('can define .ext files as ESM', async () => {
187    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
188      '--no-warnings',
189      '--experimental-loader',
190      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
191      '--input-type=module',
192      '--eval',
193      `import assert from 'node:assert';
194      await import(${JSON.stringify(fixtures.fileURL('/es-modules/file.ext'))})
195      .then((parsedModule) => {
196        assert.strictEqual(typeof parsedModule, 'object');
197        const { default: defaultExport } = parsedModule;
198        assert.strictEqual(typeof defaultExport, 'function');
199        assert.strictEqual(defaultExport.name, 'iAmReal');
200        assert.strictEqual(defaultExport(), true);
201      })`,
202    ]);
203
204    assert.strictEqual(stderr, '');
205    assert.strictEqual(stdout, '');
206    assert.strictEqual(code, 0);
207    assert.strictEqual(signal, null);
208  });
209
210  it('can predetermine the format in the custom loader resolve hook', async () => {
211    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
212      '--no-warnings',
213      '--experimental-loader',
214      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
215      '--input-type=module',
216      '--eval',
217      `import assert from 'node:assert';
218      await import('esmHook/preknownFormat.pre')
219      .then((parsedModule) => {
220        assert.strictEqual(typeof parsedModule, 'object');
221        assert.strictEqual(parsedModule.default, 'hello world');
222      })`,
223    ]);
224
225    assert.strictEqual(stderr, '');
226    assert.strictEqual(stdout, '');
227    assert.strictEqual(code, 0);
228    assert.strictEqual(signal, null);
229  });
230
231  it('can provide source for a nonexistent file', async () => {
232    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
233      '--no-warnings',
234      '--experimental-loader',
235      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
236      '--input-type=module',
237      '--eval',
238      `import assert from 'node:assert';
239      await import('esmHook/virtual.mjs')
240      .then((parsedModule) => {
241        assert.strictEqual(typeof parsedModule, 'object');
242        assert.strictEqual(typeof parsedModule.default, 'undefined');
243        assert.strictEqual(parsedModule.message, 'WOOHOO!');
244      })`,
245    ]);
246
247    assert.strictEqual(stderr, '');
248    assert.strictEqual(stdout, '');
249    assert.strictEqual(code, 0);
250    assert.strictEqual(signal, null);
251  });
252
253  it('ensures that loaders have a separate context from userland', async () => {
254    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
255      '--no-warnings',
256      '--experimental-loader',
257      fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
258      '--input-type=module',
259      '--eval',
260      `import assert from 'node:assert';
261      await import(${JSON.stringify(fixtures.fileURL('/es-modules/stateful.mjs'))})
262      .then(({ default: count }) => {
263        assert.strictEqual(count(), 1);
264      });`,
265    ]);
266
267    assert.strictEqual(stderr, '');
268    assert.strictEqual(stdout, '');
269    assert.strictEqual(code, 0);
270    assert.strictEqual(signal, null);
271  });
272
273  it('ensures that user loaders are not bound to the internal loader', async () => {
274    const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
275      '--no-warnings',
276      '--experimental-loader',
277      fixtures.fileURL('/es-module-loaders/loader-this-value-inside-hook-functions.mjs'),
278      '--input-type=module',
279      '--eval',
280      ';', // Actual test is inside the loader module.
281    ]);
282
283    assert.strictEqual(stderr, '');
284    assert.strictEqual(stdout, '');
285    assert.strictEqual(code, 0);
286    assert.strictEqual(signal, null);
287  });
288});
289