Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions tools/egg-bundler/src/lib/Bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ export class Bundler {
const packResult = await wrapStep('pack build', () => packRunner.run());
debug('pack produced %d files', packResult.files.length);

// turbopack wraps `import.meta` in a throwing getter for bundled ESM
// chunks. Patch the output so `createRequire(import.meta.url)` and
// other `import.meta.url` usages work at runtime.
const patchCount = await wrapStep('patch import.meta.url', () => this.#patchImportMetaUrl(absOutputDir));
debug('patched %d import.meta.url occurrences', patchCount);

// Merge project name into output package.json so the framework's
// getAppname() finds it (it reads baseDir/package.json).
const outputPkgPath = path.join(absOutputDir, 'package.json');
Expand Down Expand Up @@ -130,4 +136,60 @@ export class Bundler {
manifestPath: manifestPathAbs,
};
}

/**
* Turbopack replaces `import.meta` in bundled ESM chunks with an object
* that only defines a throwing `url` getter and omits `dirname`/`filename`.
*
* We post-process the output .js files in two passes:
* 1. Replace the throwing `url` IIFE with a working `file://` URL.
* 2. Inject `dirname` and `filename` getters so code like
* `path.join(import.meta.dirname, '../lib/asset.html')` works.
*/
async #patchImportMetaUrl(outputDir: string): Promise<number> {
// Pass 1 — fix the throwing url getter.
const THROWING_IIFE =
/\(\(\)\s*=>\s*\{\s*throw\s+new\s+Error\(\s*['"]could not convert import\.meta\.url to filepath['"]\s*\)\s*;?\s*\}\)\s*\(\)/g;
// Avoid require() in the replacement — turbopack modules may declare
// `const require = createRequire(import.meta.url)` which would be in
// the TDZ when our getter runs. Use globals only.
const URL_EXPR = 'new URL("file:///" + encodeURI(process.argv[1])).href';

// Pass 2 — add dirname/filename after patching url.
// Match the url-only meta object that results from pass 1.
const META_URL_ONLY =
/var __TURBOPACK__import\$2e\$meta__ = \{\s*get url \(\) \{\s*return new URL\("file:\/\/\/" \+ encodeURI\(process\.argv\[1\]\)\)\.href;\s*\}\s*\};/g;
Comment thread
killagu marked this conversation as resolved.
Outdated
const META_FULL = `var __TURBOPACK__import$2e$meta__ = {
get url () {
return new URL("file:///" + encodeURI(process.argv[1])).href;
Comment thread
killagu marked this conversation as resolved.
Outdated
},
get dirname () {
return process.argv[1].replace(/[\\\\/][^\\\\/]*$/, "");
},
get filename () {
return process.argv[1];
}
};`;
Comment thread
killagu marked this conversation as resolved.
Outdated

let totalPatches = 0;
const entries = await fs.readdir(outputDir);
for (const name of entries) {
if (!name.endsWith('.js')) continue;
const filepath = path.join(outputDir, name);
Comment thread
killagu marked this conversation as resolved.
Outdated
Comment thread
killagu marked this conversation as resolved.
Outdated
const content = await fs.readFile(filepath, 'utf8');

// Pass 1
const urlMatches = content.match(THROWING_IIFE);
if (!urlMatches) continue;
let patched = content.replace(THROWING_IIFE, URL_EXPR);

// Pass 2
patched = patched.replace(META_URL_ONLY, META_FULL);
Comment thread
killagu marked this conversation as resolved.
Outdated
Comment thread
killagu marked this conversation as resolved.
Outdated

await fs.writeFile(filepath, patched);
totalPatches += urlMatches.length;
debug('patched %d import.meta in %s', urlMatches.length, name);
}
return totalPatches;
}
}