@gjsify/rolldown-plugin-solid 0.42.0
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/README.md +72 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +145 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @gjsify/rolldown-plugin-solid
|
|
2
|
+
|
|
3
|
+
A Rolldown (and Rollup/Vite-compatible) plugin that compiles **SolidJS JSX** during gjsify builds, in `universal` generate mode — the only mode whose output contains no DOM.
|
|
4
|
+
|
|
5
|
+
Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
|
|
6
|
+
|
|
7
|
+
## Why a Babel plugin in an oxc toolchain
|
|
8
|
+
|
|
9
|
+
Solid's JSX is a *compiler*, not a runtime: `babel-plugin-jsx-dom-expressions` turns markup into straight-line calls against a renderer, and no oxc/SWC port of it exists. Rolldown's own transformer cannot stand in — pointed at a `.tsx` with no JSX configuration it defaults to the automatic React runtime and emits `import { jsx } from "react/jsx-runtime"`, which resolves to nothing under GJS.
|
|
10
|
+
|
|
11
|
+
Babel is loaded on the first matching module, so a build with no JSX in it never pays for it.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gjsify install @gjsify/rolldown-plugin-solid
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Wire it through `package.json#gjsify`, no JS-form config file needed:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"gjsify": {
|
|
26
|
+
"bundler": {
|
|
27
|
+
"plugins": [{ "name": "@gjsify/rolldown-plugin-solid" }]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or directly in a Rolldown / Vite config:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { solidPlugin } from '@gjsify/rolldown-plugin-solid';
|
|
37
|
+
|
|
38
|
+
export default {
|
|
39
|
+
plugins: [solidPlugin({ moduleName: '@gjsify/gtk-host/solid' })],
|
|
40
|
+
};
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Options
|
|
44
|
+
|
|
45
|
+
| Option | Default | Meaning |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `moduleName` | `@gjsify/gtk-host/solid` | Module the compiler imports the renderer ops from. |
|
|
48
|
+
| `generate` | `universal` | `babel-preset-solid` mode. `dom` emits `document.createElement`, which does not exist on GJS. |
|
|
49
|
+
| `include` | `/\.(m\|c)?[jt]sx$/` | Which modules to compile. A `.ts` file cannot contain JSX. |
|
|
50
|
+
|
|
51
|
+
The target module must re-export **every** member of Solid's `Renderer<NodeType>` under its contract name — `createElement`, `createTextNode`, `insertNode`, `insert`, `spread`, `setProp`, `mergeProps`, `effect`, `memo`, `createComponent`, `render`, `use`. The compiler emits those names literally, so a renderer that exports eleven of the twelve builds fine and fails with `MISSING_EXPORT` on the twelfth — only for the JSX that happens to need it.
|
|
52
|
+
|
|
53
|
+
## TypeScript
|
|
54
|
+
|
|
55
|
+
The type side is configured separately, and both halves are required:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"compilerOptions": {
|
|
60
|
+
"jsx": "preserve",
|
|
61
|
+
"jsxImportSource": "@gjsify/gtk-host",
|
|
62
|
+
"noImplicitAny": true,
|
|
63
|
+
"strictFunctionTypes": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`jsx: "preserve"` is what this plugin needs (`"react"` is refused outright with `jsxImportSource`). Without `noImplicitAny` every JSX element is implicitly `any` and `tsc` exits 0 having checked nothing.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Plugin } from 'rolldown';
|
|
2
|
+
/** `babel-preset-solid`'s `generate` modes. Only `universal` can run on GJS. */
|
|
3
|
+
export type SolidGenerateMode = 'dom' | 'ssr' | 'universal';
|
|
4
|
+
export interface SolidPluginOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Module the compiler imports the renderer ops from.
|
|
7
|
+
*
|
|
8
|
+
* Defaults to gjsify's GTK renderer. Any module works as long as it exports
|
|
9
|
+
* every member of Solid's `Renderer<NodeType>` under its contract name.
|
|
10
|
+
*/
|
|
11
|
+
moduleName?: string;
|
|
12
|
+
/** Defaults to `universal`, the only mode with no DOM in its output. */
|
|
13
|
+
generate?: SolidGenerateMode;
|
|
14
|
+
/**
|
|
15
|
+
* Which modules to compile. Defaults to `.jsx` / `.tsx` (also `.mtsx`,
|
|
16
|
+
* `.ctsx`). A `.ts` file cannot contain JSX, so widening this is a mistake.
|
|
17
|
+
*/
|
|
18
|
+
include?: RegExp;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compile Solid JSX to renderer calls.
|
|
22
|
+
*
|
|
23
|
+
* Wire it through `package.json#gjsify` so no JS-form config file is needed:
|
|
24
|
+
*
|
|
25
|
+
* ```json
|
|
26
|
+
* "gjsify": { "bundler": { "plugins": [{ "name": "@gjsify/rolldown-plugin-solid" }] } }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function solidPlugin(options?: SolidPluginOptions): Plugin;
|
|
30
|
+
export default solidPlugin;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// SolidJS JSX for Rolldown — the compile step a GTK/GJS Solid app needs.
|
|
2
|
+
//
|
|
3
|
+
// WHY BABEL AT ALL, in a repo that transforms everything else with oxc: Solid's
|
|
4
|
+
// JSX is not a runtime, it is a compiler. `babel-plugin-jsx-dom-expressions`
|
|
5
|
+
// turns `<gtk-box>…</gtk-box>` into straight-line calls against a renderer, and
|
|
6
|
+
// no oxc/SWC port of it exists. Rolldown's own transformer therefore CANNOT do
|
|
7
|
+
// this job — pointed at a `.tsx` with no JSX configuration it defaults to the
|
|
8
|
+
// automatic React runtime and emits `import { jsx } from "react/jsx-runtime"`,
|
|
9
|
+
// which resolves to nothing under GJS.
|
|
10
|
+
//
|
|
11
|
+
// WHY `generate: "universal"`: the default `"dom"` mode emits
|
|
12
|
+
// `document.createElement` plus `template()`/`cloneNode` cloning. There is no DOM
|
|
13
|
+
// under GJS, so that output cannot run at all. `"universal"` emits imports of the
|
|
14
|
+
// renderer ops from `moduleName` instead — measured on
|
|
15
|
+
// `gtk-host/type-tests/jsx/positive.tsx`, five of them:
|
|
16
|
+
//
|
|
17
|
+
// import { createElement as _$createElement } from "@gjsify/gtk-host/solid";
|
|
18
|
+
// import { insertNode as _$insertNode } from "@gjsify/gtk-host/solid";
|
|
19
|
+
// import { setProp as _$setProp } from "@gjsify/gtk-host/solid";
|
|
20
|
+
// import { insert as _$insert } from "@gjsify/gtk-host/solid";
|
|
21
|
+
// import { use as _$use } from "@gjsify/gtk-host/solid";
|
|
22
|
+
//
|
|
23
|
+
// The names are LITERAL — they are the member names of Solid's
|
|
24
|
+
// `Renderer<NodeType>`, and the target module must re-export all of them under
|
|
25
|
+
// exactly those names. A renderer that exports eleven of the twelve builds fine
|
|
26
|
+
// and fails with MISSING_EXPORT on the twelfth, only for the JSX that happens to
|
|
27
|
+
// need it (that was `setProp`).
|
|
28
|
+
//
|
|
29
|
+
// Two further shapes the measurement fixed:
|
|
30
|
+
// - Children are inserted BEFORE properties are set (`insertNode` then
|
|
31
|
+
// `setProp`), so `createElement` never sees a prop. Construct-only properties
|
|
32
|
+
// therefore cannot be handled by an adapter; the host has to defer
|
|
33
|
+
// materialisation (ADR 0027 § Decision 5).
|
|
34
|
+
// - Handlers arrive through `setProp` under their JSX spelling (`onClicked`),
|
|
35
|
+
// not through a separate op.
|
|
36
|
+
const DEFAULT_INCLUDE = /\.(m|c)?[jt]sx$/;
|
|
37
|
+
const DEFAULT_MODULE_NAME = '@gjsify/gtk-host/solid';
|
|
38
|
+
/**
|
|
39
|
+
* The two compiler modules, loaded on first transform rather than at import.
|
|
40
|
+
*
|
|
41
|
+
* Same contract as `@gjsify/rolldown-plugin-deepkit`: the heavy compiler stays
|
|
42
|
+
* uninstantiated for every build that has no JSX in it, and a project that never
|
|
43
|
+
* writes JSX never pays Babel's load time.
|
|
44
|
+
*
|
|
45
|
+
* THE CACHE HOLDS THE MODULES, NOT THE PRESET CHAIN. The chain carries
|
|
46
|
+
* `moduleName` and `generate`, so caching it made both options inert for every
|
|
47
|
+
* plugin instance after the first one in a process — and the failure is silent in
|
|
48
|
+
* the direction that matters: the second bundle imports its renderer ops from the
|
|
49
|
+
* FIRST bundle's module, which builds green and dies at its first import. Only the
|
|
50
|
+
* imports are expensive, and they do not depend on the options.
|
|
51
|
+
*/
|
|
52
|
+
let compiler = null;
|
|
53
|
+
function loadCompiler() {
|
|
54
|
+
compiler ??= (async () => {
|
|
55
|
+
const babel = await import('@babel/core');
|
|
56
|
+
// Both presets are CommonJS, so the namespace carries them on `default`.
|
|
57
|
+
// Their types come from `src/babel-presets.d.ts` — neither ships any.
|
|
58
|
+
const { default: solid } = await import('babel-preset-solid');
|
|
59
|
+
const { default: typescript } = await import('@babel/preset-typescript');
|
|
60
|
+
return { babel, solid, typescript };
|
|
61
|
+
})();
|
|
62
|
+
return compiler;
|
|
63
|
+
}
|
|
64
|
+
async function load(moduleName, generate) {
|
|
65
|
+
const { babel, solid, typescript } = await loadCompiler();
|
|
66
|
+
// Babel applies presets LAST-TO-FIRST, so `preset-typescript` listed second is
|
|
67
|
+
// the one that runs first: it strips the annotations and `babel-preset-solid`
|
|
68
|
+
// then sees plain JSX. That is the order Solid's own tooling ships, and it is
|
|
69
|
+
// the one to keep — but it is convention here, not a constraint. MEASURED, 14
|
|
70
|
+
// inputs chosen to break it (as-casts and `satisfies` inside attributes,
|
|
71
|
+
// `<T,>` and `<T extends unknown>` arrows, explicit type arguments on a
|
|
72
|
+
// component, parameter properties, `declare`, `namespace`, overloads, a spread
|
|
73
|
+
// of a cast, a conditional type): both orders emit BYTE-IDENTICAL output, and
|
|
74
|
+
// neither throws. Babel merges every preset's visitors into one traversal, and
|
|
75
|
+
// the two never claim the same node — TypeScript syntax is not JSX and JSX is
|
|
76
|
+
// not TypeScript syntax. Recorded because the reversal is the obvious
|
|
77
|
+
// "cleanup", and a reader who tries it deserves the measurement rather than a
|
|
78
|
+
// warning nothing backs.
|
|
79
|
+
return {
|
|
80
|
+
babel,
|
|
81
|
+
presets: [
|
|
82
|
+
[solid, { generate, moduleName }],
|
|
83
|
+
[typescript, { isTSX: true, allExtensions: true }],
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Compile Solid JSX to renderer calls.
|
|
89
|
+
*
|
|
90
|
+
* Wire it through `package.json#gjsify` so no JS-form config file is needed:
|
|
91
|
+
*
|
|
92
|
+
* ```json
|
|
93
|
+
* "gjsify": { "bundler": { "plugins": [{ "name": "@gjsify/rolldown-plugin-solid" }] } }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function solidPlugin(options = {}) {
|
|
97
|
+
const moduleName = options.moduleName ?? DEFAULT_MODULE_NAME;
|
|
98
|
+
const generate = options.generate ?? 'universal';
|
|
99
|
+
const include = options.include ?? DEFAULT_INCLUDE;
|
|
100
|
+
return {
|
|
101
|
+
name: 'gjsify-solid',
|
|
102
|
+
transform: {
|
|
103
|
+
// Before every other JS transform: what follows must never see JSX.
|
|
104
|
+
order: 'pre',
|
|
105
|
+
async handler(code, id) {
|
|
106
|
+
// A `\0` prefix is rollup's mark for a module another plugin MINTED,
|
|
107
|
+
// whose id names no file. Compiling one is claiming someone else's
|
|
108
|
+
// module, and the id would be reported back in every diagnostic.
|
|
109
|
+
if (id.startsWith('\0'))
|
|
110
|
+
return null;
|
|
111
|
+
// Match on the PATH, not on the id. Vite appends `?used`, `?import`
|
|
112
|
+
// and `?t=<timestamp>` on the HMR path, and an anchored `\.tsx$`
|
|
113
|
+
// silently declines every one of them — the plugin becomes a no-op
|
|
114
|
+
// and raw JSX reaches the next transform, which is the failure this
|
|
115
|
+
// package exists to prevent. This package advertises Vite support.
|
|
116
|
+
if (!include.test(id.split('?')[0]))
|
|
117
|
+
return null;
|
|
118
|
+
const { babel, presets } = await load(moduleName, generate);
|
|
119
|
+
const result = await babel.transformAsync(code, {
|
|
120
|
+
// The consumer's own Babel config must not reach this: the
|
|
121
|
+
// preset chain above is the contract, and a stray `.babelrc`
|
|
122
|
+
// in a monorepo root would silently change the output.
|
|
123
|
+
babelrc: false,
|
|
124
|
+
configFile: false,
|
|
125
|
+
filename: id,
|
|
126
|
+
sourceFileName: id,
|
|
127
|
+
sourceMaps: true,
|
|
128
|
+
presets,
|
|
129
|
+
});
|
|
130
|
+
const transformed = result?.code;
|
|
131
|
+
if (typeof transformed !== 'string') {
|
|
132
|
+
// Babel returning no code for a file that matched the filter is
|
|
133
|
+
// not a "nothing to do" — it is a silent drop, and returning
|
|
134
|
+
// null here would ship the untransformed JSX onwards.
|
|
135
|
+
throw new Error(`@gjsify/rolldown-plugin-solid: Babel produced no output for ${id}. ` +
|
|
136
|
+
`The file matched ${String(include)} and was expected to contain Solid JSX.`);
|
|
137
|
+
}
|
|
138
|
+
// The map is REAL and handed on: Babel rewrites every line here, so
|
|
139
|
+
// dropping it points every later frame at the compiled output.
|
|
140
|
+
return { code: transformed, map: result?.map ?? null };
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export default solidPlugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/rolldown-plugin-solid",
|
|
3
|
+
"version": "0.42.0",
|
|
4
|
+
"description": "SolidJS JSX compiler plugin for Rolldown / Rollup / Vite — universal generate, targeting a gjsify renderer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"module": "lib/index.js",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/index.d.ts",
|
|
12
|
+
"default": "./lib/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clear": "gjsify clear lib test.node.mjs test.gjs.mjs tsconfig.tsbuildinfo",
|
|
20
|
+
"check": "gjsify tsc --noEmit",
|
|
21
|
+
"build": "gjsify tsc -p tsconfig.build.json",
|
|
22
|
+
"build:test": "gjsify run build:test:node && gjsify run build:test:gjs",
|
|
23
|
+
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
24
|
+
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
25
|
+
"test": "gjsify run build:test && gjsify run test:node && gjsify run test:gjs",
|
|
26
|
+
"test:node": "node test.node.mjs",
|
|
27
|
+
"test:gjs": "gjsify run test.gjs.mjs"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/gjsify/gjsify.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/gjsify/gjsify/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/infra/rolldown-plugin-solid#readme",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"rolldown",
|
|
39
|
+
"rollup",
|
|
40
|
+
"vite",
|
|
41
|
+
"solid",
|
|
42
|
+
"solid-js",
|
|
43
|
+
"jsx",
|
|
44
|
+
"gtk",
|
|
45
|
+
"gjs"
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@babel/core": "^7.28.4",
|
|
50
|
+
"@babel/preset-typescript": "^7.27.1",
|
|
51
|
+
"babel-preset-solid": "^1.9.9"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"rolldown": "^1.1.4"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"rolldown": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@gjsify/cli": "^0.42.0",
|
|
63
|
+
"@gjsify/unit": "^0.42.0",
|
|
64
|
+
"@types/babel__core": "^7.20.5",
|
|
65
|
+
"@types/node": "^25.9.2",
|
|
66
|
+
"rolldown": "^1.1.4"
|
|
67
|
+
},
|
|
68
|
+
"gjsify": {
|
|
69
|
+
"tier": 1
|
|
70
|
+
}
|
|
71
|
+
}
|