@gjsify/rolldown-plugin-gjsify 0.64.15-next.287 → 0.64.16

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.
@@ -34,6 +34,7 @@ import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace
34
34
  import { GSCHEMA_REGISTER, gschemaPlugin } from '../plugins/gschema.js';
35
35
  import { gresourcePlugin } from '../plugins/gresource.js';
36
36
  import { implicitGlobalAssignPlugin } from '../plugins/implicit-global-assign.js';
37
+ import { workerUrlPlugin } from '../plugins/worker-url.js';
37
38
  export const setupForBrowser = async (input) => {
38
39
  const userExternal = input.userExternal ?? [];
39
40
  const external = [...userExternal];
@@ -148,6 +149,8 @@ export const setupForBrowser = async (input) => {
148
149
  gschemaPlugin({ mode: 'emit', register: GSCHEMA_REGISTER.browser }),
149
150
  gresourcePlugin({ compressed: 'strip' }),
150
151
  implicitGlobalAssignPlugin(),
152
+ // `new Worker(new URL('./x', import.meta.url))` → the file the CLI builds beside the bundle.
153
+ ...(input.pluginOptions.workers ? [workerUrlPlugin(input.pluginOptions.workers)] : []),
151
154
  cssAsStringPlugin(),
152
155
  // `order: 'post'` — see app/gjs.ts. The browser target's whole job is to
153
156
  // replace Node builtins with their `@gjsify/*` browser entries; when one
package/lib/app/gjs.js CHANGED
@@ -24,6 +24,7 @@ import { consoleAssignPlugin } from '../plugins/console-assign.js';
24
24
  import { gschemaPlugin } from '../plugins/gschema.js';
25
25
  import { gresourcePlugin } from '../plugins/gresource.js';
26
26
  import { implicitGlobalAssignPlugin } from '../plugins/implicit-global-assign.js';
27
+ import { workerUrlPlugin } from '../plugins/worker-url.js';
27
28
  import { shebangPlugin, resolveShebangLine, inputShebangStripPlugin } from '../plugins/shebang.js';
28
29
  import { wrapInputWithSideEffects } from '../utils/entry-wrapper.js';
29
30
  const _shimDir = dirname(fileURLToPath(import.meta.url));
@@ -206,6 +207,8 @@ export const setupForGjs = async (input) => {
206
207
  gschemaPlugin({ mode: 'ignore' }),
207
208
  gresourcePlugin({ compressed: 'keep' }),
208
209
  implicitGlobalAssignPlugin(),
210
+ // `new Worker(new URL('./x', import.meta.url))` → the file the CLI builds beside the bundle.
211
+ ...(input.pluginOptions.workers ? [workerUrlPlugin(input.pluginOptions.workers)] : []),
209
212
  // `gi://Ns?version=X&optional` → a guarded import (ADR 0087), claimed `pre`
210
213
  // so the externals policy never sees the flagged specifier.
211
214
  giOptionalPlugin('gjs'),
package/lib/app/node.js CHANGED
@@ -14,6 +14,7 @@ import { giOptionalPlugin } from '../plugins/gi-optional.js';
14
14
  import { gschemaPlugin } from '../plugins/gschema.js';
15
15
  import { gresourcePlugin } from '../plugins/gresource.js';
16
16
  import { implicitGlobalAssignPlugin } from '../plugins/implicit-global-assign.js';
17
+ import { workerUrlPlugin } from '../plugins/worker-url.js';
17
18
  import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
18
19
  import { nodeNativeExternalPlugin } from '../plugins/node-native-external.js';
19
20
  import { platformResolvePlugin, desktopSuffixChain, desktopOsSuffix, DESKTOP_REFUSED_SUFFIXES, } from '../plugins/platform-resolve.js';
@@ -356,6 +357,8 @@ export const setupForNode = async (input) => {
356
357
  gschemaPlugin({ mode: 'ignore' }),
357
358
  gresourcePlugin({ compressed: 'keep' }),
358
359
  implicitGlobalAssignPlugin(),
360
+ // `new Worker(new URL('./x', import.meta.url))` → the file the CLI builds beside the bundle.
361
+ ...(input.pluginOptions.workers ? [workerUrlPlugin(input.pluginOptions.workers)] : []),
359
362
  // Decides the fate of `@girs/*` before `aliasPlugin` and the default
360
363
  // resolver (same composition order as `app/browser.ts`). `emptyGirs` is
361
364
  // gated on `gjsSourceBuild`:
package/lib/index.d.ts CHANGED
@@ -13,6 +13,8 @@ export type { GiSystemProbe } from './plugins/gi-runtime-paths.js';
13
13
  export type { ProcessStubPluginOptions } from './plugins/process-stub.js';
14
14
  export { findImplicitGlobalAssignments, rewriteImplicitGlobalAssignments, implicitGlobalAssignPlugin, IMPLICIT_GLOBAL_ASSIGN_PLUGIN, } from './plugins/implicit-global-assign.js';
15
15
  export type { ImplicitGlobalAssignment } from './plugins/implicit-global-assign.js';
16
+ export { workerUrlPlugin, findWorkerUrls, rewriteWorkerUrls, WorkerEntries, WORKER_URL_PLUGIN, } from './plugins/worker-url.js';
17
+ export type { WorkerUrlSite } from './plugins/worker-url.js';
16
18
  export { gschemaPlugin, parseGschema, GSCHEMA_PLUGIN, GSCHEMA_REGISTER } from './plugins/gschema.js';
17
19
  export type { GschemaOut, GschemaKey, GschemaPluginOptions } from './plugins/gschema.js';
18
20
  export { gresourcePlugin, compileGresource, prepareGresourceXml, gresourceModuleSource, GRESOURCE_PLUGIN, } from './plugins/gresource.js';
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ export { giRuntimePathsStub } from './plugins/gi-runtime-paths.js';
9
9
  export { giOptionalPlugin, giOptionalShimSource, giOptionalNodeShimSource, giOptionalMarkerSource, parseOptionalGiSpecifier, GI_OPTIONAL_FLAG, GI_OPTIONAL_MARKER, } from './plugins/gi-optional.js';
10
10
  export { bindConsoleLocally, consoleAssignPlugin, freeConsoleAssignmentInsertion, CONSOLE_LOCAL_BINDING, } from './plugins/console-assign.js';
11
11
  export { findImplicitGlobalAssignments, rewriteImplicitGlobalAssignments, implicitGlobalAssignPlugin, IMPLICIT_GLOBAL_ASSIGN_PLUGIN, } from './plugins/implicit-global-assign.js';
12
+ export { workerUrlPlugin, findWorkerUrls, rewriteWorkerUrls, WorkerEntries, WORKER_URL_PLUGIN, } from './plugins/worker-url.js';
12
13
  export { gschemaPlugin, parseGschema, GSCHEMA_PLUGIN, GSCHEMA_REGISTER } from './plugins/gschema.js';
13
14
  export { gresourcePlugin, compileGresource, prepareGresourceXml, gresourceModuleSource, GRESOURCE_PLUGIN, } from './plugins/gresource.js';
14
15
  export { cssAsStringPlugin } from './plugins/css-as-string.js';
@@ -0,0 +1,40 @@
1
+ import type { Plugin } from 'rolldown';
2
+ export declare const WORKER_URL_PLUGIN = "gjsify-worker-url";
3
+ /** One `new URL('<spec>', import.meta.url)` argument of a Worker constructor. */
4
+ export interface WorkerUrlSite {
5
+ /** The specifier as written — what the registry resolves relative to the importer. */
6
+ readonly specifier: string;
7
+ /** Inclusive start offset of the string literal, quotes included. */
8
+ readonly start: number;
9
+ /** Exclusive end offset of the string literal. */
10
+ readonly end: number;
11
+ }
12
+ /** Every Worker constructor in `code` that names its script by a literal URL, source order. */
13
+ export declare function findWorkerUrls(code: string, id: string): WorkerUrlSite[];
14
+ /**
15
+ * What a worker script is called next to the bundle, and which sources still need building.
16
+ *
17
+ * ONE instance per `gjsify build`, shared with every nested worker build: the name a source
18
+ * gets must be the same in the bundle that spawns it and in the one that spawns it from
19
+ * another worker, and a source already built must not be built again.
20
+ */
21
+ export declare class WorkerEntries {
22
+ #private;
23
+ readonly extension: string;
24
+ /** @param extension the main bundle's extension (`.js`, `.mjs`), mirrored so a worker loads like its parent. */
25
+ constructor(extension?: string);
26
+ /** The file name (no directory) the worker built from `source` gets beside the bundle. */
27
+ register(source: string): string;
28
+ /** Registered sources not yet handed out, with their file names; each is returned once. */
29
+ takePending(): {
30
+ source: string;
31
+ fileName: string;
32
+ }[];
33
+ }
34
+ /** `code` with each resolvable worker URL pointed at its emitted file, or null when nothing changed. */
35
+ export declare function rewriteWorkerUrls(code: string, id: string, resolveSource: (specifier: string) => Promise<string | null>, entries: WorkerEntries): Promise<string | null>;
36
+ /**
37
+ * Composed by `--app gjs|node|browser` when the CLI hands over a {@link WorkerEntries}; absent
38
+ * registry, absent plugin — a library build keeps its `new URL` for the consumer's own bundler.
39
+ */
40
+ export declare function workerUrlPlugin(entries: WorkerEntries): Plugin;
@@ -0,0 +1,209 @@
1
+ // `new Worker(new URL('./x.ts', import.meta.url))` — ship the worker next to the bundle.
2
+ //
3
+ // A bundle is ONE file, so after `gjsify build` the literal still names `./x.ts`, a path
4
+ // that exists beside the SOURCE and nowhere beside the artifact: at runtime the Worker dies
5
+ // with `Cannot find module …/x.ts` (#2338). esbuild and Vite turn the same expression into a
6
+ // second entry and rewrite the URL; this does the same.
7
+ //
8
+ // THE SPLIT, AND WHY IT IS NOT ALL IN THE PLUGIN. A plugin could `emitFile` the worker as a
9
+ // chunk, but the native rolldown engine (`@gjsify/rolldown-native`, the only engine under
10
+ // GJS) exposes no `emitFile` — `applyBrowserHtml` is a CLI step for the same reason. So the
11
+ // plugin does the half every engine can do, the URL rewrite, and records each worker it saw
12
+ // in a {@link WorkerEntries} registry; the CLI then bundles every registered source as its
13
+ // own app build, next to the main output, with the main build's config. A nested build runs
14
+ // this plugin again, so a worker that starts a worker is followed with no extra rule, and the
15
+ // shared registry is what ends a cycle (a worker that spawns itself).
16
+ //
17
+ // WHAT COUNTS. `new <Worker>(new URL(<string literal>, import.meta.url), …)` where `<Worker>`
18
+ // is the identifier `Worker` (the WHATWG global from `@gjsify/web-worker`, or the name imported
19
+ // from `worker_threads` — also under an alias) or a `<x>.Worker` member (`wt.Worker`,
20
+ // `import * as wt`). Anything else is left byte-identical, on purpose: a computed specifier
21
+ // has no build-time answer, and a rewrite of the wrong `new URL` would break a program that
22
+ // was fine.
23
+ //
24
+ // WHY AN AST: `new Worker(new URL(` inside a string, a comment or a template shares the bytes
25
+ // of the shape. The parser is the shared one (`utils/inline-static-reads.ts`), so a `.ts`
26
+ // source arrives with its type syntax intact.
27
+ import { basename, extname } from 'node:path';
28
+ import { parseSource } from '../utils/inline-static-reads.js';
29
+ import { REWRITE_FILTER } from './rewrite-node-modules-paths.js';
30
+ export const WORKER_URL_PLUGIN = 'gjsify-worker-url';
31
+ const WORKER_THREADS = /^(?:node:)?worker_threads$/;
32
+ function childrenOf(node) {
33
+ const out = [];
34
+ for (const value of Object.values(node)) {
35
+ const items = Array.isArray(value) ? value : [value];
36
+ for (const item of items) {
37
+ if (item !== null && typeof item === 'object' && typeof item.type === 'string') {
38
+ out.push(item);
39
+ }
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ /** Local names that `worker_threads`' `Worker` is bound to — `import { Worker as W }` yields `W`. */
45
+ function workerAliases(program) {
46
+ const aliases = new Set(['Worker']);
47
+ for (const stmt of childrenOf(program)) {
48
+ if (stmt.type !== 'ImportDeclaration' || typeof stmt.source?.value !== 'string')
49
+ continue;
50
+ if (!WORKER_THREADS.test(stmt.source.value))
51
+ continue;
52
+ for (const spec of stmt.specifiers ?? []) {
53
+ if (spec.type === 'ImportSpecifier' && spec.imported?.name === 'Worker' && spec.local?.name) {
54
+ aliases.add(spec.local.name);
55
+ }
56
+ }
57
+ }
58
+ return aliases;
59
+ }
60
+ function isWorkerCallee(callee, aliases) {
61
+ if (!callee)
62
+ return false;
63
+ if (callee.type === 'Identifier')
64
+ return callee.name !== undefined && aliases.has(callee.name);
65
+ return (callee.type === 'MemberExpression' &&
66
+ callee.computed !== true &&
67
+ callee.property?.type === 'Identifier' &&
68
+ callee.property.name === 'Worker');
69
+ }
70
+ function isImportMetaUrl(node) {
71
+ return (node?.type === 'MemberExpression' &&
72
+ node.computed !== true &&
73
+ node.property?.name === 'url' &&
74
+ node.object?.type === 'MetaProperty' &&
75
+ node.object.meta?.name === 'import');
76
+ }
77
+ /** The `new URL('<literal>', import.meta.url)` shape, as the literal node, or null. */
78
+ function urlLiteral(node) {
79
+ if (node?.type !== 'NewExpression' || node.callee?.type !== 'Identifier' || node.callee.name !== 'URL')
80
+ return null;
81
+ const [spec, base] = node.arguments ?? [];
82
+ if (spec?.type !== 'Literal' || typeof spec.value !== 'string')
83
+ return null;
84
+ return isImportMetaUrl(base) ? spec : null;
85
+ }
86
+ /** Every Worker constructor in `code` that names its script by a literal URL, source order. */
87
+ export function findWorkerUrls(code, id) {
88
+ let program;
89
+ try {
90
+ program = parseSource(code, id);
91
+ }
92
+ catch {
93
+ // A module the pinned parser cannot read keeps its literal; the bundler reports the parse itself.
94
+ return [];
95
+ }
96
+ const aliases = workerAliases(program);
97
+ const sites = [];
98
+ const stack = [program];
99
+ while (stack.length > 0) {
100
+ const node = stack.pop();
101
+ if (node.type === 'NewExpression' && isWorkerCallee(node.callee, aliases)) {
102
+ const literal = urlLiteral(node.arguments?.[0]);
103
+ if (literal) {
104
+ sites.push({ specifier: literal.value, start: literal.start, end: literal.end });
105
+ }
106
+ }
107
+ for (const child of childrenOf(node))
108
+ stack.push(child);
109
+ }
110
+ return sites.sort((a, b) => a.start - b.start);
111
+ }
112
+ /** Only a relative specifier names a file beside the importer; `file:`, `http:` and `/abs` do not. */
113
+ const isRelative = (specifier) => /^\.\.?\//.test(specifier);
114
+ /**
115
+ * What a worker script is called next to the bundle, and which sources still need building.
116
+ *
117
+ * ONE instance per `gjsify build`, shared with every nested worker build: the name a source
118
+ * gets must be the same in the bundle that spawns it and in the one that spawns it from
119
+ * another worker, and a source already built must not be built again.
120
+ */
121
+ export class WorkerEntries {
122
+ extension;
123
+ #names = new Map();
124
+ #taken = new Set();
125
+ #built = new Set();
126
+ /** @param extension the main bundle's extension (`.js`, `.mjs`), mirrored so a worker loads like its parent. */
127
+ constructor(extension = '.js') {
128
+ this.extension = extension;
129
+ }
130
+ /** The file name (no directory) the worker built from `source` gets beside the bundle. */
131
+ register(source) {
132
+ const known = this.#names.get(source);
133
+ if (known !== undefined)
134
+ return known;
135
+ const stem = basename(source, extname(source));
136
+ let name = `${stem}.worker${this.extension}`;
137
+ // Two sources with one stem (`a/worker.ts`, `b/worker.ts`) must not overwrite each other.
138
+ if (this.#taken.has(name))
139
+ name = `${stem}-${pathHash(source)}.worker${this.extension}`;
140
+ this.#taken.add(name);
141
+ this.#names.set(source, name);
142
+ return name;
143
+ }
144
+ /** Registered sources not yet handed out, with their file names; each is returned once. */
145
+ takePending() {
146
+ const pending = [];
147
+ for (const [source, fileName] of this.#names) {
148
+ if (this.#built.has(source))
149
+ continue;
150
+ this.#built.add(source);
151
+ pending.push({ source, fileName });
152
+ }
153
+ return pending;
154
+ }
155
+ }
156
+ /** FNV-1a over the path, base 36 — stable across builds and engines, no crypto dependency. */
157
+ function pathHash(path) {
158
+ let hash = 0x811c9dc5;
159
+ for (let i = 0; i < path.length; i++) {
160
+ hash ^= path.charCodeAt(i);
161
+ hash = Math.imul(hash, 0x01000193) >>> 0;
162
+ }
163
+ return hash.toString(36);
164
+ }
165
+ /** `code` with each resolvable worker URL pointed at its emitted file, or null when nothing changed. */
166
+ export async function rewriteWorkerUrls(code, id, resolveSource, entries) {
167
+ const sites = findWorkerUrls(code, id).filter((site) => isRelative(site.specifier));
168
+ if (sites.length === 0)
169
+ return null;
170
+ let out = code;
171
+ let changed = false;
172
+ // Descending, so each splice leaves the offsets of the ones before it valid.
173
+ for (const site of sites.sort((a, b) => b.start - a.start)) {
174
+ const source = await resolveSource(site.specifier);
175
+ if (source === null)
176
+ continue;
177
+ out = `${out.slice(0, site.start)}${JSON.stringify(`./${entries.register(source)}`)}${out.slice(site.end)}`;
178
+ changed = true;
179
+ }
180
+ return changed ? out : null;
181
+ }
182
+ /**
183
+ * Composed by `--app gjs|node|browser` when the CLI hands over a {@link WorkerEntries}; absent
184
+ * registry, absent plugin — a library build keeps its `new URL` for the consumer's own bundler.
185
+ */
186
+ export function workerUrlPlugin(entries) {
187
+ return {
188
+ name: WORKER_URL_PLUGIN,
189
+ transform: {
190
+ filter: { id: REWRITE_FILTER },
191
+ async handler(code, id) {
192
+ if (!REWRITE_FILTER.test(id))
193
+ return null;
194
+ // Cheap gate: the parse is paid only by a module that can contain the shape.
195
+ if (!code.includes('import.meta.url') || !code.includes('Worker'))
196
+ return null;
197
+ const out = await rewriteWorkerUrls(code, id, async (specifier) => {
198
+ const resolved = await this.resolve(specifier, id);
199
+ if (!resolved || resolved.external) {
200
+ this.warn(`${id}: cannot resolve worker script ${specifier}; left as written`);
201
+ return null;
202
+ }
203
+ return resolved.id.split('?')[0];
204
+ }, entries);
205
+ return out === null ? null : { code: out, map: null };
206
+ },
207
+ },
208
+ };
209
+ }
@@ -1,3 +1,4 @@
1
+ import type { WorkerEntries } from '../plugins/worker-url.js';
1
2
  import type { NativescriptLocaleOptions } from '../plugins/nativescript-locale.js';
2
3
  import type { App } from './app.js';
3
4
  import type { SourceDialect } from './source-dialect.js';
@@ -9,6 +10,12 @@ export interface GjsifyCssOptions {
9
10
  minify?: boolean;
10
11
  }
11
12
  export interface PluginOptions {
13
+ /**
14
+ * Registry the worker-URL plugin records `new Worker(new URL('./x', import.meta.url))`
15
+ * targets in. Set by the CLI for `--app gjs|node|browser`, which then builds every
16
+ * registered source beside the bundle; unset leaves the URL untouched.
17
+ */
18
+ workers?: WorkerEntries;
12
19
  debug?: boolean;
13
20
  app?: App;
14
21
  aliases?: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/rolldown-plugin-gjsify",
3
- "version": "0.64.15-next.287",
3
+ "version": "0.64.16",
4
4
  "description": "Rolldown / Rollup / Vite plugin orchestrator for GJS, Node, and Browser targets",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -67,13 +67,13 @@
67
67
  ],
68
68
  "license": "MIT",
69
69
  "dependencies": {
70
- "@gjsify/console": "0.64.15-next.287",
71
- "@gjsify/resolve-npm": "0.64.15-next.287",
72
- "@gjsify/rolldown-plugin-deepkit": "0.64.15-next.287",
73
- "@gjsify/rolldown-plugin-pnp": "0.64.15-next.287",
74
- "@gjsify/utils": "0.64.15-next.287",
75
- "@gjsify/vite-plugin-blueprint": "0.64.15-next.287",
76
- "@gjsify/vite-plugin-gettext": "0.64.15-next.287",
70
+ "@gjsify/console": "^0.64.16",
71
+ "@gjsify/resolve-npm": "^0.64.16",
72
+ "@gjsify/rolldown-plugin-deepkit": "^0.64.16",
73
+ "@gjsify/rolldown-plugin-pnp": "^0.64.16",
74
+ "@gjsify/utils": "^0.64.16",
75
+ "@gjsify/vite-plugin-blueprint": "^0.64.16",
76
+ "@gjsify/vite-plugin-gettext": "^0.64.16",
77
77
  "@rollup/pluginutils": "^5.4.0",
78
78
  "acorn": "^8.17.0",
79
79
  "acorn-typescript": "^1.4.13",
@@ -83,7 +83,7 @@
83
83
  "sass": "^1.101.0"
84
84
  },
85
85
  "peerDependencies": {
86
- "@gjsify/lightningcss-native": "0.64.15-next.287",
86
+ "@gjsify/lightningcss-native": "^0.64.16",
87
87
  "rolldown": "^1.1.4"
88
88
  },
89
89
  "peerDependenciesMeta": {
@@ -95,8 +95,8 @@
95
95
  }
96
96
  },
97
97
  "devDependencies": {
98
- "@gjsify/cli": "0.64.15-next.287",
99
- "@gjsify/unit": "0.64.15-next.287",
98
+ "@gjsify/cli": "^0.64.16",
99
+ "@gjsify/unit": "^0.64.16",
100
100
  "@types/node": "^25.9.2",
101
101
  "rolldown": "^1.1.4",
102
102
  "typescript": "^6.0.3"
@@ -108,6 +108,5 @@
108
108
  "darwin": "supported",
109
109
  "win32": "supported"
110
110
  }
111
- },
112
- "gitHead": "5742b282a8a2bc8633dd8384ef436768447f6a83"
111
+ }
113
112
  }