jq79 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -6
- package/assets/blur-style-cover.svg +74 -0
- package/dev/vite.ts +122 -7
- 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 +8 -8
- package/dist/jq79.js.map +1 -1
- package/dist/vite.cjs +59 -2
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.js +50 -3
- package/dist/vite.js.map +1 -1
- package/package.json +2 -2
- package/src/jq79.ts +30 -0
package/dist/vite.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
1
2
|
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
7
|
var __export = (target, all) => {
|
|
6
8
|
for (var name in all)
|
|
@@ -14,6 +16,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
14
16
|
}
|
|
15
17
|
return to;
|
|
16
18
|
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
17
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
28
|
|
|
19
29
|
// dev/vite.ts
|
|
@@ -27,7 +37,7 @@ var import_promises = require("fs/promises");
|
|
|
27
37
|
var import_node_path = require("path");
|
|
28
38
|
var import_vite = require("vite");
|
|
29
39
|
var COMPONENT_QUERY = "?jq79";
|
|
30
|
-
var SCRIPT_BLOCK_RE = /<script
|
|
40
|
+
var SCRIPT_BLOCK_RE = /<script((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/script\s*>/gi;
|
|
31
41
|
var IMPORT_CALL_RE = /import\s*\(\s*(["'])([^"'\n]+?)\1\s*\)/y;
|
|
32
42
|
var STATIC_IMPORT_RE = /import\s*(?:[\w$\s,{}*]+?\s*from\s*)?(["'])([^"'\n]+)\1/y;
|
|
33
43
|
var skipString = (src, start) => {
|
|
@@ -86,7 +96,7 @@ var isHtmlUrl = (spec) => /\.html?([?#]|$)/.test(spec);
|
|
|
86
96
|
var isExternalUrl = (spec) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith("/");
|
|
87
97
|
var hoistableImports = (source, include) => {
|
|
88
98
|
const specifiers = /* @__PURE__ */ new Set();
|
|
89
|
-
for (const [, script] of source.matchAll(SCRIPT_BLOCK_RE)) {
|
|
99
|
+
for (const [, , script] of source.matchAll(SCRIPT_BLOCK_RE)) {
|
|
90
100
|
for (const spec of importSpecifiers(script)) {
|
|
91
101
|
if (isExternalUrl(spec)) continue;
|
|
92
102
|
if (isHtmlUrl(spec) && !include.test(spec)) continue;
|
|
@@ -157,6 +167,52 @@ var compileStyleBlocks = async (source, file, config, addWatchFile) => {
|
|
|
157
167
|
});
|
|
158
168
|
return out + source.slice(last);
|
|
159
169
|
};
|
|
170
|
+
var TS_LANGS = /* @__PURE__ */ new Set(["ts", "typescript"]);
|
|
171
|
+
var stripTypes = async (ts, file) => {
|
|
172
|
+
const vite = await import("vite");
|
|
173
|
+
const { code } = vite.transformWithOxc ? await vite.transformWithOxc(ts, file, { lang: "ts", typescript: { onlyRemoveTypeImports: true } }) : await (0, import_vite.transformWithEsbuild)(ts, file, {
|
|
174
|
+
loader: "ts",
|
|
175
|
+
tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } }
|
|
176
|
+
});
|
|
177
|
+
return code.replace(/^[ \t]*export\s*\{\s*\}\s*;?[ \t]*$/m, "");
|
|
178
|
+
};
|
|
179
|
+
var SETUP_ATTR_RE = /(:setup\s*=\s*)(?:"([^"]*)"|'([^']*)')/i;
|
|
180
|
+
var SIGNATURE_MARKER = "__jq79_signature__";
|
|
181
|
+
var stripSignatureTypes = async (value, file) => {
|
|
182
|
+
const compiled = await stripTypes(`(${value}) => ${SIGNATURE_MARKER}`, file);
|
|
183
|
+
const marker = compiled.indexOf(SIGNATURE_MARKER);
|
|
184
|
+
if (marker === -1) return value;
|
|
185
|
+
const head = compiled.slice(0, marker).trimEnd();
|
|
186
|
+
const params = (head.endsWith("=>") ? head.slice(0, -2) : head).trim();
|
|
187
|
+
return params.startsWith("(") && params.endsWith(")") ? params.slice(1, -1).trim() : params;
|
|
188
|
+
};
|
|
189
|
+
var quoteAttrValue = (value) => value.replace(/"/g, """);
|
|
190
|
+
var compileScriptBlocks = async (source, file) => {
|
|
191
|
+
const blocks = [...source.matchAll(SCRIPT_BLOCK_RE)];
|
|
192
|
+
const compiled = await Promise.all(
|
|
193
|
+
blocks.map(async ([, attrs, content]) => {
|
|
194
|
+
const lang = attrs.match(LANG_ATTR_RE);
|
|
195
|
+
const name = (lang?.[1] ?? lang?.[2] ?? lang?.[3])?.toLowerCase();
|
|
196
|
+
if (!name || !TS_LANGS.has(name)) return null;
|
|
197
|
+
let rest = attrs.replace(LANG_ATTR_RE, "").trimEnd();
|
|
198
|
+
const setup = rest.match(SETUP_ATTR_RE);
|
|
199
|
+
if (setup) {
|
|
200
|
+
const signature = await stripSignatureTypes(setup[2] ?? setup[3], `${file}.signature.ts`);
|
|
201
|
+
rest = rest.replace(SETUP_ATTR_RE, () => `${setup[1]}"${quoteAttrValue(signature)}"`);
|
|
202
|
+
}
|
|
203
|
+
return { attrs: rest, js: await stripTypes(content, `${file}.ts`) };
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
let out = "";
|
|
207
|
+
let last = 0;
|
|
208
|
+
blocks.forEach((block, i) => {
|
|
209
|
+
const done = compiled[i];
|
|
210
|
+
if (!done) return;
|
|
211
|
+
out += source.slice(last, block.index) + `<script${done.attrs}>${done.js}</script>`;
|
|
212
|
+
last = block.index + block[0].length;
|
|
213
|
+
});
|
|
214
|
+
return out + source.slice(last);
|
|
215
|
+
};
|
|
160
216
|
var componentModule = (source, include, filename) => {
|
|
161
217
|
const hoisted = hoistableImports(source, include);
|
|
162
218
|
const imports = hoisted.map(
|
|
@@ -218,6 +274,7 @@ function jq79(options = {}) {
|
|
|
218
274
|
if (!id.endsWith(COMPONENT_QUERY)) return null;
|
|
219
275
|
const file = id.slice(0, -COMPONENT_QUERY.length);
|
|
220
276
|
let source = await (0, import_promises.readFile)(file, "utf8");
|
|
277
|
+
source = await compileScriptBlocks(source, file);
|
|
221
278
|
if (config) source = await compileStyleBlocks(source, file, config, (dep) => this.addWatchFile(dep));
|
|
222
279
|
const filename = config ? (0, import_node_path.relative)(config.root, file) : file;
|
|
223
280
|
return { code: componentModule(source, include, filename), map: null };
|
package/dist/vite.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../dev/vite.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\"\nimport { relative } from \"node:path\"\nimport { preprocessCSS } from \"vite\"\nimport type { Plugin, ResolvedConfig } from \"vite\"\n\n// Vite plugin: import .html single-file components as modules.\n//\n// import { jq79 } from \"jq79/vite\" // vite.config\n// import UserCard from \"./UserCard.html\" // app code\n//\n// The imported value is a Component79 built from the file's source - the same\n// thing `await Component79.fetch(url)` resolves to, but bundled at build time\n// instead of fetched at runtime. The component source is inlined verbatim, so\n// a file keeps working unchanged if it's ever served from public/ and loaded\n// with fetch instead - with one deliberate exception: <style lang=\"scss\"> (or\n// less/stylus/sass) is compiled to plain CSS here. A component using `lang`\n// therefore only works through the bundler; loaded with fetch() it would\n// reach the runtime uncompiled, which the runtime warns about.\n//\n// Only .html files imported from other modules are claimed; entry points\n// (index.html) have no importer and imports carrying an explicit query\n// (?raw, ?url) keep their built-in Vite meaning.\n\nexport interface Jq79PluginOptions {\n // which import specifiers are treated as components (default: any .html)\n include?: RegExp\n // resolved absolute paths to skip even when `include` matches\n exclude?: RegExp\n}\n\n// claimed modules get this suffix so their id no longer ends in \".html\" and\n// Vite's own html handling (entries, asset pipeline) leaves them alone\nconst COMPONENT_QUERY = \"?jq79\"\n\nconst SCRIPT_BLOCK_RE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script\\s*>/gi\n// import(\"...\") with a literal specifier, tried at word boundaries the\n// scanner below reaches (which is what skips $__import and foo.import(...))\nconst IMPORT_CALL_RE = /import\\s*\\(\\s*([\"'])([^\"'\\n]+?)\\1\\s*\\)/y\n// static import statements (factory scripts): optional clause + literal\n// specifier. The clause can't contain parens/quotes, so dynamic import()\n// and import.meta never match\nconst STATIC_IMPORT_RE = /import\\s*(?:[\\w$\\s,{}*]+?\\s*from\\s*)?([\"'])([^\"'\\n]+)\\1/y\n\nconst skipString = (src: string, start: number): number => {\n const quote = src[start]\n let i = start + 1\n while (i < src.length) {\n if (src[i] === \"\\\\\") { i += 2; continue }\n if (src[i] === quote) return i + 1\n i++\n }\n return src.length\n}\n\n// the literal import specifiers in one script body. A scanner rather than a\n// bare matchAll, because a specifier mentioned in a comment or a string is\n// not an import: hoisting a commented-out `import(\"./old.html\")` would pull\n// dead files into the bundle - or break the build once the file is gone\nconst importSpecifiers = (script: string): string[] => {\n const specs: string[] = []\n let i = 0\n while (i < script.length) {\n const ch = script[i]\n if (ch === \"'\" || ch === '\"' || ch === \"`\") { i = skipString(script, i); continue }\n if (ch === \"/\" && script[i + 1] === \"/\") {\n const end = script.indexOf(\"\\n\", i)\n i = end === -1 ? script.length : end + 1\n continue\n }\n if (ch === \"/\" && script[i + 1] === \"*\") {\n const end = script.indexOf(\"*/\", i + 2)\n i = end === -1 ? script.length : end + 2\n continue\n }\n if (ch === \"i\" && (i === 0 || !/[\\w$.]/.test(script[i - 1]))) {\n IMPORT_CALL_RE.lastIndex = i\n const call = IMPORT_CALL_RE.exec(script)\n if (call) { specs.push(call[2]); i = IMPORT_CALL_RE.lastIndex; continue }\n STATIC_IMPORT_RE.lastIndex = i\n const staticImport = STATIC_IMPORT_RE.exec(script)\n if (staticImport) { specs.push(staticImport[2]); i = STATIC_IMPORT_RE.lastIndex; continue }\n }\n i++\n }\n return specs\n}\n\nconst isHtmlUrl = (spec: string) => /\\.html?([?#]|$)/.test(spec)\nconst isExternalUrl = (spec: string) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith(\"/\")\n\n// literal import specifiers in the component's script blocks - dynamic\n// `import(\"...\")` calls and static factory-script imports - that should\n// resolve from the bundle instead of at runtime. Absolute paths and full\n// URLs are left alone (they point at served files, e.g. public/), and so\n// are .html specifiers the plugin wouldn't claim as components\nconst hoistableImports = (source: string, include: RegExp): string[] => {\n const specifiers = new Set<string>()\n for (const [, script] of source.matchAll(SCRIPT_BLOCK_RE)) {\n for (const spec of importSpecifiers(script)) {\n if (isExternalUrl(spec)) continue\n if (isHtmlUrl(spec) && !include.test(spec)) continue // html left to runtime fetch\n specifiers.add(spec) // a claimed component, a source file or an npm package\n }\n }\n return [...specifiers]\n}\n\n// any start or end tag, quote-aware so a \">\" inside an attribute value doesn't\n// end it early\nconst TAG_RE = /<(\\/?)([A-Za-z][\\w-]*)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>/g\nconst NAME_ATTR_RE = /\\bname\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i\nconst COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/\nconst VOID_ELEMENTS = new Set([\n \"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\",\n \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\",\n])\n\n// the components a file declares: its *top-level* <template name=\"…\"> blocks,\n// which the emitted module re-exports by name. Depth is tracked because only\n// the top level declares - a <template> nested in the markup is a plain inert\n// element the runtime leaves alone, and exporting it would name something that\n// never exists. Script and style bodies are cut out first, so a \"<\" in JS or a\n// selector can't be read as a tag\nconst declaredComponents = (source: string): string[] => {\n const markup = source.replace(SCRIPT_BLOCK_RE, \"\").replace(STYLE_BLOCK_RE, \"\")\n const names: string[] = []\n let depth = 0\n\n for (const [, closing, tag, attrs] of markup.matchAll(TAG_RE)) {\n if (closing) {\n depth = Math.max(0, depth - 1)\n continue\n }\n const selfClosing = /\\/\\s*$/.test(attrs) || VOID_ELEMENTS.has(tag.toLowerCase())\n if (depth === 0 && !selfClosing && tag.toLowerCase() === \"template\") {\n const declared = attrs.match(NAME_ATTR_RE)\n const name = declared?.[1] ?? declared?.[2]\n // the runtime warns about the ones this skips (nameless, not PascalCase)\n if (name && COMPONENT_NAME_RE.test(name)) names.push(name)\n }\n if (!selfClosing) depth++\n }\n return names\n}\n\n// a <style> block with its attribute string, so `lang` can be read and the\n// content replaced. Attribute values are matched as quoted chunks so a \">\"\n// inside one doesn't end the tag early\nconst STYLE_BLOCK_RE = /<style((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([\\s\\S]*?)<\\/style\\s*>/gi\nconst LANG_ATTR_RE = /\\blang\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i\n\n// compiles <style lang=\"scss|less|styl|sass\"> blocks to plain CSS with Vite's\n// own preprocessing (the same call @vitejs/plugin-vue makes), so the runtime\n// only ever sees CSS. The preprocessor picks its parser from the extension,\n// and resolving relative @use/@import against a filename in the component's\n// own directory is what makes `@use \"./vars\"` work. Files the preprocessor\n// pulls in are registered as watch deps, so editing a partial re-runs HMR for\n// every component that uses it. `lang` is dropped from the emitted tag: what\n// the runtime parses is a plain <style> (with `scoped` and the rest intact)\nconst compileStyleBlocks = async (\n source: string,\n file: string,\n config: ResolvedConfig,\n addWatchFile: (id: string) => void\n): Promise<string> => {\n const blocks = [...source.matchAll(STYLE_BLOCK_RE)]\n const compiled = await Promise.all(\n blocks.map(async ([, attrs, content]) => {\n const lang = attrs.match(LANG_ATTR_RE)\n if (!lang) return null\n const extension = lang[1] ?? lang[2] ?? lang[3]\n const result = await preprocessCSS(content, `${file}.${extension}`, config)\n result.deps?.forEach(addWatchFile)\n return { attrs: attrs.replace(LANG_ATTR_RE, \"\").trimEnd(), css: result.code }\n })\n )\n\n let out = \"\"\n let last = 0\n blocks.forEach((block, i) => {\n const done = compiled[i]\n if (!done) return\n out += source.slice(last, block.index) + `<style${done.attrs}>${done.css}</style>`\n last = block.index + block[0].length\n })\n return out + source.slice(last)\n}\n\n// the emitted module. Literal import(\"...\") specifiers found in the\n// component's scripts become real module imports, handed to Component79 as a\n// resolution map: at runtime $__import checks the map before falling back to\n// fetch, so bundled components ship with their imports and nothing changes\n// for unbundled ones. Claimed components import as their default (a\n// Component79, matching what runtime fetch resolves to); everything else as\n// a namespace (matching native import()).\n//\n// A file's <template name=\"…\"> components are re-exported by name, so the\n// module shape is the one the file already has: default plus named. They read\n// off the instance, which is where the runtime hangs them - so in dev they are\n// bound to the *first* evaluation's definitions and a module that imports one\n// by name keeps the pre-edit child until the page reloads. The file's own\n// component patches in place, and its rendered children come from the reparse,\n// so this only shows in a component imported by name from another file.\n//\n// In dev, `hot.data` carries the exported instance across updates: importers\n// hold a reference to the *first* module evaluation's instance, so later\n// evaluations patch that same instance in place instead of exporting a new one\n// nobody sees. The patching itself is the runtime's `hotReplace` - the same\n// swap the jq79/dev server drives, from the one place that can reach a\n// component's markers. An instance only used as a definition has nothing to\n// re-render (nested clones can't be reached from this module), so it falls\n// back to a full reload.\nconst componentModule = (source: string, include: RegExp, filename: string): string => {\n const hoisted = hoistableImports(source, include)\n const imports = hoisted\n .map((spec, i) =>\n include.test(spec)\n ? `import __jq79_${i} from ${JSON.stringify(spec)}`\n : `import * as __jq79_${i} from ${JSON.stringify(spec)}`\n )\n .join(\"\\n\")\n const modulesMap = `{ ${hoisted.map((spec, i) => `${JSON.stringify(spec)}: __jq79_${i}`).join(\", \")} }`\n\n return `\nimport { Component79 } from \"jq79\"\n${imports}\n\nconst src = ${JSON.stringify(source)}\nconst modules = ${modulesMap}\nconst filename = ${JSON.stringify(filename)}\n\nlet component\n\nif (import.meta.hot && import.meta.hot.data.component) {\n const prior = import.meta.hot.data.component\n prior.modules = modules\n prior.filename = filename\n // re-renders it where it stands, keeping its data. false means it was never\n // rendered - a definition used only as a nested component - and a reload is\n // the only way to reach the clones made from it\n if (!prior.hotReplace(src) && !prior.data) import.meta.hot.invalidate()\n component = prior\n} else {\n component = new Component79(src, { modules, filename })\n}\n\nif (import.meta.hot) {\n import.meta.hot.data.component = component\n import.meta.hot.accept()\n}\n\nexport default component\n${declaredComponents(source).map(name => `export const ${name} = component.${name}`).join(\"\\n\")}\n`\n}\n\nexport function jq79(options: Jq79PluginOptions = {}): Plugin {\n const include = options.include ?? /\\.html$/\n const { exclude } = options\n\n let config: ResolvedConfig | null = null\n\n return {\n name: \"jq79\",\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved\n },\n\n async resolveId(source, importer) {\n if (!importer) return null // entry points are never components\n if (source.includes(\"?\")) return null // ?raw, ?url, ... keep their meaning\n if (!include.test(source)) return null\n\n const resolved = await this.resolve(source, importer, { skipSelf: true })\n if (!resolved || resolved.external) return null\n if (exclude?.test(resolved.id)) return null\n return resolved.id + COMPONENT_QUERY\n },\n\n async load(id) {\n if (!id.endsWith(COMPONENT_QUERY)) return null\n const file = id.slice(0, -COMPONENT_QUERY.length)\n\n let source = await readFile(file, \"utf8\")\n if (config) source = await compileStyleBlocks(source, file, config, dep => this.addWatchFile(dep))\n\n // the runtime names the component's setup scripts after this, so devtools\n // shows a path the user recognizes instead of an anonymous VM script\n const filename = config ? relative(config.root, file) : file\n\n return { code: componentModule(source, include, filename), map: null }\n },\n }\n}\n\nexport default jq79\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../dev/vite.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\"\nimport { relative } from \"node:path\"\nimport { preprocessCSS, transformWithEsbuild } from \"vite\"\nimport type { Plugin, ResolvedConfig } from \"vite\"\n\n// Vite plugin: import .html single-file components as modules.\n//\n// import { jq79 } from \"jq79/vite\" // vite.config\n// import UserCard from \"./UserCard.html\" // app code\n//\n// The imported value is a Component79 built from the file's source - the same\n// thing `await Component79.fetch(url)` resolves to, but bundled at build time\n// instead of fetched at runtime. The component source is inlined verbatim, so\n// a file keeps working unchanged if it's ever served from public/ and loaded\n// with fetch instead - with one deliberate exception, `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":[]}
|
package/dist/vite.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// dev/vite.ts
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
3
|
import { relative } from "path";
|
|
4
|
-
import { preprocessCSS } from "vite";
|
|
4
|
+
import { preprocessCSS, transformWithEsbuild } from "vite";
|
|
5
5
|
var COMPONENT_QUERY = "?jq79";
|
|
6
|
-
var SCRIPT_BLOCK_RE = /<script
|
|
6
|
+
var SCRIPT_BLOCK_RE = /<script((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/script\s*>/gi;
|
|
7
7
|
var IMPORT_CALL_RE = /import\s*\(\s*(["'])([^"'\n]+?)\1\s*\)/y;
|
|
8
8
|
var STATIC_IMPORT_RE = /import\s*(?:[\w$\s,{}*]+?\s*from\s*)?(["'])([^"'\n]+)\1/y;
|
|
9
9
|
var skipString = (src, start) => {
|
|
@@ -62,7 +62,7 @@ var isHtmlUrl = (spec) => /\.html?([?#]|$)/.test(spec);
|
|
|
62
62
|
var isExternalUrl = (spec) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith("/");
|
|
63
63
|
var hoistableImports = (source, include) => {
|
|
64
64
|
const specifiers = /* @__PURE__ */ new Set();
|
|
65
|
-
for (const [, script] of source.matchAll(SCRIPT_BLOCK_RE)) {
|
|
65
|
+
for (const [, , script] of source.matchAll(SCRIPT_BLOCK_RE)) {
|
|
66
66
|
for (const spec of importSpecifiers(script)) {
|
|
67
67
|
if (isExternalUrl(spec)) continue;
|
|
68
68
|
if (isHtmlUrl(spec) && !include.test(spec)) continue;
|
|
@@ -133,6 +133,52 @@ var compileStyleBlocks = async (source, file, config, addWatchFile) => {
|
|
|
133
133
|
});
|
|
134
134
|
return out + source.slice(last);
|
|
135
135
|
};
|
|
136
|
+
var TS_LANGS = /* @__PURE__ */ new Set(["ts", "typescript"]);
|
|
137
|
+
var stripTypes = async (ts, file) => {
|
|
138
|
+
const vite = await import("vite");
|
|
139
|
+
const { code } = vite.transformWithOxc ? await vite.transformWithOxc(ts, file, { lang: "ts", typescript: { onlyRemoveTypeImports: true } }) : await transformWithEsbuild(ts, file, {
|
|
140
|
+
loader: "ts",
|
|
141
|
+
tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } }
|
|
142
|
+
});
|
|
143
|
+
return code.replace(/^[ \t]*export\s*\{\s*\}\s*;?[ \t]*$/m, "");
|
|
144
|
+
};
|
|
145
|
+
var SETUP_ATTR_RE = /(:setup\s*=\s*)(?:"([^"]*)"|'([^']*)')/i;
|
|
146
|
+
var SIGNATURE_MARKER = "__jq79_signature__";
|
|
147
|
+
var stripSignatureTypes = async (value, file) => {
|
|
148
|
+
const compiled = await stripTypes(`(${value}) => ${SIGNATURE_MARKER}`, file);
|
|
149
|
+
const marker = compiled.indexOf(SIGNATURE_MARKER);
|
|
150
|
+
if (marker === -1) return value;
|
|
151
|
+
const head = compiled.slice(0, marker).trimEnd();
|
|
152
|
+
const params = (head.endsWith("=>") ? head.slice(0, -2) : head).trim();
|
|
153
|
+
return params.startsWith("(") && params.endsWith(")") ? params.slice(1, -1).trim() : params;
|
|
154
|
+
};
|
|
155
|
+
var quoteAttrValue = (value) => value.replace(/"/g, """);
|
|
156
|
+
var compileScriptBlocks = async (source, file) => {
|
|
157
|
+
const blocks = [...source.matchAll(SCRIPT_BLOCK_RE)];
|
|
158
|
+
const compiled = await Promise.all(
|
|
159
|
+
blocks.map(async ([, attrs, content]) => {
|
|
160
|
+
const lang = attrs.match(LANG_ATTR_RE);
|
|
161
|
+
const name = (lang?.[1] ?? lang?.[2] ?? lang?.[3])?.toLowerCase();
|
|
162
|
+
if (!name || !TS_LANGS.has(name)) return null;
|
|
163
|
+
let rest = attrs.replace(LANG_ATTR_RE, "").trimEnd();
|
|
164
|
+
const setup = rest.match(SETUP_ATTR_RE);
|
|
165
|
+
if (setup) {
|
|
166
|
+
const signature = await stripSignatureTypes(setup[2] ?? setup[3], `${file}.signature.ts`);
|
|
167
|
+
rest = rest.replace(SETUP_ATTR_RE, () => `${setup[1]}"${quoteAttrValue(signature)}"`);
|
|
168
|
+
}
|
|
169
|
+
return { attrs: rest, js: await stripTypes(content, `${file}.ts`) };
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
let out = "";
|
|
173
|
+
let last = 0;
|
|
174
|
+
blocks.forEach((block, i) => {
|
|
175
|
+
const done = compiled[i];
|
|
176
|
+
if (!done) return;
|
|
177
|
+
out += source.slice(last, block.index) + `<script${done.attrs}>${done.js}</script>`;
|
|
178
|
+
last = block.index + block[0].length;
|
|
179
|
+
});
|
|
180
|
+
return out + source.slice(last);
|
|
181
|
+
};
|
|
136
182
|
var componentModule = (source, include, filename) => {
|
|
137
183
|
const hoisted = hoistableImports(source, include);
|
|
138
184
|
const imports = hoisted.map(
|
|
@@ -194,6 +240,7 @@ function jq79(options = {}) {
|
|
|
194
240
|
if (!id.endsWith(COMPONENT_QUERY)) return null;
|
|
195
241
|
const file = id.slice(0, -COMPONENT_QUERY.length);
|
|
196
242
|
let source = await readFile(file, "utf8");
|
|
243
|
+
source = await compileScriptBlocks(source, file);
|
|
197
244
|
if (config) source = await compileStyleBlocks(source, file, config, (dep) => this.addWatchFile(dep));
|
|
198
245
|
const filename = config ? relative(config.root, file) : file;
|
|
199
246
|
return { code: componentModule(source, include, filename), map: null };
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../dev/vite.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\"\nimport { relative } from \"node:path\"\nimport { preprocessCSS } from \"vite\"\nimport type { Plugin, ResolvedConfig } from \"vite\"\n\n// Vite plugin: import .html single-file components as modules.\n//\n// import { jq79 } from \"jq79/vite\" // vite.config\n// import UserCard from \"./UserCard.html\" // app code\n//\n// The imported value is a Component79 built from the file's source - the same\n// thing `await Component79.fetch(url)` resolves to, but bundled at build time\n// instead of fetched at runtime. The component source is inlined verbatim, so\n// a file keeps working unchanged if it's ever served from public/ and loaded\n// with fetch instead - with one deliberate exception: <style lang=\"scss\"> (or\n// less/stylus/sass) is compiled to plain CSS here. A component using `lang`\n// therefore only works through the bundler; loaded with fetch() it would\n// reach the runtime uncompiled, which the runtime warns about.\n//\n// Only .html files imported from other modules are claimed; entry points\n// (index.html) have no importer and imports carrying an explicit query\n// (?raw, ?url) keep their built-in Vite meaning.\n\nexport interface Jq79PluginOptions {\n // which import specifiers are treated as components (default: any .html)\n include?: RegExp\n // resolved absolute paths to skip even when `include` matches\n exclude?: RegExp\n}\n\n// claimed modules get this suffix so their id no longer ends in \".html\" and\n// Vite's own html handling (entries, asset pipeline) leaves them alone\nconst COMPONENT_QUERY = \"?jq79\"\n\nconst SCRIPT_BLOCK_RE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script\\s*>/gi\n// import(\"...\") with a literal specifier, tried at word boundaries the\n// scanner below reaches (which is what skips $__import and foo.import(...))\nconst IMPORT_CALL_RE = /import\\s*\\(\\s*([\"'])([^\"'\\n]+?)\\1\\s*\\)/y\n// static import statements (factory scripts): optional clause + literal\n// specifier. The clause can't contain parens/quotes, so dynamic import()\n// and import.meta never match\nconst STATIC_IMPORT_RE = /import\\s*(?:[\\w$\\s,{}*]+?\\s*from\\s*)?([\"'])([^\"'\\n]+)\\1/y\n\nconst skipString = (src: string, start: number): number => {\n const quote = src[start]\n let i = start + 1\n while (i < src.length) {\n if (src[i] === \"\\\\\") { i += 2; continue }\n if (src[i] === quote) return i + 1\n i++\n }\n return src.length\n}\n\n// the literal import specifiers in one script body. A scanner rather than a\n// bare matchAll, because a specifier mentioned in a comment or a string is\n// not an import: hoisting a commented-out `import(\"./old.html\")` would pull\n// dead files into the bundle - or break the build once the file is gone\nconst importSpecifiers = (script: string): string[] => {\n const specs: string[] = []\n let i = 0\n while (i < script.length) {\n const ch = script[i]\n if (ch === \"'\" || ch === '\"' || ch === \"`\") { i = skipString(script, i); continue }\n if (ch === \"/\" && script[i + 1] === \"/\") {\n const end = script.indexOf(\"\\n\", i)\n i = end === -1 ? script.length : end + 1\n continue\n }\n if (ch === \"/\" && script[i + 1] === \"*\") {\n const end = script.indexOf(\"*/\", i + 2)\n i = end === -1 ? script.length : end + 2\n continue\n }\n if (ch === \"i\" && (i === 0 || !/[\\w$.]/.test(script[i - 1]))) {\n IMPORT_CALL_RE.lastIndex = i\n const call = IMPORT_CALL_RE.exec(script)\n if (call) { specs.push(call[2]); i = IMPORT_CALL_RE.lastIndex; continue }\n STATIC_IMPORT_RE.lastIndex = i\n const staticImport = STATIC_IMPORT_RE.exec(script)\n if (staticImport) { specs.push(staticImport[2]); i = STATIC_IMPORT_RE.lastIndex; continue }\n }\n i++\n }\n return specs\n}\n\nconst isHtmlUrl = (spec: string) => /\\.html?([?#]|$)/.test(spec)\nconst isExternalUrl = (spec: string) => /^[a-z][a-z0-9+.-]*:/i.test(spec) || spec.startsWith(\"/\")\n\n// literal import specifiers in the component's script blocks - dynamic\n// `import(\"...\")` calls and static factory-script imports - that should\n// resolve from the bundle instead of at runtime. Absolute paths and full\n// URLs are left alone (they point at served files, e.g. public/), and so\n// are .html specifiers the plugin wouldn't claim as components\nconst hoistableImports = (source: string, include: RegExp): string[] => {\n const specifiers = new Set<string>()\n for (const [, script] of source.matchAll(SCRIPT_BLOCK_RE)) {\n for (const spec of importSpecifiers(script)) {\n if (isExternalUrl(spec)) continue\n if (isHtmlUrl(spec) && !include.test(spec)) continue // html left to runtime fetch\n specifiers.add(spec) // a claimed component, a source file or an npm package\n }\n }\n return [...specifiers]\n}\n\n// any start or end tag, quote-aware so a \">\" inside an attribute value doesn't\n// end it early\nconst TAG_RE = /<(\\/?)([A-Za-z][\\w-]*)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>/g\nconst NAME_ATTR_RE = /\\bname\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i\nconst COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/\nconst VOID_ELEMENTS = new Set([\n \"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\",\n \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\",\n])\n\n// the components a file declares: its *top-level* <template name=\"…\"> blocks,\n// which the emitted module re-exports by name. Depth is tracked because only\n// the top level declares - a <template> nested in the markup is a plain inert\n// element the runtime leaves alone, and exporting it would name something that\n// never exists. Script and style bodies are cut out first, so a \"<\" in JS or a\n// selector can't be read as a tag\nconst declaredComponents = (source: string): string[] => {\n const markup = source.replace(SCRIPT_BLOCK_RE, \"\").replace(STYLE_BLOCK_RE, \"\")\n const names: string[] = []\n let depth = 0\n\n for (const [, closing, tag, attrs] of markup.matchAll(TAG_RE)) {\n if (closing) {\n depth = Math.max(0, depth - 1)\n continue\n }\n const selfClosing = /\\/\\s*$/.test(attrs) || VOID_ELEMENTS.has(tag.toLowerCase())\n if (depth === 0 && !selfClosing && tag.toLowerCase() === \"template\") {\n const declared = attrs.match(NAME_ATTR_RE)\n const name = declared?.[1] ?? declared?.[2]\n // the runtime warns about the ones this skips (nameless, not PascalCase)\n if (name && COMPONENT_NAME_RE.test(name)) names.push(name)\n }\n if (!selfClosing) depth++\n }\n return names\n}\n\n// a <style> block with its attribute string, so `lang` can be read and the\n// content replaced. Attribute values are matched as quoted chunks so a \">\"\n// inside one doesn't end the tag early\nconst STYLE_BLOCK_RE = /<style((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([\\s\\S]*?)<\\/style\\s*>/gi\nconst LANG_ATTR_RE = /\\blang\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i\n\n// compiles <style lang=\"scss|less|styl|sass\"> blocks to plain CSS with Vite's\n// own preprocessing (the same call @vitejs/plugin-vue makes), so the runtime\n// only ever sees CSS. The preprocessor picks its parser from the extension,\n// and resolving relative @use/@import against a filename in the component's\n// own directory is what makes `@use \"./vars\"` work. Files the preprocessor\n// pulls in are registered as watch deps, so editing a partial re-runs HMR for\n// every component that uses it. `lang` is dropped from the emitted tag: what\n// the runtime parses is a plain <style> (with `scoped` and the rest intact)\nconst compileStyleBlocks = async (\n source: string,\n file: string,\n config: ResolvedConfig,\n addWatchFile: (id: string) => void\n): Promise<string> => {\n const blocks = [...source.matchAll(STYLE_BLOCK_RE)]\n const compiled = await Promise.all(\n blocks.map(async ([, attrs, content]) => {\n const lang = attrs.match(LANG_ATTR_RE)\n if (!lang) return null\n const extension = lang[1] ?? lang[2] ?? lang[3]\n const result = await preprocessCSS(content, `${file}.${extension}`, config)\n result.deps?.forEach(addWatchFile)\n return { attrs: attrs.replace(LANG_ATTR_RE, \"\").trimEnd(), css: result.code }\n })\n )\n\n let out = \"\"\n let last = 0\n blocks.forEach((block, i) => {\n const done = compiled[i]\n if (!done) return\n out += source.slice(last, block.index) + `<style${done.attrs}>${done.css}</style>`\n last = block.index + block[0].length\n })\n return out + source.slice(last)\n}\n\n// the emitted module. Literal import(\"...\") specifiers found in the\n// component's scripts become real module imports, handed to Component79 as a\n// resolution map: at runtime $__import checks the map before falling back to\n// fetch, so bundled components ship with their imports and nothing changes\n// for unbundled ones. Claimed components import as their default (a\n// Component79, matching what runtime fetch resolves to); everything else as\n// a namespace (matching native import()).\n//\n// A file's <template name=\"…\"> components are re-exported by name, so the\n// module shape is the one the file already has: default plus named. They read\n// off the instance, which is where the runtime hangs them - so in dev they are\n// bound to the *first* evaluation's definitions and a module that imports one\n// by name keeps the pre-edit child until the page reloads. The file's own\n// component patches in place, and its rendered children come from the reparse,\n// so this only shows in a component imported by name from another file.\n//\n// In dev, `hot.data` carries the exported instance across updates: importers\n// hold a reference to the *first* module evaluation's instance, so later\n// evaluations patch that same instance in place instead of exporting a new one\n// nobody sees. The patching itself is the runtime's `hotReplace` - the same\n// swap the jq79/dev server drives, from the one place that can reach a\n// component's markers. An instance only used as a definition has nothing to\n// re-render (nested clones can't be reached from this module), so it falls\n// back to a full reload.\nconst componentModule = (source: string, include: RegExp, filename: string): string => {\n const hoisted = hoistableImports(source, include)\n const imports = hoisted\n .map((spec, i) =>\n include.test(spec)\n ? `import __jq79_${i} from ${JSON.stringify(spec)}`\n : `import * as __jq79_${i} from ${JSON.stringify(spec)}`\n )\n .join(\"\\n\")\n const modulesMap = `{ ${hoisted.map((spec, i) => `${JSON.stringify(spec)}: __jq79_${i}`).join(\", \")} }`\n\n return `\nimport { Component79 } from \"jq79\"\n${imports}\n\nconst src = ${JSON.stringify(source)}\nconst modules = ${modulesMap}\nconst filename = ${JSON.stringify(filename)}\n\nlet component\n\nif (import.meta.hot && import.meta.hot.data.component) {\n const prior = import.meta.hot.data.component\n prior.modules = modules\n prior.filename = filename\n // re-renders it where it stands, keeping its data. false means it was never\n // rendered - a definition used only as a nested component - and a reload is\n // the only way to reach the clones made from it\n if (!prior.hotReplace(src) && !prior.data) import.meta.hot.invalidate()\n component = prior\n} else {\n component = new Component79(src, { modules, filename })\n}\n\nif (import.meta.hot) {\n import.meta.hot.data.component = component\n import.meta.hot.accept()\n}\n\nexport default component\n${declaredComponents(source).map(name => `export const ${name} = component.${name}`).join(\"\\n\")}\n`\n}\n\nexport function jq79(options: Jq79PluginOptions = {}): Plugin {\n const include = options.include ?? /\\.html$/\n const { exclude } = options\n\n let config: ResolvedConfig | null = null\n\n return {\n name: \"jq79\",\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved\n },\n\n async resolveId(source, importer) {\n if (!importer) return null // entry points are never components\n if (source.includes(\"?\")) return null // ?raw, ?url, ... keep their meaning\n if (!include.test(source)) return null\n\n const resolved = await this.resolve(source, importer, { skipSelf: true })\n if (!resolved || resolved.external) return null\n if (exclude?.test(resolved.id)) return null\n return resolved.id + COMPONENT_QUERY\n },\n\n async load(id) {\n if (!id.endsWith(COMPONENT_QUERY)) return null\n const file = id.slice(0, -COMPONENT_QUERY.length)\n\n let source = await readFile(file, \"utf8\")\n if (config) source = await compileStyleBlocks(source, file, config, dep => this.addWatchFile(dep))\n\n // the runtime names the component's setup scripts after this, so devtools\n // shows a path the user recognizes instead of an anonymous VM script\n const filename = config ? relative(config.root, file) : file\n\n return { code: componentModule(source, include, filename), map: null }\n },\n }\n}\n\nexport default jq79\n"],"mappings":"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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, `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":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jq79",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "Mini reactive component library: single-file components, Svelte-style setup scripts, fine-grained proxy reactivity.
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"description": "Mini reactive component library: single-file .html components, Svelte-style setup scripts, fine-grained proxy reactivity. No compiler, no bundler, no dependencies — works from any static host.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
|
7
7
|
"components",
|
package/src/jq79.ts
CHANGED
|
@@ -2633,6 +2633,20 @@ const warnDirectiveTypo = (node: TemplateNode) => {
|
|
|
2633
2633
|
for (const attr in node.attrs) {
|
|
2634
2634
|
if (!attr.startsWith(":") || isControlAttr(attr) || attr === ":model" || attr.startsWith(":model.")) continue
|
|
2635
2635
|
const name = attr.slice(1)
|
|
2636
|
+
// every dotted `:` name that means something was claimed by the line above -
|
|
2637
|
+
// :class. :props. :slot. :html.allowed :model. - so what reaches here with a
|
|
2638
|
+
// dot in it is a modifier list on the wrong sigil: @submit.prevent written
|
|
2639
|
+
// with a colon, which binds an attribute called "submit.prevent" holding the
|
|
2640
|
+
// handler's source text and leaves the form submitting natively. The dot is
|
|
2641
|
+
// structural, not a table of event names to keep in step with the platform;
|
|
2642
|
+
// plannableAttr already computes it, for a different purpose
|
|
2643
|
+
if (name.includes(".")) {
|
|
2644
|
+
console.warn(
|
|
2645
|
+
`jq79: ${attr} is not a directive - it bound an attribute named "${name}". ` +
|
|
2646
|
+
`Dotted modifiers belong to an event: if you meant @${name}, that is the spelling`
|
|
2647
|
+
)
|
|
2648
|
+
continue
|
|
2649
|
+
}
|
|
2636
2650
|
const directive = DIRECTIVE_NAMES.find(known => name !== known && name.startsWith(known))
|
|
2637
2651
|
if (directive === undefined) continue
|
|
2638
2652
|
console.warn(
|
|
@@ -3282,6 +3296,22 @@ const componentPartsFrom = (elements: Element[], hashSource: string): ComponentP
|
|
|
3282
3296
|
else template.push(elementToAST(el))
|
|
3283
3297
|
})
|
|
3284
3298
|
|
|
3299
|
+
// <script lang="ts"> is compiled by the jq79/vite plugin, like <style lang>
|
|
3300
|
+
// below, and a `lang` still here means the same thing: this component never
|
|
3301
|
+
// went through the bundler. It matters more on a script, because the failure
|
|
3302
|
+
// is not always loud - `interface`/`as`/generics throw at compile time, but
|
|
3303
|
+
// `let x: T = v` is a valid labeled statement, so it runs and leaves x
|
|
3304
|
+
// undeclared and non-reactive with nothing in the console
|
|
3305
|
+
scripts.forEach(script => {
|
|
3306
|
+
if ("lang" in script.attrs) {
|
|
3307
|
+
console.warn(
|
|
3308
|
+
`jq79: <script lang="${script.attrs.lang}"> needs the jq79/vite plugin to compile it. ` +
|
|
3309
|
+
"This component didn't go through the bundler, so its types were never stripped: the script " +
|
|
3310
|
+
"will throw, or - for a plain `let x: T = ...` - silently fail to declare x."
|
|
3311
|
+
)
|
|
3312
|
+
}
|
|
3313
|
+
})
|
|
3314
|
+
|
|
3285
3315
|
// <style lang="scss"> is compiled by the jq79/vite plugin, so a `lang` still
|
|
3286
3316
|
// here means this component never went through it - it was fetched, loaded
|
|
3287
3317
|
// from a URL, or built from an inline string. The browser would drop the
|