@webjsdev/server 0.8.43 → 0.8.44
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/package.json +1 -1
- package/src/check.js +49 -0
- package/src/vendor.js +2 -1
package/package.json
CHANGED
package/src/check.js
CHANGED
|
@@ -139,6 +139,11 @@ export const RULES = [
|
|
|
139
139
|
description:
|
|
140
140
|
'API route handlers (`route.{js,ts}`) must NOT call `redirect()` from `@webjsdev/core`. That function throws a control-flow signal designed for the SSR page renderer; in a route handler it goes uncaught and produces a 500. Use `Response.redirect(url, 303)` for external redirects or return a 3xx Response directly. Page functions, layouts, and server actions may still use `redirect()` (caught by the SSR pipeline).',
|
|
141
141
|
},
|
|
142
|
+
{
|
|
143
|
+
name: 'no-interpolation-in-raw-text-element',
|
|
144
|
+
description:
|
|
145
|
+
'Flags a template interpolation (`${...}`) placed as a child of a `<style>` or `<script>` element inside a COMPONENT `html` template. Raw-text elements are an SSR/client asymmetry trap: the server renderer emits the interpolated content, but the client renderer drops it (a raw-text hole is a `noop`, since the compile cache is keyed on the static strings), so the element renders correctly on the server and then wipes to empty on hydration. Scoped to components (files with a `WebComponent` class), which hydrate; pages and layouts render server-only and never hydrate, so a page interpolating a `css` result into a `<style>` is a legitimate pattern and is not flagged. In a component, author scoped CSS with `static styles` (shadow DOM) or a `css` template. Found dogfooding a tic-tac-toe app (#845): a `<style>${STYLE}</style>` painted at SSR then vanished on hydrate.',
|
|
146
|
+
},
|
|
142
147
|
];
|
|
143
148
|
|
|
144
149
|
/** Set of all known rule names for fast lookup. */
|
|
@@ -634,6 +639,50 @@ export async function checkConventions(appDir) {
|
|
|
634
639
|
}
|
|
635
640
|
}
|
|
636
641
|
|
|
642
|
+
// --- Rule: no-interpolation-in-raw-text-element ---
|
|
643
|
+
// A `${...}` hole inside a `<style>` or `<script>` element in an `html`
|
|
644
|
+
// template is an SSR/client asymmetry: `renderToString` emits it, but the
|
|
645
|
+
// client parser drops a raw-text hole as a `noop` (the compile cache is keyed
|
|
646
|
+
// on the static strings, so a per-render value cannot be baked in), so the
|
|
647
|
+
// element paints at SSR then wipes to empty on hydration.
|
|
648
|
+
//
|
|
649
|
+
// Scoped to COMPONENTS. The drop only happens on the CLIENT renderer, which
|
|
650
|
+
// runs for components (hydration + re-render). Pages and layouts render
|
|
651
|
+
// server-only (never hydrate), so a page's `<style>${STYLES.text}</style>` is
|
|
652
|
+
// a legitimate, taught pattern and must NOT be flagged. Scan raw source with
|
|
653
|
+
// comments stripped (the tag text lives in a template string, which the
|
|
654
|
+
// redacted `scan` view blanks). One violation per file.
|
|
655
|
+
{
|
|
656
|
+
for (const { rel, content } of files) {
|
|
657
|
+
// Only files that define a hydrating custom element. A page/layout that
|
|
658
|
+
// interpolates a `css` result into a `<style>` is server-only and fine.
|
|
659
|
+
if (!/class\s+\w+\s+extends\s+WebComponent/.test(content)) continue;
|
|
660
|
+
const stripped = content
|
|
661
|
+
.replace(/\/\/.*$/gm, '')
|
|
662
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
663
|
+
for (const tag of ['style', 'script']) {
|
|
664
|
+
// `<tag ...> ... ${ ... </tag>`, where the hole sits before the close
|
|
665
|
+
// tag. The negative lookahead keeps the match from crossing `</tag>`.
|
|
666
|
+
const re = new RegExp(
|
|
667
|
+
`<${tag}\\b[^>]*>(?:(?!<\\/${tag}>)[\\s\\S])*?\\$\\{(?:(?!<\\/${tag}>)[\\s\\S])*?<\\/${tag}>`,
|
|
668
|
+
'i',
|
|
669
|
+
);
|
|
670
|
+
if (re.test(stripped)) {
|
|
671
|
+
violations.push({
|
|
672
|
+
rule: 'no-interpolation-in-raw-text-element',
|
|
673
|
+
file: rel,
|
|
674
|
+
message: `An interpolation (\`\${...}\`) sits inside a <${tag}> element in an html template. The server renderer emits it but the client renderer drops it, so it paints at SSR then wipes to empty on hydration.`,
|
|
675
|
+
fix:
|
|
676
|
+
tag === 'style'
|
|
677
|
+
? `Move the CSS out of the raw-text hole: use \`static styles\` (shadow DOM) or a \`css\` template for a component, or put page CSS in the layout. Static \`<style>...</style>\` with no \`\${}\` is fine.`
|
|
678
|
+
: `Build the script body outside the raw-text element (set attributes/properties via bindings, or compute the value before the template). Static \`<script>...</script>\` with no \`\${}\` is fine.`,
|
|
679
|
+
});
|
|
680
|
+
break; // one violation per file is enough
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
637
686
|
// --- Rule: no-server-env-in-components ---
|
|
638
687
|
// Catches `process.env.X` reads in component files where X is not a
|
|
639
688
|
// WEBJS_PUBLIC_* var and not NODE_ENV. The SSR shim only exposes those
|
package/src/vendor.js
CHANGED
|
@@ -726,7 +726,8 @@ function pinFilePath(appDir) {
|
|
|
726
726
|
* The three-line `.gitignore` pattern that ignores the transient
|
|
727
727
|
* `.webjs` caches at any depth while re-including the committed
|
|
728
728
|
* `.webjs/vendor/` pin output. This mirrors the scaffold template
|
|
729
|
-
* (`packages/cli/templates
|
|
729
|
+
* (`packages/cli/templates/gitignore`, shipped dotless so npm cannot
|
|
730
|
+
* strip it, renamed to `.gitignore` on copy) and the `vendor-gitignore`
|
|
730
731
|
* check in `doctor.js` verbatim, so a self-healed `.gitignore`
|
|
731
732
|
* ends up byte-identical to a freshly scaffolded one.
|
|
732
733
|
*/
|