@gjsify/rolldown-plugin-gjsify 0.3.14
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/browser.d.ts +17 -0
- package/lib/app/browser.js +77 -0
- package/lib/app/gjs.d.ts +27 -0
- package/lib/app/gjs.js +211 -0
- package/lib/app/index.d.ts +6 -0
- package/lib/app/index.js +3 -0
- package/lib/app/node.d.ts +17 -0
- package/lib/app/node.js +102 -0
- package/lib/globals.d.ts +4 -0
- package/lib/globals.js +9 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +15 -0
- package/lib/library/index.d.ts +2 -0
- package/lib/library/index.js +1 -0
- package/lib/library/lib.d.ts +16 -0
- package/lib/library/lib.js +118 -0
- package/lib/plugin.d.ts +25 -0
- package/lib/plugin.js +67 -0
- package/lib/plugins/alias.d.ts +5 -0
- package/lib/plugins/alias.js +45 -0
- package/lib/plugins/css-as-string.d.ts +2 -0
- package/lib/plugins/css-as-string.js +34 -0
- package/lib/plugins/gjs-imports-empty.d.ts +2 -0
- package/lib/plugins/gjs-imports-empty.js +26 -0
- package/lib/plugins/process-stub.d.ts +28 -0
- package/lib/plugins/process-stub.js +60 -0
- package/lib/plugins/rewrite-node-modules-paths.d.ts +38 -0
- package/lib/plugins/rewrite-node-modules-paths.js +132 -0
- package/lib/plugins/shebang.d.ts +8 -0
- package/lib/plugins/shebang.js +26 -0
- package/lib/shims/console-gjs.d.ts +24 -0
- package/lib/shims/console-gjs.js +24 -0
- package/lib/types/app.d.ts +1 -0
- package/lib/types/app.js +1 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/index.js +3 -0
- package/lib/types/plugin-options.d.ts +46 -0
- package/lib/types/plugin-options.js +1 -0
- package/lib/types/resolve-alias-options.d.ts +2 -0
- package/lib/types/resolve-alias-options.js +1 -0
- package/lib/utils/alias.d.ts +12 -0
- package/lib/utils/alias.js +29 -0
- package/lib/utils/auto-globals.d.ts +72 -0
- package/lib/utils/auto-globals.js +193 -0
- package/lib/utils/detect-free-globals.d.ts +18 -0
- package/lib/utils/detect-free-globals.js +268 -0
- package/lib/utils/entry-points.d.ts +2 -0
- package/lib/utils/entry-points.js +38 -0
- package/lib/utils/extension.d.ts +1 -0
- package/lib/utils/extension.js +7 -0
- package/lib/utils/index.d.ts +7 -0
- package/lib/utils/index.js +7 -0
- package/lib/utils/inline-static-reads.d.ts +11 -0
- package/lib/utils/inline-static-reads.js +549 -0
- package/lib/utils/merge.d.ts +2 -0
- package/lib/utils/merge.js +23 -0
- package/lib/utils/scan-globals.d.ts +32 -0
- package/lib/utils/scan-globals.js +85 -0
- package/package.json +68 -0
- package/src/app/browser.ts +102 -0
- package/src/app/gjs.ts +260 -0
- package/src/app/index.ts +6 -0
- package/src/app/node.ts +128 -0
- package/src/globals.ts +11 -0
- package/src/index.ts +32 -0
- package/src/library/index.ts +2 -0
- package/src/library/lib.ts +142 -0
- package/src/plugin.ts +91 -0
- package/src/plugins/alias.ts +53 -0
- package/src/plugins/css-as-string.ts +37 -0
- package/src/plugins/gjs-imports-empty.ts +29 -0
- package/src/plugins/process-stub.ts +91 -0
- package/src/plugins/rewrite-node-modules-paths.ts +169 -0
- package/src/plugins/shebang.ts +33 -0
- package/src/shims/console-gjs.ts +25 -0
- package/src/types/app.ts +1 -0
- package/src/types/index.ts +3 -0
- package/src/types/plugin-options.ts +48 -0
- package/src/types/resolve-alias-options.ts +1 -0
- package/src/utils/alias.ts +46 -0
- package/src/utils/auto-globals.ts +283 -0
- package/src/utils/detect-free-globals.ts +278 -0
- package/src/utils/entry-points.ts +48 -0
- package/src/utils/extension.ts +7 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/inline-static-reads.ts +541 -0
- package/src/utils/merge.ts +22 -0
- package/src/utils/scan-globals.ts +91 -0
- package/tsconfig.json +16 -0
package/lib/plugin.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
|
+
import type { PluginOptions } from './types/plugin-options.js';
|
|
3
|
+
export interface GjsifyConfig {
|
|
4
|
+
options: RolldownOptions;
|
|
5
|
+
plugins: RolldownPluginOption[];
|
|
6
|
+
}
|
|
7
|
+
export interface GjsifyPluginInput {
|
|
8
|
+
input?: RolldownOptions['input'];
|
|
9
|
+
output: {
|
|
10
|
+
file?: string;
|
|
11
|
+
dir?: string;
|
|
12
|
+
};
|
|
13
|
+
userExternal?: string[];
|
|
14
|
+
userBanner?: string;
|
|
15
|
+
userAliases?: Record<string, string>;
|
|
16
|
+
/** Whether to prepend `#!/usr/bin/env -S gjs -m` to the GJS bundle. */
|
|
17
|
+
shebang?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build the Rolldown configuration template + plugin array for the given
|
|
21
|
+
* pluginOptions. The caller composes the returned `options.plugins` with
|
|
22
|
+
* its own user plugins and passes the merged options to `rolldown(...)`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const gjsifyPlugin: (input: GjsifyPluginInput, pluginOptions?: PluginOptions) => Promise<GjsifyConfig>;
|
|
25
|
+
export default gjsifyPlugin;
|
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// `gjsifyPlugin` — orchestrator entry point.
|
|
2
|
+
//
|
|
3
|
+
// Picks the platform-specific factory based on `pluginOptions.app`
|
|
4
|
+
// (or `library` for library mode) and returns the resolved Rolldown
|
|
5
|
+
// configuration. Unlike the esbuild predecessor, this is NOT a single
|
|
6
|
+
// `Plugin` object — it returns a *config bundle* the caller composes into
|
|
7
|
+
// `rolldown(opts)`, because Rolldown does not have esbuild's `setup(build)`
|
|
8
|
+
// hook through which a single plugin can mutate `build.initialOptions`.
|
|
9
|
+
//
|
|
10
|
+
// The CLI consumer (`@gjsify/cli`) calls `gjsifyPlugin(...)` to get back
|
|
11
|
+
// `{ options, plugins }`, then calls `rolldown({ ...options, plugins:
|
|
12
|
+
// [...userPlugins, ...plugins] })`.
|
|
13
|
+
import { setupForGjs, setupForNode, setupForBrowser } from './app/index.js';
|
|
14
|
+
import { setupLib } from './library/index.js';
|
|
15
|
+
/**
|
|
16
|
+
* Build the Rolldown configuration template + plugin array for the given
|
|
17
|
+
* pluginOptions. The caller composes the returned `options.plugins` with
|
|
18
|
+
* its own user plugins and passes the merged options to `rolldown(...)`.
|
|
19
|
+
*/
|
|
20
|
+
export const gjsifyPlugin = async (input, pluginOptions = {}) => {
|
|
21
|
+
if (pluginOptions.library) {
|
|
22
|
+
switch (pluginOptions.library) {
|
|
23
|
+
case 'esm':
|
|
24
|
+
case 'cjs':
|
|
25
|
+
return await setupLib({
|
|
26
|
+
input: input.input,
|
|
27
|
+
output: input.output,
|
|
28
|
+
userAliases: input.userAliases,
|
|
29
|
+
pluginOptions,
|
|
30
|
+
});
|
|
31
|
+
default:
|
|
32
|
+
throw new TypeError('Unknown library type: ' + pluginOptions.library);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const app = pluginOptions.app ?? 'gjs';
|
|
36
|
+
switch (app) {
|
|
37
|
+
case 'gjs':
|
|
38
|
+
return await setupForGjs({
|
|
39
|
+
input: input.input,
|
|
40
|
+
output: input.output,
|
|
41
|
+
userExternal: input.userExternal,
|
|
42
|
+
userBanner: input.userBanner,
|
|
43
|
+
userAliases: input.userAliases,
|
|
44
|
+
shebang: input.shebang,
|
|
45
|
+
pluginOptions,
|
|
46
|
+
});
|
|
47
|
+
case 'node':
|
|
48
|
+
return await setupForNode({
|
|
49
|
+
input: input.input,
|
|
50
|
+
output: input.output,
|
|
51
|
+
userExternal: input.userExternal,
|
|
52
|
+
userAliases: input.userAliases,
|
|
53
|
+
pluginOptions,
|
|
54
|
+
});
|
|
55
|
+
case 'browser':
|
|
56
|
+
return await setupForBrowser({
|
|
57
|
+
input: input.input,
|
|
58
|
+
output: input.output,
|
|
59
|
+
userExternal: input.userExternal,
|
|
60
|
+
userAliases: input.userAliases,
|
|
61
|
+
pluginOptions,
|
|
62
|
+
});
|
|
63
|
+
default:
|
|
64
|
+
throw new TypeError('Unknown app platform: ' + app);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
export default gjsifyPlugin;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Custom alias plugin — `@rollup/plugin-alias` returns the rewritten
|
|
2
|
+
// specifier as the resolved id, then leaves Rolldown's default resolver to
|
|
3
|
+
// load it. That fails for workspace package targets (`@gjsify/crypto` etc.)
|
|
4
|
+
// when the importer's package.json doesn't list them as direct deps —
|
|
5
|
+
// Rolldown rejects packages that aren't declared in the importer's manifest
|
|
6
|
+
// even when they exist in a hoisted workspace `node_modules`.
|
|
7
|
+
//
|
|
8
|
+
// This plugin instead calls `this.resolve(target, importer, { skipSelf: true })`
|
|
9
|
+
// which goes through the full plugin chain (including any subsequent resolvers)
|
|
10
|
+
// and resolves to a real file path the default loader can read.
|
|
11
|
+
//
|
|
12
|
+
// Behaviour preserved from the esbuild predecessor's `aliasPlugin`:
|
|
13
|
+
// - exact string match (no prefix-aware semantics needed at this layer)
|
|
14
|
+
// - `node:<name>` specifiers map to the same target as `<name>`
|
|
15
|
+
// (handled in the alias-builder helpers, not here).
|
|
16
|
+
export function aliasPlugin(options) {
|
|
17
|
+
const entries = options.entries;
|
|
18
|
+
const keys = Object.keys(entries);
|
|
19
|
+
return {
|
|
20
|
+
name: 'gjsify-alias',
|
|
21
|
+
resolveId: {
|
|
22
|
+
order: 'pre',
|
|
23
|
+
async handler(source, importer) {
|
|
24
|
+
if (!Object.prototype.hasOwnProperty.call(entries, source)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const target = entries[source];
|
|
28
|
+
// Self-reference guard: if a user maps a specifier to itself
|
|
29
|
+
// (rare but legal), skip to avoid infinite loops.
|
|
30
|
+
if (target === source)
|
|
31
|
+
return null;
|
|
32
|
+
const resolved = await this.resolve(target, importer, {
|
|
33
|
+
skipSelf: true,
|
|
34
|
+
});
|
|
35
|
+
if (resolved !== null) {
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
// Fall through to other resolvers if we couldn't load it
|
|
39
|
+
// ourselves. `null` from a `pre`-order resolveId lets the
|
|
40
|
+
// default chain continue.
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Wrap `.css` imports as JS string default exports.
|
|
2
|
+
//
|
|
3
|
+
// Rolldown's experimental CSS bundling was removed (see
|
|
4
|
+
// https://github.com/rolldown/rolldown/issues/4271) — when the bundler
|
|
5
|
+
// sees a `.css` extension it errors out unless something else loads the
|
|
6
|
+
// file first. We hook into `load` (with a path filter) BEFORE Rolldown's
|
|
7
|
+
// CSS classification fires and emit a JS module whose default export is
|
|
8
|
+
// the CSS source as a string. Consumers can then do:
|
|
9
|
+
//
|
|
10
|
+
// import css from './app.css';
|
|
11
|
+
// provider.load_from_string(css);
|
|
12
|
+
//
|
|
13
|
+
// — the canonical pattern for `Gtk.CssProvider` under GJS.
|
|
14
|
+
//
|
|
15
|
+
// `@import` resolution is left to the user / CSS preprocessor. For simple
|
|
16
|
+
// app CSS this is fine; for @import-heavy CSS, run a preprocessor (e.g.
|
|
17
|
+
// sass / postcss) ahead of `gjsify build` so the input file is already
|
|
18
|
+
// flat.
|
|
19
|
+
import { readFile } from 'node:fs/promises';
|
|
20
|
+
export function cssAsStringPlugin() {
|
|
21
|
+
return {
|
|
22
|
+
name: 'gjsify-css-as-string',
|
|
23
|
+
load: {
|
|
24
|
+
filter: { id: /\.css$/ },
|
|
25
|
+
async handler(id) {
|
|
26
|
+
const code = await readFile(id, 'utf8');
|
|
27
|
+
return {
|
|
28
|
+
code: `export default ${JSON.stringify(code)};`,
|
|
29
|
+
moduleType: 'js',
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// For `--app browser`: redirect `@girs/*` and `gi://*` imports to an empty
|
|
2
|
+
// module. These are GJS-specific (GObject introspection bindings / GI
|
|
3
|
+
// protocol) with no browser equivalent. They appear transitively via
|
|
4
|
+
// `@gjsify/unit` and similar packages that have GJS-specific code paths.
|
|
5
|
+
//
|
|
6
|
+
// Marking them external would leave bare specifiers in the bundle that the
|
|
7
|
+
// browser cannot resolve at runtime; instead we resolve them to a virtual
|
|
8
|
+
// empty ESM module so the bundle is self-contained.
|
|
9
|
+
const GJSIMPORTS_VIRTUAL_ID = '\0gjsify-empty-gjs-import';
|
|
10
|
+
export function gjsImportsEmptyPlugin() {
|
|
11
|
+
return {
|
|
12
|
+
name: 'gjsify-gjs-imports-empty',
|
|
13
|
+
resolveId: {
|
|
14
|
+
order: 'pre',
|
|
15
|
+
filter: { id: /^(@girs\/|gi:\/\/)/ },
|
|
16
|
+
handler(_source) {
|
|
17
|
+
return { id: GJSIMPORTS_VIRTUAL_ID };
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
load(id) {
|
|
21
|
+
if (id !== GJSIMPORTS_VIRTUAL_ID)
|
|
22
|
+
return null;
|
|
23
|
+
return { code: 'export {}; export default {};', moduleSideEffects: false };
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Plugin } from 'rolldown';
|
|
2
|
+
export declare const GJS_PROCESS_STUB: string;
|
|
3
|
+
/**
|
|
4
|
+
* Compose the GJS process stub with the user-supplied banner so the result
|
|
5
|
+
* is valid syntax for `gjs -m`. A leading `#!shebang` line in the user
|
|
6
|
+
* banner is hoisted to byte 0 of the output. Any `#` character that appears
|
|
7
|
+
* anywhere except byte 0 is a fatal SyntaxError under SpiderMonkey 128+ —
|
|
8
|
+
* putting our process stub before the user's shebang would break the bundle.
|
|
9
|
+
*
|
|
10
|
+
* Output shape:
|
|
11
|
+
* [#!shebang\n][<process-stub>\n<rest-of-user-banner>]
|
|
12
|
+
*
|
|
13
|
+
* Either side of the bracket may be empty; the result is always concatenated
|
|
14
|
+
* without leading whitespace.
|
|
15
|
+
*/
|
|
16
|
+
export declare function composeBanner(stub: string, userBanner: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Build a Rolldown plugin that injects the GJS process stub as a chunk
|
|
19
|
+
* banner. Runs with `enforce: 'post'`-equivalent ordering so the stub
|
|
20
|
+
* lands *after* any user `output.banner` value, except when the user
|
|
21
|
+
* banner starts with a `#!shebang` line — which is hoisted to byte 0
|
|
22
|
+
* by `composeBanner`.
|
|
23
|
+
*/
|
|
24
|
+
export interface ProcessStubPluginOptions {
|
|
25
|
+
/** User-supplied banner string. May contain a leading `#!shebang`. */
|
|
26
|
+
userBanner?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function processStubPlugin(options?: ProcessStubPluginOptions): Plugin;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const GJS_PROCESS_STUB = 'if(typeof globalThis.process==="undefined"){' +
|
|
2
|
+
'const _s=imports.system,_G=imports.gi.GLib;' +
|
|
3
|
+
'globalThis.process={' +
|
|
4
|
+
'platform:"linux",arch:"x64",version:"v20.0.0",' +
|
|
5
|
+
'env:new Proxy({},{' +
|
|
6
|
+
'get(_,p){return typeof p==="string"?(_G.getenv(p)??undefined):undefined},' +
|
|
7
|
+
'set(_,p,v){if(typeof p==="string")_G.setenv(p,String(v),true);return true},' +
|
|
8
|
+
'has(_,p){return typeof p==="string"&&_G.getenv(p)!==null},' +
|
|
9
|
+
'deleteProperty(_,p){if(typeof p==="string")_G.unsetenv(p);return true},' +
|
|
10
|
+
'ownKeys(){return _G.listenv()??[]},' +
|
|
11
|
+
'getOwnPropertyDescriptor(_,p){const v=_G.getenv(p);return v!==null?{value:v,writable:true,enumerable:true,configurable:true}:undefined}' +
|
|
12
|
+
'}),' +
|
|
13
|
+
'argv:_s?.programArgs?["gjs",_s.programInvocationName||"",..._s.programArgs]:["gjs"],' +
|
|
14
|
+
'versions:{},config:{},' +
|
|
15
|
+
'cwd(){return _G.get_current_dir()||"/"},' +
|
|
16
|
+
'exit(c){_s.exit(c??0)},' +
|
|
17
|
+
'stderr:{write(s){printerr(s)}},stdout:{write(s){print(s)}},stdin:null,' +
|
|
18
|
+
'exitCode:undefined,' +
|
|
19
|
+
'nextTick(fn,...a){Promise.resolve().then(()=>fn(...a))},' +
|
|
20
|
+
'hrtime(t){return t?[0,0]:[0,0]},' +
|
|
21
|
+
'};' +
|
|
22
|
+
'}';
|
|
23
|
+
/**
|
|
24
|
+
* Compose the GJS process stub with the user-supplied banner so the result
|
|
25
|
+
* is valid syntax for `gjs -m`. A leading `#!shebang` line in the user
|
|
26
|
+
* banner is hoisted to byte 0 of the output. Any `#` character that appears
|
|
27
|
+
* anywhere except byte 0 is a fatal SyntaxError under SpiderMonkey 128+ —
|
|
28
|
+
* putting our process stub before the user's shebang would break the bundle.
|
|
29
|
+
*
|
|
30
|
+
* Output shape:
|
|
31
|
+
* [#!shebang\n][<process-stub>\n<rest-of-user-banner>]
|
|
32
|
+
*
|
|
33
|
+
* Either side of the bracket may be empty; the result is always concatenated
|
|
34
|
+
* without leading whitespace.
|
|
35
|
+
*/
|
|
36
|
+
export function composeBanner(stub, userBanner) {
|
|
37
|
+
if (!userBanner)
|
|
38
|
+
return stub;
|
|
39
|
+
const shebangMatch = userBanner.match(/^#![^\n]*\n/);
|
|
40
|
+
if (!shebangMatch) {
|
|
41
|
+
return stub + '\n' + userBanner;
|
|
42
|
+
}
|
|
43
|
+
const shebang = shebangMatch[0];
|
|
44
|
+
const rest = userBanner.slice(shebang.length);
|
|
45
|
+
return shebang + stub + (rest ? '\n' + rest : '');
|
|
46
|
+
}
|
|
47
|
+
export function processStubPlugin(options = {}) {
|
|
48
|
+
const banner = composeBanner(GJS_PROCESS_STUB, options.userBanner ?? '');
|
|
49
|
+
return {
|
|
50
|
+
name: 'gjsify-process-stub',
|
|
51
|
+
renderChunk: {
|
|
52
|
+
order: 'post',
|
|
53
|
+
handler(code, chunk) {
|
|
54
|
+
if (!chunk.isEntry)
|
|
55
|
+
return null;
|
|
56
|
+
return { code: banner + '\n' + code, map: null };
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Plugin } from 'rolldown';
|
|
2
|
+
export declare const REWRITE_FILTER: RegExp;
|
|
3
|
+
/** True when the rewriter wants to look at this path — node_modules + supported ext. */
|
|
4
|
+
export declare function shouldRewrite(path: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Compute the directory the bundle's outfile lives in.
|
|
7
|
+
*
|
|
8
|
+
* For `import.meta.url` rewriting we emit a relative URL whose base is the
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getBundleDirFromOutput(opts: {
|
|
13
|
+
file?: string;
|
|
14
|
+
dir?: string;
|
|
15
|
+
}): string;
|
|
16
|
+
export interface RewriteResult {
|
|
17
|
+
code: string;
|
|
18
|
+
moduleType?: 'ts' | 'js';
|
|
19
|
+
map?: null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pure rewriter — same body as the esbuild predecessor. Returns the rewritten
|
|
23
|
+
* code (and module type for re-parsing) or `null` if the file doesn't reference
|
|
24
|
+
* any of the patterns we care about.
|
|
25
|
+
*/
|
|
26
|
+
export declare function rewriteContents(args: {
|
|
27
|
+
path: string;
|
|
28
|
+
}, srcInput: string, bundleDir: string): RewriteResult | null;
|
|
29
|
+
export interface NodeModulesPathRewriteOptions {
|
|
30
|
+
/** Bundle output directory, derived from `output.file` / `output.dir`. */
|
|
31
|
+
bundleDir: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a Rolldown plugin that runs the path rewriter as a `transform(code, id)`
|
|
35
|
+
* hook with `order: 'post'` — runs after the deepkit/blueprint/css pre-transforms
|
|
36
|
+
* but still during module loading, before chunking.
|
|
37
|
+
*/
|
|
38
|
+
export declare function nodeModulesPathRewritePlugin(options: NodeModulesPathRewriteOptions): Plugin;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Per-source rewriter for node_modules files that reference
|
|
2
|
+
// `import.meta.url`, `__dirname`, or `__filename`. Mirrors the esbuild
|
|
3
|
+
// predecessor's logic — body is identical because the rewrite is purely
|
|
4
|
+
// a string transform on already-loaded source. The only delta is the
|
|
5
|
+
// host: a Rolldown `transform(code, id)` plugin instead of an esbuild
|
|
6
|
+
// `onLoad` registered inside the PnP plugin.
|
|
7
|
+
//
|
|
8
|
+
// Why a separate plugin and not nested in the PnP loader, like esbuild?
|
|
9
|
+
// Rolldown / Rollup's `transform` hooks all run in sequence on every
|
|
10
|
+
// loaded module — there is no first-onLoad-wins race. So the PnP loader
|
|
11
|
+
// (`@gjsify/rolldown-plugin-pnp`) is solely responsible for reading
|
|
12
|
+
// zip-resident bytes; this plugin runs as a separate `transform` step
|
|
13
|
+
// after the bytes have been loaded, regardless of which loader produced
|
|
14
|
+
// them. No more F5-bug folklore.
|
|
15
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
16
|
+
import { inlineStaticReads } from '../utils/inline-static-reads.js';
|
|
17
|
+
export const REWRITE_FILTER = /\.(m?js|cjs|[cm]?tsx?)$/;
|
|
18
|
+
const DIRNAME_DECL_RE = /(?:var|let|const)\s+__dirname\b|export\s+(?:var|let|const)\s+__dirname\b/;
|
|
19
|
+
const FILENAME_DECL_RE = /(?:var|let|const)\s+__filename\b|export\s+(?:var|let|const)\s+__filename\b/;
|
|
20
|
+
/** True when the rewriter wants to look at this path — node_modules + supported ext. */
|
|
21
|
+
export function shouldRewrite(path) {
|
|
22
|
+
return path.includes('node_modules') && REWRITE_FILTER.test(path);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Compute the directory the bundle's outfile lives in.
|
|
26
|
+
*
|
|
27
|
+
* For `import.meta.url` rewriting we emit a relative URL whose base is the
|
|
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.
|
|
30
|
+
*/
|
|
31
|
+
export function getBundleDirFromOutput(opts) {
|
|
32
|
+
const outFile = opts.file ?? join(opts.dir ?? '.', 'bundle.mjs');
|
|
33
|
+
return dirname(resolve(outFile));
|
|
34
|
+
}
|
|
35
|
+
/** Pick the per-file loader Rolldown should re-parse with. */
|
|
36
|
+
function moduleTypeForPath(path) {
|
|
37
|
+
const ext = path.split('.').pop() ?? 'js';
|
|
38
|
+
return ['ts', 'mts', 'cts', 'tsx'].includes(ext) ? 'ts' : 'js';
|
|
39
|
+
}
|
|
40
|
+
function buildDirFilenamePreamble(args) {
|
|
41
|
+
const lines = [];
|
|
42
|
+
if (args.needDirname && !args.dirnameDeclared) {
|
|
43
|
+
if (args.kind === 'esm-zip') {
|
|
44
|
+
lines.push(`var __dirname = new URL(".", import.meta.url).pathname.replace(/\\/$/, "");`);
|
|
45
|
+
}
|
|
46
|
+
else if (args.kind === 'esm-relative') {
|
|
47
|
+
lines.push(`var __dirname = new URL(${JSON.stringify(args.relDirWithSlash)}, import.meta.url).pathname.replace(/\\/$/, "");`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
lines.push(`var __dirname = ${JSON.stringify(args.sourceDir)};`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (args.needFilename && !args.filenameDeclared) {
|
|
54
|
+
if (args.kind === 'esm-zip') {
|
|
55
|
+
lines.push(`var __filename = new URL(import.meta.url).pathname;`);
|
|
56
|
+
}
|
|
57
|
+
else if (args.kind === 'esm-relative') {
|
|
58
|
+
lines.push(`var __filename = new URL(${JSON.stringify(args.relPath)}, import.meta.url).pathname;`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
lines.push(`var __filename = ${JSON.stringify(args.sourcePath)};`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return lines;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Pure rewriter — same body as the esbuild predecessor. Returns the rewritten
|
|
68
|
+
* code (and module type for re-parsing) or `null` if the file doesn't reference
|
|
69
|
+
* any of the patterns we care about.
|
|
70
|
+
*/
|
|
71
|
+
export function rewriteContents(args, srcInput, bundleDir) {
|
|
72
|
+
if (!shouldRewrite(args.path))
|
|
73
|
+
return null;
|
|
74
|
+
// Step 1: inline statically-resolvable filesystem reads.
|
|
75
|
+
const inlined = inlineStaticReads(srcInput, args.path);
|
|
76
|
+
const src = inlined.contents;
|
|
77
|
+
const hasMetaUrl = src.includes('import.meta.url');
|
|
78
|
+
const hasDirname = src.includes('__dirname');
|
|
79
|
+
const hasFilename = src.includes('__filename');
|
|
80
|
+
if (!hasMetaUrl && !hasDirname && !hasFilename) {
|
|
81
|
+
if (inlined.inlined === 0)
|
|
82
|
+
return null;
|
|
83
|
+
return { code: src, moduleType: moduleTypeForPath(args.path) };
|
|
84
|
+
}
|
|
85
|
+
// Step 2: classify rewrite kind.
|
|
86
|
+
const dir = dirname(args.path);
|
|
87
|
+
const relPath = hasMetaUrl ? relative(bundleDir, args.path) : '';
|
|
88
|
+
const isZipResident = hasMetaUrl && relPath.includes('.zip/');
|
|
89
|
+
const kind = !hasMetaUrl ? 'cjs-absolute' : isZipResident ? 'esm-zip' : 'esm-relative';
|
|
90
|
+
const preamble = buildDirFilenamePreamble({
|
|
91
|
+
needDirname: hasDirname,
|
|
92
|
+
needFilename: hasFilename,
|
|
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`);
|
|
106
|
+
}
|
|
107
|
+
if (preamble.length > 0)
|
|
108
|
+
code = preamble.join('\n') + '\n' + code;
|
|
109
|
+
return { code, moduleType: moduleTypeForPath(args.path) };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Build a Rolldown plugin that runs the path rewriter as a `transform(code, id)`
|
|
113
|
+
* hook with `order: 'post'` — runs after the deepkit/blueprint/css pre-transforms
|
|
114
|
+
* but still during module loading, before chunking.
|
|
115
|
+
*/
|
|
116
|
+
export function nodeModulesPathRewritePlugin(options) {
|
|
117
|
+
return {
|
|
118
|
+
name: 'gjsify-node-modules-path-rewrite',
|
|
119
|
+
transform: {
|
|
120
|
+
order: 'post',
|
|
121
|
+
filter: { id: REWRITE_FILTER },
|
|
122
|
+
handler(code, id) {
|
|
123
|
+
if (!id.includes('node_modules'))
|
|
124
|
+
return null;
|
|
125
|
+
const result = rewriteContents({ path: id }, code, options.bundleDir);
|
|
126
|
+
if (!result)
|
|
127
|
+
return null;
|
|
128
|
+
return { code: result.code, map: null };
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from 'rolldown';
|
|
2
|
+
export declare const GJS_SHEBANG = "#!/usr/bin/env -S gjs -m";
|
|
3
|
+
export interface ShebangPluginOptions {
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
/** Override the shebang line. Defaults to `GJS_SHEBANG`. */
|
|
6
|
+
line?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function shebangPlugin(options?: ShebangPluginOptions): Plugin | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Inject a `#!/usr/bin/env -S gjs -m` shebang at byte 0 of entry chunks.
|
|
2
|
+
//
|
|
3
|
+
// Rolldown's `output.banner` would also work, but a renderChunk hook with
|
|
4
|
+
// `order: 'post'` makes ordering declarative — the shebang lands AFTER all
|
|
5
|
+
// other banner / process-stub plugins have run, which is required because
|
|
6
|
+
// the `#` character is only valid as the very first byte of the file under
|
|
7
|
+
// SpiderMonkey 128+.
|
|
8
|
+
export const GJS_SHEBANG = '#!/usr/bin/env -S gjs -m';
|
|
9
|
+
export function shebangPlugin(options = {}) {
|
|
10
|
+
if (!options.enabled)
|
|
11
|
+
return null;
|
|
12
|
+
const line = options.line ?? GJS_SHEBANG;
|
|
13
|
+
return {
|
|
14
|
+
name: 'gjsify-shebang',
|
|
15
|
+
renderChunk: {
|
|
16
|
+
order: 'post',
|
|
17
|
+
handler(code, chunk) {
|
|
18
|
+
if (!chunk.isEntry)
|
|
19
|
+
return null;
|
|
20
|
+
if (code.startsWith('#!'))
|
|
21
|
+
return null;
|
|
22
|
+
return { code: line + '\n' + code, map: null };
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const console: {
|
|
2
|
+
log: (...args: unknown[]) => void;
|
|
3
|
+
info: (...args: unknown[]) => void;
|
|
4
|
+
debug: (...args: unknown[]) => void;
|
|
5
|
+
warn: (...args: unknown[]) => void;
|
|
6
|
+
error: (...args: unknown[]) => void;
|
|
7
|
+
dir: (obj: unknown, options?: object) => void;
|
|
8
|
+
dirxml: (...args: unknown[]) => void;
|
|
9
|
+
table: (data: unknown, properties?: string[]) => void;
|
|
10
|
+
time: (label?: string) => void;
|
|
11
|
+
timeEnd: (label?: string) => void;
|
|
12
|
+
timeLog: (label?: string, ...args: unknown[]) => void;
|
|
13
|
+
trace: (...args: unknown[]) => void;
|
|
14
|
+
assert: (value: unknown, ...args: unknown[]) => void;
|
|
15
|
+
clear: () => void;
|
|
16
|
+
count: (label?: string) => void;
|
|
17
|
+
countReset: (label?: string) => void;
|
|
18
|
+
group: (...args: unknown[]) => void;
|
|
19
|
+
groupCollapsed: (...args: unknown[]) => void;
|
|
20
|
+
groupEnd: () => void;
|
|
21
|
+
profile: (_label?: string) => void;
|
|
22
|
+
profileEnd: (_label?: string) => void;
|
|
23
|
+
timeStamp: (_label?: string) => void;
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// GJS console shim — bundled into GJS user builds via Rolldown's `inject`.
|
|
2
|
+
// Uses print()/printerr() on GJS, bypassing GLib.log_structured() — no
|
|
3
|
+
// `Gjs-Console-Message:` prefix, ANSI escapes work, output goes to
|
|
4
|
+
// stdout/stderr instead of GLib's logging stream.
|
|
5
|
+
//
|
|
6
|
+
// `@gjsify/console` is resolved by the user's `gjsify build` Rolldown
|
|
7
|
+
// run, NOT by tsc here. The bare specifier survives compilation and only
|
|
8
|
+
// gets followed at user-build time, where the CLI's `@gjsify/node-polyfills`
|
|
9
|
+
// dep tree has the package. tsc on this package would otherwise need the
|
|
10
|
+
// `@gjsify/console` lib to be pre-built (build-order coupling).
|
|
11
|
+
//
|
|
12
|
+
// We can't reassign `globalThis.console` on SpiderMonkey 128 — the
|
|
13
|
+
// property is non-configurable. Rolldown's `inject` option rewrites bare
|
|
14
|
+
// `console` references to a named import from this shim instead, leaving
|
|
15
|
+
// `globalThis.console` untouched and routing user `console.log(…)` calls
|
|
16
|
+
// through our object.
|
|
17
|
+
// @ts-ignore — resolved by Rolldown at user-build time, not by tsc here.
|
|
18
|
+
import { log, info, debug, warn, error, dir, dirxml, table, time, timeEnd, timeLog, trace, assert, clear, count, countReset, group, groupCollapsed, groupEnd, profile, profileEnd, timeStamp } from '@gjsify/console';
|
|
19
|
+
export const console = {
|
|
20
|
+
log, info, debug, warn, error, dir, dirxml, table,
|
|
21
|
+
time, timeEnd, timeLog, trace, assert, clear,
|
|
22
|
+
count, countReset, group, groupCollapsed, groupEnd,
|
|
23
|
+
profile, profileEnd, timeStamp,
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type App = 'gjs' | 'node' | 'browser';
|
package/lib/types/app.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { App } from './app.js';
|
|
2
|
+
/** CSS handling forwarded to Rolldown / Lightning CSS. */
|
|
3
|
+
export interface GjsifyCssOptions {
|
|
4
|
+
/** Browserslist-compatible target list. Defaults to `['firefox60']` for `--app gjs`. */
|
|
5
|
+
target?: string[];
|
|
6
|
+
/** Whether to minify the emitted CSS. Defaults to bundle-level `minify`. */
|
|
7
|
+
minify?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface PluginOptions {
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
app?: App;
|
|
12
|
+
aliases?: Record<string, string>;
|
|
13
|
+
/** Glob patterns to exclude when expanding entry points. */
|
|
14
|
+
exclude?: string[];
|
|
15
|
+
jsExtension?: string;
|
|
16
|
+
/** Override the bundle output format. */
|
|
17
|
+
format?: 'esm' | 'cjs';
|
|
18
|
+
/**
|
|
19
|
+
* Library mode — `'esm' | 'cjs'`. When set, the plugin emits an
|
|
20
|
+
* unbundled multi-entry library suitable for republication on npm
|
|
21
|
+
* rather than a single application bundle.
|
|
22
|
+
*/
|
|
23
|
+
library?: 'esm' | 'cjs';
|
|
24
|
+
/**
|
|
25
|
+
* Inject a console shim into GJS builds that uses print()/printerr() instead
|
|
26
|
+
* of `GLib.log_structured()`. Removes the "Gjs-Console-Message:" prefix and
|
|
27
|
+
* lets the terminal interpret ANSI escape codes correctly. Only applies to
|
|
28
|
+
* `--app gjs`. Defaults to `true`.
|
|
29
|
+
*/
|
|
30
|
+
consoleShim?: boolean;
|
|
31
|
+
/** Enable Deepkit TypeScript reflection. Defaults to `false`. */
|
|
32
|
+
reflection?: boolean;
|
|
33
|
+
/** CSS pipeline options forwarded to the Rolldown / Lightning CSS layer. */
|
|
34
|
+
css?: GjsifyCssOptions;
|
|
35
|
+
/**
|
|
36
|
+
* Path to a pre-computed globals stub file. The stub is an ESM file
|
|
37
|
+
* containing one `import '<pkg>/register';` per entry from the user's
|
|
38
|
+
* `--globals` CLI flag. When set, the plugin appends the stub path to
|
|
39
|
+
* Rolldown's inject list alongside the console shim.
|
|
40
|
+
*
|
|
41
|
+
* The plugin does no scanning or inference at this layer — the CLI is the
|
|
42
|
+
* sole source of truth for which `/register` modules get included. Only
|
|
43
|
+
* applies to `--app gjs`.
|
|
44
|
+
*/
|
|
45
|
+
autoGlobalsInject?: string;
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ResolveAliasOptions } from '../types/index.js';
|
|
2
|
+
export declare const setNodeAliasPrefix: (ALIASES: Record<string, string>) => Record<string, string>;
|
|
3
|
+
export declare const getAliasesForGjs: (options: ResolveAliasOptions) => {
|
|
4
|
+
[x: string]: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const getAliasesForNode: (options: ResolveAliasOptions) => {
|
|
7
|
+
[x: string]: string;
|
|
8
|
+
};
|
|
9
|
+
/** Array of Node.js build in module names (also with node: prefix) */
|
|
10
|
+
export declare const externalNode: string[];
|
|
11
|
+
/** Array of NPM module names for which we have our own implementation */
|
|
12
|
+
export declare const externalNPM: string[];
|