Skip to content

Commit 074e47a

Browse files
committed
Refactor module loading strategy to improve compatibility with Jest and ESM
1 parent 27fde67 commit 074e47a

File tree

1 file changed

+87
-100
lines changed

1 file changed

+87
-100
lines changed
Lines changed: 87 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,119 @@
11
import { LatestSupportedPHPVersion } from '@php-wasm/universal';
22
import type { PHPLoaderModule, SupportedPHPVersion } from '@php-wasm/universal';
33

4+
/**
5+
* Determines whether to use require() or dynamic import() to load PHP modules.
6+
*/
7+
function getModuleLoadingStrategy(): 'require' | 'import' {
8+
// Check if we're in Jest environment
9+
// Jest runs tests in a VM context that doesn't support dynamic import() without
10+
// the --experimental-vm-modules flag. Jest sets JEST_WORKER_ID when running tests.
11+
// Error without flag: "A dynamic import callback was invoked without --experimental-vm-modules"
12+
// See: https://jestjs.io/docs/ecmascript-modules
13+
if (process.env && process.env['JEST_WORKER_ID']) {
14+
// Use require() in Jest unless explicitly configured for ESM
15+
return 'require';
16+
}
17+
18+
// Check if require() is even available
19+
// In pure ESM environments, require won't exist and we must use import()
20+
if (typeof require !== 'function') {
21+
return 'import';
22+
}
23+
24+
// Check if we're in a CommonJS environment without module/module.exports
25+
// If module or module.exports is undefined, we're in an ESM-only context
26+
// where require() won't work anyway
27+
if (
28+
typeof module === 'undefined' ||
29+
typeof module.exports === 'undefined'
30+
) {
31+
return 'import';
32+
}
33+
34+
// All checks passed - we're in a Node.js environment that supports dynamic import()
35+
// Modern Node.js (12.20+) supports dynamic import() in both ESM and CommonJS contexts
36+
// This includes Vitest and other modern test runners
37+
return 'import';
38+
}
39+
440
/**
541
* Loads the PHP loader module for the given PHP version.
642
*
7-
* Each PHP version is packaged separately to reduce bundle size:
8-
* - @php-wasm/node-8-5
9-
* - @php-wasm/node-8-4
10-
* - @php-wasm/node-8-3
11-
* - etc.
12-
*
13-
* ## Module Loading Strategy
14-
*
15-
* Uses a try-catch fallback approach:
16-
* 1. **Tries `import()` first** - Works in ESM, modern CJS (Node 12.20+), and test
17-
* runners like Vitest
18-
* 2. **Falls back to `require()`** - Only if import() fails (rare in modern
19-
* environments)
20-
*
21-
* ### Why This Approach?
22-
*
23-
* **Development**: In monorepo development, workspace symlinks point to source directories
24-
* without built files. Modern test runners (like Vitest) provide both `module` and `require`
25-
* but should use `import()` to work with these symlinks. The try-catch approach correctly
26-
* uses `import()` in these environments.
27-
*
28-
* **Production**: Published packages have built files in the correct locations, so both
29-
* `import()` (ESM consumers) and `require()` (old CJS consumers) work correctly.
30-
*
31-
* This avoids environment-specific detection hacks and is future-proof as environments evolve.
32-
*
33-
* @param version The PHP version to load.
34-
* @returns The PHP loader module.
43+
* Uses dynamic import() by default, falling back to require() in Jest environments
44+
* where dynamic import isn't supported without --experimental-vm-modules.
3545
*/
3646
export async function getPHPLoaderModule(
3747
version: SupportedPHPVersion = LatestSupportedPHPVersion
3848
): Promise<PHPLoaderModule> {
39-
// Try dynamic import() first - the modern, preferred approach
40-
// Works in: ESM, Vitest, modern Node.js CJS (12.20+), and with workspace symlinks
41-
try {
49+
const loadingStrategy = getModuleLoadingStrategy();
50+
51+
if (loadingStrategy === 'require') {
52+
// Use require() for environments that don't support dynamic import
4253
switch (version) {
4354
case '8.5':
4455
// @ts-ignore
45-
return (
46-
await import('@php-wasm/node-8-5')
47-
).getPHPLoaderModule();
56+
return require('@php-wasm/node-8-5').getPHPLoaderModule();
4857
case '8.4':
4958
// @ts-ignore
50-
return (
51-
await import('@php-wasm/node-8-4')
52-
).getPHPLoaderModule();
59+
return require('@php-wasm/node-8-4').getPHPLoaderModule();
5360
case '8.3':
5461
// @ts-ignore
55-
return (
56-
await import('@php-wasm/node-8-3')
57-
).getPHPLoaderModule();
62+
return require('@php-wasm/node-8-3').getPHPLoaderModule();
5863
case '8.2':
5964
// @ts-ignore
60-
return (
61-
await import('@php-wasm/node-8-2')
62-
).getPHPLoaderModule();
65+
return require('@php-wasm/node-8-2').getPHPLoaderModule();
6366
case '8.1':
6467
// @ts-ignore
65-
return (
66-
await import('@php-wasm/node-8-1')
67-
).getPHPLoaderModule();
68+
return require('@php-wasm/node-8-1').getPHPLoaderModule();
6869
case '8.0':
6970
// @ts-ignore
70-
return (
71-
await import('@php-wasm/node-8-0')
72-
).getPHPLoaderModule();
71+
return require('@php-wasm/node-8-0').getPHPLoaderModule();
7372
case '7.4':
7473
// @ts-ignore
75-
return (
76-
await import('@php-wasm/node-7-4')
77-
).getPHPLoaderModule();
74+
return require('@php-wasm/node-7-4').getPHPLoaderModule();
7875
case '7.3':
7976
// @ts-ignore
80-
return (
81-
await import('@php-wasm/node-7-3')
82-
).getPHPLoaderModule();
77+
return require('@php-wasm/node-7-3').getPHPLoaderModule();
8378
case '7.2':
8479
// @ts-ignore
85-
return (
86-
await import('@php-wasm/node-7-2')
87-
).getPHPLoaderModule();
88-
}
89-
throw new Error(`Unsupported PHP version ${version}`);
90-
} catch (error) {
91-
// Fallback: Use require() only if import() failed
92-
// This handles extremely rare cases: old Node.js versions or restricted environments
93-
// where dynamic import() is not available
94-
if (
95-
typeof module !== 'undefined' &&
96-
typeof module.exports !== 'undefined' &&
97-
typeof require === 'function'
98-
) {
99-
switch (version) {
100-
case '8.5':
101-
// @ts-ignore
102-
return require('@php-wasm/node-8-5').getPHPLoaderModule();
103-
case '8.4':
104-
// @ts-ignore
105-
return require('@php-wasm/node-8-4').getPHPLoaderModule();
106-
case '8.3':
107-
// @ts-ignore
108-
return require('@php-wasm/node-8-3').getPHPLoaderModule();
109-
case '8.2':
110-
// @ts-ignore
111-
return require('@php-wasm/node-8-2').getPHPLoaderModule();
112-
case '8.1':
113-
// @ts-ignore
114-
return require('@php-wasm/node-8-1').getPHPLoaderModule();
115-
case '8.0':
116-
// @ts-ignore
117-
return require('@php-wasm/node-8-0').getPHPLoaderModule();
118-
case '7.4':
119-
// @ts-ignore
120-
return require('@php-wasm/node-7-4').getPHPLoaderModule();
121-
case '7.3':
122-
// @ts-ignore
123-
return require('@php-wasm/node-7-3').getPHPLoaderModule();
124-
case '7.2':
125-
// @ts-ignore
126-
return require('@php-wasm/node-7-2').getPHPLoaderModule();
127-
}
80+
return require('@php-wasm/node-7-2').getPHPLoaderModule();
81+
default:
82+
throw new Error(`Unsupported PHP version ${version}`);
12883
}
129-
// Re-throw the original error if require() also isn't available
130-
throw error;
84+
}
85+
86+
// Use dynamic import() - the modern, preferred approach
87+
// Works in: ESM, Vitest, modern Node.js CJS (12.20+), and with workspace symlinks
88+
switch (version) {
89+
case '8.5':
90+
// @ts-ignore
91+
return (await import('@php-wasm/node-8-5')).getPHPLoaderModule();
92+
case '8.4':
93+
// @ts-ignore
94+
return (await import('@php-wasm/node-8-4')).getPHPLoaderModule();
95+
case '8.3':
96+
// @ts-ignore
97+
return (await import('@php-wasm/node-8-3')).getPHPLoaderModule();
98+
case '8.2':
99+
// @ts-ignore
100+
return (await import('@php-wasm/node-8-2')).getPHPLoaderModule();
101+
case '8.1':
102+
// @ts-ignore
103+
return (await import('@php-wasm/node-8-1')).getPHPLoaderModule();
104+
case '8.0':
105+
// @ts-ignore
106+
return (await import('@php-wasm/node-8-0')).getPHPLoaderModule();
107+
case '7.4':
108+
// @ts-ignore
109+
return (await import('@php-wasm/node-7-4')).getPHPLoaderModule();
110+
case '7.3':
111+
// @ts-ignore
112+
return (await import('@php-wasm/node-7-3')).getPHPLoaderModule();
113+
case '7.2':
114+
// @ts-ignore
115+
return (await import('@php-wasm/node-7-2')).getPHPLoaderModule();
116+
default:
117+
throw new Error(`Unsupported PHP version ${version}`);
131118
}
132119
}

0 commit comments

Comments
 (0)