|
1 | 1 | import { LatestSupportedPHPVersion } from '@php-wasm/universal'; |
2 | 2 | import type { PHPLoaderModule, SupportedPHPVersion } from '@php-wasm/universal'; |
3 | 3 |
|
| 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 | + |
4 | 40 | /** |
5 | 41 | * Loads the PHP loader module for the given PHP version. |
6 | 42 | * |
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. |
35 | 45 | */ |
36 | 46 | export async function getPHPLoaderModule( |
37 | 47 | version: SupportedPHPVersion = LatestSupportedPHPVersion |
38 | 48 | ): 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 |
42 | 53 | switch (version) { |
43 | 54 | case '8.5': |
44 | 55 | // @ts-ignore |
45 | | - return ( |
46 | | - await import('@php-wasm/node-8-5') |
47 | | - ).getPHPLoaderModule(); |
| 56 | + return require('@php-wasm/node-8-5').getPHPLoaderModule(); |
48 | 57 | case '8.4': |
49 | 58 | // @ts-ignore |
50 | | - return ( |
51 | | - await import('@php-wasm/node-8-4') |
52 | | - ).getPHPLoaderModule(); |
| 59 | + return require('@php-wasm/node-8-4').getPHPLoaderModule(); |
53 | 60 | case '8.3': |
54 | 61 | // @ts-ignore |
55 | | - return ( |
56 | | - await import('@php-wasm/node-8-3') |
57 | | - ).getPHPLoaderModule(); |
| 62 | + return require('@php-wasm/node-8-3').getPHPLoaderModule(); |
58 | 63 | case '8.2': |
59 | 64 | // @ts-ignore |
60 | | - return ( |
61 | | - await import('@php-wasm/node-8-2') |
62 | | - ).getPHPLoaderModule(); |
| 65 | + return require('@php-wasm/node-8-2').getPHPLoaderModule(); |
63 | 66 | case '8.1': |
64 | 67 | // @ts-ignore |
65 | | - return ( |
66 | | - await import('@php-wasm/node-8-1') |
67 | | - ).getPHPLoaderModule(); |
| 68 | + return require('@php-wasm/node-8-1').getPHPLoaderModule(); |
68 | 69 | case '8.0': |
69 | 70 | // @ts-ignore |
70 | | - return ( |
71 | | - await import('@php-wasm/node-8-0') |
72 | | - ).getPHPLoaderModule(); |
| 71 | + return require('@php-wasm/node-8-0').getPHPLoaderModule(); |
73 | 72 | case '7.4': |
74 | 73 | // @ts-ignore |
75 | | - return ( |
76 | | - await import('@php-wasm/node-7-4') |
77 | | - ).getPHPLoaderModule(); |
| 74 | + return require('@php-wasm/node-7-4').getPHPLoaderModule(); |
78 | 75 | case '7.3': |
79 | 76 | // @ts-ignore |
80 | | - return ( |
81 | | - await import('@php-wasm/node-7-3') |
82 | | - ).getPHPLoaderModule(); |
| 77 | + return require('@php-wasm/node-7-3').getPHPLoaderModule(); |
83 | 78 | case '7.2': |
84 | 79 | // @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}`); |
128 | 83 | } |
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}`); |
131 | 118 | } |
132 | 119 | } |
0 commit comments