@sigil-dev/compiler 0.5.0 → 0.6.1
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 +161 -2
- package/jsr.json +1 -1
- package/package.json +22 -22
- package/src/babel/jsx/element.ts +6 -2
- package/src/babel/jsx/fragment.ts +4 -1
package/README.md
CHANGED
|
@@ -1,3 +1,162 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sigil-dev/compiler
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Compile-time JSX transform for Sigil.
|
|
4
|
+
Turns reactive macros and JSX into direct DOM operations with a tiny runtime overhead.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
bun add -d @sigil-dev/compiler
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
The compiler runs at build time and does two things:
|
|
13
|
+
|
|
14
|
+
**1. Transforms reactive macros into signal primitives**
|
|
15
|
+
This helps you avoid footguns ~~which should hardly be possible, we're not react~~
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// you write:
|
|
19
|
+
let count = $state(0);
|
|
20
|
+
let doubled = $derived(count * 2);
|
|
21
|
+
|
|
22
|
+
$effect(() => {
|
|
23
|
+
console.log(doubled);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// compiler outputs:
|
|
27
|
+
const count = createSignal(0);
|
|
28
|
+
const doubled = createMemo(() => count() * 2);
|
|
29
|
+
|
|
30
|
+
createEffect(() => {
|
|
31
|
+
console.log(doubled());
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**2. Transforms JSX into imperative DOM**
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// you write:
|
|
39
|
+
const el = <div class="box">{count}</div>;
|
|
40
|
+
|
|
41
|
+
// compiler outputs (dom mode):
|
|
42
|
+
const el = document.createElement("div");
|
|
43
|
+
el.className = "box";
|
|
44
|
+
const t = document.createTextNode("");
|
|
45
|
+
createEffect(() => { t.data = String(count()); });
|
|
46
|
+
el.appendChild(t);
|
|
47
|
+
```
|
|
48
|
+
There's no virtual DOM involved, and none of the nonsense involved with it. Output is either a string or DOM manipulation. You won't fight with the runtime over what actually changed and what didn't
|
|
49
|
+
|
|
50
|
+
## Modes
|
|
51
|
+
|
|
52
|
+
Three output modes for the full SSR lifecycle:
|
|
53
|
+
|
|
54
|
+
| Mode | Use | Output |
|
|
55
|
+
|------|-----|--------|
|
|
56
|
+
| `dom` | Client SPA navigation | Imperative DOM calls |
|
|
57
|
+
| `ssr` | Server render | HTML string concatenation |
|
|
58
|
+
| `hydrate` | Initial client load | Claims existing SSR nodes |
|
|
59
|
+
|
|
60
|
+
The same source file compiles to different output depending on mode. In `ssr` mode, `$effect` is dropped entirely (effects don't make sense on the server). In `hydrate` mode, `claim()` calls replace `createElement` to reuse server-rendered nodes.
|
|
61
|
+
|
|
62
|
+
## Macros
|
|
63
|
+
|
|
64
|
+
### `$state`
|
|
65
|
+
|
|
66
|
+
Declares a reactive variable. Primitive values use subscription sets. Objects and arrays use Proxy for deep reactivity.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
let count = $state(0);
|
|
70
|
+
count++; // compiled to count.set(count.peek() + 1)
|
|
71
|
+
count = 10; // compiled to count.set(10)
|
|
72
|
+
|
|
73
|
+
let user = $state({ name: "Rei" });
|
|
74
|
+
user.name = "Asuka"; // proxy handles this — no rewrite needed
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `$derived`
|
|
78
|
+
|
|
79
|
+
A memoized value derived from other reactive state. Cached until dependencies change.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
let doubled = $derived(count * 2);
|
|
83
|
+
// compiled to: const doubled = createMemo(() => count() * 2)
|
|
84
|
+
// which is another tiny shim over createEffect()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `$effect`
|
|
88
|
+
|
|
89
|
+
Runs a side effect that re-runs when its reactive dependencies change.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
$effect(() => {
|
|
93
|
+
document.title = `Count: ${count}`;
|
|
94
|
+
});
|
|
95
|
+
// compiled to: createEffect(() => { document.title = `Count: ${count()}`; })
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Keyed lists
|
|
99
|
+
|
|
100
|
+
`.map()` with a `key` prop compiles to a reconciled list that surgically updates only changed items:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
const items = $state([{ id: 1, name: "Rei" }, { id: 2, name: "Asuka" }]);
|
|
104
|
+
|
|
105
|
+
<ul>
|
|
106
|
+
{items.map(item => (
|
|
107
|
+
<li key={item.id}>{item.name}</li>
|
|
108
|
+
))}
|
|
109
|
+
</ul>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Compiled to `reconcile()` — moves, inserts, and removes DOM nodes by key without recreating unchanged elements.
|
|
113
|
+
|
|
114
|
+
## Scoped CSS
|
|
115
|
+
|
|
116
|
+
CSS defined in a component file is automatically scoped with a deterministic hash:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// styles in your component file get a unique hash prefix
|
|
120
|
+
// .box → .box.s-a3f9b2c1
|
|
121
|
+
// :global(.reset) escapes scoping
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Setup
|
|
125
|
+
|
|
126
|
+
### Vite
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// vite.config.ts
|
|
130
|
+
import { sigil } from "@sigil-dev/compiler/vite";
|
|
131
|
+
|
|
132
|
+
export default {
|
|
133
|
+
plugins: [sigil()]
|
|
134
|
+
};
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Bun (reccommended)
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// build script
|
|
141
|
+
import { sigil } from "@sigil-dev/compiler/bun";
|
|
142
|
+
|
|
143
|
+
await Bun.build({
|
|
144
|
+
entrypoints: ["src/index.tsx"],
|
|
145
|
+
plugins: [sigil({ mode: "dom" })],
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Babel
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// babel.config.ts
|
|
153
|
+
import { sigilBabelPlugin } from "@sigil-dev/compiler/babel";
|
|
154
|
+
|
|
155
|
+
export default {
|
|
156
|
+
plugins: [sigilBabelPlugin({ mode: "dom" })]
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Used by
|
|
161
|
+
|
|
162
|
+
`@sigil-dev/grimoire` uses this compiler internally to process route files in three passes (ssr, hydrate, dom) during the build step. You can use it standalone for SPA projects via Vite, or wire it into any Babel-compatible build pipeline.
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
2
|
+
"name": "@sigil-dev/compiler",
|
|
3
|
+
"module": "index.ts",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"version": "0.6.1",
|
|
6
|
+
"private": false,
|
|
7
|
+
"description": "Compiler for the Sigil framework",
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"typescript": "^5"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./index.ts",
|
|
13
|
+
"./babel": "./src/babel/index.ts",
|
|
14
|
+
"./vite": "./src/vite/index.ts",
|
|
15
|
+
"./bun": "./src/bun-plugin.ts"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@babel/core": "next",
|
|
19
|
+
"@babel/plugin-syntax-jsx": "next",
|
|
20
|
+
"@babel/plugin-syntax-typescript": "next",
|
|
21
|
+
"@babel/traverse": "next",
|
|
22
|
+
"@babel/types": "next"
|
|
23
|
+
}
|
|
24
24
|
}
|
package/src/babel/jsx/element.ts
CHANGED
|
@@ -153,7 +153,8 @@ export function processElement(
|
|
|
153
153
|
seenAttrs.add(realAttr);
|
|
154
154
|
if (attrName === "use") {
|
|
155
155
|
// use={directive} or use={[directive, params]}
|
|
156
|
-
const expr = (attr.value as t.JSXExpressionContainer)
|
|
156
|
+
const expr = (attr.value as t.JSXExpressionContainer)
|
|
157
|
+
.expression as t.Expression;
|
|
157
158
|
statements.push(
|
|
158
159
|
t.expressionStatement(
|
|
159
160
|
t.callExpression(t.identifier("applyDirective"), [
|
|
@@ -335,7 +336,10 @@ export function processElement(
|
|
|
335
336
|
t.binaryExpression("===", poolLen, t.numericLiteral(0)),
|
|
336
337
|
t.expressionStatement(
|
|
337
338
|
t.callExpression(
|
|
338
|
-
t.memberExpression(
|
|
339
|
+
t.memberExpression(
|
|
340
|
+
t.identifier(varName),
|
|
341
|
+
t.identifier("append"),
|
|
342
|
+
),
|
|
339
343
|
[
|
|
340
344
|
t.callExpression(
|
|
341
345
|
t.memberExpression(
|
|
@@ -90,7 +90,10 @@ export function processFragment(
|
|
|
90
90
|
t.binaryExpression("===", poolLen, t.numericLiteral(0)),
|
|
91
91
|
t.expressionStatement(
|
|
92
92
|
t.callExpression(
|
|
93
|
-
t.memberExpression(
|
|
93
|
+
t.memberExpression(
|
|
94
|
+
t.identifier(effectiveParent),
|
|
95
|
+
t.identifier("append"),
|
|
96
|
+
),
|
|
94
97
|
[
|
|
95
98
|
t.callExpression(
|
|
96
99
|
t.memberExpression(
|