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 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 += ` ${attrName}="${escapeHtml(serialized)}"`;
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 += ` ${attrName}=""`;
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
- html += ` ${attrName}="${escapeHtml(rawValue)}"`;
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 += ` ${attrName}`;
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), or a spread is on a native element (`<div {...props}>`). In `dev: true`, skipped hoists log a warning.
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinwan-compiler",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Shared compiler core for Sinwan plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",