sinwan-compiler 0.2.5 → 0.2.6
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 +2 -0
- package/dist/index.js +71 -5
- package/docs/plugins.md +3 -1
- package/docs/transform.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ bun add -d sinwan-compiler
|
|
|
19
19
|
|
|
20
20
|
App templates already depend on the Bun or Vite plugin. You only need this package if you call the compiler yourself.
|
|
21
21
|
|
|
22
|
+
The plugins declare `sinwan-compiler` as `>=0.2.5 <1.0.0`. After you publish a new 0.x compiler, do **not** bump the plugins. Apps pick it up with a fresh install or `bun update sinwan-compiler`. Republish a plugin only if its own API changed.
|
|
23
|
+
|
|
22
24
|
## Production analysis
|
|
23
25
|
|
|
24
26
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -92,6 +92,63 @@ function cleanJSXText(value) {
|
|
|
92
92
|
}
|
|
93
93
|
return str;
|
|
94
94
|
}
|
|
95
|
+
function htmlAttributeName(attrName) {
|
|
96
|
+
if (attrName === "defaultValue")
|
|
97
|
+
return "value";
|
|
98
|
+
if (attrName === "defaultChecked")
|
|
99
|
+
return "checked";
|
|
100
|
+
return attrName;
|
|
101
|
+
}
|
|
102
|
+
function jsxAttrName(attr) {
|
|
103
|
+
if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
return attr.name.name;
|
|
107
|
+
}
|
|
108
|
+
function hasAttr(attributes, name) {
|
|
109
|
+
for (const attr of attributes) {
|
|
110
|
+
if (jsxAttrName(attr) === name)
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function attrIsExpression(attributes, name) {
|
|
116
|
+
for (const attr of attributes) {
|
|
117
|
+
if (attr.type !== "JSXAttribute" || jsxAttrName(attr) !== name)
|
|
118
|
+
continue;
|
|
119
|
+
return attr.value?.type === "JSXExpressionContainer";
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
function elementNeedsJsxEnhancer(node) {
|
|
124
|
+
const tagName = jsxNameToString(node.openingElement.name);
|
|
125
|
+
const attrs = node.openingElement.attributes;
|
|
126
|
+
switch (tagName) {
|
|
127
|
+
case "select":
|
|
128
|
+
return hasAttr(attrs, "defaultValue") || hasAttr(attrs, "value");
|
|
129
|
+
case "textarea":
|
|
130
|
+
return hasAttr(attrs, "value");
|
|
131
|
+
case "option":
|
|
132
|
+
return hasAttr(attrs, "selected");
|
|
133
|
+
case "form":
|
|
134
|
+
return attrIsExpression(attrs, "action");
|
|
135
|
+
case "input":
|
|
136
|
+
case "button":
|
|
137
|
+
return attrIsExpression(attrs, "formAction");
|
|
138
|
+
case "progress":
|
|
139
|
+
return attrIsExpression(attrs, "value");
|
|
140
|
+
case "style":
|
|
141
|
+
return hasAttr(attrs, "href") && hasAttr(attrs, "precedence");
|
|
142
|
+
case "script":
|
|
143
|
+
return hasAttr(attrs, "src") && hasAttr(attrs, "async");
|
|
144
|
+
case "link":
|
|
145
|
+
case "meta":
|
|
146
|
+
case "title":
|
|
147
|
+
return !hasAttr(attrs, "itemProp");
|
|
148
|
+
default:
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
95
152
|
function extractTemplate(node, filename) {
|
|
96
153
|
const counter = { value: 0 };
|
|
97
154
|
const slots = [];
|
|
@@ -175,6 +232,7 @@ function elementToHtml(node, slots, path, filename, counter) {
|
|
|
175
232
|
}
|
|
176
233
|
const isVoid = VOID_ELEMENTS.has(tagName);
|
|
177
234
|
let html = `<${tagName}`;
|
|
235
|
+
let textareaDefault = "";
|
|
178
236
|
for (const attr of node.openingElement.attributes) {
|
|
179
237
|
if (attr.type === "JSXSpreadAttribute") {
|
|
180
238
|
throw new Error("Cannot hoist element with spread attributes");
|
|
@@ -196,13 +254,14 @@ function elementToHtml(node, slots, path, filename, counter) {
|
|
|
196
254
|
}
|
|
197
255
|
continue;
|
|
198
256
|
}
|
|
257
|
+
const htmlName = htmlAttributeName(attrName);
|
|
199
258
|
if (attr.value?.type === "JSXExpressionContainer") {
|
|
200
259
|
const expr = attr.value.expression;
|
|
201
260
|
if (expr.type === "JSXEmptyExpression")
|
|
202
261
|
continue;
|
|
203
262
|
if (attrName === "style" && t.isExpression(expr) && isStaticStyleValue(expr)) {
|
|
204
263
|
const serialized = serializeStyleValue(expr);
|
|
205
|
-
html += ` ${
|
|
264
|
+
html += ` ${htmlName}="${escapeHtml(serialized)}"`;
|
|
206
265
|
continue;
|
|
207
266
|
}
|
|
208
267
|
slots.push({
|
|
@@ -211,7 +270,7 @@ function elementToHtml(node, slots, path, filename, counter) {
|
|
|
211
270
|
name: attrName,
|
|
212
271
|
expr
|
|
213
272
|
});
|
|
214
|
-
html += ` ${
|
|
273
|
+
html += ` ${htmlName}=""`;
|
|
215
274
|
continue;
|
|
216
275
|
}
|
|
217
276
|
if (attr.value?.type === "JSXText" || attr.value?.type === "StringLiteral") {
|
|
@@ -219,9 +278,13 @@ function elementToHtml(node, slots, path, filename, counter) {
|
|
|
219
278
|
if (attrName === "style") {
|
|
220
279
|
warnSuspectedStyleInterpolation(rawValue, filename, attr.value.loc?.start?.line);
|
|
221
280
|
}
|
|
222
|
-
|
|
281
|
+
if (tagName === "textarea" && attrName === "defaultValue") {
|
|
282
|
+
textareaDefault = escapeHtml(rawValue);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
html += ` ${htmlName}="${escapeHtml(rawValue)}"`;
|
|
223
286
|
} else if (!attr.value) {
|
|
224
|
-
html += ` ${
|
|
287
|
+
html += ` ${htmlName}`;
|
|
225
288
|
}
|
|
226
289
|
}
|
|
227
290
|
html += isVoid ? " />" : ">";
|
|
@@ -229,6 +292,7 @@ function elementToHtml(node, slots, path, filename, counter) {
|
|
|
229
292
|
return html;
|
|
230
293
|
const childResult = childrenToHtml(node.children, slots, path, filename, 0, counter);
|
|
231
294
|
html += childResult.html;
|
|
295
|
+
html += textareaDefault;
|
|
232
296
|
html += `</${tagName}>`;
|
|
233
297
|
return html;
|
|
234
298
|
}
|
|
@@ -257,7 +321,7 @@ function childrenToHtml(children, slots, path, filename, startIndex, counter) {
|
|
|
257
321
|
const childName = child.openingElement.name;
|
|
258
322
|
const childTag = childName.type === "JSXIdentifier" ? childName.name : "";
|
|
259
323
|
const isFirstCharUpper = childTag && childTag.charAt(0) === childTag.charAt(0).toUpperCase();
|
|
260
|
-
if (isFirstCharUpper) {
|
|
324
|
+
if (isFirstCharUpper || elementNeedsJsxEnhancer(child)) {
|
|
261
325
|
const slot = nextSlotId(counter);
|
|
262
326
|
slots.push({
|
|
263
327
|
path: [...path, childIndex],
|
|
@@ -316,6 +380,8 @@ function transformJSX(code, filename, options = {}) {
|
|
|
316
380
|
const name = tagName.name;
|
|
317
381
|
if (!name || name[0] !== name[0].toLowerCase())
|
|
318
382
|
return;
|
|
383
|
+
if (elementNeedsJsxEnhancer(path.node))
|
|
384
|
+
return;
|
|
319
385
|
try {
|
|
320
386
|
const extracted = extractTemplate(path.node, filename);
|
|
321
387
|
const tmplId = `_$tmpl_${templateCounter++}`;
|
package/docs/plugins.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Using the compiler from Bun and Vite
|
|
2
2
|
|
|
3
|
-
App projects should depend on **`bun-plugin-sinwan`** or **`vite-plugin-sinwan`**. Those packages import `sinwan-compiler` and pass the right options. You do not add `sinwan-compiler` unless you customize the pipeline.
|
|
3
|
+
App projects should depend on **`bun-plugin-sinwan`** or **`vite-plugin-sinwan`**. Those packages import `sinwan-compiler` at runtime (`packages: "external"` in their builds) and pass the right options. You do not add `sinwan-compiler` unless you customize the pipeline.
|
|
4
|
+
|
|
5
|
+
The plugins accept any compiler `>=0.2.5 <1.0.0`. A new compiler release does not need a matching plugin publish. Existing apps update with `bun update sinwan-compiler`.
|
|
4
6
|
|
|
5
7
|
## Bun
|
|
6
8
|
|
package/docs/transform.md
CHANGED
|
@@ -113,7 +113,7 @@ Slots:
|
|
|
113
113
|
|
|
114
114
|
Static `style` objects and strings are written into the HTML (`backgroundColor` → `background-color`). Quoted style strings that contain `${…}` log a warning in the compiler: use a template literal, `style={\`…${x}\`}`.
|
|
115
115
|
|
|
116
|
-
Hoisting is skipped (the JSX is left as JSX) when the root is a component, the tag is a member expression (`Icons.Star` as the root),
|
|
116
|
+
Hoisting is skipped (the JSX is left as JSX) when the root is a component, the tag is a member expression (`Icons.Star` as the root), a spread is on a native element (`<div {...props}>`), or the element needs a JSX enhancer (`<select defaultValue>`, function `action`/`formAction`, head tags without `itemProp`, and the other cases in `enhanced-elements.ts`). Nested enhanced tags become child slots so the native shell can still hoist. In `dev: true`, skipped hoists from extract errors log a warning.
|
|
117
117
|
|
|
118
118
|
Boolean attributes without a value are emitted as HTML flags: `<button disabled>`.
|
|
119
119
|
|