jq79 0.7.0 → 0.7.2

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/dist/vite.cjs CHANGED
@@ -1,6 +1,8 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
4
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
7
  var __export = (target, all) => {
6
8
  for (var name in all)
@@ -14,6 +16,14 @@ var __copyProps = (to, from, except, desc) => {
14
16
  }
15
17
  return to;
16
18
  };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
17
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
28
 
19
29
  // dev/vite.ts
@@ -27,7 +37,7 @@ var import_promises = require("fs/promises");
27
37
  var import_node_path = require("path");
28
38
  var import_vite = require("vite");
29
39
  var COMPONENT_QUERY = "?jq79";
30
- var SCRIPT_BLOCK_RE = /<script\b[^>]*>([\s\S]*?)<\/script\s*>/gi;
40
+ var SCRIPT_BLOCK_RE = /<script((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/script\s*>/gi;
31
41
  var IMPORT_CALL_RE = /import\s*\(\s*(["'])([^"'\n]+?)\1\s*\)/y;
32
42
  var STATIC_IMPORT_RE = /import\s*(?:[\w$\s,{}*]+?\s*from\s*)?(["'])([^"'\n]+)\1/y;
33
43
  var skipString = (src, start) => {
@@ -86,7 +96,7 @@ var isHtmlUrl = (spec) => /\.html?([?#]|$)/.test(spec);
86
96
  var isExternalUrl = (spec) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith("/");
87
97
  var hoistableImports = (source, include) => {
88
98
  const specifiers = /* @__PURE__ */ new Set();
89
- for (const [, script] of source.matchAll(SCRIPT_BLOCK_RE)) {
99
+ for (const [, , script] of source.matchAll(SCRIPT_BLOCK_RE)) {
90
100
  for (const spec of importSpecifiers(script)) {
91
101
  if (isExternalUrl(spec)) continue;
92
102
  if (isHtmlUrl(spec) && !include.test(spec)) continue;
@@ -135,13 +145,17 @@ var declaredComponents = (source) => {
135
145
  };
136
146
  var STYLE_BLOCK_RE = /<style((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/style\s*>/gi;
137
147
  var LANG_ATTR_RE = /\blang\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
148
+ var TYPE_ATTR_RE = /\btype\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
149
+ var attrValue = (attrs, re) => {
150
+ const found = attrs.match(re);
151
+ return found ? found[1] ?? found[2] ?? found[3] : null;
152
+ };
138
153
  var compileStyleBlocks = async (source, file, config, addWatchFile) => {
139
154
  const blocks = [...source.matchAll(STYLE_BLOCK_RE)];
140
155
  const compiled = await Promise.all(
141
156
  blocks.map(async ([, attrs, content]) => {
142
- const lang = attrs.match(LANG_ATTR_RE);
143
- if (!lang) return null;
144
- const extension = lang[1] ?? lang[2] ?? lang[3];
157
+ const extension = attrValue(attrs, LANG_ATTR_RE);
158
+ if (extension === null) return null;
145
159
  const result = await (0, import_vite.preprocessCSS)(content, `${file}.${extension}`, config);
146
160
  result.deps?.forEach(addWatchFile);
147
161
  return { attrs: attrs.replace(LANG_ATTR_RE, "").trimEnd(), css: result.code };
@@ -157,6 +171,58 @@ var compileStyleBlocks = async (source, file, config, addWatchFile) => {
157
171
  });
158
172
  return out + source.slice(last);
159
173
  };
174
+ var TS_LANGS = /* @__PURE__ */ new Set(["ts", "typescript"]);
175
+ var TS_TYPE_RE = /^(?:text|application)\/(?:x-)?typescript$/;
176
+ var typescriptAttr = (attrs) => {
177
+ const lang = attrValue(attrs, LANG_ATTR_RE);
178
+ if (lang !== null) return TS_LANGS.has(lang.trim().toLowerCase()) ? LANG_ATTR_RE : null;
179
+ const type = attrValue(attrs, TYPE_ATTR_RE);
180
+ return type !== null && TS_TYPE_RE.test(type.trim().toLowerCase()) ? TYPE_ATTR_RE : null;
181
+ };
182
+ var stripTypes = async (ts, file) => {
183
+ const vite = await import("vite");
184
+ const { code } = vite.transformWithOxc ? await vite.transformWithOxc(ts, file, { lang: "ts", typescript: { onlyRemoveTypeImports: true } }) : await (0, import_vite.transformWithEsbuild)(ts, file, {
185
+ loader: "ts",
186
+ tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } }
187
+ });
188
+ return code.replace(/^[ \t]*export\s*\{\s*\}\s*;?[ \t]*$/m, "");
189
+ };
190
+ var SETUP_ATTR_RE = /(:setup\s*=\s*)(?:"([^"]*)"|'([^']*)')/i;
191
+ var SIGNATURE_MARKER = "__jq79_signature__";
192
+ var stripSignatureTypes = async (value, file) => {
193
+ const compiled = await stripTypes(`(${value}) => ${SIGNATURE_MARKER}`, file);
194
+ const marker = compiled.indexOf(SIGNATURE_MARKER);
195
+ if (marker === -1) return value;
196
+ const head = compiled.slice(0, marker).trimEnd();
197
+ const params = (head.endsWith("=>") ? head.slice(0, -2) : head).trim();
198
+ return params.startsWith("(") && params.endsWith(")") ? params.slice(1, -1).trim() : params;
199
+ };
200
+ var quoteAttrValue = (value) => value.replace(/"/g, "&quot;");
201
+ var compileScriptBlocks = async (source, file) => {
202
+ const blocks = [...source.matchAll(SCRIPT_BLOCK_RE)];
203
+ const compiled = await Promise.all(
204
+ blocks.map(async ([, attrs, content]) => {
205
+ const marker = typescriptAttr(attrs);
206
+ if (!marker) return null;
207
+ let rest = attrs.replace(marker, "").trimEnd();
208
+ const setup = rest.match(SETUP_ATTR_RE);
209
+ if (setup) {
210
+ const signature = await stripSignatureTypes(setup[2] ?? setup[3], `${file}.signature.ts`);
211
+ rest = rest.replace(SETUP_ATTR_RE, () => `${setup[1]}"${quoteAttrValue(signature)}"`);
212
+ }
213
+ return { attrs: rest, js: await stripTypes(content, `${file}.ts`) };
214
+ })
215
+ );
216
+ let out = "";
217
+ let last = 0;
218
+ blocks.forEach((block, i) => {
219
+ const done = compiled[i];
220
+ if (!done) return;
221
+ out += source.slice(last, block.index) + `<script${done.attrs}>${done.js}</script>`;
222
+ last = block.index + block[0].length;
223
+ });
224
+ return out + source.slice(last);
225
+ };
160
226
  var componentModule = (source, include, filename) => {
161
227
  const hoisted = hoistableImports(source, include);
162
228
  const imports = hoisted.map(
@@ -218,6 +284,7 @@ function jq79(options = {}) {
218
284
  if (!id.endsWith(COMPONENT_QUERY)) return null;
219
285
  const file = id.slice(0, -COMPONENT_QUERY.length);
220
286
  let source = await (0, import_promises.readFile)(file, "utf8");
287
+ source = await compileScriptBlocks(source, file);
221
288
  if (config) source = await compileStyleBlocks(source, file, config, (dep) => this.addWatchFile(dep));
222
289
  const filename = config ? (0, import_node_path.relative)(config.root, file) : file;
223
290
  return { code: componentModule(source, include, filename), map: null };
package/dist/vite.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../dev/vite.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\"\nimport { relative } from \"node:path\"\nimport { preprocessCSS } from \"vite\"\nimport type { Plugin, ResolvedConfig } from \"vite\"\n\n// Vite plugin: import .html single-file components as modules.\n//\n// import { jq79 } from \"jq79/vite\" // vite.config\n// import UserCard from \"./UserCard.html\" // app code\n//\n// The imported value is a Component79 built from the file's source - the same\n// thing `await Component79.fetch(url)` resolves to, but bundled at build time\n// instead of fetched at runtime. The component source is inlined verbatim, so\n// a file keeps working unchanged if it's ever served from public/ and loaded\n// with fetch instead - with one deliberate exception: <style lang=\"scss\"> (or\n// less/stylus/sass) is compiled to plain CSS here. A component using `lang`\n// therefore only works through the bundler; loaded with fetch() it would\n// reach the runtime uncompiled, which the runtime warns about.\n//\n// Only .html files imported from other modules are claimed; entry points\n// (index.html) have no importer and imports carrying an explicit query\n// (?raw, ?url) keep their built-in Vite meaning.\n\nexport interface Jq79PluginOptions {\n // which import specifiers are treated as components (default: any .html)\n include?: RegExp\n // resolved absolute paths to skip even when `include` matches\n exclude?: RegExp\n}\n\n// claimed modules get this suffix so their id no longer ends in \".html\" and\n// Vite's own html handling (entries, asset pipeline) leaves them alone\nconst COMPONENT_QUERY = \"?jq79\"\n\nconst SCRIPT_BLOCK_RE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script\\s*>/gi\n// import(\"...\") with a literal specifier, tried at word boundaries the\n// scanner below reaches (which is what skips $__import and foo.import(...))\nconst IMPORT_CALL_RE = /import\\s*\\(\\s*([\"'])([^\"'\\n]+?)\\1\\s*\\)/y\n// static import statements (factory scripts): optional clause + literal\n// specifier. The clause can't contain parens/quotes, so dynamic import()\n// and import.meta never match\nconst STATIC_IMPORT_RE = /import\\s*(?:[\\w$\\s,{}*]+?\\s*from\\s*)?([\"'])([^\"'\\n]+)\\1/y\n\nconst skipString = (src: string, start: number): number => {\n const quote = src[start]\n let i = start + 1\n while (i < src.length) {\n if (src[i] === \"\\\\\") { i += 2; continue }\n if (src[i] === quote) return i + 1\n i++\n }\n return src.length\n}\n\n// the literal import specifiers in one script body. A scanner rather than a\n// bare matchAll, because a specifier mentioned in a comment or a string is\n// not an import: hoisting a commented-out `import(\"./old.html\")` would pull\n// dead files into the bundle - or break the build once the file is gone\nconst importSpecifiers = (script: string): string[] => {\n const specs: string[] = []\n let i = 0\n while (i < script.length) {\n const ch = script[i]\n if (ch === \"'\" || ch === '\"' || ch === \"`\") { i = skipString(script, i); continue }\n if (ch === \"/\" && script[i + 1] === \"/\") {\n const end = script.indexOf(\"\\n\", i)\n i = end === -1 ? script.length : end + 1\n continue\n }\n if (ch === \"/\" && script[i + 1] === \"*\") {\n const end = script.indexOf(\"*/\", i + 2)\n i = end === -1 ? script.length : end + 2\n continue\n }\n if (ch === \"i\" && (i === 0 || !/[\\w$.]/.test(script[i - 1]))) {\n IMPORT_CALL_RE.lastIndex = i\n const call = IMPORT_CALL_RE.exec(script)\n if (call) { specs.push(call[2]); i = IMPORT_CALL_RE.lastIndex; continue }\n STATIC_IMPORT_RE.lastIndex = i\n const staticImport = STATIC_IMPORT_RE.exec(script)\n if (staticImport) { specs.push(staticImport[2]); i = STATIC_IMPORT_RE.lastIndex; continue }\n }\n i++\n }\n return specs\n}\n\nconst isHtmlUrl = (spec: string) => /\\.html?([?#]|$)/.test(spec)\nconst isExternalUrl = (spec: string) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith(\"/\")\n\n// literal import specifiers in the component's script blocks - dynamic\n// `import(\"...\")` calls and static factory-script imports - that should\n// resolve from the bundle instead of at runtime. Absolute paths and full\n// URLs are left alone (they point at served files, e.g. public/), and so\n// are .html specifiers the plugin wouldn't claim as components\nconst hoistableImports = (source: string, include: RegExp): string[] => {\n const specifiers = new Set<string>()\n for (const [, script] of source.matchAll(SCRIPT_BLOCK_RE)) {\n for (const spec of importSpecifiers(script)) {\n if (isExternalUrl(spec)) continue\n if (isHtmlUrl(spec) && !include.test(spec)) continue // html left to runtime fetch\n specifiers.add(spec) // a claimed component, a source file or an npm package\n }\n }\n return [...specifiers]\n}\n\n// any start or end tag, quote-aware so a \">\" inside an attribute value doesn't\n// end it early\nconst TAG_RE = /<(\\/?)([A-Za-z][\\w-]*)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>/g\nconst NAME_ATTR_RE = /\\bname\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i\nconst COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/\nconst VOID_ELEMENTS = new Set([\n \"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\",\n \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\",\n])\n\n// the components a file declares: its *top-level* <template name=\"…\"> blocks,\n// which the emitted module re-exports by name. Depth is tracked because only\n// the top level declares - a <template> nested in the markup is a plain inert\n// element the runtime leaves alone, and exporting it would name something that\n// never exists. Script and style bodies are cut out first, so a \"<\" in JS or a\n// selector can't be read as a tag\nconst declaredComponents = (source: string): string[] => {\n const markup = source.replace(SCRIPT_BLOCK_RE, \"\").replace(STYLE_BLOCK_RE, \"\")\n const names: string[] = []\n let depth = 0\n\n for (const [, closing, tag, attrs] of markup.matchAll(TAG_RE)) {\n if (closing) {\n depth = Math.max(0, depth - 1)\n continue\n }\n const selfClosing = /\\/\\s*$/.test(attrs) || VOID_ELEMENTS.has(tag.toLowerCase())\n if (depth === 0 && !selfClosing && tag.toLowerCase() === \"template\") {\n const declared = attrs.match(NAME_ATTR_RE)\n const name = declared?.[1] ?? declared?.[2]\n // the runtime warns about the ones this skips (nameless, not PascalCase)\n if (name && COMPONENT_NAME_RE.test(name)) names.push(name)\n }\n if (!selfClosing) depth++\n }\n return names\n}\n\n// a <style> block with its attribute string, so `lang` can be read and the\n// content replaced. Attribute values are matched as quoted chunks so a \">\"\n// inside one doesn't end the tag early\nconst STYLE_BLOCK_RE = /<style((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([\\s\\S]*?)<\\/style\\s*>/gi\nconst LANG_ATTR_RE = /\\blang\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i\n\n// compiles <style lang=\"scss|less|styl|sass\"> blocks to plain CSS with Vite's\n// own preprocessing (the same call @vitejs/plugin-vue makes), so the runtime\n// only ever sees CSS. The preprocessor picks its parser from the extension,\n// and resolving relative @use/@import against a filename in the component's\n// own directory is what makes `@use \"./vars\"` work. Files the preprocessor\n// pulls in are registered as watch deps, so editing a partial re-runs HMR for\n// every component that uses it. `lang` is dropped from the emitted tag: what\n// the runtime parses is a plain <style> (with `scoped` and the rest intact)\nconst compileStyleBlocks = async (\n source: string,\n file: string,\n config: ResolvedConfig,\n addWatchFile: (id: string) => void\n): Promise<string> => {\n const blocks = [...source.matchAll(STYLE_BLOCK_RE)]\n const compiled = await Promise.all(\n blocks.map(async ([, attrs, content]) => {\n const lang = attrs.match(LANG_ATTR_RE)\n if (!lang) return null\n const extension = lang[1] ?? lang[2] ?? lang[3]\n const result = await preprocessCSS(content, `${file}.${extension}`, config)\n result.deps?.forEach(addWatchFile)\n return { attrs: attrs.replace(LANG_ATTR_RE, \"\").trimEnd(), css: result.code }\n })\n )\n\n let out = \"\"\n let last = 0\n blocks.forEach((block, i) => {\n const done = compiled[i]\n if (!done) return\n out += source.slice(last, block.index) + `<style${done.attrs}>${done.css}</style>`\n last = block.index + block[0].length\n })\n return out + source.slice(last)\n}\n\n// the emitted module. Literal import(\"...\") specifiers found in the\n// component's scripts become real module imports, handed to Component79 as a\n// resolution map: at runtime $__import checks the map before falling back to\n// fetch, so bundled components ship with their imports and nothing changes\n// for unbundled ones. Claimed components import as their default (a\n// Component79, matching what runtime fetch resolves to); everything else as\n// a namespace (matching native import()).\n//\n// A file's <template name=\"…\"> components are re-exported by name, so the\n// module shape is the one the file already has: default plus named. They read\n// off the instance, which is where the runtime hangs them - so in dev they are\n// bound to the *first* evaluation's definitions and a module that imports one\n// by name keeps the pre-edit child until the page reloads. The file's own\n// component patches in place, and its rendered children come from the reparse,\n// so this only shows in a component imported by name from another file.\n//\n// In dev, `hot.data` carries the exported instance across updates: importers\n// hold a reference to the *first* module evaluation's instance, so later\n// evaluations patch that same instance in place instead of exporting a new one\n// nobody sees. The patching itself is the runtime's `hotReplace` - the same\n// swap the jq79/dev server drives, from the one place that can reach a\n// component's markers. An instance only used as a definition has nothing to\n// re-render (nested clones can't be reached from this module), so it falls\n// back to a full reload.\nconst componentModule = (source: string, include: RegExp, filename: string): string => {\n const hoisted = hoistableImports(source, include)\n const imports = hoisted\n .map((spec, i) =>\n include.test(spec)\n ? `import __jq79_${i} from ${JSON.stringify(spec)}`\n : `import * as __jq79_${i} from ${JSON.stringify(spec)}`\n )\n .join(\"\\n\")\n const modulesMap = `{ ${hoisted.map((spec, i) => `${JSON.stringify(spec)}: __jq79_${i}`).join(\", \")} }`\n\n return `\nimport { Component79 } from \"jq79\"\n${imports}\n\nconst src = ${JSON.stringify(source)}\nconst modules = ${modulesMap}\nconst filename = ${JSON.stringify(filename)}\n\nlet component\n\nif (import.meta.hot && import.meta.hot.data.component) {\n const prior = import.meta.hot.data.component\n prior.modules = modules\n prior.filename = filename\n // re-renders it where it stands, keeping its data. false means it was never\n // rendered - a definition used only as a nested component - and a reload is\n // the only way to reach the clones made from it\n if (!prior.hotReplace(src) && !prior.data) import.meta.hot.invalidate()\n component = prior\n} else {\n component = new Component79(src, { modules, filename })\n}\n\nif (import.meta.hot) {\n import.meta.hot.data.component = component\n import.meta.hot.accept()\n}\n\nexport default component\n${declaredComponents(source).map(name => `export const ${name} = component.${name}`).join(\"\\n\")}\n`\n}\n\nexport function jq79(options: Jq79PluginOptions = {}): Plugin {\n const include = options.include ?? /\\.html$/\n const { exclude } = options\n\n let config: ResolvedConfig | null = null\n\n return {\n name: \"jq79\",\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved\n },\n\n async resolveId(source, importer) {\n if (!importer) return null // entry points are never components\n if (source.includes(\"?\")) return null // ?raw, ?url, ... keep their meaning\n if (!include.test(source)) return null\n\n const resolved = await this.resolve(source, importer, { skipSelf: true })\n if (!resolved || resolved.external) return null\n if (exclude?.test(resolved.id)) return null\n return resolved.id + COMPONENT_QUERY\n },\n\n async load(id) {\n if (!id.endsWith(COMPONENT_QUERY)) return null\n const file = id.slice(0, -COMPONENT_QUERY.length)\n\n let source = await readFile(file, \"utf8\")\n if (config) source = await compileStyleBlocks(source, file, config, dep => this.addWatchFile(dep))\n\n // the runtime names the component's setup scripts after this, so devtools\n // shows a path the user recognizes instead of an anonymous VM script\n const filename = config ? relative(config.root, file) : file\n\n return { code: componentModule(source, include, filename), map: null }\n },\n }\n}\n\nexport default jq79\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../dev/vite.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\"\nimport { relative } from \"node:path\"\nimport { preprocessCSS, transformWithEsbuild } from \"vite\"\nimport type { Plugin, ResolvedConfig } from \"vite\"\n\n// Vite plugin: import .html single-file components as modules.\n//\n// import { jq79 } from \"jq79/vite\" // vite.config\n// import UserCard from \"./UserCard.html\" // app code\n//\n// The imported value is a Component79 built from the file's source - the same\n// thing `await Component79.fetch(url)` resolves to, but bundled at build time\n// instead of fetched at runtime. The component source is inlined verbatim, so\n// a file keeps working unchanged if it's ever served from public/ and loaded\n// with fetch instead - with one deliberate exception, a block that says it is\n// written in something else: <style lang=\"scss\"> (or less/stylus/sass) is\n// compiled to plain CSS here, and a TypeScript script - <script lang=\"ts\">, or\n// the <script type=\"text/typescript\"> editors read as one - to plain JS. Such a\n// component only works through the bundler; loaded with fetch() it would reach\n// the runtime uncompiled, which the runtime warns about.\n//\n// Only .html files imported from other modules are claimed; entry points\n// (index.html) have no importer and imports carrying an explicit query\n// (?raw, ?url) keep their built-in Vite meaning.\n\nexport interface Jq79PluginOptions {\n // which import specifiers are treated as components (default: any .html)\n include?: RegExp\n // resolved absolute paths to skip even when `include` matches\n exclude?: RegExp\n}\n\n// claimed modules get this suffix so their id no longer ends in \".html\" and\n// Vite's own html handling (entries, asset pipeline) leaves them alone\nconst COMPONENT_QUERY = \"?jq79\"\n\n// a <script> block with its attribute string, so `lang` can be read and the\n// body replaced - the same shape as STYLE_BLOCK_RE below, quote-aware so a\n// \">\" inside an attribute value (`:setup=\"{ n = a > 1 }\"`) doesn't end the tag\nconst SCRIPT_BLOCK_RE = /<script((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([\\s\\S]*?)<\\/script\\s*>/gi\n// import(\"...\") with a literal specifier, tried at word boundaries the\n// scanner below reaches (which is what skips $__import and foo.import(...))\nconst IMPORT_CALL_RE = /import\\s*\\(\\s*([\"'])([^\"'\\n]+?)\\1\\s*\\)/y\n// static import statements (factory scripts): optional clause + literal\n// specifier. The clause can't contain parens/quotes, so dynamic import()\n// and import.meta never match\nconst STATIC_IMPORT_RE = /import\\s*(?:[\\w$\\s,{}*]+?\\s*from\\s*)?([\"'])([^\"'\\n]+)\\1/y\n\nconst skipString = (src: string, start: number): number => {\n const quote = src[start]\n let i = start + 1\n while (i < src.length) {\n if (src[i] === \"\\\\\") { i += 2; continue }\n if (src[i] === quote) return i + 1\n i++\n }\n return src.length\n}\n\n// the literal import specifiers in one script body. A scanner rather than a\n// bare matchAll, because a specifier mentioned in a comment or a string is\n// not an import: hoisting a commented-out `import(\"./old.html\")` would pull\n// dead files into the bundle - or break the build once the file is gone\nconst importSpecifiers = (script: string): string[] => {\n const specs: string[] = []\n let i = 0\n while (i < script.length) {\n const ch = script[i]\n if (ch === \"'\" || ch === '\"' || ch === \"`\") { i = skipString(script, i); continue }\n if (ch === \"/\" && script[i + 1] === \"/\") {\n const end = script.indexOf(\"\\n\", i)\n i = end === -1 ? script.length : end + 1\n continue\n }\n if (ch === \"/\" && script[i + 1] === \"*\") {\n const end = script.indexOf(\"*/\", i + 2)\n i = end === -1 ? script.length : end + 2\n continue\n }\n if (ch === \"i\" && (i === 0 || !/[\\w$.]/.test(script[i - 1]))) {\n IMPORT_CALL_RE.lastIndex = i\n const call = IMPORT_CALL_RE.exec(script)\n if (call) { specs.push(call[2]); i = IMPORT_CALL_RE.lastIndex; continue }\n STATIC_IMPORT_RE.lastIndex = i\n const staticImport = STATIC_IMPORT_RE.exec(script)\n if (staticImport) { specs.push(staticImport[2]); i = STATIC_IMPORT_RE.lastIndex; continue }\n }\n i++\n }\n return specs\n}\n\nconst isHtmlUrl = (spec: string) => /\\.html?([?#]|$)/.test(spec)\nconst isExternalUrl = (spec: string) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith(\"/\")\n\n// literal import specifiers in the component's script blocks - dynamic\n// `import(\"...\")` calls and static factory-script imports - that should\n// resolve from the bundle instead of at runtime. Absolute paths and full\n// URLs are left alone (they point at served files, e.g. public/), and so\n// are .html specifiers the plugin wouldn't claim as components\nconst hoistableImports = (source: string, include: RegExp): string[] => {\n const specifiers = new Set<string>()\n for (const [, , script] of source.matchAll(SCRIPT_BLOCK_RE)) {\n for (const spec of importSpecifiers(script)) {\n if (isExternalUrl(spec)) continue\n if (isHtmlUrl(spec) && !include.test(spec)) continue // html left to runtime fetch\n specifiers.add(spec) // a claimed component, a source file or an npm package\n }\n }\n return [...specifiers]\n}\n\n// any start or end tag, quote-aware so a \">\" inside an attribute value doesn't\n// end it early\nconst TAG_RE = /<(\\/?)([A-Za-z][\\w-]*)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>/g\nconst NAME_ATTR_RE = /\\bname\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i\nconst COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/\nconst VOID_ELEMENTS = new Set([\n \"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\",\n \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\",\n])\n\n// the components a file declares: its *top-level* <template name=\"…\"> blocks,\n// which the emitted module re-exports by name. Depth is tracked because only\n// the top level declares - a <template> nested in the markup is a plain inert\n// element the runtime leaves alone, and exporting it would name something that\n// never exists. Script and style bodies are cut out first, so a \"<\" in JS or a\n// selector can't be read as a tag\nconst declaredComponents = (source: string): string[] => {\n const markup = source.replace(SCRIPT_BLOCK_RE, \"\").replace(STYLE_BLOCK_RE, \"\")\n const names: string[] = []\n let depth = 0\n\n for (const [, closing, tag, attrs] of markup.matchAll(TAG_RE)) {\n if (closing) {\n depth = Math.max(0, depth - 1)\n continue\n }\n const selfClosing = /\\/\\s*$/.test(attrs) || VOID_ELEMENTS.has(tag.toLowerCase())\n if (depth === 0 && !selfClosing && tag.toLowerCase() === \"template\") {\n const declared = attrs.match(NAME_ATTR_RE)\n const name = declared?.[1] ?? declared?.[2]\n // the runtime warns about the ones this skips (nameless, not PascalCase)\n if (name && COMPONENT_NAME_RE.test(name)) names.push(name)\n }\n if (!selfClosing) depth++\n }\n return names\n}\n\n// a <style> block with its attribute string, so `lang` can be read and the\n// content replaced. Attribute values are matched as quoted chunks so a \">\"\n// inside one doesn't end the tag early\nconst STYLE_BLOCK_RE = /<style((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([\\s\\S]*?)<\\/style\\s*>/gi\nconst LANG_ATTR_RE = /\\blang\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i\nconst TYPE_ATTR_RE = /\\btype\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i\n\n// one attribute's value out of a tag's attribute string, whichever way it was\n// quoted - null when the attribute isn't there at all\nconst attrValue = (attrs: string, re: RegExp): string | null => {\n const found = attrs.match(re)\n return found ? found[1] ?? found[2] ?? found[3] : null\n}\n\n// compiles <style lang=\"scss|less|styl|sass\"> blocks to plain CSS with Vite's\n// own preprocessing (the same call @vitejs/plugin-vue makes), so the runtime\n// only ever sees CSS. The preprocessor picks its parser from the extension,\n// and resolving relative @use/@import against a filename in the component's\n// own directory is what makes `@use \"./vars\"` work. Files the preprocessor\n// pulls in are registered as watch deps, so editing a partial re-runs HMR for\n// every component that uses it. `lang` is dropped from the emitted tag: what\n// the runtime parses is a plain <style> (with `scoped` and the rest intact)\nconst compileStyleBlocks = async (\n source: string,\n file: string,\n config: ResolvedConfig,\n addWatchFile: (id: string) => void\n): Promise<string> => {\n const blocks = [...source.matchAll(STYLE_BLOCK_RE)]\n const compiled = await Promise.all(\n blocks.map(async ([, attrs, content]) => {\n const extension = attrValue(attrs, LANG_ATTR_RE)\n if (extension === null) return null\n const result = await preprocessCSS(content, `${file}.${extension}`, config)\n result.deps?.forEach(addWatchFile)\n return { attrs: attrs.replace(LANG_ATTR_RE, \"\").trimEnd(), css: result.code }\n })\n )\n\n let out = \"\"\n let last = 0\n blocks.forEach((block, i) => {\n const done = compiled[i]\n if (!done) return\n out += source.slice(last, block.index) + `<style${done.attrs}>${done.css}</style>`\n last = block.index + block[0].length\n })\n return out + source.slice(last)\n}\n\n// languages a <script lang> is compiled from. Anything else is left as written\n// for the runtime to warn about, rather than guessed at\nconst TS_LANGS = new Set([\"ts\", \"typescript\"])\n// the other spelling of the same mark, and the one editors read: a component is\n// a plain .html file, not an SFC, so nothing in an IDE knows what `lang` means\n// there - embedded-script tooling picks a language from `type`, and a typed\n// block without one is linted as JavaScript. The `x-` forms are the historical\n// spelling of the same two media types\nconst TS_TYPE_RE = /^(?:text|application)\\/(?:x-)?typescript$/\n\n// what marks a script block as TypeScript, given back as the attribute to drop\n// from the emitted tag - the block is compiled to JS, so the mark goes with the\n// types whichever one carried it. A `lang` answers on its own: `lang=\"coffee\"`\n// is a language this plugin doesn't compile, and reading `type` past it would\n// compile a block the author said was something else\nconst typescriptAttr = (attrs: string): RegExp | null => {\n const lang = attrValue(attrs, LANG_ATTR_RE)\n if (lang !== null) return TS_LANGS.has(lang.trim().toLowerCase()) ? LANG_ATTR_RE : null\n const type = attrValue(attrs, TYPE_ATTR_RE)\n return type !== null && TS_TYPE_RE.test(type.trim().toLowerCase()) ? TYPE_ATTR_RE : null\n}\n\ntype ViteTransform = (code: string, id: string, options?: unknown) => Promise<{ code: string }>\n\n// what strips the types: vite's own transform, so the plugin carries no\n// compiler of its own. *Which* transform is a version question, and neither\n// answer covers the peer range (vite >= 5) alone - transformWithOxc is the one\n// vite is moving to but only exists from vite 7, while transformWithEsbuild is\n// deprecated under vite 8 and throws there unless esbuild is installed\n// separately. So it is looked up at call time: oxc where it exists, esbuild on\n// the older versions that ship it.\n//\n// Both drop unused value imports by default, because a TS transform can't tell\n// a type-only import from an unused one. That would be silent damage here: a\n// factory script's `import Row from \"./row.html\"` would vanish, taking with it\n// the specifier hoistableImports needs to pull the child into the bundle. The\n// flags below are what keep it - only `import type` is erased\nconst stripTypes = async (ts: string, file: string): Promise<string> => {\n const vite = (await import(\"vite\")) as unknown as { transformWithOxc?: ViteTransform }\n const { code } = vite.transformWithOxc\n ? await vite.transformWithOxc(ts, file, { lang: \"ts\", typescript: { onlyRemoveTypeImports: true } })\n : await transformWithEsbuild(ts, file, {\n loader: \"ts\",\n tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } },\n })\n // a script whose only imports were `import type` comes back marked as a\n // module. `export {}` exports nothing, and it is a SyntaxError inside the\n // Function body the runtime compiles a script into\n return code.replace(/^[ \\t]*export\\s*\\{\\s*\\}\\s*;?[ \\t]*$/m, \"\")\n}\n\n// the `:setup` attribute, when it carries a value (a bare `:setup` is the\n// closed signature and can't be typed)\nconst SETUP_ATTR_RE = /(:setup\\s*=\\s*)(?:\"([^\"]*)\"|'([^']*)')/i\n// the arrow body the signature is wrapped in, so the parameter list can be\n// found again in the output without matching brackets\nconst SIGNATURE_MARKER = \"__jq79_signature__\"\n\n// a component's props signature lives in the `:setup` *attribute*, not in the\n// script body, so the body's transform never sees it - and the TypeScript mark\n// has to mean the same thing on both halves of the block, or a typed signature\n// either survives into a component the plugin just promised was JS or, for\n// `_: Props`, stops reading as a signature at all.\n//\n// It goes through the same transform as everything else, wrapped as a\n// parameter list, rather than being cut with a scanner of its own: the\n// annotation can sit on the pattern (`{ a }: Props`), on the permissive `_`, or\n// inside a default (`{ step = 1 as number }`, which the runtime would evaluate\n// and silently drop), and telling those apart is a parser's job. Both transforms\n// hand back `(<params>) => <marker>` on one line, parens intact\nconst stripSignatureTypes = async (value: string, file: string): Promise<string> => {\n const compiled = await stripTypes(`(${value}) => ${SIGNATURE_MARKER}`, file)\n const marker = compiled.indexOf(SIGNATURE_MARKER)\n if (marker === -1) return value // not the shape expected: leave it as written\n const head = compiled.slice(0, marker).trimEnd()\n const params = (head.endsWith(\"=>\") ? head.slice(0, -2) : head).trim()\n return params.startsWith(\"(\") && params.endsWith(\")\") ? params.slice(1, -1).trim() : params\n}\n\n// the signature back into a double-quoted attribute. Only `\"` needs escaping:\n// the transforms normalize string literals to double quotes, so a default the\n// author wrote as `'x'` comes back as `\"x\"` and would end the attribute early.\n// `&` is deliberately left alone - `&&` in a default is not an entity and\n// survives the parse, while escaping it would double-encode a source that\n// already wrote `&quot;`\nconst quoteAttrValue = (value: string) => value.replace(/\"/g, \"&quot;\")\n\n// compiles TypeScript script blocks to plain JS, so the runtime only ever sees\n// JS - the same deal <style lang=\"scss\"> gets, for a sharper reason. The setup\n// scanner is not a parser: `let count: number = 0` reaching it is not a syntax\n// error but a labeled statement that assigns to `number`, so it runs and leaves\n// `count` undeclared. Whichever attribute marked the block (`lang=\"ts\"` or\n// `type=\"text/typescript\"`) is dropped from the emitted tag and every other one\n// (`:setup`, `:mounted`) is left as written.\n//\n// This runs before hoistableImports reads the source, so an `import type`\n// specifier is already gone by the time the plugin decides what to bundle\nconst compileScriptBlocks = async (source: string, file: string): Promise<string> => {\n const blocks = [...source.matchAll(SCRIPT_BLOCK_RE)]\n const compiled = await Promise.all(\n blocks.map(async ([, attrs, content]) => {\n const marker = typescriptAttr(attrs)\n if (!marker) return null\n\n let rest = attrs.replace(marker, \"\").trimEnd()\n const setup = rest.match(SETUP_ATTR_RE)\n if (setup) {\n const signature = await stripSignatureTypes(setup[2] ?? setup[3], `${file}.signature.ts`)\n // a function replacement, not a string: `$&` and friends are live in a\n // replacement string and a default value is arbitrary source\n rest = rest.replace(SETUP_ATTR_RE, () => `${setup[1]}\"${quoteAttrValue(signature)}\"`)\n }\n\n return { attrs: rest, js: await stripTypes(content, `${file}.ts`) }\n })\n )\n\n let out = \"\"\n let last = 0\n blocks.forEach((block, i) => {\n const done = compiled[i]\n if (!done) return\n out += source.slice(last, block.index) + `<script${done.attrs}>${done.js}</script>`\n last = block.index + block[0].length\n })\n return out + source.slice(last)\n}\n\n// the emitted module. Literal import(\"...\") specifiers found in the\n// component's scripts become real module imports, handed to Component79 as a\n// resolution map: at runtime $__import checks the map before falling back to\n// fetch, so bundled components ship with their imports and nothing changes\n// for unbundled ones. Claimed components import as their default (a\n// Component79, matching what runtime fetch resolves to); everything else as\n// a namespace (matching native import()).\n//\n// A file's <template name=\"…\"> components are re-exported by name, so the\n// module shape is the one the file already has: default plus named. They read\n// off the instance, which is where the runtime hangs them - so in dev they are\n// bound to the *first* evaluation's definitions and a module that imports one\n// by name keeps the pre-edit child until the page reloads. The file's own\n// component patches in place, and its rendered children come from the reparse,\n// so this only shows in a component imported by name from another file.\n//\n// In dev, `hot.data` carries the exported instance across updates: importers\n// hold a reference to the *first* module evaluation's instance, so later\n// evaluations patch that same instance in place instead of exporting a new one\n// nobody sees. The patching itself is the runtime's `hotReplace` - the same\n// swap the jq79/dev server drives, from the one place that can reach a\n// component's markers. An instance only used as a definition has nothing to\n// re-render (nested clones can't be reached from this module), so it falls\n// back to a full reload.\nconst componentModule = (source: string, include: RegExp, filename: string): string => {\n const hoisted = hoistableImports(source, include)\n const imports = hoisted\n .map((spec, i) =>\n include.test(spec)\n ? `import __jq79_${i} from ${JSON.stringify(spec)}`\n : `import * as __jq79_${i} from ${JSON.stringify(spec)}`\n )\n .join(\"\\n\")\n const modulesMap = `{ ${hoisted.map((spec, i) => `${JSON.stringify(spec)}: __jq79_${i}`).join(\", \")} }`\n\n return `\nimport { Component79 } from \"jq79\"\n${imports}\n\nconst src = ${JSON.stringify(source)}\nconst modules = ${modulesMap}\nconst filename = ${JSON.stringify(filename)}\n\nlet component\n\nif (import.meta.hot && import.meta.hot.data.component) {\n const prior = import.meta.hot.data.component\n prior.modules = modules\n prior.filename = filename\n // re-renders it where it stands, keeping its data. false means it was never\n // rendered - a definition used only as a nested component - and a reload is\n // the only way to reach the clones made from it\n if (!prior.hotReplace(src) && !prior.data) import.meta.hot.invalidate()\n component = prior\n} else {\n component = new Component79(src, { modules, filename })\n}\n\nif (import.meta.hot) {\n import.meta.hot.data.component = component\n import.meta.hot.accept()\n}\n\nexport default component\n${declaredComponents(source).map(name => `export const ${name} = component.${name}`).join(\"\\n\")}\n`\n}\n\nexport function jq79(options: Jq79PluginOptions = {}): Plugin {\n const include = options.include ?? /\\.html$/\n const { exclude } = options\n\n let config: ResolvedConfig | null = null\n\n return {\n name: \"jq79\",\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved\n },\n\n async resolveId(source, importer) {\n if (!importer) return null // entry points are never components\n if (source.includes(\"?\")) return null // ?raw, ?url, ... keep their meaning\n if (!include.test(source)) return null\n\n const resolved = await this.resolve(source, importer, { skipSelf: true })\n if (!resolved || resolved.external) return null\n if (exclude?.test(resolved.id)) return null\n return resolved.id + COMPONENT_QUERY\n },\n\n async load(id) {\n if (!id.endsWith(COMPONENT_QUERY)) return null\n const file = id.slice(0, -COMPONENT_QUERY.length)\n\n let source = await readFile(file, \"utf8\")\n source = await compileScriptBlocks(source, file)\n if (config) source = await compileStyleBlocks(source, file, config, dep => this.addWatchFile(dep))\n\n // the runtime names the component's setup scripts after this, so devtools\n // shows a path the user recognizes instead of an anonymous VM script\n const filename = config ? relative(config.root, file) : file\n\n return { code: componentModule(source, include, filename), map: null }\n },\n }\n}\n\nexport default jq79\n"],"mappings":"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","names":[]}
package/dist/vite.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // dev/vite.ts
2
2
  import { readFile } from "fs/promises";
3
3
  import { relative } from "path";
4
- import { preprocessCSS } from "vite";
4
+ import { preprocessCSS, transformWithEsbuild } from "vite";
5
5
  var COMPONENT_QUERY = "?jq79";
6
- var SCRIPT_BLOCK_RE = /<script\b[^>]*>([\s\S]*?)<\/script\s*>/gi;
6
+ var SCRIPT_BLOCK_RE = /<script((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/script\s*>/gi;
7
7
  var IMPORT_CALL_RE = /import\s*\(\s*(["'])([^"'\n]+?)\1\s*\)/y;
8
8
  var STATIC_IMPORT_RE = /import\s*(?:[\w$\s,{}*]+?\s*from\s*)?(["'])([^"'\n]+)\1/y;
9
9
  var skipString = (src, start) => {
@@ -62,7 +62,7 @@ var isHtmlUrl = (spec) => /\.html?([?#]|$)/.test(spec);
62
62
  var isExternalUrl = (spec) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith("/");
63
63
  var hoistableImports = (source, include) => {
64
64
  const specifiers = /* @__PURE__ */ new Set();
65
- for (const [, script] of source.matchAll(SCRIPT_BLOCK_RE)) {
65
+ for (const [, , script] of source.matchAll(SCRIPT_BLOCK_RE)) {
66
66
  for (const spec of importSpecifiers(script)) {
67
67
  if (isExternalUrl(spec)) continue;
68
68
  if (isHtmlUrl(spec) && !include.test(spec)) continue;
@@ -111,13 +111,17 @@ var declaredComponents = (source) => {
111
111
  };
112
112
  var STYLE_BLOCK_RE = /<style((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/style\s*>/gi;
113
113
  var LANG_ATTR_RE = /\blang\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
114
+ var TYPE_ATTR_RE = /\btype\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
115
+ var attrValue = (attrs, re) => {
116
+ const found = attrs.match(re);
117
+ return found ? found[1] ?? found[2] ?? found[3] : null;
118
+ };
114
119
  var compileStyleBlocks = async (source, file, config, addWatchFile) => {
115
120
  const blocks = [...source.matchAll(STYLE_BLOCK_RE)];
116
121
  const compiled = await Promise.all(
117
122
  blocks.map(async ([, attrs, content]) => {
118
- const lang = attrs.match(LANG_ATTR_RE);
119
- if (!lang) return null;
120
- const extension = lang[1] ?? lang[2] ?? lang[3];
123
+ const extension = attrValue(attrs, LANG_ATTR_RE);
124
+ if (extension === null) return null;
121
125
  const result = await preprocessCSS(content, `${file}.${extension}`, config);
122
126
  result.deps?.forEach(addWatchFile);
123
127
  return { attrs: attrs.replace(LANG_ATTR_RE, "").trimEnd(), css: result.code };
@@ -133,6 +137,58 @@ var compileStyleBlocks = async (source, file, config, addWatchFile) => {
133
137
  });
134
138
  return out + source.slice(last);
135
139
  };
140
+ var TS_LANGS = /* @__PURE__ */ new Set(["ts", "typescript"]);
141
+ var TS_TYPE_RE = /^(?:text|application)\/(?:x-)?typescript$/;
142
+ var typescriptAttr = (attrs) => {
143
+ const lang = attrValue(attrs, LANG_ATTR_RE);
144
+ if (lang !== null) return TS_LANGS.has(lang.trim().toLowerCase()) ? LANG_ATTR_RE : null;
145
+ const type = attrValue(attrs, TYPE_ATTR_RE);
146
+ return type !== null && TS_TYPE_RE.test(type.trim().toLowerCase()) ? TYPE_ATTR_RE : null;
147
+ };
148
+ var stripTypes = async (ts, file) => {
149
+ const vite = await import("vite");
150
+ const { code } = vite.transformWithOxc ? await vite.transformWithOxc(ts, file, { lang: "ts", typescript: { onlyRemoveTypeImports: true } }) : await transformWithEsbuild(ts, file, {
151
+ loader: "ts",
152
+ tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } }
153
+ });
154
+ return code.replace(/^[ \t]*export\s*\{\s*\}\s*;?[ \t]*$/m, "");
155
+ };
156
+ var SETUP_ATTR_RE = /(:setup\s*=\s*)(?:"([^"]*)"|'([^']*)')/i;
157
+ var SIGNATURE_MARKER = "__jq79_signature__";
158
+ var stripSignatureTypes = async (value, file) => {
159
+ const compiled = await stripTypes(`(${value}) => ${SIGNATURE_MARKER}`, file);
160
+ const marker = compiled.indexOf(SIGNATURE_MARKER);
161
+ if (marker === -1) return value;
162
+ const head = compiled.slice(0, marker).trimEnd();
163
+ const params = (head.endsWith("=>") ? head.slice(0, -2) : head).trim();
164
+ return params.startsWith("(") && params.endsWith(")") ? params.slice(1, -1).trim() : params;
165
+ };
166
+ var quoteAttrValue = (value) => value.replace(/"/g, "&quot;");
167
+ var compileScriptBlocks = async (source, file) => {
168
+ const blocks = [...source.matchAll(SCRIPT_BLOCK_RE)];
169
+ const compiled = await Promise.all(
170
+ blocks.map(async ([, attrs, content]) => {
171
+ const marker = typescriptAttr(attrs);
172
+ if (!marker) return null;
173
+ let rest = attrs.replace(marker, "").trimEnd();
174
+ const setup = rest.match(SETUP_ATTR_RE);
175
+ if (setup) {
176
+ const signature = await stripSignatureTypes(setup[2] ?? setup[3], `${file}.signature.ts`);
177
+ rest = rest.replace(SETUP_ATTR_RE, () => `${setup[1]}"${quoteAttrValue(signature)}"`);
178
+ }
179
+ return { attrs: rest, js: await stripTypes(content, `${file}.ts`) };
180
+ })
181
+ );
182
+ let out = "";
183
+ let last = 0;
184
+ blocks.forEach((block, i) => {
185
+ const done = compiled[i];
186
+ if (!done) return;
187
+ out += source.slice(last, block.index) + `<script${done.attrs}>${done.js}</script>`;
188
+ last = block.index + block[0].length;
189
+ });
190
+ return out + source.slice(last);
191
+ };
136
192
  var componentModule = (source, include, filename) => {
137
193
  const hoisted = hoistableImports(source, include);
138
194
  const imports = hoisted.map(
@@ -194,6 +250,7 @@ function jq79(options = {}) {
194
250
  if (!id.endsWith(COMPONENT_QUERY)) return null;
195
251
  const file = id.slice(0, -COMPONENT_QUERY.length);
196
252
  let source = await readFile(file, "utf8");
253
+ source = await compileScriptBlocks(source, file);
197
254
  if (config) source = await compileStyleBlocks(source, file, config, (dep) => this.addWatchFile(dep));
198
255
  const filename = config ? relative(config.root, file) : file;
199
256
  return { code: componentModule(source, include, filename), map: null };
package/dist/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../dev/vite.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\"\nimport { relative } from \"node:path\"\nimport { preprocessCSS } from \"vite\"\nimport type { Plugin, ResolvedConfig } from \"vite\"\n\n// Vite plugin: import .html single-file components as modules.\n//\n// import { jq79 } from \"jq79/vite\" // vite.config\n// import UserCard from \"./UserCard.html\" // app code\n//\n// The imported value is a Component79 built from the file's source - the same\n// thing `await Component79.fetch(url)` resolves to, but bundled at build time\n// instead of fetched at runtime. The component source is inlined verbatim, so\n// a file keeps working unchanged if it's ever served from public/ and loaded\n// with fetch instead - with one deliberate exception: <style lang=\"scss\"> (or\n// less/stylus/sass) is compiled to plain CSS here. A component using `lang`\n// therefore only works through the bundler; loaded with fetch() it would\n// reach the runtime uncompiled, which the runtime warns about.\n//\n// Only .html files imported from other modules are claimed; entry points\n// (index.html) have no importer and imports carrying an explicit query\n// (?raw, ?url) keep their built-in Vite meaning.\n\nexport interface Jq79PluginOptions {\n // which import specifiers are treated as components (default: any .html)\n include?: RegExp\n // resolved absolute paths to skip even when `include` matches\n exclude?: RegExp\n}\n\n// claimed modules get this suffix so their id no longer ends in \".html\" and\n// Vite's own html handling (entries, asset pipeline) leaves them alone\nconst COMPONENT_QUERY = \"?jq79\"\n\nconst SCRIPT_BLOCK_RE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script\\s*>/gi\n// import(\"...\") with a literal specifier, tried at word boundaries the\n// scanner below reaches (which is what skips $__import and foo.import(...))\nconst IMPORT_CALL_RE = /import\\s*\\(\\s*([\"'])([^\"'\\n]+?)\\1\\s*\\)/y\n// static import statements (factory scripts): optional clause + literal\n// specifier. The clause can't contain parens/quotes, so dynamic import()\n// and import.meta never match\nconst STATIC_IMPORT_RE = /import\\s*(?:[\\w$\\s,{}*]+?\\s*from\\s*)?([\"'])([^\"'\\n]+)\\1/y\n\nconst skipString = (src: string, start: number): number => {\n const quote = src[start]\n let i = start + 1\n while (i < src.length) {\n if (src[i] === \"\\\\\") { i += 2; continue }\n if (src[i] === quote) return i + 1\n i++\n }\n return src.length\n}\n\n// the literal import specifiers in one script body. A scanner rather than a\n// bare matchAll, because a specifier mentioned in a comment or a string is\n// not an import: hoisting a commented-out `import(\"./old.html\")` would pull\n// dead files into the bundle - or break the build once the file is gone\nconst importSpecifiers = (script: string): string[] => {\n const specs: string[] = []\n let i = 0\n while (i < script.length) {\n const ch = script[i]\n if (ch === \"'\" || ch === '\"' || ch === \"`\") { i = skipString(script, i); continue }\n if (ch === \"/\" && script[i + 1] === \"/\") {\n const end = script.indexOf(\"\\n\", i)\n i = end === -1 ? script.length : end + 1\n continue\n }\n if (ch === \"/\" && script[i + 1] === \"*\") {\n const end = script.indexOf(\"*/\", i + 2)\n i = end === -1 ? script.length : end + 2\n continue\n }\n if (ch === \"i\" && (i === 0 || !/[\\w$.]/.test(script[i - 1]))) {\n IMPORT_CALL_RE.lastIndex = i\n const call = IMPORT_CALL_RE.exec(script)\n if (call) { specs.push(call[2]); i = IMPORT_CALL_RE.lastIndex; continue }\n STATIC_IMPORT_RE.lastIndex = i\n const staticImport = STATIC_IMPORT_RE.exec(script)\n if (staticImport) { specs.push(staticImport[2]); i = STATIC_IMPORT_RE.lastIndex; continue }\n }\n i++\n }\n return specs\n}\n\nconst isHtmlUrl = (spec: string) => /\\.html?([?#]|$)/.test(spec)\nconst isExternalUrl = (spec: string) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith(\"/\")\n\n// literal import specifiers in the component's script blocks - dynamic\n// `import(\"...\")` calls and static factory-script imports - that should\n// resolve from the bundle instead of at runtime. Absolute paths and full\n// URLs are left alone (they point at served files, e.g. public/), and so\n// are .html specifiers the plugin wouldn't claim as components\nconst hoistableImports = (source: string, include: RegExp): string[] => {\n const specifiers = new Set<string>()\n for (const [, script] of source.matchAll(SCRIPT_BLOCK_RE)) {\n for (const spec of importSpecifiers(script)) {\n if (isExternalUrl(spec)) continue\n if (isHtmlUrl(spec) && !include.test(spec)) continue // html left to runtime fetch\n specifiers.add(spec) // a claimed component, a source file or an npm package\n }\n }\n return [...specifiers]\n}\n\n// any start or end tag, quote-aware so a \">\" inside an attribute value doesn't\n// end it early\nconst TAG_RE = /<(\\/?)([A-Za-z][\\w-]*)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>/g\nconst NAME_ATTR_RE = /\\bname\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i\nconst COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/\nconst VOID_ELEMENTS = new Set([\n \"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\",\n \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\",\n])\n\n// the components a file declares: its *top-level* <template name=\"…\"> blocks,\n// which the emitted module re-exports by name. Depth is tracked because only\n// the top level declares - a <template> nested in the markup is a plain inert\n// element the runtime leaves alone, and exporting it would name something that\n// never exists. Script and style bodies are cut out first, so a \"<\" in JS or a\n// selector can't be read as a tag\nconst declaredComponents = (source: string): string[] => {\n const markup = source.replace(SCRIPT_BLOCK_RE, \"\").replace(STYLE_BLOCK_RE, \"\")\n const names: string[] = []\n let depth = 0\n\n for (const [, closing, tag, attrs] of markup.matchAll(TAG_RE)) {\n if (closing) {\n depth = Math.max(0, depth - 1)\n continue\n }\n const selfClosing = /\\/\\s*$/.test(attrs) || VOID_ELEMENTS.has(tag.toLowerCase())\n if (depth === 0 && !selfClosing && tag.toLowerCase() === \"template\") {\n const declared = attrs.match(NAME_ATTR_RE)\n const name = declared?.[1] ?? declared?.[2]\n // the runtime warns about the ones this skips (nameless, not PascalCase)\n if (name && COMPONENT_NAME_RE.test(name)) names.push(name)\n }\n if (!selfClosing) depth++\n }\n return names\n}\n\n// a <style> block with its attribute string, so `lang` can be read and the\n// content replaced. Attribute values are matched as quoted chunks so a \">\"\n// inside one doesn't end the tag early\nconst STYLE_BLOCK_RE = /<style((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([\\s\\S]*?)<\\/style\\s*>/gi\nconst LANG_ATTR_RE = /\\blang\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i\n\n// compiles <style lang=\"scss|less|styl|sass\"> blocks to plain CSS with Vite's\n// own preprocessing (the same call @vitejs/plugin-vue makes), so the runtime\n// only ever sees CSS. The preprocessor picks its parser from the extension,\n// and resolving relative @use/@import against a filename in the component's\n// own directory is what makes `@use \"./vars\"` work. Files the preprocessor\n// pulls in are registered as watch deps, so editing a partial re-runs HMR for\n// every component that uses it. `lang` is dropped from the emitted tag: what\n// the runtime parses is a plain <style> (with `scoped` and the rest intact)\nconst compileStyleBlocks = async (\n source: string,\n file: string,\n config: ResolvedConfig,\n addWatchFile: (id: string) => void\n): Promise<string> => {\n const blocks = [...source.matchAll(STYLE_BLOCK_RE)]\n const compiled = await Promise.all(\n blocks.map(async ([, attrs, content]) => {\n const lang = attrs.match(LANG_ATTR_RE)\n if (!lang) return null\n const extension = lang[1] ?? lang[2] ?? lang[3]\n const result = await preprocessCSS(content, `${file}.${extension}`, config)\n result.deps?.forEach(addWatchFile)\n return { attrs: attrs.replace(LANG_ATTR_RE, \"\").trimEnd(), css: result.code }\n })\n )\n\n let out = \"\"\n let last = 0\n blocks.forEach((block, i) => {\n const done = compiled[i]\n if (!done) return\n out += source.slice(last, block.index) + `<style${done.attrs}>${done.css}</style>`\n last = block.index + block[0].length\n })\n return out + source.slice(last)\n}\n\n// the emitted module. Literal import(\"...\") specifiers found in the\n// component's scripts become real module imports, handed to Component79 as a\n// resolution map: at runtime $__import checks the map before falling back to\n// fetch, so bundled components ship with their imports and nothing changes\n// for unbundled ones. Claimed components import as their default (a\n// Component79, matching what runtime fetch resolves to); everything else as\n// a namespace (matching native import()).\n//\n// A file's <template name=\"…\"> components are re-exported by name, so the\n// module shape is the one the file already has: default plus named. They read\n// off the instance, which is where the runtime hangs them - so in dev they are\n// bound to the *first* evaluation's definitions and a module that imports one\n// by name keeps the pre-edit child until the page reloads. The file's own\n// component patches in place, and its rendered children come from the reparse,\n// so this only shows in a component imported by name from another file.\n//\n// In dev, `hot.data` carries the exported instance across updates: importers\n// hold a reference to the *first* module evaluation's instance, so later\n// evaluations patch that same instance in place instead of exporting a new one\n// nobody sees. The patching itself is the runtime's `hotReplace` - the same\n// swap the jq79/dev server drives, from the one place that can reach a\n// component's markers. An instance only used as a definition has nothing to\n// re-render (nested clones can't be reached from this module), so it falls\n// back to a full reload.\nconst componentModule = (source: string, include: RegExp, filename: string): string => {\n const hoisted = hoistableImports(source, include)\n const imports = hoisted\n .map((spec, i) =>\n include.test(spec)\n ? `import __jq79_${i} from ${JSON.stringify(spec)}`\n : `import * as __jq79_${i} from ${JSON.stringify(spec)}`\n )\n .join(\"\\n\")\n const modulesMap = `{ ${hoisted.map((spec, i) => `${JSON.stringify(spec)}: __jq79_${i}`).join(\", \")} }`\n\n return `\nimport { Component79 } from \"jq79\"\n${imports}\n\nconst src = ${JSON.stringify(source)}\nconst modules = ${modulesMap}\nconst filename = ${JSON.stringify(filename)}\n\nlet component\n\nif (import.meta.hot && import.meta.hot.data.component) {\n const prior = import.meta.hot.data.component\n prior.modules = modules\n prior.filename = filename\n // re-renders it where it stands, keeping its data. false means it was never\n // rendered - a definition used only as a nested component - and a reload is\n // the only way to reach the clones made from it\n if (!prior.hotReplace(src) && !prior.data) import.meta.hot.invalidate()\n component = prior\n} else {\n component = new Component79(src, { modules, filename })\n}\n\nif (import.meta.hot) {\n import.meta.hot.data.component = component\n import.meta.hot.accept()\n}\n\nexport default component\n${declaredComponents(source).map(name => `export const ${name} = component.${name}`).join(\"\\n\")}\n`\n}\n\nexport function jq79(options: Jq79PluginOptions = {}): Plugin {\n const include = options.include ?? /\\.html$/\n const { exclude } = options\n\n let config: ResolvedConfig | null = null\n\n return {\n name: \"jq79\",\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved\n },\n\n async resolveId(source, importer) {\n if (!importer) return null // entry points are never components\n if (source.includes(\"?\")) return null // ?raw, ?url, ... keep their meaning\n if (!include.test(source)) return null\n\n const resolved = await this.resolve(source, importer, { skipSelf: true })\n if (!resolved || resolved.external) return null\n if (exclude?.test(resolved.id)) return null\n return resolved.id + COMPONENT_QUERY\n },\n\n async load(id) {\n if (!id.endsWith(COMPONENT_QUERY)) return null\n const file = id.slice(0, -COMPONENT_QUERY.length)\n\n let source = await readFile(file, \"utf8\")\n if (config) source = await compileStyleBlocks(source, file, config, dep => this.addWatchFile(dep))\n\n // the runtime names the component's setup scripts after this, so devtools\n // shows a path the user recognizes instead of an anonymous VM script\n const filename = config ? relative(config.root, file) : file\n\n return { code: componentModule(source, include, filename), map: null }\n },\n }\n}\n\nexport default jq79\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AA8B9B,IAAM,kBAAkB;AAExB,IAAM,kBAAkB;AAGxB,IAAM,iBAAiB;AAIvB,IAAM,mBAAmB;AAEzB,IAAM,aAAa,CAAC,KAAa,UAA0B;AACzD,QAAM,QAAQ,IAAI,KAAK;AACvB,MAAI,IAAI,QAAQ;AAChB,SAAO,IAAI,IAAI,QAAQ;AACrB,QAAI,IAAI,CAAC,MAAM,MAAM;AAAE,WAAK;AAAG;AAAA,IAAS;AACxC,QAAI,IAAI,CAAC,MAAM,MAAO,QAAO,IAAI;AACjC;AAAA,EACF;AACA,SAAO,IAAI;AACb;AAMA,IAAM,mBAAmB,CAAC,WAA6B;AACrD,QAAM,QAAkB,CAAC;AACzB,MAAI,IAAI;AACR,SAAO,IAAI,OAAO,QAAQ;AACxB,UAAM,KAAK,OAAO,CAAC;AACnB,QAAI,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AAAE,UAAI,WAAW,QAAQ,CAAC;AAAG;AAAA,IAAS;AAClF,QAAI,OAAO,OAAO,OAAO,IAAI,CAAC,MAAM,KAAK;AACvC,YAAM,MAAM,OAAO,QAAQ,MAAM,CAAC;AAClC,UAAI,QAAQ,KAAK,OAAO,SAAS,MAAM;AACvC;AAAA,IACF;AACA,QAAI,OAAO,OAAO,OAAO,IAAI,CAAC,MAAM,KAAK;AACvC,YAAM,MAAM,OAAO,QAAQ,MAAM,IAAI,CAAC;AACtC,UAAI,QAAQ,KAAK,OAAO,SAAS,MAAM;AACvC;AAAA,IACF;AACA,QAAI,OAAO,QAAQ,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI;AAC5D,qBAAe,YAAY;AAC3B,YAAM,OAAO,eAAe,KAAK,MAAM;AACvC,UAAI,MAAM;AAAE,cAAM,KAAK,KAAK,CAAC,CAAC;AAAG,YAAI,eAAe;AAAW;AAAA,MAAS;AACxE,uBAAiB,YAAY;AAC7B,YAAM,eAAe,iBAAiB,KAAK,MAAM;AACjD,UAAI,cAAc;AAAE,cAAM,KAAK,aAAa,CAAC,CAAC;AAAG,YAAI,iBAAiB;AAAW;AAAA,MAAS;AAAA,IAC5F;AACA;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,YAAY,CAAC,SAAiB,kBAAkB,KAAK,IAAI;AAC/D,IAAM,gBAAgB,CAAC,SAAiB,uBAAuB,KAAK,IAAI,KAAK,KAAK,WAAW,GAAG;AAOhG,IAAM,mBAAmB,CAAC,QAAgB,YAA8B;AACtE,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,CAAC,EAAE,MAAM,KAAK,OAAO,SAAS,eAAe,GAAG;AACzD,eAAW,QAAQ,iBAAiB,MAAM,GAAG;AAC3C,UAAI,cAAc,IAAI,EAAG;AACzB,UAAI,UAAU,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAG;AAC5C,iBAAW,IAAI,IAAI;AAAA,IACrB;AAAA,EACF;AACA,SAAO,CAAC,GAAG,UAAU;AACvB;AAIA,IAAM,SAAS;AACf,IAAM,eAAe;AACrB,IAAM,oBAAoB;AAC1B,IAAM,gBAAgB,oBAAI,IAAI;AAAA,EAC5B;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAM;AAAA,EAAO;AAAA,EAAS;AAAA,EAAM;AAAA,EAAO;AAAA,EACnD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAC9C,CAAC;AAQD,IAAM,qBAAqB,CAAC,WAA6B;AACvD,QAAM,SAAS,OAAO,QAAQ,iBAAiB,EAAE,EAAE,QAAQ,gBAAgB,EAAE;AAC7E,QAAM,QAAkB,CAAC;AACzB,MAAI,QAAQ;AAEZ,aAAW,CAAC,EAAE,SAAS,KAAK,KAAK,KAAK,OAAO,SAAS,MAAM,GAAG;AAC7D,QAAI,SAAS;AACX,cAAQ,KAAK,IAAI,GAAG,QAAQ,CAAC;AAC7B;AAAA,IACF;AACA,UAAM,cAAc,SAAS,KAAK,KAAK,KAAK,cAAc,IAAI,IAAI,YAAY,CAAC;AAC/E,QAAI,UAAU,KAAK,CAAC,eAAe,IAAI,YAAY,MAAM,YAAY;AACnE,YAAM,WAAW,MAAM,MAAM,YAAY;AACzC,YAAM,OAAO,WAAW,CAAC,KAAK,WAAW,CAAC;AAE1C,UAAI,QAAQ,kBAAkB,KAAK,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,IAC3D;AACA,QAAI,CAAC,YAAa;AAAA,EACpB;AACA,SAAO;AACT;AAKA,IAAM,iBAAiB;AACvB,IAAM,eAAe;AAUrB,IAAM,qBAAqB,OACzB,QACA,MACA,QACA,iBACoB;AACpB,QAAM,SAAS,CAAC,GAAG,OAAO,SAAS,cAAc,CAAC;AAClD,QAAM,WAAW,MAAM,QAAQ;AAAA,IAC7B,OAAO,IAAI,OAAO,CAAC,EAAE,OAAO,OAAO,MAAM;AACvC,YAAM,OAAO,MAAM,MAAM,YAAY;AACrC,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,YAAY,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK,KAAK,CAAC;AAC9C,YAAM,SAAS,MAAM,cAAc,SAAS,GAAG,IAAI,IAAI,SAAS,IAAI,MAAM;AAC1E,aAAO,MAAM,QAAQ,YAAY;AACjC,aAAO,EAAE,OAAO,MAAM,QAAQ,cAAc,EAAE,EAAE,QAAQ,GAAG,KAAK,OAAO,KAAK;AAAA,IAC9E,CAAC;AAAA,EACH;AAEA,MAAI,MAAM;AACV,MAAI,OAAO;AACX,SAAO,QAAQ,CAAC,OAAO,MAAM;AAC3B,UAAM,OAAO,SAAS,CAAC;AACvB,QAAI,CAAC,KAAM;AACX,WAAO,OAAO,MAAM,MAAM,MAAM,KAAK,IAAI,SAAS,KAAK,KAAK,IAAI,KAAK,GAAG;AACxE,WAAO,MAAM,QAAQ,MAAM,CAAC,EAAE;AAAA,EAChC,CAAC;AACD,SAAO,MAAM,OAAO,MAAM,IAAI;AAChC;AA0BA,IAAM,kBAAkB,CAAC,QAAgB,SAAiB,aAA6B;AACrF,QAAM,UAAU,iBAAiB,QAAQ,OAAO;AAChD,QAAM,UAAU,QACb;AAAA,IAAI,CAAC,MAAM,MACV,QAAQ,KAAK,IAAI,IACb,iBAAiB,CAAC,SAAS,KAAK,UAAU,IAAI,CAAC,KAC/C,sBAAsB,CAAC,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA,EAC1D,EACC,KAAK,IAAI;AACZ,QAAM,aAAa,KAAK,QAAQ,IAAI,CAAC,MAAM,MAAM,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC;AAEnG,SAAO;AAAA;AAAA,EAEP,OAAO;AAAA;AAAA,cAEK,KAAK,UAAU,MAAM,CAAC;AAAA,kBAClB,UAAU;AAAA,mBACT,KAAK,UAAU,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBzC,mBAAmB,MAAM,EAAE,IAAI,UAAQ,gBAAgB,IAAI,gBAAgB,IAAI,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA;AAE/F;AAEO,SAAS,KAAK,UAA6B,CAAC,GAAW;AAC5D,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,EAAE,QAAQ,IAAI;AAEpB,MAAI,SAAgC;AAEpC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IAET,eAAe,UAAU;AACvB,eAAS;AAAA,IACX;AAAA,IAEA,MAAM,UAAU,QAAQ,UAAU;AAChC,UAAI,CAAC,SAAU,QAAO;AACtB,UAAI,OAAO,SAAS,GAAG,EAAG,QAAO;AACjC,UAAI,CAAC,QAAQ,KAAK,MAAM,EAAG,QAAO;AAElC,YAAM,WAAW,MAAM,KAAK,QAAQ,QAAQ,UAAU,EAAE,UAAU,KAAK,CAAC;AACxE,UAAI,CAAC,YAAY,SAAS,SAAU,QAAO;AAC3C,UAAI,SAAS,KAAK,SAAS,EAAE,EAAG,QAAO;AACvC,aAAO,SAAS,KAAK;AAAA,IACvB;AAAA,IAEA,MAAM,KAAK,IAAI;AACb,UAAI,CAAC,GAAG,SAAS,eAAe,EAAG,QAAO;AAC1C,YAAM,OAAO,GAAG,MAAM,GAAG,CAAC,gBAAgB,MAAM;AAEhD,UAAI,SAAS,MAAM,SAAS,MAAM,MAAM;AACxC,UAAI,OAAQ,UAAS,MAAM,mBAAmB,QAAQ,MAAM,QAAQ,SAAO,KAAK,aAAa,GAAG,CAAC;AAIjG,YAAM,WAAW,SAAS,SAAS,OAAO,MAAM,IAAI,IAAI;AAExD,aAAO,EAAE,MAAM,gBAAgB,QAAQ,SAAS,QAAQ,GAAG,KAAK,KAAK;AAAA,IACvE;AAAA,EACF;AACF;AAEA,IAAO,eAAQ;","names":[]}
1
+ {"version":3,"sources":["../dev/vite.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\"\nimport { relative } from \"node:path\"\nimport { preprocessCSS, transformWithEsbuild } from \"vite\"\nimport type { Plugin, ResolvedConfig } from \"vite\"\n\n// Vite plugin: import .html single-file components as modules.\n//\n// import { jq79 } from \"jq79/vite\" // vite.config\n// import UserCard from \"./UserCard.html\" // app code\n//\n// The imported value is a Component79 built from the file's source - the same\n// thing `await Component79.fetch(url)` resolves to, but bundled at build time\n// instead of fetched at runtime. The component source is inlined verbatim, so\n// a file keeps working unchanged if it's ever served from public/ and loaded\n// with fetch instead - with one deliberate exception, a block that says it is\n// written in something else: <style lang=\"scss\"> (or less/stylus/sass) is\n// compiled to plain CSS here, and a TypeScript script - <script lang=\"ts\">, or\n// the <script type=\"text/typescript\"> editors read as one - to plain JS. Such a\n// component only works through the bundler; loaded with fetch() it would reach\n// the runtime uncompiled, which the runtime warns about.\n//\n// Only .html files imported from other modules are claimed; entry points\n// (index.html) have no importer and imports carrying an explicit query\n// (?raw, ?url) keep their built-in Vite meaning.\n\nexport interface Jq79PluginOptions {\n // which import specifiers are treated as components (default: any .html)\n include?: RegExp\n // resolved absolute paths to skip even when `include` matches\n exclude?: RegExp\n}\n\n// claimed modules get this suffix so their id no longer ends in \".html\" and\n// Vite's own html handling (entries, asset pipeline) leaves them alone\nconst COMPONENT_QUERY = \"?jq79\"\n\n// a <script> block with its attribute string, so `lang` can be read and the\n// body replaced - the same shape as STYLE_BLOCK_RE below, quote-aware so a\n// \">\" inside an attribute value (`:setup=\"{ n = a > 1 }\"`) doesn't end the tag\nconst SCRIPT_BLOCK_RE = /<script((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([\\s\\S]*?)<\\/script\\s*>/gi\n// import(\"...\") with a literal specifier, tried at word boundaries the\n// scanner below reaches (which is what skips $__import and foo.import(...))\nconst IMPORT_CALL_RE = /import\\s*\\(\\s*([\"'])([^\"'\\n]+?)\\1\\s*\\)/y\n// static import statements (factory scripts): optional clause + literal\n// specifier. The clause can't contain parens/quotes, so dynamic import()\n// and import.meta never match\nconst STATIC_IMPORT_RE = /import\\s*(?:[\\w$\\s,{}*]+?\\s*from\\s*)?([\"'])([^\"'\\n]+)\\1/y\n\nconst skipString = (src: string, start: number): number => {\n const quote = src[start]\n let i = start + 1\n while (i < src.length) {\n if (src[i] === \"\\\\\") { i += 2; continue }\n if (src[i] === quote) return i + 1\n i++\n }\n return src.length\n}\n\n// the literal import specifiers in one script body. A scanner rather than a\n// bare matchAll, because a specifier mentioned in a comment or a string is\n// not an import: hoisting a commented-out `import(\"./old.html\")` would pull\n// dead files into the bundle - or break the build once the file is gone\nconst importSpecifiers = (script: string): string[] => {\n const specs: string[] = []\n let i = 0\n while (i < script.length) {\n const ch = script[i]\n if (ch === \"'\" || ch === '\"' || ch === \"`\") { i = skipString(script, i); continue }\n if (ch === \"/\" && script[i + 1] === \"/\") {\n const end = script.indexOf(\"\\n\", i)\n i = end === -1 ? script.length : end + 1\n continue\n }\n if (ch === \"/\" && script[i + 1] === \"*\") {\n const end = script.indexOf(\"*/\", i + 2)\n i = end === -1 ? script.length : end + 2\n continue\n }\n if (ch === \"i\" && (i === 0 || !/[\\w$.]/.test(script[i - 1]))) {\n IMPORT_CALL_RE.lastIndex = i\n const call = IMPORT_CALL_RE.exec(script)\n if (call) { specs.push(call[2]); i = IMPORT_CALL_RE.lastIndex; continue }\n STATIC_IMPORT_RE.lastIndex = i\n const staticImport = STATIC_IMPORT_RE.exec(script)\n if (staticImport) { specs.push(staticImport[2]); i = STATIC_IMPORT_RE.lastIndex; continue }\n }\n i++\n }\n return specs\n}\n\nconst isHtmlUrl = (spec: string) => /\\.html?([?#]|$)/.test(spec)\nconst isExternalUrl = (spec: string) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith(\"/\")\n\n// literal import specifiers in the component's script blocks - dynamic\n// `import(\"...\")` calls and static factory-script imports - that should\n// resolve from the bundle instead of at runtime. Absolute paths and full\n// URLs are left alone (they point at served files, e.g. public/), and so\n// are .html specifiers the plugin wouldn't claim as components\nconst hoistableImports = (source: string, include: RegExp): string[] => {\n const specifiers = new Set<string>()\n for (const [, , script] of source.matchAll(SCRIPT_BLOCK_RE)) {\n for (const spec of importSpecifiers(script)) {\n if (isExternalUrl(spec)) continue\n if (isHtmlUrl(spec) && !include.test(spec)) continue // html left to runtime fetch\n specifiers.add(spec) // a claimed component, a source file or an npm package\n }\n }\n return [...specifiers]\n}\n\n// any start or end tag, quote-aware so a \">\" inside an attribute value doesn't\n// end it early\nconst TAG_RE = /<(\\/?)([A-Za-z][\\w-]*)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>/g\nconst NAME_ATTR_RE = /\\bname\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i\nconst COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/\nconst VOID_ELEMENTS = new Set([\n \"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\",\n \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\",\n])\n\n// the components a file declares: its *top-level* <template name=\"…\"> blocks,\n// which the emitted module re-exports by name. Depth is tracked because only\n// the top level declares - a <template> nested in the markup is a plain inert\n// element the runtime leaves alone, and exporting it would name something that\n// never exists. Script and style bodies are cut out first, so a \"<\" in JS or a\n// selector can't be read as a tag\nconst declaredComponents = (source: string): string[] => {\n const markup = source.replace(SCRIPT_BLOCK_RE, \"\").replace(STYLE_BLOCK_RE, \"\")\n const names: string[] = []\n let depth = 0\n\n for (const [, closing, tag, attrs] of markup.matchAll(TAG_RE)) {\n if (closing) {\n depth = Math.max(0, depth - 1)\n continue\n }\n const selfClosing = /\\/\\s*$/.test(attrs) || VOID_ELEMENTS.has(tag.toLowerCase())\n if (depth === 0 && !selfClosing && tag.toLowerCase() === \"template\") {\n const declared = attrs.match(NAME_ATTR_RE)\n const name = declared?.[1] ?? declared?.[2]\n // the runtime warns about the ones this skips (nameless, not PascalCase)\n if (name && COMPONENT_NAME_RE.test(name)) names.push(name)\n }\n if (!selfClosing) depth++\n }\n return names\n}\n\n// a <style> block with its attribute string, so `lang` can be read and the\n// content replaced. Attribute values are matched as quoted chunks so a \">\"\n// inside one doesn't end the tag early\nconst STYLE_BLOCK_RE = /<style((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([\\s\\S]*?)<\\/style\\s*>/gi\nconst LANG_ATTR_RE = /\\blang\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i\nconst TYPE_ATTR_RE = /\\btype\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i\n\n// one attribute's value out of a tag's attribute string, whichever way it was\n// quoted - null when the attribute isn't there at all\nconst attrValue = (attrs: string, re: RegExp): string | null => {\n const found = attrs.match(re)\n return found ? found[1] ?? found[2] ?? found[3] : null\n}\n\n// compiles <style lang=\"scss|less|styl|sass\"> blocks to plain CSS with Vite's\n// own preprocessing (the same call @vitejs/plugin-vue makes), so the runtime\n// only ever sees CSS. The preprocessor picks its parser from the extension,\n// and resolving relative @use/@import against a filename in the component's\n// own directory is what makes `@use \"./vars\"` work. Files the preprocessor\n// pulls in are registered as watch deps, so editing a partial re-runs HMR for\n// every component that uses it. `lang` is dropped from the emitted tag: what\n// the runtime parses is a plain <style> (with `scoped` and the rest intact)\nconst compileStyleBlocks = async (\n source: string,\n file: string,\n config: ResolvedConfig,\n addWatchFile: (id: string) => void\n): Promise<string> => {\n const blocks = [...source.matchAll(STYLE_BLOCK_RE)]\n const compiled = await Promise.all(\n blocks.map(async ([, attrs, content]) => {\n const extension = attrValue(attrs, LANG_ATTR_RE)\n if (extension === null) return null\n const result = await preprocessCSS(content, `${file}.${extension}`, config)\n result.deps?.forEach(addWatchFile)\n return { attrs: attrs.replace(LANG_ATTR_RE, \"\").trimEnd(), css: result.code }\n })\n )\n\n let out = \"\"\n let last = 0\n blocks.forEach((block, i) => {\n const done = compiled[i]\n if (!done) return\n out += source.slice(last, block.index) + `<style${done.attrs}>${done.css}</style>`\n last = block.index + block[0].length\n })\n return out + source.slice(last)\n}\n\n// languages a <script lang> is compiled from. Anything else is left as written\n// for the runtime to warn about, rather than guessed at\nconst TS_LANGS = new Set([\"ts\", \"typescript\"])\n// the other spelling of the same mark, and the one editors read: a component is\n// a plain .html file, not an SFC, so nothing in an IDE knows what `lang` means\n// there - embedded-script tooling picks a language from `type`, and a typed\n// block without one is linted as JavaScript. The `x-` forms are the historical\n// spelling of the same two media types\nconst TS_TYPE_RE = /^(?:text|application)\\/(?:x-)?typescript$/\n\n// what marks a script block as TypeScript, given back as the attribute to drop\n// from the emitted tag - the block is compiled to JS, so the mark goes with the\n// types whichever one carried it. A `lang` answers on its own: `lang=\"coffee\"`\n// is a language this plugin doesn't compile, and reading `type` past it would\n// compile a block the author said was something else\nconst typescriptAttr = (attrs: string): RegExp | null => {\n const lang = attrValue(attrs, LANG_ATTR_RE)\n if (lang !== null) return TS_LANGS.has(lang.trim().toLowerCase()) ? LANG_ATTR_RE : null\n const type = attrValue(attrs, TYPE_ATTR_RE)\n return type !== null && TS_TYPE_RE.test(type.trim().toLowerCase()) ? TYPE_ATTR_RE : null\n}\n\ntype ViteTransform = (code: string, id: string, options?: unknown) => Promise<{ code: string }>\n\n// what strips the types: vite's own transform, so the plugin carries no\n// compiler of its own. *Which* transform is a version question, and neither\n// answer covers the peer range (vite >= 5) alone - transformWithOxc is the one\n// vite is moving to but only exists from vite 7, while transformWithEsbuild is\n// deprecated under vite 8 and throws there unless esbuild is installed\n// separately. So it is looked up at call time: oxc where it exists, esbuild on\n// the older versions that ship it.\n//\n// Both drop unused value imports by default, because a TS transform can't tell\n// a type-only import from an unused one. That would be silent damage here: a\n// factory script's `import Row from \"./row.html\"` would vanish, taking with it\n// the specifier hoistableImports needs to pull the child into the bundle. The\n// flags below are what keep it - only `import type` is erased\nconst stripTypes = async (ts: string, file: string): Promise<string> => {\n const vite = (await import(\"vite\")) as unknown as { transformWithOxc?: ViteTransform }\n const { code } = vite.transformWithOxc\n ? await vite.transformWithOxc(ts, file, { lang: \"ts\", typescript: { onlyRemoveTypeImports: true } })\n : await transformWithEsbuild(ts, file, {\n loader: \"ts\",\n tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } },\n })\n // a script whose only imports were `import type` comes back marked as a\n // module. `export {}` exports nothing, and it is a SyntaxError inside the\n // Function body the runtime compiles a script into\n return code.replace(/^[ \\t]*export\\s*\\{\\s*\\}\\s*;?[ \\t]*$/m, \"\")\n}\n\n// the `:setup` attribute, when it carries a value (a bare `:setup` is the\n// closed signature and can't be typed)\nconst SETUP_ATTR_RE = /(:setup\\s*=\\s*)(?:\"([^\"]*)\"|'([^']*)')/i\n// the arrow body the signature is wrapped in, so the parameter list can be\n// found again in the output without matching brackets\nconst SIGNATURE_MARKER = \"__jq79_signature__\"\n\n// a component's props signature lives in the `:setup` *attribute*, not in the\n// script body, so the body's transform never sees it - and the TypeScript mark\n// has to mean the same thing on both halves of the block, or a typed signature\n// either survives into a component the plugin just promised was JS or, for\n// `_: Props`, stops reading as a signature at all.\n//\n// It goes through the same transform as everything else, wrapped as a\n// parameter list, rather than being cut with a scanner of its own: the\n// annotation can sit on the pattern (`{ a }: Props`), on the permissive `_`, or\n// inside a default (`{ step = 1 as number }`, which the runtime would evaluate\n// and silently drop), and telling those apart is a parser's job. Both transforms\n// hand back `(<params>) => <marker>` on one line, parens intact\nconst stripSignatureTypes = async (value: string, file: string): Promise<string> => {\n const compiled = await stripTypes(`(${value}) => ${SIGNATURE_MARKER}`, file)\n const marker = compiled.indexOf(SIGNATURE_MARKER)\n if (marker === -1) return value // not the shape expected: leave it as written\n const head = compiled.slice(0, marker).trimEnd()\n const params = (head.endsWith(\"=>\") ? head.slice(0, -2) : head).trim()\n return params.startsWith(\"(\") && params.endsWith(\")\") ? params.slice(1, -1).trim() : params\n}\n\n// the signature back into a double-quoted attribute. Only `\"` needs escaping:\n// the transforms normalize string literals to double quotes, so a default the\n// author wrote as `'x'` comes back as `\"x\"` and would end the attribute early.\n// `&` is deliberately left alone - `&&` in a default is not an entity and\n// survives the parse, while escaping it would double-encode a source that\n// already wrote `&quot;`\nconst quoteAttrValue = (value: string) => value.replace(/\"/g, \"&quot;\")\n\n// compiles TypeScript script blocks to plain JS, so the runtime only ever sees\n// JS - the same deal <style lang=\"scss\"> gets, for a sharper reason. The setup\n// scanner is not a parser: `let count: number = 0` reaching it is not a syntax\n// error but a labeled statement that assigns to `number`, so it runs and leaves\n// `count` undeclared. Whichever attribute marked the block (`lang=\"ts\"` or\n// `type=\"text/typescript\"`) is dropped from the emitted tag and every other one\n// (`:setup`, `:mounted`) is left as written.\n//\n// This runs before hoistableImports reads the source, so an `import type`\n// specifier is already gone by the time the plugin decides what to bundle\nconst compileScriptBlocks = async (source: string, file: string): Promise<string> => {\n const blocks = [...source.matchAll(SCRIPT_BLOCK_RE)]\n const compiled = await Promise.all(\n blocks.map(async ([, attrs, content]) => {\n const marker = typescriptAttr(attrs)\n if (!marker) return null\n\n let rest = attrs.replace(marker, \"\").trimEnd()\n const setup = rest.match(SETUP_ATTR_RE)\n if (setup) {\n const signature = await stripSignatureTypes(setup[2] ?? setup[3], `${file}.signature.ts`)\n // a function replacement, not a string: `$&` and friends are live in a\n // replacement string and a default value is arbitrary source\n rest = rest.replace(SETUP_ATTR_RE, () => `${setup[1]}\"${quoteAttrValue(signature)}\"`)\n }\n\n return { attrs: rest, js: await stripTypes(content, `${file}.ts`) }\n })\n )\n\n let out = \"\"\n let last = 0\n blocks.forEach((block, i) => {\n const done = compiled[i]\n if (!done) return\n out += source.slice(last, block.index) + `<script${done.attrs}>${done.js}</script>`\n last = block.index + block[0].length\n })\n return out + source.slice(last)\n}\n\n// the emitted module. Literal import(\"...\") specifiers found in the\n// component's scripts become real module imports, handed to Component79 as a\n// resolution map: at runtime $__import checks the map before falling back to\n// fetch, so bundled components ship with their imports and nothing changes\n// for unbundled ones. Claimed components import as their default (a\n// Component79, matching what runtime fetch resolves to); everything else as\n// a namespace (matching native import()).\n//\n// A file's <template name=\"…\"> components are re-exported by name, so the\n// module shape is the one the file already has: default plus named. They read\n// off the instance, which is where the runtime hangs them - so in dev they are\n// bound to the *first* evaluation's definitions and a module that imports one\n// by name keeps the pre-edit child until the page reloads. The file's own\n// component patches in place, and its rendered children come from the reparse,\n// so this only shows in a component imported by name from another file.\n//\n// In dev, `hot.data` carries the exported instance across updates: importers\n// hold a reference to the *first* module evaluation's instance, so later\n// evaluations patch that same instance in place instead of exporting a new one\n// nobody sees. The patching itself is the runtime's `hotReplace` - the same\n// swap the jq79/dev server drives, from the one place that can reach a\n// component's markers. An instance only used as a definition has nothing to\n// re-render (nested clones can't be reached from this module), so it falls\n// back to a full reload.\nconst componentModule = (source: string, include: RegExp, filename: string): string => {\n const hoisted = hoistableImports(source, include)\n const imports = hoisted\n .map((spec, i) =>\n include.test(spec)\n ? `import __jq79_${i} from ${JSON.stringify(spec)}`\n : `import * as __jq79_${i} from ${JSON.stringify(spec)}`\n )\n .join(\"\\n\")\n const modulesMap = `{ ${hoisted.map((spec, i) => `${JSON.stringify(spec)}: __jq79_${i}`).join(\", \")} }`\n\n return `\nimport { Component79 } from \"jq79\"\n${imports}\n\nconst src = ${JSON.stringify(source)}\nconst modules = ${modulesMap}\nconst filename = ${JSON.stringify(filename)}\n\nlet component\n\nif (import.meta.hot && import.meta.hot.data.component) {\n const prior = import.meta.hot.data.component\n prior.modules = modules\n prior.filename = filename\n // re-renders it where it stands, keeping its data. false means it was never\n // rendered - a definition used only as a nested component - and a reload is\n // the only way to reach the clones made from it\n if (!prior.hotReplace(src) && !prior.data) import.meta.hot.invalidate()\n component = prior\n} else {\n component = new Component79(src, { modules, filename })\n}\n\nif (import.meta.hot) {\n import.meta.hot.data.component = component\n import.meta.hot.accept()\n}\n\nexport default component\n${declaredComponents(source).map(name => `export const ${name} = component.${name}`).join(\"\\n\")}\n`\n}\n\nexport function jq79(options: Jq79PluginOptions = {}): Plugin {\n const include = options.include ?? /\\.html$/\n const { exclude } = options\n\n let config: ResolvedConfig | null = null\n\n return {\n name: \"jq79\",\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved\n },\n\n async resolveId(source, importer) {\n if (!importer) return null // entry points are never components\n if (source.includes(\"?\")) return null // ?raw, ?url, ... keep their meaning\n if (!include.test(source)) return null\n\n const resolved = await this.resolve(source, importer, { skipSelf: true })\n if (!resolved || resolved.external) return null\n if (exclude?.test(resolved.id)) return null\n return resolved.id + COMPONENT_QUERY\n },\n\n async load(id) {\n if (!id.endsWith(COMPONENT_QUERY)) return null\n const file = id.slice(0, -COMPONENT_QUERY.length)\n\n let source = await readFile(file, \"utf8\")\n source = await compileScriptBlocks(source, file)\n if (config) source = await compileStyleBlocks(source, file, config, dep => this.addWatchFile(dep))\n\n // the runtime names the component's setup scripts after this, so devtools\n // shows a path the user recognizes instead of an anonymous VM script\n const filename = config ? relative(config.root, file) : file\n\n return { code: componentModule(source, include, filename), map: null }\n },\n }\n}\n\nexport default jq79\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jq79",
3
- "version": "0.7.0",
4
- "description": "Mini reactive component library: single-file components, Svelte-style setup scripts, fine-grained proxy reactivity. Single-file build, zero dependencies.",
3
+ "version": "0.7.2",
4
+ "description": "Mini reactive component library: single-file .html components, Svelte-style setup scripts, fine-grained proxy reactivity. No compiler, no bundler, no dependencies — works from any static host.",
5
5
  "keywords": [
6
6
  "reactive",
7
7
  "components",
package/src/jq79.ts CHANGED
@@ -626,9 +626,9 @@ const plainScopes = new WeakSet<object>()
626
626
  // opened and closed by hand rather than by a wrapper taking a callback: a
627
627
  // component that renders itself through :each stacks one renderEach per level,
628
628
  // and a callback would add a frame to each of them. The cyclic-component test
629
- // cuts off at 200 levels, and on a CI runner that extra frame per level was the
630
- // difference between cutting off and a RangeError - the same reason $effect
631
- // keeps its own shape (see reactive.ts)
629
+ // cuts off at MAX_NESTING_DEPTH levels, and on a CI runner that frame per level
630
+ // was the difference between cutting off and a RangeError - the same reason
631
+ // $effect keeps its own shape (see reactive.ts)
632
632
  type RenderPass = { memo: Map<string, string | null> | null; base: object | null }
633
633
 
634
634
  const openRenderPass = (base: Record<string, any>): RenderPass => {
@@ -714,8 +714,15 @@ const unresolvedComponent = (tag: string, scope: Record<string, any>): Error =>
714
714
 
715
715
  // how deep a component may nest inside itself before the runtime calls it a
716
716
  // cycle. Deeper than any real tree, shallower than the JS stack: a truncated
717
- // render with an error on the console beats a stack overflow with none
718
- const MAX_NESTING_DEPTH = 200
717
+ // render with an error on the console beats a stack overflow with none.
718
+ //
719
+ // It was 200, which is roughly where the stack actually gives out - measured at
720
+ // ~196 levels for the cyclic-data test on macOS arm64 - so the guard lost to the
721
+ // stack whenever a frame on the render path grew, and it did, three times. The
722
+ // ceiling is a property of the host (arm64 vs x64, worker vs main thread), so no
723
+ // number near it can be right everywhere; this one is half of the one ceiling
724
+ // that was measured. See RECORD/2026-09-06.the-depth-guard-lost-its-margin.md
725
+ const MAX_NESTING_DEPTH = 100
719
726
  let nestingDepth = 0
720
727
 
721
728
  // ---------------------------------------------------------------------------
@@ -2633,6 +2640,20 @@ const warnDirectiveTypo = (node: TemplateNode) => {
2633
2640
  for (const attr in node.attrs) {
2634
2641
  if (!attr.startsWith(":") || isControlAttr(attr) || attr === ":model" || attr.startsWith(":model.")) continue
2635
2642
  const name = attr.slice(1)
2643
+ // every dotted `:` name that means something was claimed by the line above -
2644
+ // :class. :props. :slot. :html.allowed :model. - so what reaches here with a
2645
+ // dot in it is a modifier list on the wrong sigil: @submit.prevent written
2646
+ // with a colon, which binds an attribute called "submit.prevent" holding the
2647
+ // handler's source text and leaves the form submitting natively. The dot is
2648
+ // structural, not a table of event names to keep in step with the platform;
2649
+ // plannableAttr already computes it, for a different purpose
2650
+ if (name.includes(".")) {
2651
+ console.warn(
2652
+ `jq79: ${attr} is not a directive - it bound an attribute named "${name}". ` +
2653
+ `Dotted modifiers belong to an event: if you meant @${name}, that is the spelling`
2654
+ )
2655
+ continue
2656
+ }
2636
2657
  const directive = DIRECTIVE_NAMES.find(known => name !== known && name.startsWith(known))
2637
2658
  if (directive === undefined) continue
2638
2659
  console.warn(
@@ -3265,6 +3286,19 @@ const parseComponentString = (component: string): ComponentParts => {
3265
3286
  return parts
3266
3287
  }
3267
3288
 
3289
+ // the media types an editor reads as TypeScript. A component is a plain .html
3290
+ // file, not an SFC, so no IDE knows what `lang` means inside one - embedded
3291
+ // script tooling picks a language from `type` - and the plugin compiles a block
3292
+ // marked either way. Either mark still here means the same thing
3293
+ const TS_SCRIPT_TYPE_RE = /^(?:text|application)\/(?:x-)?typescript$/i
3294
+
3295
+ // the mark a script is still carrying that says the plugin never compiled it,
3296
+ // spelled as the author spelled it
3297
+ const uncompiledScriptMark = (attrs: Record<string, string>): string | null =>
3298
+ "lang" in attrs ? `lang="${attrs.lang}"`
3299
+ : TS_SCRIPT_TYPE_RE.test(attrs.type?.trim() ?? "") ? `type="${attrs.type}"`
3300
+ : null
3301
+
3268
3302
  // the script/style/markup split of one component's top-level elements, with
3269
3303
  // <style scoped> resolved against the source those elements came from - the
3270
3304
  // whole file for its own component, a <template>'s contents for a named one,
@@ -3282,6 +3316,23 @@ const componentPartsFrom = (elements: Element[], hashSource: string): ComponentP
3282
3316
  else template.push(elementToAST(el))
3283
3317
  })
3284
3318
 
3319
+ // a TypeScript script is compiled by the jq79/vite plugin, like <style lang>
3320
+ // below, and a mark still here means the same thing: this component never
3321
+ // went through the bundler. It matters more on a script, because the failure
3322
+ // is not always loud - `interface`/`as`/generics throw at compile time, but
3323
+ // `let x: T = v` is a valid labeled statement, so it runs and leaves x
3324
+ // undeclared and non-reactive with nothing in the console
3325
+ scripts.forEach(script => {
3326
+ const mark = uncompiledScriptMark(script.attrs)
3327
+ if (mark) {
3328
+ console.warn(
3329
+ `jq79: <script ${mark}> needs the jq79/vite plugin to compile it. ` +
3330
+ "This component didn't go through the bundler, so its types were never stripped: the script " +
3331
+ "will throw, or - for a plain `let x: T = ...` - silently fail to declare x."
3332
+ )
3333
+ }
3334
+ })
3335
+
3285
3336
  // <style lang="scss"> is compiled by the jq79/vite plugin, so a `lang` still
3286
3337
  // here means this component never went through it - it was fetched, loaded
3287
3338
  // from a URL, or built from an inline string. The browser would drop the