@sigil-dev/compiler 0.9.1 → 0.9.3
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 +162 -162
- package/index.ts +4 -4
- package/jsr.json +8 -8
- package/package.json +1 -1
- package/src/babel/handlers/dom/derived.ts +62 -62
- package/src/babel/handlers/dom/effect.ts +15 -15
- package/src/babel/handlers/dom/globals.ts +105 -105
- package/src/babel/handlers/dom/state.ts +107 -104
- package/src/babel/handlers/ssr/derived.ts +27 -27
- package/src/babel/handlers/ssr/state.ts +11 -11
- package/src/babel/index.ts +594 -589
- package/src/babel/jsx/anchor-mount.ts +389 -389
- package/src/babel/jsx/children.ts +124 -124
- package/src/babel/jsx/element.ts +831 -831
- package/src/babel/jsx/fragment.ts +282 -282
- package/src/babel/jsx/index.ts +2 -2
- package/src/babel/jsx/keyed-list.ts +278 -278
- package/src/babel/jsx/ssr.ts +319 -319
- package/src/babel/jsx/text-node.ts +192 -192
- package/src/babel/jsx/utils.ts +179 -179
- package/src/babel/util/bind.ts +139 -139
- package/src/babel/util/css.ts +151 -151
- package/src/babel/util/dead-code.ts +28 -28
- package/src/babel/util/helpers.ts +46 -46
- package/src/babel/util/magic.ts +11 -11
- package/src/bun-plugin.ts +92 -90
- package/src/vite/index.ts +58 -58
- package/test/async-components.test.ts +24 -24
- package/test/components.test.ts +129 -129
- package/test/containsSignal.test.ts +288 -288
- package/test/fragments.test.ts +90 -90
- package/test/helpers/transform.ts +45 -48
- package/test/hydration.test.ts +147 -147
- package/test/jsx.test.ts +197 -197
- package/test/keyed-lists.test.ts +88 -88
- package/test/reactivity.test.ts +225 -225
- package/test/scoped-css.test.ts +129 -129
- package/test/ssr.test.ts +130 -130
- package/test/tree-shaking.test.ts +78 -78
- package/tsconfig.json +30 -30
package/src/babel/util/css.ts
CHANGED
|
@@ -1,151 +1,151 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Compute a deterministic hash from a file path.
|
|
5
|
-
* Same file always gets same hash. Different files always get different hashes.
|
|
6
|
-
* 's' prefix for Sigil.
|
|
7
|
-
*/
|
|
8
|
-
export function computeHash(filePath: string): string {
|
|
9
|
-
return `s${createHash("md5").update(filePath).digest("hex").slice(0, 8)}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Scope CSS by rewriting selectors with hash.
|
|
14
|
-
* :global(.selector) escapes scoping (passed through unchanged).
|
|
15
|
-
*/
|
|
16
|
-
export function scopeCSS(css: string, hash: string): string {
|
|
17
|
-
let result = "";
|
|
18
|
-
let i = 0;
|
|
19
|
-
|
|
20
|
-
while (i < css.length) {
|
|
21
|
-
// Skip comments
|
|
22
|
-
if (css[i] === "/" && css[i + 1] === "*") {
|
|
23
|
-
const end = css.indexOf("*/", i + 2);
|
|
24
|
-
if (end === -1) {
|
|
25
|
-
result += css.slice(i);
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
result += css.slice(i, end + 2);
|
|
29
|
-
i = end + 2;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Skip string literals
|
|
34
|
-
if (css[i] === '"' || css[i] === "'") {
|
|
35
|
-
const quote = css[i];
|
|
36
|
-
let j = i + 1;
|
|
37
|
-
while (j < css.length && css[j] !== quote) {
|
|
38
|
-
if (css[j] === "\\") j++;
|
|
39
|
-
j++;
|
|
40
|
-
}
|
|
41
|
-
result += css.slice(i, j + 1);
|
|
42
|
-
i = j + 1;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Find next { or end of string
|
|
47
|
-
if (css[i] === "{") {
|
|
48
|
-
// Find the matching }
|
|
49
|
-
let depth = 1;
|
|
50
|
-
let j = i + 1;
|
|
51
|
-
while (j < css.length && depth > 0) {
|
|
52
|
-
if (css[j] === "{") depth++;
|
|
53
|
-
else if (css[j] === "}") depth--;
|
|
54
|
-
j++;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// The block content (inside { }) - recurse into it
|
|
58
|
-
const blockContent = css.slice(i + 1, j - 1);
|
|
59
|
-
const scopedBlock = scopeCSS(blockContent, hash);
|
|
60
|
-
|
|
61
|
-
// Find where the selector starts (look back from {)
|
|
62
|
-
let selectorEnd = i;
|
|
63
|
-
let k = i - 1;
|
|
64
|
-
// Skip whitespace before {
|
|
65
|
-
while (k >= 0 && css[k] === " ") k--;
|
|
66
|
-
selectorEnd = k + 1;
|
|
67
|
-
|
|
68
|
-
// Find start of selector (look back for } or ; or start of string)
|
|
69
|
-
let selectorStart = selectorEnd;
|
|
70
|
-
k = selectorEnd - 1;
|
|
71
|
-
while (k >= 0) {
|
|
72
|
-
if (css[k] === "}" || css[k] === ";") {
|
|
73
|
-
selectorStart = k + 1;
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
if (k === 0) {
|
|
77
|
-
selectorStart = 0;
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
k--;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const selector = css.slice(selectorStart, selectorEnd).trim();
|
|
84
|
-
|
|
85
|
-
if (selector) {
|
|
86
|
-
// Rewrite selector with hash
|
|
87
|
-
result += css.slice(0, selectorStart);
|
|
88
|
-
result += rewriteSelector(selector, hash);
|
|
89
|
-
result += " {";
|
|
90
|
-
result += scopedBlock;
|
|
91
|
-
result += "}";
|
|
92
|
-
} else {
|
|
93
|
-
// No selector found, just output the block
|
|
94
|
-
result += css.slice(0, i);
|
|
95
|
-
result += " {";
|
|
96
|
-
result += scopedBlock;
|
|
97
|
-
result += "}";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
i = j;
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
result += css[i];
|
|
105
|
-
i++;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return result;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Rewrite a selector (or comma-separated list) by appending hash to each part.
|
|
113
|
-
* Handles :global() by passing through unchanged.
|
|
114
|
-
*/
|
|
115
|
-
function rewriteSelector(selector: string, hash: string): string {
|
|
116
|
-
// First, extract all :global() blocks and replace with placeholders
|
|
117
|
-
const globals: string[] = [];
|
|
118
|
-
let processed = selector;
|
|
119
|
-
|
|
120
|
-
// Find :global(...) with nested parens
|
|
121
|
-
while (processed.includes(":global(")) {
|
|
122
|
-
const start = processed.indexOf(":global(");
|
|
123
|
-
let depth = 1;
|
|
124
|
-
let j = start + 7; // Skip ':global('
|
|
125
|
-
while (j < processed.length && depth > 0) {
|
|
126
|
-
if (processed[j] === "(") depth++;
|
|
127
|
-
else if (processed[j] === ")") depth--;
|
|
128
|
-
j++;
|
|
129
|
-
}
|
|
130
|
-
const globalContent = processed.slice(start + 7, j - 1); // Content inside :global()
|
|
131
|
-
const placeholder = `__GLOBAL_${globals.length}__`;
|
|
132
|
-
globals.push(globalContent);
|
|
133
|
-
processed = processed.slice(0, start) + placeholder + processed.slice(j);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Now split by comma (safe - no commas inside :global())
|
|
137
|
-
const parts = processed.split(",").map((part) => {
|
|
138
|
-
const trimmed = part.trim();
|
|
139
|
-
if (!trimmed) return trimmed;
|
|
140
|
-
|
|
141
|
-
// Check for placeholder
|
|
142
|
-
const globalMatch = trimmed.match(/^__GLOBAL_(\d+)__$/);
|
|
143
|
-
if (globalMatch) {
|
|
144
|
-
return globals[parseInt(globalMatch[1]!, 10)];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return `${trimmed}.${hash}`;
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return parts.join(", ");
|
|
151
|
-
}
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compute a deterministic hash from a file path.
|
|
5
|
+
* Same file always gets same hash. Different files always get different hashes.
|
|
6
|
+
* 's' prefix for Sigil.
|
|
7
|
+
*/
|
|
8
|
+
export function computeHash(filePath: string): string {
|
|
9
|
+
return `s${createHash("md5").update(filePath).digest("hex").slice(0, 8)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Scope CSS by rewriting selectors with hash.
|
|
14
|
+
* :global(.selector) escapes scoping (passed through unchanged).
|
|
15
|
+
*/
|
|
16
|
+
export function scopeCSS(css: string, hash: string): string {
|
|
17
|
+
let result = "";
|
|
18
|
+
let i = 0;
|
|
19
|
+
|
|
20
|
+
while (i < css.length) {
|
|
21
|
+
// Skip comments
|
|
22
|
+
if (css[i] === "/" && css[i + 1] === "*") {
|
|
23
|
+
const end = css.indexOf("*/", i + 2);
|
|
24
|
+
if (end === -1) {
|
|
25
|
+
result += css.slice(i);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
result += css.slice(i, end + 2);
|
|
29
|
+
i = end + 2;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Skip string literals
|
|
34
|
+
if (css[i] === '"' || css[i] === "'") {
|
|
35
|
+
const quote = css[i];
|
|
36
|
+
let j = i + 1;
|
|
37
|
+
while (j < css.length && css[j] !== quote) {
|
|
38
|
+
if (css[j] === "\\") j++;
|
|
39
|
+
j++;
|
|
40
|
+
}
|
|
41
|
+
result += css.slice(i, j + 1);
|
|
42
|
+
i = j + 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Find next { or end of string
|
|
47
|
+
if (css[i] === "{") {
|
|
48
|
+
// Find the matching }
|
|
49
|
+
let depth = 1;
|
|
50
|
+
let j = i + 1;
|
|
51
|
+
while (j < css.length && depth > 0) {
|
|
52
|
+
if (css[j] === "{") depth++;
|
|
53
|
+
else if (css[j] === "}") depth--;
|
|
54
|
+
j++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The block content (inside { }) - recurse into it
|
|
58
|
+
const blockContent = css.slice(i + 1, j - 1);
|
|
59
|
+
const scopedBlock = scopeCSS(blockContent, hash);
|
|
60
|
+
|
|
61
|
+
// Find where the selector starts (look back from {)
|
|
62
|
+
let selectorEnd = i;
|
|
63
|
+
let k = i - 1;
|
|
64
|
+
// Skip whitespace before {
|
|
65
|
+
while (k >= 0 && css[k] === " ") k--;
|
|
66
|
+
selectorEnd = k + 1;
|
|
67
|
+
|
|
68
|
+
// Find start of selector (look back for } or ; or start of string)
|
|
69
|
+
let selectorStart = selectorEnd;
|
|
70
|
+
k = selectorEnd - 1;
|
|
71
|
+
while (k >= 0) {
|
|
72
|
+
if (css[k] === "}" || css[k] === ";") {
|
|
73
|
+
selectorStart = k + 1;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (k === 0) {
|
|
77
|
+
selectorStart = 0;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
k--;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const selector = css.slice(selectorStart, selectorEnd).trim();
|
|
84
|
+
|
|
85
|
+
if (selector) {
|
|
86
|
+
// Rewrite selector with hash
|
|
87
|
+
result += css.slice(0, selectorStart);
|
|
88
|
+
result += rewriteSelector(selector, hash);
|
|
89
|
+
result += " {";
|
|
90
|
+
result += scopedBlock;
|
|
91
|
+
result += "}";
|
|
92
|
+
} else {
|
|
93
|
+
// No selector found, just output the block
|
|
94
|
+
result += css.slice(0, i);
|
|
95
|
+
result += " {";
|
|
96
|
+
result += scopedBlock;
|
|
97
|
+
result += "}";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
i = j;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
result += css[i];
|
|
105
|
+
i++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Rewrite a selector (or comma-separated list) by appending hash to each part.
|
|
113
|
+
* Handles :global() by passing through unchanged.
|
|
114
|
+
*/
|
|
115
|
+
function rewriteSelector(selector: string, hash: string): string {
|
|
116
|
+
// First, extract all :global() blocks and replace with placeholders
|
|
117
|
+
const globals: string[] = [];
|
|
118
|
+
let processed = selector;
|
|
119
|
+
|
|
120
|
+
// Find :global(...) with nested parens
|
|
121
|
+
while (processed.includes(":global(")) {
|
|
122
|
+
const start = processed.indexOf(":global(");
|
|
123
|
+
let depth = 1;
|
|
124
|
+
let j = start + 7; // Skip ':global('
|
|
125
|
+
while (j < processed.length && depth > 0) {
|
|
126
|
+
if (processed[j] === "(") depth++;
|
|
127
|
+
else if (processed[j] === ")") depth--;
|
|
128
|
+
j++;
|
|
129
|
+
}
|
|
130
|
+
const globalContent = processed.slice(start + 7, j - 1); // Content inside :global()
|
|
131
|
+
const placeholder = `__GLOBAL_${globals.length}__`;
|
|
132
|
+
globals.push(globalContent);
|
|
133
|
+
processed = processed.slice(0, start) + placeholder + processed.slice(j);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Now split by comma (safe - no commas inside :global())
|
|
137
|
+
const parts = processed.split(",").map((part) => {
|
|
138
|
+
const trimmed = part.trim();
|
|
139
|
+
if (!trimmed) return trimmed;
|
|
140
|
+
|
|
141
|
+
// Check for placeholder
|
|
142
|
+
const globalMatch = trimmed.match(/^__GLOBAL_(\d+)__$/);
|
|
143
|
+
if (globalMatch) {
|
|
144
|
+
return globals[parseInt(globalMatch[1]!, 10)];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return `${trimmed}.${hash}`;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return parts.join(", ");
|
|
151
|
+
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { type NodePath, types as t } from "@babel/core";
|
|
2
|
-
|
|
3
|
-
export function warnDeadReactivity(
|
|
4
|
-
path: NodePath,
|
|
5
|
-
signals: Set<string>,
|
|
6
|
-
storeSignals: Set<string>,
|
|
7
|
-
filename?: string,
|
|
8
|
-
): void {
|
|
9
|
-
const used = new Set<string>();
|
|
10
|
-
|
|
11
|
-
path.traverse({
|
|
12
|
-
CallExpression(innerPath: any) {
|
|
13
|
-
if (t.isIdentifier(innerPath.node.callee)) {
|
|
14
|
-
used.add(innerPath.node.callee.name);
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
for (const name of signals) {
|
|
20
|
-
if (storeSignals.has(name)) continue; // exported, used across files
|
|
21
|
-
if (!used.has(name)) {
|
|
22
|
-
const tag = filename ? `[${filename}]` : "[sigil]";
|
|
23
|
-
console.warn(
|
|
24
|
-
`${tag} unused reactive declaration: "${name}" is declared with $state or $derived but never read.`,
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
import { type NodePath, types as t } from "@babel/core";
|
|
2
|
+
|
|
3
|
+
export function warnDeadReactivity(
|
|
4
|
+
path: NodePath,
|
|
5
|
+
signals: Set<string>,
|
|
6
|
+
storeSignals: Set<string>,
|
|
7
|
+
filename?: string,
|
|
8
|
+
): void {
|
|
9
|
+
const used = new Set<string>();
|
|
10
|
+
|
|
11
|
+
path.traverse({
|
|
12
|
+
CallExpression(innerPath: any) {
|
|
13
|
+
if (t.isIdentifier(innerPath.node.callee)) {
|
|
14
|
+
used.add(innerPath.node.callee.name);
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
for (const name of signals) {
|
|
20
|
+
if (storeSignals.has(name)) continue; // exported, used across files
|
|
21
|
+
if (!used.has(name)) {
|
|
22
|
+
const tag = filename ? `[${filename}]` : "[sigil]";
|
|
23
|
+
console.warn(
|
|
24
|
+
`${tag} unused reactive declaration: "${name}" is declared with $state or $derived but never read.`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import type { NodePath } from "@babel/core";
|
|
2
|
-
import { types as t } from "@babel/core";
|
|
3
|
-
|
|
4
|
-
export function getMacro(declarator: NodePath<t.VariableDeclarator>) {
|
|
5
|
-
const init = declarator.get("init");
|
|
6
|
-
if (!init.isCallExpression()) return null;
|
|
7
|
-
const callee = init.get("callee");
|
|
8
|
-
|
|
9
|
-
// Handle identifier: $state(x), $derived(x)
|
|
10
|
-
if (callee.isIdentifier()) {
|
|
11
|
-
return { name: callee.node.name, init };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Handle member expression: $state.raw(x), $state.snapshot(x)
|
|
15
|
-
if (callee.isMemberExpression()) {
|
|
16
|
-
const object = callee.get("object");
|
|
17
|
-
const property = callee.get("property");
|
|
18
|
-
if (object.isIdentifier() && property.isIdentifier()) {
|
|
19
|
-
return { name: `${object.node.name}.${property.node.name}`, init };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function getTagName(
|
|
27
|
-
name: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName,
|
|
28
|
-
): string {
|
|
29
|
-
if (t.isJSXIdentifier(name)) {
|
|
30
|
-
return name.name;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (t.isJSXMemberExpression(name)) {
|
|
34
|
-
const object = t.isJSXIdentifier(name.object)
|
|
35
|
-
? name.object.name
|
|
36
|
-
: getTagName(name.object);
|
|
37
|
-
|
|
38
|
-
return `${object}.${name.property.name}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (t.isJSXNamespacedName(name)) {
|
|
42
|
-
return `${name.namespace.name}:${name.name.name}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
throw new Error("Unknown JSX name type");
|
|
46
|
-
}
|
|
1
|
+
import type { NodePath } from "@babel/core";
|
|
2
|
+
import { types as t } from "@babel/core";
|
|
3
|
+
|
|
4
|
+
export function getMacro(declarator: NodePath<t.VariableDeclarator>) {
|
|
5
|
+
const init = declarator.get("init");
|
|
6
|
+
if (!init.isCallExpression()) return null;
|
|
7
|
+
const callee = init.get("callee");
|
|
8
|
+
|
|
9
|
+
// Handle identifier: $state(x), $derived(x)
|
|
10
|
+
if (callee.isIdentifier()) {
|
|
11
|
+
return { name: callee.node.name, init };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Handle member expression: $state.raw(x), $state.snapshot(x)
|
|
15
|
+
if (callee.isMemberExpression()) {
|
|
16
|
+
const object = callee.get("object");
|
|
17
|
+
const property = callee.get("property");
|
|
18
|
+
if (object.isIdentifier() && property.isIdentifier()) {
|
|
19
|
+
return { name: `${object.node.name}.${property.node.name}`, init };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getTagName(
|
|
27
|
+
name: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName,
|
|
28
|
+
): string {
|
|
29
|
+
if (t.isJSXIdentifier(name)) {
|
|
30
|
+
return name.name;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (t.isJSXMemberExpression(name)) {
|
|
34
|
+
const object = t.isJSXIdentifier(name.object)
|
|
35
|
+
? name.object.name
|
|
36
|
+
: getTagName(name.object);
|
|
37
|
+
|
|
38
|
+
return `${object}.${name.property.name}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (t.isJSXNamespacedName(name)) {
|
|
42
|
+
return `${name.namespace.name}:${name.name.name}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new Error("Unknown JSX name type");
|
|
46
|
+
}
|
package/src/babel/util/magic.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export const MAGIC = {
|
|
2
|
-
createSignal: "createSignal",
|
|
3
|
-
createRawSignal: "createRawSignal",
|
|
4
|
-
createMemo: "createMemo",
|
|
5
|
-
createEffect: "createEffect",
|
|
6
|
-
createEffectPre: "createEffectPre",
|
|
7
|
-
createInspectEffect: "createInspectEffect",
|
|
8
|
-
withEffectScope: "withEffectScope",
|
|
9
|
-
tracking: "tracking",
|
|
10
|
-
snapshot: "snapshot",
|
|
11
|
-
} as const;
|
|
1
|
+
export const MAGIC = {
|
|
2
|
+
createSignal: "createSignal",
|
|
3
|
+
createRawSignal: "createRawSignal",
|
|
4
|
+
createMemo: "createMemo",
|
|
5
|
+
createEffect: "createEffect",
|
|
6
|
+
createEffectPre: "createEffectPre",
|
|
7
|
+
createInspectEffect: "createInspectEffect",
|
|
8
|
+
withEffectScope: "withEffectScope",
|
|
9
|
+
tracking: "tracking",
|
|
10
|
+
snapshot: "snapshot",
|
|
11
|
+
} as const;
|
package/src/bun-plugin.ts
CHANGED
|
@@ -1,90 +1,92 @@
|
|
|
1
|
-
import { transformSync } from "@babel/core";
|
|
2
|
-
import type { BunPlugin } from "bun";
|
|
3
|
-
import sigilPlugin from "./babel/index.ts";
|
|
4
|
-
import { computeHash, scopeCSS } from "./babel/util/css.ts";
|
|
5
|
-
|
|
6
|
-
const STYLE_RE = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
7
|
-
const STYLE_TEST_RE = /<style[^>]*>[\s\S]*?<\/style>/i;
|
|
8
|
-
const SOURCE_RE = /\.[jt]sx?$/;
|
|
9
|
-
const JSX_RE = /\.[jt]sx$/;
|
|
10
|
-
const SIGIL_MACRO_RE = /\$(?:state|store|derived|effect)\s*\(/;
|
|
11
|
-
|
|
12
|
-
function normalizePath(path: string): string {
|
|
13
|
-
return path.replace(/\\/g, "/");
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function shouldTransform(path: string): boolean {
|
|
17
|
-
const normalized = normalizePath(path);
|
|
18
|
-
if (!SOURCE_RE.test(normalized)) return false;
|
|
19
|
-
if (normalized.includes("/.grimoire/")) return false;
|
|
20
|
-
if (normalized.includes("/node_modules/")) return false;
|
|
21
|
-
if (normalized.includes("/packages/runtime/")) return false;
|
|
22
|
-
if (normalized.includes("/packages/grimoire/")) return false;
|
|
23
|
-
if (normalized.includes("/packages/compiler/")) return false;
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function shouldTransformSource(path: string, code: string): boolean {
|
|
28
|
-
const normalized = normalizePath(path);
|
|
29
|
-
return (
|
|
30
|
-
JSX_RE.test(normalized) ||
|
|
31
|
-
SIGIL_MACRO_RE.test(code) ||
|
|
32
|
-
STYLE_TEST_RE.test(code)
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function sigil(options?: {
|
|
37
|
-
mode?: "dom" | "ssr" | "hydrate";
|
|
38
|
-
dev?: boolean;
|
|
39
|
-
}): BunPlugin {
|
|
40
|
-
return {
|
|
41
|
-
name: "sigil",
|
|
42
|
-
setup(build) {
|
|
43
|
-
const transpiler = new Bun.Transpiler({ loader: "tsx" });
|
|
44
|
-
|
|
45
|
-
// This is required for both linking errors,
|
|
46
|
-
// and for a bug in the bundler, which was reported in https://github.com/oven-sh/bun/issues/13897
|
|
47
|
-
// 2 YEARS AGO and apparently fixed in https://github.com/oven-sh/bun/pull/15119. It wasnt
|
|
48
|
-
build.onResolve({ filter: /^@sigil-dev\/runtime($|\/)/ }, (args) => {
|
|
49
|
-
try {
|
|
50
|
-
const resolved = Bun.resolveSync(args.path, process.cwd());
|
|
51
|
-
return { path: resolved.replaceAll("\\", "/") };
|
|
52
|
-
} catch {
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
build.onLoad({ filter: SOURCE_RE }, async ({ path }) => {
|
|
58
|
-
if (!shouldTransform(path)) return undefined;
|
|
59
|
-
let code = await Bun.file(path).text();
|
|
60
|
-
if (!shouldTransformSource(path, code)) return undefined;
|
|
61
|
-
// console.log("[bun-plugin] intercepting:", path);
|
|
62
|
-
const hash = computeHash(path);
|
|
63
|
-
let scopedCSS = "";
|
|
64
|
-
code = code.replace(STYLE_RE, (_, css) => {
|
|
65
|
-
scopedCSS = scopeCSS(css.trim(), hash);
|
|
66
|
-
return "";
|
|
67
|
-
});
|
|
68
|
-
const res = transformSync(code, {
|
|
69
|
-
parserOpts: {
|
|
70
|
-
//@ts-expect-error holy shut up
|
|
71
|
-
plugins: [["typescript", { isTSX: true }], "jsx"],
|
|
72
|
-
},
|
|
73
|
-
plugins: [
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
__style
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
1
|
+
import { transformSync } from "@babel/core";
|
|
2
|
+
import type { BunPlugin } from "bun";
|
|
3
|
+
import sigilPlugin from "./babel/index.ts";
|
|
4
|
+
import { computeHash, scopeCSS } from "./babel/util/css.ts";
|
|
5
|
+
|
|
6
|
+
const STYLE_RE = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
7
|
+
const STYLE_TEST_RE = /<style[^>]*>[\s\S]*?<\/style>/i;
|
|
8
|
+
const SOURCE_RE = /\.[jt]sx?$/;
|
|
9
|
+
const JSX_RE = /\.[jt]sx$/;
|
|
10
|
+
const SIGIL_MACRO_RE = /\$(?:state|store|derived|effect)\s*\(/;
|
|
11
|
+
|
|
12
|
+
function normalizePath(path: string): string {
|
|
13
|
+
return path.replace(/\\/g, "/");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function shouldTransform(path: string): boolean {
|
|
17
|
+
const normalized = normalizePath(path);
|
|
18
|
+
if (!SOURCE_RE.test(normalized)) return false;
|
|
19
|
+
if (normalized.includes("/.grimoire/")) return false;
|
|
20
|
+
if (normalized.includes("/node_modules/")) return false;
|
|
21
|
+
if (normalized.includes("/packages/runtime/")) return false;
|
|
22
|
+
if (normalized.includes("/packages/grimoire/")) return false;
|
|
23
|
+
if (normalized.includes("/packages/compiler/")) return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function shouldTransformSource(path: string, code: string): boolean {
|
|
28
|
+
const normalized = normalizePath(path);
|
|
29
|
+
return (
|
|
30
|
+
JSX_RE.test(normalized) ||
|
|
31
|
+
SIGIL_MACRO_RE.test(code) ||
|
|
32
|
+
STYLE_TEST_RE.test(code)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function sigil(options?: {
|
|
37
|
+
mode?: "dom" | "ssr" | "hydrate";
|
|
38
|
+
dev?: boolean;
|
|
39
|
+
}): BunPlugin {
|
|
40
|
+
return {
|
|
41
|
+
name: "sigil",
|
|
42
|
+
setup(build) {
|
|
43
|
+
const transpiler = new Bun.Transpiler({ loader: "tsx" });
|
|
44
|
+
|
|
45
|
+
// This is required for both linking errors,
|
|
46
|
+
// and for a bug in the bundler, which was reported in https://github.com/oven-sh/bun/issues/13897
|
|
47
|
+
// 2 YEARS AGO and apparently fixed in https://github.com/oven-sh/bun/pull/15119. It wasnt
|
|
48
|
+
build.onResolve({ filter: /^@sigil-dev\/runtime($|\/)/ }, (args) => {
|
|
49
|
+
try {
|
|
50
|
+
const resolved = Bun.resolveSync(args.path, process.cwd());
|
|
51
|
+
return { path: resolved.replaceAll("\\", "/") };
|
|
52
|
+
} catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
build.onLoad({ filter: SOURCE_RE }, async ({ path }) => {
|
|
58
|
+
if (!shouldTransform(path)) return undefined;
|
|
59
|
+
let code = await Bun.file(path).text();
|
|
60
|
+
if (!shouldTransformSource(path, code)) return undefined;
|
|
61
|
+
// console.log("[bun-plugin] intercepting:", path);
|
|
62
|
+
const hash = computeHash(path);
|
|
63
|
+
let scopedCSS = "";
|
|
64
|
+
code = code.replace(STYLE_RE, (_, css) => {
|
|
65
|
+
scopedCSS = scopeCSS(css.trim(), hash);
|
|
66
|
+
return "";
|
|
67
|
+
});
|
|
68
|
+
const res = transformSync(code, {
|
|
69
|
+
parserOpts: {
|
|
70
|
+
//@ts-expect-error holy shut up
|
|
71
|
+
plugins: [["typescript", { isTSX: true }], "jsx"],
|
|
72
|
+
},
|
|
73
|
+
plugins: [
|
|
74
|
+
[sigilPlugin, { hash, mode: options?.mode, dev: options?.dev }],
|
|
75
|
+
],
|
|
76
|
+
filename: path,
|
|
77
|
+
});
|
|
78
|
+
let out = transpiler.transformSync(res?.code ?? "");
|
|
79
|
+
if (scopedCSS && options?.mode !== "ssr") {
|
|
80
|
+
out = `
|
|
81
|
+
if (typeof document !== 'undefined' && !document.getElementById('sigil-${hash}')) {
|
|
82
|
+
const __style = document.createElement('style');
|
|
83
|
+
__style.id = 'sigil-${hash}';
|
|
84
|
+
__style.textContent = ${JSON.stringify(scopedCSS)};
|
|
85
|
+
document.head.appendChild(__style);
|
|
86
|
+
}${out}`;
|
|
87
|
+
}
|
|
88
|
+
return { contents: out, loader: "js" };
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|