@gjsify/rolldown-plugin-gjsify 0.4.25 → 0.4.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/app/gjs.js +2 -2
- package/lib/plugins/bundle-url-banner.d.ts +1 -0
- package/lib/plugins/bundle-url-banner.js +13 -0
- package/lib/plugins/process-stub.d.ts +5 -0
- package/lib/plugins/process-stub.js +5 -1
- package/lib/plugins/rewrite-node-modules-paths.d.ts +27 -11
- package/lib/plugins/rewrite-node-modules-paths.js +177 -81
- package/lib/shims/module-resolve.d.ts +17 -0
- package/lib/shims/module-resolve.js +140 -0
- package/package.json +10 -7
package/lib/app/gjs.js
CHANGED
|
@@ -190,8 +190,8 @@ export const setupForGjs = async (input) => {
|
|
|
190
190
|
// `firefox: 60 << 16` makes lightningcss flatten the source
|
|
191
191
|
// into the subset GTK4 understands.
|
|
192
192
|
cssAsStringPlugin({ targets: { firefox: 60 << 16 } }),
|
|
193
|
-
nodeModulesPathRewritePlugin({ bundleDir }),
|
|
194
|
-
processStubPlugin({ userBanner: input.userBanner }),
|
|
193
|
+
nodeModulesPathRewritePlugin({ bundleDir, runtimeResolve: format === 'esm' }),
|
|
194
|
+
processStubPlugin({ userBanner: input.userBanner, captureBundleUrl: format === 'esm' }),
|
|
195
195
|
// resolveShebangLine returns null when disabled (false/undefined) and
|
|
196
196
|
// the resolved line otherwise — also handles `${env:…}` expansion.
|
|
197
197
|
(() => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BUNDLE_URL_BANNER = "globalThis.__gjsifyBundleUrl??=import.meta.url;";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// One-line banner that captures the bundle's own module URL into a global,
|
|
2
|
+
// read by the module-resolve shim (shims/module-resolve.ts) as its
|
|
3
|
+
// `createRequire` anchor.
|
|
4
|
+
//
|
|
5
|
+
// It must run at the top of the entry chunk — the single point where
|
|
6
|
+
// `import.meta.url` is unambiguously the bundle's own URL, BEFORE the
|
|
7
|
+
// node-modules path rewriter (rewrite-node-modules-paths.ts) could have
|
|
8
|
+
// rewritten any per-module `import.meta.url`. `??=` keeps it idempotent and
|
|
9
|
+
// cheap (a second entry in the same realm is a no-op).
|
|
10
|
+
//
|
|
11
|
+
// ESM-only — uses `import.meta.url`. Orchestrators gate its inclusion on
|
|
12
|
+
// `format === 'esm'`, matching the `runtimeResolve` gate on the rewriter.
|
|
13
|
+
export const BUNDLE_URL_BANNER = 'globalThis.__gjsifyBundleUrl??=import.meta.url;';
|
|
@@ -24,5 +24,10 @@ export declare function composeBanner(stub: string, userBanner: string): string;
|
|
|
24
24
|
export interface ProcessStubPluginOptions {
|
|
25
25
|
/** User-supplied banner string. May contain a leading `#!shebang`. */
|
|
26
26
|
userBanner?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Prepend the bundle-URL anchor banner (read by the module-resolve shim).
|
|
29
|
+
* ESM-only — set by the orchestrator when `format === 'esm'`.
|
|
30
|
+
*/
|
|
31
|
+
captureBundleUrl?: boolean;
|
|
27
32
|
}
|
|
28
33
|
export declare function processStubPlugin(options?: ProcessStubPluginOptions): Plugin;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BUNDLE_URL_BANNER } from './bundle-url-banner.js';
|
|
1
2
|
export const GJS_PROCESS_STUB = 'if(typeof globalThis.process==="undefined"){' +
|
|
2
3
|
'const _s=imports.system,_G=imports.gi.GLib;' +
|
|
3
4
|
// process.hrtime needs a `.bigint` property attached to the function
|
|
@@ -50,7 +51,10 @@ export function composeBanner(stub, userBanner) {
|
|
|
50
51
|
return shebang + stub + (rest ? '\n' + rest : '');
|
|
51
52
|
}
|
|
52
53
|
export function processStubPlugin(options = {}) {
|
|
53
|
-
|
|
54
|
+
// The anchor capture must precede the process stub so it runs at the very
|
|
55
|
+
// top of the chunk (where `import.meta.url` is the bundle's own URL).
|
|
56
|
+
const stub = (options.captureBundleUrl ? BUNDLE_URL_BANNER : '') + GJS_PROCESS_STUB;
|
|
57
|
+
const banner = composeBanner(stub, options.userBanner ?? '');
|
|
54
58
|
return {
|
|
55
59
|
name: 'gjsify-process-stub',
|
|
56
60
|
renderChunk: {
|
|
@@ -3,36 +3,52 @@ export declare const REWRITE_FILTER: RegExp;
|
|
|
3
3
|
/** True when the rewriter wants to look at this path — node_modules + supported ext. */
|
|
4
4
|
export declare function shouldRewrite(path: string): boolean;
|
|
5
5
|
/**
|
|
6
|
-
* Compute the directory the bundle's outfile lives in
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* bundle's `import.meta.url` — so we need to know where the bundle will be
|
|
10
|
-
* written. Both `output.file` and `output.dir` are accepted.
|
|
6
|
+
* Compute the directory the bundle's outfile lives in (used for the zip-resident
|
|
7
|
+
* check and the legacy relative paths). Both `output.file` and `output.dir` are
|
|
8
|
+
* accepted.
|
|
11
9
|
*/
|
|
12
10
|
export declare function getBundleDirFromOutput(opts: {
|
|
13
11
|
file?: string;
|
|
14
12
|
dir?: string;
|
|
15
13
|
}): string;
|
|
14
|
+
/**
|
|
15
|
+
* The package-qualified spec for a node_modules file: everything after the LAST
|
|
16
|
+
* `node_modules/` segment. This is what the runtime resolver feeds to
|
|
17
|
+
* `createRequire(...).resolve` (via the package root).
|
|
18
|
+
*
|
|
19
|
+
* ".../node_modules/typedoc/dist/lib/app.js" → "typedoc/dist/lib/app.js"
|
|
20
|
+
* ".../node_modules/@scope/name/sub.js" → "@scope/name/sub.js"
|
|
21
|
+
* ".../node_modules/a/node_modules/b/file.js" → "b/file.js"
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractPackageSpec(path: string): string;
|
|
16
24
|
export interface RewriteResult {
|
|
17
25
|
code: string;
|
|
18
26
|
moduleType?: 'ts' | 'js';
|
|
19
27
|
map?: null;
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
22
|
-
* Pure rewriter
|
|
23
|
-
*
|
|
24
|
-
*
|
|
30
|
+
* Pure rewriter. Returns the rewritten code (and module type for re-parsing) or
|
|
31
|
+
* `null` if the file references none of the patterns we care about.
|
|
32
|
+
*
|
|
33
|
+
* @param runtimeResolve When true (ESM output, banner present), on-disk ESM
|
|
34
|
+
* files resolve their location at runtime (case 1). When false, they fall back
|
|
35
|
+
* to the legacy build-relative rewrite (case 2).
|
|
25
36
|
*/
|
|
26
37
|
export declare function rewriteContents(args: {
|
|
27
38
|
path: string;
|
|
28
|
-
}, srcInput: string, bundleDir: string): RewriteResult | null;
|
|
39
|
+
}, srcInput: string, bundleDir: string, runtimeResolve: boolean): RewriteResult | null;
|
|
29
40
|
export interface NodeModulesPathRewriteOptions {
|
|
30
41
|
/** Bundle output directory, derived from `output.file` / `output.dir`. */
|
|
31
42
|
bundleDir: string;
|
|
43
|
+
/**
|
|
44
|
+
* Use runtime resolution for on-disk ESM files (case 1). Requires the
|
|
45
|
+
* bundle-URL banner — set by the orchestrator only for `format === 'esm'`.
|
|
46
|
+
* Defaults to false (legacy build-relative behavior).
|
|
47
|
+
*/
|
|
48
|
+
runtimeResolve?: boolean;
|
|
32
49
|
}
|
|
33
50
|
/**
|
|
34
51
|
* Build a Rolldown plugin that runs the path rewriter as a `transform(code, id)`
|
|
35
|
-
* hook with `order: 'post'
|
|
36
|
-
* but still during module loading, before chunking.
|
|
52
|
+
* hook with `order: 'post'`.
|
|
37
53
|
*/
|
|
38
54
|
export declare function nodeModulesPathRewritePlugin(options: NodeModulesPathRewriteOptions): Plugin;
|
|
@@ -1,119 +1,215 @@
|
|
|
1
|
-
// Per-source rewriter for node_modules files that reference
|
|
2
|
-
// `
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// `onLoad` registered inside the PnP plugin.
|
|
1
|
+
// Per-source rewriter for `node_modules` files that reference `import.meta.url`,
|
|
2
|
+
// `__dirname`, or `__filename`. These tokens normally point at the file's own
|
|
3
|
+
// on-disk location; once the file is bundled, the bundler must rewrite them so
|
|
4
|
+
// runtime data-file reads (a dep loading its own i18n JSON, `package.json`,
|
|
5
|
+
// templates, …) still resolve.
|
|
7
6
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
7
|
+
// Cases, each with its own strategy:
|
|
8
|
+
//
|
|
9
|
+
// 1. on-disk ESM, runtime-resolve (`import.meta.url` present, not zip-resident,
|
|
10
|
+
// `format === 'esm'`)
|
|
11
|
+
// → rewrite to RUNTIME resolution via the `module-resolve` shim. The shim
|
|
12
|
+
// resolves the file's `<pkg>/<subpath>` from the bundle's actual location
|
|
13
|
+
// at run time, so the bundle works wherever it ends up — crucially, after
|
|
14
|
+
// being PUBLISHED in an npm package (where it sits at a different
|
|
15
|
+
// `node_modules` depth than at build time). See module-resolve.ts. Relies
|
|
16
|
+
// on the bundle-URL banner (bundle-url-banner.ts) having captured the
|
|
17
|
+
// anchor, hence the `runtimeResolve` gate.
|
|
18
|
+
//
|
|
19
|
+
// 2. on-disk ESM, legacy (same, but a non-ESM output format where the banner
|
|
20
|
+
// can't run)
|
|
21
|
+
// → the original behavior: rewrite to a path relative to the bundle's BUILD
|
|
22
|
+
// location. Correct only while bundle ↔ node_modules keep their build-time
|
|
23
|
+
// arrangement; preserved unchanged for the rare `--format iife|cjs` app
|
|
24
|
+
// build that can't host the `import.meta.url` anchor banner.
|
|
25
|
+
//
|
|
26
|
+
// 3. PnP zip-resident (`import.meta.url`, path inside a `.zip/`)
|
|
27
|
+
// → the file lives inside a Yarn-PnP zip; `import.meta.url` stays the
|
|
28
|
+
// bundle's own URL and `__dirname`/`__filename` derive from it.
|
|
29
|
+
//
|
|
30
|
+
// 4. CJS (`__dirname`/`__filename` only, no `import.meta.url`)
|
|
31
|
+
// → declare them from the file's absolute build path. NOTE: still
|
|
32
|
+
// build-location-coupled and shares the "breaks when published" class of
|
|
33
|
+
// bug case (1) fixes; injecting an ESM import into a CJS module is unsafe,
|
|
34
|
+
// so a CJS-safe runtime-resolve is tracked as a follow-up. ESM deps (the
|
|
35
|
+
// common data-reader shape, e.g. typedoc) take path (1).
|
|
36
|
+
//
|
|
37
|
+
// Hosting: a Rolldown `transform(code, id)` hook with `order: 'post'` — runs
|
|
38
|
+
// after deepkit/blueprint/css pre-transforms but still during module loading,
|
|
39
|
+
// before chunking.
|
|
15
40
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
16
41
|
import { inlineStaticReads } from '../utils/inline-static-reads.js';
|
|
17
42
|
export const REWRITE_FILTER = /\.(m?js|cjs|[cm]?tsx?)$/;
|
|
18
43
|
const DIRNAME_DECL_RE = /(?:var|let|const)\s+__dirname\b|export\s+(?:var|let|const)\s+__dirname\b/;
|
|
19
44
|
const FILENAME_DECL_RE = /(?:var|let|const)\s+__filename\b|export\s+(?:var|let|const)\s+__filename\b/;
|
|
45
|
+
// Bare specifier of the runtime module-path resolver shim (see module-resolve.ts).
|
|
46
|
+
// No `.js` — must match the package's `exports` subpath key (exports maps are
|
|
47
|
+
// exact-match), mirroring the `./shims/console-gjs` convention.
|
|
48
|
+
const MODULE_RESOLVE_SHIM = '@gjsify/rolldown-plugin-gjsify/shims/module-resolve';
|
|
49
|
+
// Our own shims (module-resolve, console-gjs) get bundled into user output, so
|
|
50
|
+
// they live in `node_modules/@gjsify/rolldown-plugin-gjsify/{lib,src}/shims/`
|
|
51
|
+
// when consumed — but they must NEVER be rewritten. In particular the
|
|
52
|
+
// module-resolve shim DECLARES `__gjsifyModule*` and its comments mention
|
|
53
|
+
// `import.meta.url`; the naive token check below would otherwise treat that as
|
|
54
|
+
// a rewrite target and prepend a self-import that collides with its exports.
|
|
55
|
+
const GJSIFY_SHIM_RE = /[\\/]rolldown-plugin-gjsify[\\/](?:lib|src)[\\/]shims[\\/]/;
|
|
20
56
|
/** True when the rewriter wants to look at this path — node_modules + supported ext. */
|
|
21
57
|
export function shouldRewrite(path) {
|
|
22
|
-
|
|
58
|
+
if (!path.includes('node_modules') || !REWRITE_FILTER.test(path))
|
|
59
|
+
return false;
|
|
60
|
+
if (GJSIFY_SHIM_RE.test(path))
|
|
61
|
+
return false;
|
|
62
|
+
return true;
|
|
23
63
|
}
|
|
24
64
|
/**
|
|
25
|
-
* Compute the directory the bundle's outfile lives in
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* bundle's `import.meta.url` — so we need to know where the bundle will be
|
|
29
|
-
* written. Both `output.file` and `output.dir` are accepted.
|
|
65
|
+
* Compute the directory the bundle's outfile lives in (used for the zip-resident
|
|
66
|
+
* check and the legacy relative paths). Both `output.file` and `output.dir` are
|
|
67
|
+
* accepted.
|
|
30
68
|
*/
|
|
31
69
|
export function getBundleDirFromOutput(opts) {
|
|
32
70
|
const outFile = opts.file ?? join(opts.dir ?? '.', 'bundle.mjs');
|
|
33
71
|
return dirname(resolve(outFile));
|
|
34
72
|
}
|
|
35
|
-
/** Pick the per-file loader Rolldown should re-parse with. */
|
|
73
|
+
/** Pick the per-file loader Rolldown should re-parse the rewritten code with. */
|
|
36
74
|
function moduleTypeForPath(path) {
|
|
37
75
|
const ext = path.split('.').pop() ?? 'js';
|
|
38
76
|
return ['ts', 'mts', 'cts', 'tsx'].includes(ext) ? 'ts' : 'js';
|
|
39
77
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
/**
|
|
79
|
+
* The package-qualified spec for a node_modules file: everything after the LAST
|
|
80
|
+
* `node_modules/` segment. This is what the runtime resolver feeds to
|
|
81
|
+
* `createRequire(...).resolve` (via the package root).
|
|
82
|
+
*
|
|
83
|
+
* ".../node_modules/typedoc/dist/lib/app.js" → "typedoc/dist/lib/app.js"
|
|
84
|
+
* ".../node_modules/@scope/name/sub.js" → "@scope/name/sub.js"
|
|
85
|
+
* ".../node_modules/a/node_modules/b/file.js" → "b/file.js"
|
|
86
|
+
*/
|
|
87
|
+
export function extractPackageSpec(path) {
|
|
88
|
+
const marker = 'node_modules/';
|
|
89
|
+
const idx = path.lastIndexOf(marker);
|
|
90
|
+
return idx < 0 ? path : path.slice(idx + marker.length);
|
|
91
|
+
}
|
|
92
|
+
/** Whether the file needs a `var __dirname`/`__filename` declaration injected. */
|
|
93
|
+
function needsDirnameDecl(src, flags) {
|
|
94
|
+
return flags.hasDirname && !DIRNAME_DECL_RE.test(src);
|
|
95
|
+
}
|
|
96
|
+
function needsFilenameDecl(src, flags) {
|
|
97
|
+
return flags.hasFilename && !FILENAME_DECL_RE.test(src);
|
|
98
|
+
}
|
|
99
|
+
/** Prepend preamble + (optional) shim import to the source. */
|
|
100
|
+
function withPreamble(src, lines, importHeader) {
|
|
101
|
+
const parts = importHeader ? [importHeader, ...lines, src] : [...lines, src];
|
|
102
|
+
return parts.join('\n');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Case 1 — on-disk ESM, runtime-resolve. Rewrite the file to resolve its own
|
|
106
|
+
* location at runtime via the module-resolve shim — location-independent, so it
|
|
107
|
+
* survives being published/relocated. The fix for the "works in the workspace,
|
|
108
|
+
* crashes once installed" class of bug.
|
|
109
|
+
*/
|
|
110
|
+
function rewriteOnDiskEsm(src, path, flags) {
|
|
111
|
+
const spec = JSON.stringify(extractPackageSpec(path));
|
|
112
|
+
// Collect only the named imports we actually use — tree-shaking has nothing
|
|
113
|
+
// to prune and the import line stays honest.
|
|
114
|
+
const used = ['__gjsifyModuleUrl'];
|
|
115
|
+
const preamble = [];
|
|
116
|
+
if (needsDirnameDecl(src, flags)) {
|
|
117
|
+
preamble.push(`var __dirname = __gjsifyModuleDir(${spec});`);
|
|
118
|
+
used.push('__gjsifyModuleDir');
|
|
52
119
|
}
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
120
|
+
if (needsFilenameDecl(src, flags)) {
|
|
121
|
+
preamble.push(`var __filename = __gjsifyModuleFile(${spec});`);
|
|
122
|
+
used.push('__gjsifyModuleFile');
|
|
123
|
+
}
|
|
124
|
+
const code = src.replace(/\bimport\.meta\.url\b/g, `__gjsifyModuleUrl(${spec})`);
|
|
125
|
+
const header = `import { ${used.join(', ')} } from ${JSON.stringify(MODULE_RESOLVE_SHIM)};`;
|
|
126
|
+
return { code: withPreamble(code, preamble, header), moduleType: moduleTypeForPath(path) };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Case 2 — on-disk ESM, legacy. Original build-relative behavior, kept for the
|
|
130
|
+
* rare non-ESM output format that can't host the bundle-URL anchor banner.
|
|
131
|
+
*/
|
|
132
|
+
function rewriteOnDiskEsmLegacy(src, path, bundleDir, flags) {
|
|
133
|
+
const relPath = relative(bundleDir, path);
|
|
134
|
+
const relDirWithSlash = (relative(bundleDir, dirname(path)) || '.') + '/';
|
|
135
|
+
const preamble = [];
|
|
136
|
+
if (needsDirnameDecl(src, flags)) {
|
|
137
|
+
preamble.push(`var __dirname = new URL(${JSON.stringify(relDirWithSlash)}, import.meta.url).pathname.replace(/\\/$/, "");`);
|
|
63
138
|
}
|
|
64
|
-
|
|
139
|
+
if (needsFilenameDecl(src, flags)) {
|
|
140
|
+
preamble.push(`var __filename = new URL(${JSON.stringify(relPath)}, import.meta.url).pathname;`);
|
|
141
|
+
}
|
|
142
|
+
const code = src.replace(/\bimport\.meta\.url\b/g, `new URL(${JSON.stringify(relPath)}, import.meta.url).href`);
|
|
143
|
+
return { code: withPreamble(code, preamble), moduleType: moduleTypeForPath(path) };
|
|
65
144
|
}
|
|
66
145
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* any of the patterns we care about.
|
|
146
|
+
* Case 3 — PnP zip-resident. Keep `import.meta.url` as the bundle's own URL and
|
|
147
|
+
* derive `__dirname`/`__filename` from it.
|
|
70
148
|
*/
|
|
71
|
-
|
|
149
|
+
function rewriteZipResident(src, path, flags) {
|
|
150
|
+
const preamble = [];
|
|
151
|
+
if (needsDirnameDecl(src, flags)) {
|
|
152
|
+
preamble.push(`var __dirname = new URL(".", import.meta.url).pathname.replace(/\\/$/, "");`);
|
|
153
|
+
}
|
|
154
|
+
if (needsFilenameDecl(src, flags)) {
|
|
155
|
+
preamble.push(`var __filename = new URL(import.meta.url).pathname;`);
|
|
156
|
+
}
|
|
157
|
+
return { code: withPreamble(src, preamble), moduleType: moduleTypeForPath(path) };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Case 4 — CJS (no `import.meta.url`). Declare `__dirname`/`__filename` from the
|
|
161
|
+
* absolute build path. Build-location-coupled — see file header note.
|
|
162
|
+
*/
|
|
163
|
+
function rewriteCjsAbsolute(src, path, flags) {
|
|
164
|
+
const preamble = [];
|
|
165
|
+
if (needsDirnameDecl(src, flags)) {
|
|
166
|
+
preamble.push(`var __dirname = ${JSON.stringify(dirname(path))};`);
|
|
167
|
+
}
|
|
168
|
+
if (needsFilenameDecl(src, flags)) {
|
|
169
|
+
preamble.push(`var __filename = ${JSON.stringify(path)};`);
|
|
170
|
+
}
|
|
171
|
+
return { code: withPreamble(src, preamble), moduleType: moduleTypeForPath(path) };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Pure rewriter. Returns the rewritten code (and module type for re-parsing) or
|
|
175
|
+
* `null` if the file references none of the patterns we care about.
|
|
176
|
+
*
|
|
177
|
+
* @param runtimeResolve When true (ESM output, banner present), on-disk ESM
|
|
178
|
+
* files resolve their location at runtime (case 1). When false, they fall back
|
|
179
|
+
* to the legacy build-relative rewrite (case 2).
|
|
180
|
+
*/
|
|
181
|
+
export function rewriteContents(args, srcInput, bundleDir, runtimeResolve) {
|
|
72
182
|
if (!shouldRewrite(args.path))
|
|
73
183
|
return null;
|
|
74
184
|
// Step 1: inline statically-resolvable filesystem reads.
|
|
75
185
|
const inlined = inlineStaticReads(srcInput, args.path);
|
|
76
186
|
const src = inlined.contents;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
187
|
+
const flags = {
|
|
188
|
+
hasMetaUrl: src.includes('import.meta.url'),
|
|
189
|
+
hasDirname: src.includes('__dirname'),
|
|
190
|
+
hasFilename: src.includes('__filename'),
|
|
191
|
+
};
|
|
192
|
+
if (!flags.hasMetaUrl && !flags.hasDirname && !flags.hasFilename) {
|
|
193
|
+
// No tokens to rewrite — emit only if inlining changed something.
|
|
194
|
+
return inlined.inlined > 0 ? { code: src, moduleType: moduleTypeForPath(args.path) } : null;
|
|
84
195
|
}
|
|
85
|
-
// Step 2:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
dirnameDeclared: DIRNAME_DECL_RE.test(src),
|
|
94
|
-
filenameDeclared: FILENAME_DECL_RE.test(src),
|
|
95
|
-
kind,
|
|
96
|
-
sourcePath: args.path,
|
|
97
|
-
sourceDir: dir,
|
|
98
|
-
relPath,
|
|
99
|
-
relDirWithSlash: (relative(bundleDir, dir) || '.') + '/',
|
|
100
|
-
});
|
|
101
|
-
// Step 3: rewrite import.meta.url for the regular esm-relative case.
|
|
102
|
-
let code = src;
|
|
103
|
-
if (kind === 'esm-relative') {
|
|
104
|
-
const runtimeFileUrl = `new URL(${JSON.stringify(relPath)}, import.meta.url)`;
|
|
105
|
-
code = code.replace(/\bimport\.meta\.url\b/g, `${runtimeFileUrl}.href`);
|
|
196
|
+
// Step 2: dispatch by case (see file header).
|
|
197
|
+
if (flags.hasMetaUrl) {
|
|
198
|
+
if (relative(bundleDir, args.path).includes('.zip/')) {
|
|
199
|
+
return rewriteZipResident(src, args.path, flags);
|
|
200
|
+
}
|
|
201
|
+
return runtimeResolve
|
|
202
|
+
? rewriteOnDiskEsm(src, args.path, flags)
|
|
203
|
+
: rewriteOnDiskEsmLegacy(src, args.path, bundleDir, flags);
|
|
106
204
|
}
|
|
107
|
-
|
|
108
|
-
code = preamble.join('\n') + '\n' + code;
|
|
109
|
-
return { code, moduleType: moduleTypeForPath(args.path) };
|
|
205
|
+
return rewriteCjsAbsolute(src, args.path, flags);
|
|
110
206
|
}
|
|
111
207
|
/**
|
|
112
208
|
* Build a Rolldown plugin that runs the path rewriter as a `transform(code, id)`
|
|
113
|
-
* hook with `order: 'post'
|
|
114
|
-
* but still during module loading, before chunking.
|
|
209
|
+
* hook with `order: 'post'`.
|
|
115
210
|
*/
|
|
116
211
|
export function nodeModulesPathRewritePlugin(options) {
|
|
212
|
+
const runtimeResolve = options.runtimeResolve ?? false;
|
|
117
213
|
return {
|
|
118
214
|
name: 'gjsify-node-modules-path-rewrite',
|
|
119
215
|
transform: {
|
|
@@ -122,7 +218,7 @@ export function nodeModulesPathRewritePlugin(options) {
|
|
|
122
218
|
handler(code, id) {
|
|
123
219
|
if (!id.includes('node_modules'))
|
|
124
220
|
return null;
|
|
125
|
-
const result = rewriteContents({ path: id }, code, options.bundleDir);
|
|
221
|
+
const result = rewriteContents({ path: id }, code, options.bundleDir, runtimeResolve);
|
|
126
222
|
if (!result)
|
|
127
223
|
return null;
|
|
128
224
|
return { code: result.code, map: null };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split a bundled-file spec into its package name and the path within the
|
|
3
|
+
* package. Handles scoped names:
|
|
4
|
+
* "typedoc/dist/lib/app.js" → { pkg: "typedoc", subpath: "dist/lib/app.js" }
|
|
5
|
+
* "@scope/name/sub/file.js" → { pkg: "@scope/name", subpath: "sub/file.js" }
|
|
6
|
+
* "typedoc" → { pkg: "typedoc", subpath: "" }
|
|
7
|
+
*/
|
|
8
|
+
export declare function splitPackageSpec(spec: string): {
|
|
9
|
+
pkg: string;
|
|
10
|
+
subpath: string;
|
|
11
|
+
};
|
|
12
|
+
/** Absolute path of the bundled dep file `<pkg>/<subpath>` at runtime. */
|
|
13
|
+
export declare function __gjsifyModuleFile(spec: string): string;
|
|
14
|
+
/** `file://` URL of `<pkg>/<subpath>` — replaces a rewritten `import.meta.url`. */
|
|
15
|
+
export declare function __gjsifyModuleUrl(spec: string): string;
|
|
16
|
+
/** Directory of `<pkg>/<subpath>` — replaces a rewritten `__dirname`. */
|
|
17
|
+
export declare function __gjsifyModuleDir(spec: string): string;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Runtime resolver for the on-disk location of a bundled `node_modules` file.
|
|
2
|
+
//
|
|
3
|
+
// Why this exists
|
|
4
|
+
// ---------------
|
|
5
|
+
// `rewrite-node-modules-paths.ts` rewrites a bundled module's `import.meta.url`
|
|
6
|
+
// / `__dirname` / `__filename` so that dynamic data-file reads (a dep loading
|
|
7
|
+
// its own i18n JSON, `package.json`, templates, …) still find those files on
|
|
8
|
+
// disk after bundling. The naive rewrite baked a path relative to the bundle's
|
|
9
|
+
// BUILD location, which is only correct while the bundle and `node_modules/`
|
|
10
|
+
// keep the arrangement they had at build time. Once the bundle is PUBLISHED in
|
|
11
|
+
// an npm package and installed at `node_modules/<pkg>/bin/<bundle>`, that baked
|
|
12
|
+
// path lands one `node_modules` level too deep → ENOENT at runtime.
|
|
13
|
+
//
|
|
14
|
+
// Instead we resolve the dep at RUNTIME from the bundle's actual location, so
|
|
15
|
+
// it works wherever the bundle ends up.
|
|
16
|
+
//
|
|
17
|
+
// Three correctness constraints shape the implementation:
|
|
18
|
+
//
|
|
19
|
+
// 1. *Location anchor.* The resolver must anchor at the BUNDLE's URL, not its
|
|
20
|
+
// own. When a consumer bundles this shim it lives under
|
|
21
|
+
// `node_modules/@gjsify/rolldown-plugin-gjsify/...`, so the path rewriter
|
|
22
|
+
// would rewrite this file's own `import.meta.url` too. We therefore take no
|
|
23
|
+
// `import.meta.url` here and read the anchor from `globalThis.
|
|
24
|
+
// __gjsifyBundleUrl`, captured by a one-line banner at byte 0 of the bundle —
|
|
25
|
+
// the single point where `import.meta.url` is unambiguously the bundle's URL.
|
|
26
|
+
//
|
|
27
|
+
// 2. *exports-map safety.* `createRequire(...).resolve("<pkg>/<deep/path>")` is
|
|
28
|
+
// rejected by strict `"exports"` maps under Node's native `createRequire`
|
|
29
|
+
// (the `--app node` target), and `@gjsify/module` is exports-aware too. So
|
|
30
|
+
// we never resolve the deep path directly: we resolve the PACKAGE ROOT and
|
|
31
|
+
// join the subpath literally, which no `exports` map can block.
|
|
32
|
+
//
|
|
33
|
+
// 3. *Lazy / self-contained safety.* `import.meta.url` is normally a passive
|
|
34
|
+
// string — the extremely common `createRequire(import.meta.url)` pattern
|
|
35
|
+
// does NOT require the URL to point at an existing file (createRequire only
|
|
36
|
+
// resolves later, lazily). A self-contained bootstrap bundle (e.g. gjsify's
|
|
37
|
+
// own committed `cli.gjs.mjs`, which runs to CREATE `node_modules` before it
|
|
38
|
+
// exists) bundles its deps' code but has no `node_modules` at runtime. So
|
|
39
|
+
// resolution must never THROW: when a dep can't be resolved we fall back to
|
|
40
|
+
// the bundle's own location, keeping `createRequire(import.meta.url)` /
|
|
41
|
+
// `new URL(rel, import.meta.url)` valid (lazy) instead of crashing — exactly
|
|
42
|
+
// the pre-fix semantics for that case.
|
|
43
|
+
//
|
|
44
|
+
// @ts-ignore — `node:{module,url,path}` are resolved by the consumer's
|
|
45
|
+
// `gjsify build` run (aliased to `@gjsify/{module,url,path}`), not by tsc here.
|
|
46
|
+
import { createRequire } from 'node:module';
|
|
47
|
+
// @ts-ignore — see above.
|
|
48
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
49
|
+
// @ts-ignore — see above.
|
|
50
|
+
import { dirname, join } from 'node:path';
|
|
51
|
+
/**
|
|
52
|
+
* Split a bundled-file spec into its package name and the path within the
|
|
53
|
+
* package. Handles scoped names:
|
|
54
|
+
* "typedoc/dist/lib/app.js" → { pkg: "typedoc", subpath: "dist/lib/app.js" }
|
|
55
|
+
* "@scope/name/sub/file.js" → { pkg: "@scope/name", subpath: "sub/file.js" }
|
|
56
|
+
* "typedoc" → { pkg: "typedoc", subpath: "" }
|
|
57
|
+
*/
|
|
58
|
+
export function splitPackageSpec(spec) {
|
|
59
|
+
const parts = spec.split('/');
|
|
60
|
+
const segments = spec.startsWith('@') ? 2 : 1;
|
|
61
|
+
return {
|
|
62
|
+
pkg: parts.slice(0, segments).join('/'),
|
|
63
|
+
subpath: parts.slice(segments).join('/'),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** The bundle's own URL (set by the byte-0 banner). Throws only if the banner didn't run. */
|
|
67
|
+
function bundleAnchorUrl() {
|
|
68
|
+
const anchor = globalThis.__gjsifyBundleUrl;
|
|
69
|
+
if (!anchor) {
|
|
70
|
+
throw new Error('gjsify: __gjsifyBundleUrl is not set — the bundle-URL banner did not run. ' +
|
|
71
|
+
'The module-resolve shim is only valid in single-file app builds (gjsify build --app gjs|node).');
|
|
72
|
+
}
|
|
73
|
+
return anchor;
|
|
74
|
+
}
|
|
75
|
+
// `createRequire` anchored at the bundle URL — built lazily so the banner is
|
|
76
|
+
// guaranteed to have run, and so a build that never hits a rewritten read pays
|
|
77
|
+
// nothing.
|
|
78
|
+
let _resolve = null;
|
|
79
|
+
function bundleResolve() {
|
|
80
|
+
if (!_resolve)
|
|
81
|
+
_resolve = createRequire(bundleAnchorUrl()).resolve;
|
|
82
|
+
return _resolve;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Find the on-disk root directory of `pkg`, exports-map-agnostic.
|
|
86
|
+
*
|
|
87
|
+
* `package.json` is resolvable for nearly every package and points straight at
|
|
88
|
+
* the root. When a strict `exports` map blocks it, fall back to the package's
|
|
89
|
+
* main entry (always an export) and derive the root from the
|
|
90
|
+
* `node_modules/<pkg>` boundary in its path. Throws when `pkg` is not installed.
|
|
91
|
+
*/
|
|
92
|
+
function resolvePackageRoot(pkg) {
|
|
93
|
+
const resolve = bundleResolve();
|
|
94
|
+
try {
|
|
95
|
+
return dirname(resolve(`${pkg}/package.json`));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
const main = resolve(pkg);
|
|
99
|
+
const marker = `/node_modules/${pkg}/`;
|
|
100
|
+
const idx = main.lastIndexOf(marker);
|
|
101
|
+
return idx >= 0 ? main.slice(0, idx + marker.length - 1) : dirname(main);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Per-spec memo — resolution walks the filesystem and bundled deps read their
|
|
105
|
+
// data files repeatedly (per-locale i18n lookups, etc.).
|
|
106
|
+
const _cache = new Map();
|
|
107
|
+
/**
|
|
108
|
+
* Absolute on-disk path for a bundled dep file `<pkg>/<subpath>`, or — when the
|
|
109
|
+
* dep can't be resolved at runtime (no `node_modules`, e.g. a self-contained
|
|
110
|
+
* bootstrap bundle) — the bundle's own path as a non-throwing fallback. See
|
|
111
|
+
* constraint 3 in the file header.
|
|
112
|
+
*/
|
|
113
|
+
function resolveFile(spec) {
|
|
114
|
+
const cached = _cache.get(spec);
|
|
115
|
+
if (cached !== undefined)
|
|
116
|
+
return cached;
|
|
117
|
+
let abs;
|
|
118
|
+
try {
|
|
119
|
+
const { pkg, subpath } = splitPackageSpec(spec);
|
|
120
|
+
const root = resolvePackageRoot(pkg);
|
|
121
|
+
abs = subpath ? join(root, subpath) : root;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
abs = fileURLToPath(bundleAnchorUrl());
|
|
125
|
+
}
|
|
126
|
+
_cache.set(spec, abs);
|
|
127
|
+
return abs;
|
|
128
|
+
}
|
|
129
|
+
/** Absolute path of the bundled dep file `<pkg>/<subpath>` at runtime. */
|
|
130
|
+
export function __gjsifyModuleFile(spec) {
|
|
131
|
+
return resolveFile(spec);
|
|
132
|
+
}
|
|
133
|
+
/** `file://` URL of `<pkg>/<subpath>` — replaces a rewritten `import.meta.url`. */
|
|
134
|
+
export function __gjsifyModuleUrl(spec) {
|
|
135
|
+
return pathToFileURL(resolveFile(spec)).href;
|
|
136
|
+
}
|
|
137
|
+
/** Directory of `<pkg>/<subpath>` — replaces a rewritten `__dirname`. */
|
|
138
|
+
export function __gjsifyModuleDir(spec) {
|
|
139
|
+
return dirname(resolveFile(spec));
|
|
140
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/rolldown-plugin-gjsify",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.26",
|
|
4
4
|
"description": "Rolldown / Rollup / Vite plugin orchestrator for GJS, Node, and Browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
},
|
|
18
18
|
"./shims/console-gjs": {
|
|
19
19
|
"default": "./lib/shims/console-gjs.js"
|
|
20
|
+
},
|
|
21
|
+
"./shims/module-resolve": {
|
|
22
|
+
"default": "./lib/shims/module-resolve.js"
|
|
20
23
|
}
|
|
21
24
|
},
|
|
22
25
|
"files": [
|
|
@@ -45,11 +48,11 @@
|
|
|
45
48
|
],
|
|
46
49
|
"license": "MIT",
|
|
47
50
|
"dependencies": {
|
|
48
|
-
"@gjsify/console": "^0.4.
|
|
49
|
-
"@gjsify/resolve-npm": "^0.4.
|
|
50
|
-
"@gjsify/rolldown-plugin-deepkit": "^0.4.
|
|
51
|
-
"@gjsify/rolldown-plugin-pnp": "^0.4.
|
|
52
|
-
"@gjsify/vite-plugin-blueprint": "^0.4.
|
|
51
|
+
"@gjsify/console": "^0.4.26",
|
|
52
|
+
"@gjsify/resolve-npm": "^0.4.26",
|
|
53
|
+
"@gjsify/rolldown-plugin-deepkit": "^0.4.26",
|
|
54
|
+
"@gjsify/rolldown-plugin-pnp": "^0.4.26",
|
|
55
|
+
"@gjsify/vite-plugin-blueprint": "^0.4.26",
|
|
53
56
|
"@rollup/pluginutils": "^5.3.0",
|
|
54
57
|
"acorn": "^8.16.0",
|
|
55
58
|
"acorn-walk": "^8.3.5",
|
|
@@ -57,7 +60,7 @@
|
|
|
57
60
|
"lightningcss": "^1.32.0"
|
|
58
61
|
},
|
|
59
62
|
"peerDependencies": {
|
|
60
|
-
"@gjsify/lightningcss-native": "^0.4.
|
|
63
|
+
"@gjsify/lightningcss-native": "^0.4.26",
|
|
61
64
|
"rolldown": "^1.0.0-rc.18"
|
|
62
65
|
},
|
|
63
66
|
"peerDependenciesMeta": {
|