jq79 0.7.1 → 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/dev/vite.ts +45 -19
- package/dist/jq79.cjs +8 -8
- package/dist/jq79.cjs.map +1 -1
- package/dist/jq79.global.js +8 -8
- package/dist/jq79.global.js.map +1 -1
- package/dist/jq79.js +10 -10
- package/dist/jq79.js.map +1 -1
- package/dist/vite.cjs +17 -7
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.js +17 -7
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/jq79.ts +30 -9
package/dist/vite.cjs
CHANGED
|
@@ -145,13 +145,17 @@ var declaredComponents = (source) => {
|
|
|
145
145
|
};
|
|
146
146
|
var STYLE_BLOCK_RE = /<style((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/style\s*>/gi;
|
|
147
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
|
+
};
|
|
148
153
|
var compileStyleBlocks = async (source, file, config, addWatchFile) => {
|
|
149
154
|
const blocks = [...source.matchAll(STYLE_BLOCK_RE)];
|
|
150
155
|
const compiled = await Promise.all(
|
|
151
156
|
blocks.map(async ([, attrs, content]) => {
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
const extension = lang[1] ?? lang[2] ?? lang[3];
|
|
157
|
+
const extension = attrValue(attrs, LANG_ATTR_RE);
|
|
158
|
+
if (extension === null) return null;
|
|
155
159
|
const result = await (0, import_vite.preprocessCSS)(content, `${file}.${extension}`, config);
|
|
156
160
|
result.deps?.forEach(addWatchFile);
|
|
157
161
|
return { attrs: attrs.replace(LANG_ATTR_RE, "").trimEnd(), css: result.code };
|
|
@@ -168,6 +172,13 @@ var compileStyleBlocks = async (source, file, config, addWatchFile) => {
|
|
|
168
172
|
return out + source.slice(last);
|
|
169
173
|
};
|
|
170
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
|
+
};
|
|
171
182
|
var stripTypes = async (ts, file) => {
|
|
172
183
|
const vite = await import("vite");
|
|
173
184
|
const { code } = vite.transformWithOxc ? await vite.transformWithOxc(ts, file, { lang: "ts", typescript: { onlyRemoveTypeImports: true } }) : await (0, import_vite.transformWithEsbuild)(ts, file, {
|
|
@@ -191,10 +202,9 @@ var compileScriptBlocks = async (source, file) => {
|
|
|
191
202
|
const blocks = [...source.matchAll(SCRIPT_BLOCK_RE)];
|
|
192
203
|
const compiled = await Promise.all(
|
|
193
204
|
blocks.map(async ([, attrs, content]) => {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
let rest = attrs.replace(LANG_ATTR_RE, "").trimEnd();
|
|
205
|
+
const marker = typescriptAttr(attrs);
|
|
206
|
+
if (!marker) return null;
|
|
207
|
+
let rest = attrs.replace(marker, "").trimEnd();
|
|
198
208
|
const setup = rest.match(SETUP_ATTR_RE);
|
|
199
209
|
if (setup) {
|
|
200
210
|
const signature = await stripSignatureTypes(setup[2] ?? setup[3], `${file}.signature.ts`);
|
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, 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, `lang`: <style lang=\"scss\">\n// (or less/stylus/sass) is compiled to plain CSS here, and <script lang=\"ts\"> to\n// plain JS. A component using `lang` therefore only works through the bundler;\n// loaded with fetch() it would reach the runtime uncompiled, which the runtime\n// 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\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// 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\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 `lang=\"ts\"` has to\n// mean the same thing on both halves of the block, or a typed signature either\n// survives into a component the plugin just promised was JS or, for `_: Props`,\n// 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 `"`\nconst quoteAttrValue = (value: string) => value.replace(/\"/g, \""\")\n\n// compiles <script lang=\"ts\"> 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. `lang` is dropped from the emitted tag and every other\n// attribute (`: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 lang = attrs.match(LANG_ATTR_RE)\n const name = (lang?.[1] ?? lang?.[2] ?? lang?.[3])?.toLowerCase()\n if (!name || !TS_LANGS.has(name)) return null\n\n let rest = attrs.replace(LANG_ATTR_RE, \"\").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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAyB;AACzB,uBAAyB;AACzB,kBAAoD;AA+BpD,IAAM,kBAAkB;AAKxB,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,EAAE,MAAM,KAAK,OAAO,SAAS,eAAe,GAAG;AAC3D,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,UAAM,2BAAc,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;AAIA,IAAM,WAAW,oBAAI,IAAI,CAAC,MAAM,YAAY,CAAC;AAiB7C,IAAM,aAAa,OAAO,IAAY,SAAkC;AACtE,QAAM,OAAQ,MAAM,OAAO,MAAM;AACjC,QAAM,EAAE,KAAK,IAAI,KAAK,mBAClB,MAAM,KAAK,iBAAiB,IAAI,MAAM,EAAE,MAAM,MAAM,YAAY,EAAE,uBAAuB,KAAK,EAAE,CAAC,IACjG,UAAM,kCAAqB,IAAI,MAAM;AAAA,IACrC,QAAQ;AAAA,IACR,aAAa,EAAE,iBAAiB,EAAE,sBAAsB,KAAK,EAAE;AAAA,EACjE,CAAC;AAIH,SAAO,KAAK,QAAQ,wCAAwC,EAAE;AAChE;AAIA,IAAM,gBAAgB;AAGtB,IAAM,mBAAmB;AAczB,IAAM,sBAAsB,OAAO,OAAe,SAAkC;AAClF,QAAM,WAAW,MAAM,WAAW,IAAI,KAAK,QAAQ,gBAAgB,IAAI,IAAI;AAC3E,QAAM,SAAS,SAAS,QAAQ,gBAAgB;AAChD,MAAI,WAAW,GAAI,QAAO;AAC1B,QAAM,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,QAAQ;AAC/C,QAAM,UAAU,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,MAAM,KAAK;AACrE,SAAO,OAAO,WAAW,GAAG,KAAK,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI;AACvF;AAQA,IAAM,iBAAiB,CAAC,UAAkB,MAAM,QAAQ,MAAM,QAAQ;AAWtE,IAAM,sBAAsB,OAAO,QAAgB,SAAkC;AACnF,QAAM,SAAS,CAAC,GAAG,OAAO,SAAS,eAAe,CAAC;AACnD,QAAM,WAAW,MAAM,QAAQ;AAAA,IAC7B,OAAO,IAAI,OAAO,CAAC,EAAE,OAAO,OAAO,MAAM;AACvC,YAAM,OAAO,MAAM,MAAM,YAAY;AACrC,YAAM,QAAQ,OAAO,CAAC,KAAK,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,YAAY;AAChE,UAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,IAAI,EAAG,QAAO;AAEzC,UAAI,OAAO,MAAM,QAAQ,cAAc,EAAE,EAAE,QAAQ;AACnD,YAAM,QAAQ,KAAK,MAAM,aAAa;AACtC,UAAI,OAAO;AACT,cAAM,YAAY,MAAM,oBAAoB,MAAM,CAAC,KAAK,MAAM,CAAC,GAAG,GAAG,IAAI,eAAe;AAGxF,eAAO,KAAK,QAAQ,eAAe,MAAM,GAAG,MAAM,CAAC,CAAC,IAAI,eAAe,SAAS,CAAC,GAAG;AAAA,MACtF;AAEA,aAAO,EAAE,OAAO,MAAM,IAAI,MAAM,WAAW,SAAS,GAAG,IAAI,KAAK,EAAE;AAAA,IACpE,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,UAAU,KAAK,KAAK,IAAI,KAAK,EAAE;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,UAAM,0BAAS,MAAM,MAAM;AACxC,eAAS,MAAM,oBAAoB,QAAQ,IAAI;AAC/C,UAAI,OAAQ,UAAS,MAAM,mBAAmB,QAAQ,MAAM,QAAQ,SAAO,KAAK,aAAa,GAAG,CAAC;AAIjG,YAAM,WAAW,aAAS,2BAAS,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 `"`\nconst quoteAttrValue = (value: string) => value.replace(/\"/g, \""\")\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
|
@@ -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
|
|
119
|
-
if (
|
|
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 };
|
|
@@ -134,6 +138,13 @@ var compileStyleBlocks = async (source, file, config, addWatchFile) => {
|
|
|
134
138
|
return out + source.slice(last);
|
|
135
139
|
};
|
|
136
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
|
+
};
|
|
137
148
|
var stripTypes = async (ts, file) => {
|
|
138
149
|
const vite = await import("vite");
|
|
139
150
|
const { code } = vite.transformWithOxc ? await vite.transformWithOxc(ts, file, { lang: "ts", typescript: { onlyRemoveTypeImports: true } }) : await transformWithEsbuild(ts, file, {
|
|
@@ -157,10 +168,9 @@ var compileScriptBlocks = async (source, file) => {
|
|
|
157
168
|
const blocks = [...source.matchAll(SCRIPT_BLOCK_RE)];
|
|
158
169
|
const compiled = await Promise.all(
|
|
159
170
|
blocks.map(async ([, attrs, content]) => {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
let rest = attrs.replace(LANG_ATTR_RE, "").trimEnd();
|
|
171
|
+
const marker = typescriptAttr(attrs);
|
|
172
|
+
if (!marker) return null;
|
|
173
|
+
let rest = attrs.replace(marker, "").trimEnd();
|
|
164
174
|
const setup = rest.match(SETUP_ATTR_RE);
|
|
165
175
|
if (setup) {
|
|
166
176
|
const signature = await stripSignatureTypes(setup[2] ?? setup[3], `${file}.signature.ts`);
|
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, 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, `lang`: <style lang=\"scss\">\n// (or less/stylus/sass) is compiled to plain CSS here, and <script lang=\"ts\"> to\n// plain JS. A component using `lang` therefore only works through the bundler;\n// loaded with fetch() it would reach the runtime uncompiled, which the runtime\n// 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\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// 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\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 `lang=\"ts\"` has to\n// mean the same thing on both halves of the block, or a typed signature either\n// survives into a component the plugin just promised was JS or, for `_: Props`,\n// 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 `"`\nconst quoteAttrValue = (value: string) => value.replace(/\"/g, \""\")\n\n// compiles <script lang=\"ts\"> 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. `lang` is dropped from the emitted tag and every other\n// attribute (`: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 lang = attrs.match(LANG_ATTR_RE)\n const name = (lang?.[1] ?? lang?.[2] ?? lang?.[3])?.toLowerCase()\n if (!name || !TS_LANGS.has(name)) return null\n\n let rest = attrs.replace(LANG_ATTR_RE, \"\").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":[]}
|
|
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 `"`\nconst quoteAttrValue = (value: string) => value.replace(/\"/g, \""\")\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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jq79",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
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",
|
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
|
|
630
|
-
// difference between cutting off and a RangeError - the same reason
|
|
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
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -3279,6 +3286,19 @@ const parseComponentString = (component: string): ComponentParts => {
|
|
|
3279
3286
|
return parts
|
|
3280
3287
|
}
|
|
3281
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
|
+
|
|
3282
3302
|
// the script/style/markup split of one component's top-level elements, with
|
|
3283
3303
|
// <style scoped> resolved against the source those elements came from - the
|
|
3284
3304
|
// whole file for its own component, a <template>'s contents for a named one,
|
|
@@ -3296,16 +3316,17 @@ const componentPartsFrom = (elements: Element[], hashSource: string): ComponentP
|
|
|
3296
3316
|
else template.push(elementToAST(el))
|
|
3297
3317
|
})
|
|
3298
3318
|
|
|
3299
|
-
//
|
|
3300
|
-
// below, and a
|
|
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
|
|
3301
3321
|
// went through the bundler. It matters more on a script, because the failure
|
|
3302
3322
|
// is not always loud - `interface`/`as`/generics throw at compile time, but
|
|
3303
3323
|
// `let x: T = v` is a valid labeled statement, so it runs and leaves x
|
|
3304
3324
|
// undeclared and non-reactive with nothing in the console
|
|
3305
3325
|
scripts.forEach(script => {
|
|
3306
|
-
|
|
3326
|
+
const mark = uncompiledScriptMark(script.attrs)
|
|
3327
|
+
if (mark) {
|
|
3307
3328
|
console.warn(
|
|
3308
|
-
`jq79: <script
|
|
3329
|
+
`jq79: <script ${mark}> needs the jq79/vite plugin to compile it. ` +
|
|
3309
3330
|
"This component didn't go through the bundler, so its types were never stripped: the script " +
|
|
3310
3331
|
"will throw, or - for a plain `let x: T = ...` - silently fail to declare x."
|
|
3311
3332
|
)
|