Skip to content

Commit 7296d82

Browse files
committed
document custom loader
1 parent cd005a8 commit 7296d82

File tree

2 files changed

+38
-21
lines changed

2 files changed

+38
-21
lines changed

apps/site/tests/loader.mjs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
1-
const mockBaseUrl = new URL('mocks/', import.meta.url);
2-
1+
// Map of module specifier prefixes to their corresponding mock files
32
const mockEntries = [
4-
['next-intl', 'next-intl.jsx'],
5-
['next-router', 'next-router.mjs'],
3+
['next-intl', new URL('mocks/next-intl.jsx', import.meta.url).href],
4+
['next-router', new URL('mocks/next-intl.jsx', import.meta.url).href],
65
];
76

7+
/**
8+
* Returns the corresponding mock file for a given module specifier, if matched.
9+
*
10+
* @param {string} specifier - Module name or path being imported.
11+
* @returns {string|null} - File name of the mock module or null if no match.
12+
*/
813
function getMockFile(specifier) {
9-
for (let i = 0; i < mockEntries.length; i++) {
10-
const [prefix, file] = mockEntries[i];
11-
const len = prefix.length;
12-
if (
13-
specifier === prefix ||
14-
(specifier.startsWith(prefix) && specifier[len] === '/')
15-
) {
16-
return file;
17-
}
18-
}
19-
return null;
14+
const entry = mockEntries.find(
15+
([prefix]) => specifier === prefix || specifier.startsWith(prefix + '/')
16+
);
17+
return entry?.[1] ?? null;
2018
}
2119

2220
/**
21+
* Node.js custom module resolution hook to inject mock modules.
22+
*
2323
* @type {import('node:module').ResolveHook}
2424
*/
2525
export async function resolve(specifier, ctx, nextResolve) {
2626
const mockFile = getMockFile(specifier);
27+
2728
if (mockFile) {
2829
return {
2930
format: 'module',
30-
url: new URL(mockFile, mockBaseUrl).href,
31+
url: mockFile,
3132
shortCircuit: true,
3233
};
3334
}

tests/loader.mjs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import { extname } from 'path';
22

33
const requiredCode = `import * as React from 'react';`;
4-
const mockBaseUrl = new URL('mocks/', import.meta.url);
4+
const css = new URL('mocks/css.mjs', import.meta.url).href;
55

66
/**
7+
*
8+
* This hook intercepts module resolution, allowing us to handle
9+
* CSS/SCSS files in a custom way. Instead of actually loading the CSS,
10+
* we short-circuit the resolution and return a mock module.
11+
*
712
* @type {import('node:module').ResolveHook}
813
*/
914
export async function resolve(specifier, ctx, nextResolve) {
10-
if (specifier.endsWith('.css')) {
15+
const ext = extname(specifier);
16+
if (ext === '.css' || ext === '.scss') {
17+
// For CSS/SCSS, return the mock CSS module and skip default resolution.
1118
return {
1219
format: 'module',
13-
url: new URL('css.mjs', mockBaseUrl).href,
20+
url: css,
1421
shortCircuit: true,
1522
};
1623
}
@@ -19,12 +26,21 @@ export async function resolve(specifier, ctx, nextResolve) {
1926
}
2027

2128
/**
29+
*
30+
* This hook is used to modify the source of JSX/TSX files on the fly.
31+
* We prepend the necessary React import to ensure React is available,
32+
* which is required for JSX to work without explicitly importing React.
33+
*
2234
* @type {import('node:module').LoadHook}
2335
*/
2436
export async function load(url, ctx, nextLoad) {
2537
const ext = extname(url);
26-
if (ext[ext.length - 1] !== 'x') return nextLoad(url);
2738
const result = await nextLoad(url, ctx);
28-
result.source = requiredCode + result.source;
39+
40+
if (ext === '.jsx' || ext === '.tsx') {
41+
// Ensure React is in scope for JSX transforms.
42+
result.source = requiredCode + result.source;
43+
}
44+
2945
return result;
3046
}

0 commit comments

Comments
 (0)