Skip to content

Commit 9897c62

Browse files
Avoid undefined __dirname error when running unbuilt Playground CLI in Node.js (#2233)
## Motivation for the change, related issues We currently use `__dirname` to resolve ICU data, but that does not work for running unbuilt TS modules in Node.js (for example, via `npx nx dev-node playground-cli`). In that case, we get an error that `__dirname` not defined because the global does not exist for ES modules in Node. This PR fixes this so the path can be resolved by both. cc @mho22 ## Implementation details This PR updates the unbuilt module loader to replace uses of `__dirname` and `__filename`. Some notes: - `__dirname` is replaced by `import.meta.dirname` which is supported for ES modules at least as early as Node.js 20.x. - `__filename` is replaced by `import.meta.filename` which is supported for ES modules at least as early as Node.js 20.x. - This is a text-only replacement, which is fast but not as ideal as an AST transform. - The text-only replacement avoids replacing declarations of `__dirname` and `__filename` because that would cause a syntax error. This PR also removes [the conditional declarations of `__dirname` and `__filename`](https://github.com/WordPress/wordpress-playground/blob/8ccf4e47f9d84401c8f237ae9b039060a45a0f8a/packages/php-wasm/compile/php/esm-node-boilerplate.js#L8-L21) from `esm-node-boilerplate.js` because they are no longer needed. ## Testing Instructions (or ideally a Blueprint) We can test this with CI. There is a new `test-running-unbuilt-cli.sh` script for running an unbuilt version of Playground CLI, and it is integrated in the CI workflow as a standalone job.
1 parent 8bff942 commit 9897c62

File tree

36 files changed

+94
-261
lines changed

36 files changed

+94
-261
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,16 @@ jobs:
251251
fi
252252
npm run test
253253
254+
test-running-unbuilt-playground-cli:
255+
runs-on: ubuntu-latest
256+
needs: [lint-and-typecheck]
257+
steps:
258+
- uses: actions/checkout@v4
259+
with:
260+
submodules: true
261+
- uses: ./.github/actions/prepare-playground
262+
- run: packages/playground/cli/src/test/test-running-unbuilt-cli.sh
263+
254264
build:
255265
runs-on: ubuntu-latest
256266
needs: [lint-and-typecheck]

packages/meta/src/node-es-module-loader/loader.mts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ const tsconfig: TsConfig = JSON.parse(readFileSync(tsconfigPath, 'utf-8'));
1616
const pathAliases = tsconfig.compilerOptions.paths;
1717
const baseUrl = tsconfig.compilerOptions.baseUrl || '.';
1818

19+
const playgroundPackageRoot = resolvePath(
20+
import.meta.dirname,
21+
'..',
22+
'..',
23+
'..'
24+
);
25+
1926
const aliasMap = new Map<string, string>();
2027
for (const [alias, paths] of Object.entries(pathAliases)) {
2128
// Our config is simple and doesn't use wildcards,
@@ -158,6 +165,29 @@ export async function load(
158165
};
159166
}
160167

168+
const supportedModuleFormats = ['module', 'module-typescript'];
169+
if (
170+
urlObj.pathname.startsWith(playgroundPackageRoot) &&
171+
supportedModuleFormats.includes(context.format!)
172+
) {
173+
const loadResult = await nextLoad(url, context);
174+
if (supportedModuleFormats.includes(loadResult.format)) {
175+
const source = (loadResult.source as Buffer).toString('utf8');
176+
// Some of our code uses __dirname and __filename which are not available in ES modules.
177+
// https://nodejs.org/api/esm.html#no-__filename-or-__dirname
178+
//
179+
// Let's try simple text replacement for now. It is fast and will probably be fine.
180+
// If we run into problems, we can consider an AST transform instead.
181+
const updatedSource = source.replace(
182+
// Replace __dirname and __filename but not if they are declarations.
183+
/(?<!(?:const|var|let)\s*)\b__(dirname|filename)/g,
184+
'import.meta.$1'
185+
);
186+
loadResult.source = Buffer.from(updatedSource, 'utf8');
187+
}
188+
return loadResult;
189+
}
190+
161191
// Pass everything else to the next loader
162192
return nextLoad(url, context);
163193
}

packages/php-wasm/compile/php/esm-node-boilerplate.js

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,6 @@
33
// this code in Node.js as an ES module.
44
import { createRequire } from 'module';
55
const require = createRequire(import.meta.url);
6-
import { fileURLToPath } from 'url';
7-
8-
/**
9-
* __filename and __dirname are not available in ES modules, so we need to
10-
* polyfill them to ensure the debug command (npx nx debug playground-cli)
11-
* works.
12-
*
13-
* @see https://nodejs.org/api/esm.html#no-__filename-or-__dirname
14-
*/
6+
// Note: The path module is currently needed by code injected by the php-wasm Dockerfile.
157
import path from 'path';
16-
if (typeof __filename === 'undefined') {
17-
var __filename = fileURLToPath(import.meta.url);
18-
}
19-
if (typeof __dirname === 'undefined') {
20-
var __dirname = path.dirname(__filename);
21-
}
228

0 commit comments

Comments
 (0)