@torpor/unplugin 1.1.0 → 1.2.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;cAQa,iBAAiB,gBAAgB;cA4IjC,UAAU,iBAAiB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;cAWa,iBAAiB,gBAAgB;cA+TjC,UAAU,iBAAiB"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,7 @@
1
+ import { createRequire } from "node:module";
1
2
  import { build, parse } from "@torpor/view/compile";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import path from "node:path";
2
5
  import { createUnplugin } from "unplugin";
3
6
  import { transformWithOxc } from "vite";
4
7
  //#region src/index.ts
@@ -12,20 +15,31 @@ const unpluginFactory = (options) => ({
12
15
  if (styles.has(id)) return styles.get(id);
13
16
  },
14
17
  transformInclude(id) {
15
- return /\.torp\?*/.test(id);
18
+ if (/\.torp([?#]|$)/.test(id)) return true;
19
+ const query = getQuery(id);
20
+ if (query?.has("client") || query?.has("server")) return /\.(js|mjs|cjs|ts|mts|cts|jsx|tsx)$/.test(cleanId(id));
21
+ return false;
16
22
  },
17
23
  transform(code, id, viteOptions) {
18
24
  let transformOptions = { ...options };
19
25
  if (viteOptions && viteOptions.dev !== void 0) transformOptions.dev = viteOptions.dev;
20
- const query = getQuery(id);
21
- if (query?.has("client")) transformOptions.server = false;
22
- else if (query?.has("server")) transformOptions.server = true;
26
+ const override = getOverride(getQuery(id));
27
+ if (override === "client") transformOptions.server = false;
28
+ else if (override === "server") transformOptions.server = true;
23
29
  else {
24
30
  if (viteOptions && viteOptions.ssr !== void 0) transformOptions.server = viteOptions.ssr;
25
31
  if (transformOptions.test) transformOptions.server = true;
26
32
  }
33
+ if (!/\.torp([?#]|$)/.test(id)) {
34
+ if (!transformOptions.test || !override) return;
35
+ const rewritten = propagateOverride(code, override, path.dirname(cleanId(id)));
36
+ return rewritten === code ? void 0 : {
37
+ code: rewritten,
38
+ map: null
39
+ };
40
+ }
27
41
  let parsed = parse(code);
28
- if (parsed.ok && parsed.template) return transform(parsed.template, id, transformOptions);
42
+ if (parsed.ok && parsed.template) return transform(parsed.template, id, transformOptions, override);
29
43
  else {
30
44
  let name = id.split(/[\\/]/).at(-1).replace(/\.torp.*$/, "");
31
45
  let errorMessages = parsed.errors.map((e) => `${e.startLine + 1},${e.startChar}: ${e.message}`);
@@ -44,7 +58,7 @@ export default function Error() {
44
58
  }
45
59
  }`;
46
60
  let errorParsed = parse(errorCode);
47
- if (errorParsed.ok && errorParsed.template) return transform(errorParsed.template, id, transformOptions);
61
+ if (errorParsed.ok && errorParsed.template) return transform(errorParsed.template, id, transformOptions, override);
48
62
  throw new Error(`Parse failed for ${id}, ${errorMessages.join("\n")}`);
49
63
  }
50
64
  }
@@ -57,16 +71,111 @@ function getQuery(id) {
57
71
  if (queryStart === -1) return;
58
72
  return new URLSearchParams(id.substring(queryStart + 1));
59
73
  }
60
- function transform(template, id, options) {
74
+ /**
75
+ * Gets the client/server override from a module id's query, if any
76
+ */
77
+ function getOverride(query) {
78
+ if (query?.has("client")) return "client";
79
+ if (query?.has("server")) return "server";
80
+ }
81
+ /**
82
+ * Gets the id without its query string (e.g. `Foo.torp` for `Foo.torp?client`)
83
+ */
84
+ function cleanId(id) {
85
+ return id.replace(/[?#].*$/, "");
86
+ }
87
+ function transform(template, id, options, override) {
61
88
  const built = build(template, options);
62
89
  let transformed = built.code;
63
- if (options?.test && options.server === false) transformed = transformed.replace(/(from\s*['"])([^'"]+\.torp)(['"])/g, "$1$2?client$3");
90
+ if (options?.test && override) transformed = propagateOverride(transformed, override, path.dirname(cleanId(id)));
64
91
  if (built.styles) for (let style of built.styles) {
65
92
  transformed = `import '${style.hash}.css';\n` + transformed;
66
93
  styles.set(style.hash + ".css", style.style);
67
94
  }
68
95
  return transformWithOxc(transformed, id.replace(/\.torp.*$/, ".ts"));
69
96
  }
97
+ /**
98
+ * Rewrites a component's imports so that the components it imports get the
99
+ * same `?client`/`?server` override:
100
+ *
101
+ * - relative `.torp` imports get the query appended
102
+ * - bare package imports that resolve into a torpor package (one that ships
103
+ * `.torp` files, e.g. `@torpor/ui/Progress` or `phosphor-torpor/lib/Camera`)
104
+ * get the query appended too -- their specifiers don't end in `.torp`, but
105
+ * they resolve to `.torp` files (directly or through plain-JS re-export
106
+ * barrels), so without the query they'd be compiled for the default side
107
+ */
108
+ function propagateOverride(code, override, importerDir) {
109
+ let result = code;
110
+ result = result.replace(/(from\s*['"])([^'"]+\.torp)(['"])/g, `$1$2?${override}$3`);
111
+ result = result.replace(/(from\s*['"])([^'".#/][^'"]*)(['"])/g, (match, pre, specifier, post) => {
112
+ if (specifier.includes("?")) return match;
113
+ if (!isTorporPackageImport(specifier, importerDir)) return match;
114
+ return `${pre}${specifier}?${override}${post}`;
115
+ });
116
+ return result;
117
+ }
118
+ /**
119
+ * Cache of whether a package (per importing directory) is a torpor package
120
+ */
121
+ const torporPackages = /* @__PURE__ */ new Map();
122
+ /**
123
+ * Whether a bare import specifier (e.g. `@torpor/ui/Progress`) resolves into a
124
+ * package that ships `.torp` files, and so needs override queries passed on
125
+ * to it
126
+ */
127
+ function isTorporPackageImport(specifier, importerDir) {
128
+ const pkgName = /^(@[^/]+\/[^/]+|[^@./][^/]*)/.exec(specifier)?.[0];
129
+ if (!pkgName) return false;
130
+ const cacheKey = `${importerDir}\0${pkgName}`;
131
+ let isTorpor = torporPackages.get(cacheKey);
132
+ if (isTorpor === void 0) {
133
+ isTorpor = packageShipsTorp(pkgName, importerDir);
134
+ torporPackages.set(cacheKey, isTorpor);
135
+ }
136
+ return isTorpor;
137
+ }
138
+ /**
139
+ * Whether a package is a torpor package: it declares a `torpor` entry in its
140
+ * package.json (usually `true`, or a path mirroring the `svelte` field
141
+ * convention), or any of its export targets is a `.torp` file
142
+ */
143
+ function packageShipsTorp(pkgName, importerDir) {
144
+ const packageJsonPath = findPackageJson(pkgName, importerDir);
145
+ if (!packageJsonPath) return false;
146
+ try {
147
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
148
+ if (pkg.torpor === true || typeof pkg.torpor === "string") return true;
149
+ return exportsIncludeTorp(pkg.exports);
150
+ } catch {
151
+ return false;
152
+ }
153
+ }
154
+ /**
155
+ * Finds a package's package.json, either by resolving it through the
156
+ * package's own exports map, or (when the exports map doesn't expose
157
+ * `./package.json`) through a node_modules walk-up from the importing file
158
+ */
159
+ function findPackageJson(pkgName, importerDir) {
160
+ try {
161
+ const packageJson = createRequire(path.join(importerDir, "index.js")).resolve(`${pkgName}/package.json`);
162
+ if (path.isAbsolute(packageJson)) return packageJson;
163
+ } catch {}
164
+ let dir = importerDir;
165
+ while (true) {
166
+ const packageJson = path.join(dir, "node_modules", ...pkgName.split("/"), "package.json");
167
+ if (existsSync(packageJson)) return packageJson;
168
+ const parent = path.dirname(dir);
169
+ if (parent === dir) return;
170
+ dir = parent;
171
+ }
172
+ }
173
+ function exportsIncludeTorp(exports) {
174
+ if (typeof exports === "string") return exports.endsWith(".torp");
175
+ if (Array.isArray(exports)) return exports.some(exportsIncludeTorp);
176
+ if (exports && typeof exports === "object") return Object.values(exports).some(exportsIncludeTorp);
177
+ return false;
178
+ }
70
179
  const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory);
71
180
  //#endregion
72
181
  export { unplugin as default, unplugin, unpluginFactory };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Template, build, parse } from \"@torpor/view/compile\";\nimport { type UnpluginFactory, type UnpluginInstance } from \"unplugin\";\nimport { createUnplugin } from \"unplugin\";\nimport { transformWithOxc } from \"vite\";\nimport type Options from \"./types\";\n\nconst styles = new Map<string, string>();\n\nexport const unpluginFactory: UnpluginFactory<Options | undefined> = (options) => ({\n\tname: \"unplugin-torpor\",\n\tresolveId(id /*, importer, options*/) {\n\t\tif (styles.has(id)) {\n\t\t\treturn id;\n\t\t}\n\t\treturn undefined;\n\t},\n\tload(id) {\n\t\tif (styles.has(id)) {\n\t\t\treturn styles.get(id);\n\t\t}\n\t\treturn undefined;\n\t},\n\ttransformInclude(id) {\n\t\t// Check for *.torp files\n\t\treturn /\\.torp\\?*/.test(id);\n\t},\n\t// @ts-ignore\n\ttransform(code, id, viteOptions) {\n\t\t// Copy the factory options instead of mutating them, so that the\n\t\t// per-request dev/server overrides below don't stick around and\n\t\t// surprise the next request\n\t\tlet transformOptions: Options = { ...options };\n\n\t\t// We may be in dev mode\n\t\tif (viteOptions && viteOptions.dev !== undefined) {\n\t\t\ttransformOptions.dev = viteOptions.dev;\n\t\t}\n\n\t\t// An explicit ?client or ?server query on the import overrides\n\t\t// everything else -- e.g. importing `Component.torp?client` in a test\n\t\t// run compiles the component for the client, so that it can be\n\t\t// mounted, while other components stay SSR-compiled\n\t\tconst query = getQuery(id);\n\t\tif (query?.has(\"client\")) {\n\t\t\ttransformOptions.server = false;\n\t\t} else if (query?.has(\"server\")) {\n\t\t\ttransformOptions.server = true;\n\t\t} else {\n\t\t\t// Vite can override user server options\n\t\t\tif (viteOptions && viteOptions.ssr !== undefined) {\n\t\t\t\ttransformOptions.server = viteOptions.ssr;\n\t\t\t}\n\n\t\t\t// But when testing we always generate for the server\n\t\t\tif (transformOptions.test) {\n\t\t\t\ttransformOptions.server = true;\n\t\t\t}\n\t\t}\n\n\t\t// Try to parse the code\n\t\tlet parsed = parse(code);\n\t\tif (parsed.ok && parsed.template) {\n\t\t\t// Transform for server or client\n\t\t\treturn transform(parsed.template, id, transformOptions);\n\t\t} else {\n\t\t\t// Show an error component\n\t\t\tlet name = id\n\t\t\t\t.split(/[\\\\/]/)\n\t\t\t\t.at(-1)\n\t\t\t\t.replace(/\\.torp.*$/, \"\")!;\n\t\t\tlet errorMessages = parsed.errors.map(\n\t\t\t\t(e) => `${e.startLine + 1},${e.startChar}: ${e.message}`,\n\t\t\t);\n\t\t\tconsole.log(`\\nERRORS: ${id}\\n======\\n${errorMessages.join(\"\\n\")}`);\n\t\t\tlet errorCode = `\nexport default function Error() {\n\t@render {\n\t\t<div style=\"background-color: #222; color: #f44\"; font-size: 15px; line-height: 1.5;\">\n\t\t\t<p style=\"margin: 0; padding: 0;\">\n\t\t\t\t<strong>Error${parsed.errors.length === 1 ? \"\" : \"s\"} in ${name}:</strong>\n\t\t\t</p>\n\t\t\t<ul style=\"margin: 0; padding: 0 20px\">\n\t\t\t\t${errorMessages.map((e) => `<li>${e}</li>`).join(\"\\n\")}\n\t\t\t</ul>\n\t\t</div>\n\t}\n}`;\n\t\t\tlet errorParsed = parse(errorCode);\n\t\t\tif (errorParsed.ok && errorParsed.template) {\n\t\t\t\treturn transform(errorParsed.template, id, transformOptions);\n\t\t\t}\n\t\t\t// This should never be reached, but just in case...\n\t\t\tthrow new Error(`Parse failed for ${id}, ${errorMessages.join(\"\\n\")}`);\n\t\t}\n\t},\n});\n\n/**\n * Gets the query string of a module id (e.g. `client` for `Foo.torp?client`)\n */\nfunction getQuery(id: string): URLSearchParams | undefined {\n\tconst queryStart = id.lastIndexOf(\"?\");\n\tif (queryStart === -1) {\n\t\treturn undefined;\n\t}\n\treturn new URLSearchParams(id.substring(queryStart + 1));\n}\n\nfunction transform(template: Template, id: string, options?: Options) {\n\tconst built = build(template, options);\n\tlet transformed = built.code;\n\n\t// When a component is compiled for the client in a test run, any child\n\t// components it imports must also be compiled for the client -- otherwise\n\t// mounting the parent would try to render SSR children (which don't show\n\t// anything). Pass the ?client query on to imported components\n\tif (options?.test && options.server === false) {\n\t\ttransformed = transformed.replace(/(from\\s*['\"])([^'\"]+\\.torp)(['\"])/g, \"$1$2?client$3\");\n\t}\n\n\tif (built.styles) {\n\t\tfor (let style of built.styles) {\n\t\t\t// Add a dynamic import for the component's CSS with a name from\n\t\t\t// the hash and add the styles to a map. Then resolveId will\n\t\t\t// pass the CSS id onto load, which will load the the actual CSS\n\t\t\t// from the map\n\t\t\ttransformed = `import '${style.hash}.css';\\n` + transformed;\n\t\t\tstyles.set(style.hash + \".css\", style.style);\n\t\t}\n\t}\n\n\t//printTransformed(transformed);\n\n\t// TODO: Compile typescript only if script lang=\"ts\" or config.lang=\"ts\"\n\treturn transformWithOxc(transformed, id.replace(/\\.torp.*$/, \".ts\"));\n}\n\n/*\nfunction printTransformed(transformed: string) {\n\tconsole.log(\n\t\ttransformed\n\t\t\t.split(\"\\n\")\n\t\t\t.map((l, i) => `${(i + 1).toString().padEnd(3)} ${l}`)\n\t\t\t.join(\"\\n\"),\n\t);\n}\n*/\n\nexport const unplugin: UnpluginInstance<Options | undefined, boolean> =\n\t/* #__PURE__ */ createUnplugin(unpluginFactory);\n\nexport default unplugin;\n"],"mappings":";;;;AAMA,MAAM,yBAAS,IAAI,IAAoB;AAEvC,MAAa,mBAAyD,aAAa;CAClF,MAAM;CACN,UAAU,IAA4B;EACrC,IAAI,OAAO,IAAI,EAAE,GAChB,OAAO;CAGT;CACA,KAAK,IAAI;EACR,IAAI,OAAO,IAAI,EAAE,GAChB,OAAO,OAAO,IAAI,EAAE;CAGtB;CACA,iBAAiB,IAAI;EAEpB,OAAO,YAAY,KAAK,EAAE;CAC3B;CAEA,UAAU,MAAM,IAAI,aAAa;EAIhC,IAAI,mBAA4B,EAAE,GAAG,QAAQ;EAG7C,IAAI,eAAe,YAAY,QAAQ,KAAA,GACtC,iBAAiB,MAAM,YAAY;EAOpC,MAAM,QAAQ,SAAS,EAAE;EACzB,IAAI,OAAO,IAAI,QAAQ,GACtB,iBAAiB,SAAS;OACpB,IAAI,OAAO,IAAI,QAAQ,GAC7B,iBAAiB,SAAS;OACpB;GAEN,IAAI,eAAe,YAAY,QAAQ,KAAA,GACtC,iBAAiB,SAAS,YAAY;GAIvC,IAAI,iBAAiB,MACpB,iBAAiB,SAAS;EAE5B;EAGA,IAAI,SAAS,MAAM,IAAI;EACvB,IAAI,OAAO,MAAM,OAAO,UAEvB,OAAO,UAAU,OAAO,UAAU,IAAI,gBAAgB;OAChD;GAEN,IAAI,OAAO,GACT,MAAM,OAAO,CAAC,CACd,GAAG,EAAE,CAAC,CACN,QAAQ,aAAa,EAAE;GACzB,IAAI,gBAAgB,OAAO,OAAO,KAChC,MAAM,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,IAAI,EAAE,SAChD;GACA,QAAQ,IAAI,aAAa,GAAG,YAAY,cAAc,KAAK,IAAI,GAAG;GAClE,IAAI,YAAY;;;;;mBAKA,OAAO,OAAO,WAAW,IAAI,KAAK,IAAI,MAAM,KAAK;;;MAG9D,cAAc,KAAK,MAAM,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE;;;;;GAKxD,IAAI,cAAc,MAAM,SAAS;GACjC,IAAI,YAAY,MAAM,YAAY,UACjC,OAAO,UAAU,YAAY,UAAU,IAAI,gBAAgB;GAG5D,MAAM,IAAI,MAAM,oBAAoB,GAAG,IAAI,cAAc,KAAK,IAAI,GAAG;EACtE;CACD;AACD;;;;AAKA,SAAS,SAAS,IAAyC;CAC1D,MAAM,aAAa,GAAG,YAAY,GAAG;CACrC,IAAI,eAAe,IAClB;CAED,OAAO,IAAI,gBAAgB,GAAG,UAAU,aAAa,CAAC,CAAC;AACxD;AAEA,SAAS,UAAU,UAAoB,IAAY,SAAmB;CACrE,MAAM,QAAQ,MAAM,UAAU,OAAO;CACrC,IAAI,cAAc,MAAM;CAMxB,IAAI,SAAS,QAAQ,QAAQ,WAAW,OACvC,cAAc,YAAY,QAAQ,sCAAsC,eAAe;CAGxF,IAAI,MAAM,QACT,KAAK,IAAI,SAAS,MAAM,QAAQ;EAK/B,cAAc,WAAW,MAAM,KAAK,YAAY;EAChD,OAAO,IAAI,MAAM,OAAO,QAAQ,MAAM,KAAK;CAC5C;CAMD,OAAO,iBAAiB,aAAa,GAAG,QAAQ,aAAa,KAAK,CAAC;AACpE;AAaA,MAAa,WACI,+BAAe,eAAe"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Template, build, parse } from \"@torpor/view/compile\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport { type UnpluginFactory, type UnpluginInstance } from \"unplugin\";\nimport { createUnplugin } from \"unplugin\";\nimport { transformWithOxc } from \"vite\";\nimport type Options from \"./types\";\n\nconst styles = new Map<string, string>();\n\nexport const unpluginFactory: UnpluginFactory<Options | undefined> = (options) => ({\n\tname: \"unplugin-torpor\",\n\tresolveId(id /*, importer, options*/) {\n\t\tif (styles.has(id)) {\n\t\t\treturn id;\n\t\t}\n\t\treturn undefined;\n\t},\n\tload(id) {\n\t\tif (styles.has(id)) {\n\t\t\treturn styles.get(id);\n\t\t}\n\t\treturn undefined;\n\t},\n\ttransformInclude(id) {\n\t\t// Check for *.torp files (with or without a query)\n\t\tif (/\\.torp([?#]|$)/.test(id)) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also plain-JS modules that are imported with an override query --\n\t\t// re-export barrels from torpor packages (e.g. the `index.js` files\n\t\t// that `@torpor/ui/*` resolves to), which need to pass the query on\n\t\t// to the `.torp` files they re-export\n\t\tconst query = getQuery(id);\n\t\tif (query?.has(\"client\") || query?.has(\"server\")) {\n\t\t\treturn /\\.(js|mjs|cjs|ts|mts|cts|jsx|tsx)$/.test(cleanId(id));\n\t\t}\n\t\treturn false;\n\t},\n\t// @ts-ignore\n\ttransform(code, id, viteOptions) {\n\t\t// Copy the factory options instead of mutating them, so that the\n\t\t// per-request dev/server overrides below don't stick around and\n\t\t// surprise the next request\n\t\tlet transformOptions: Options = { ...options };\n\n\t\t// We may be in dev mode\n\t\tif (viteOptions && viteOptions.dev !== undefined) {\n\t\t\ttransformOptions.dev = viteOptions.dev;\n\t\t}\n\n\t\t// An explicit ?client or ?server query on the import overrides\n\t\t// everything else -- e.g. importing `Component.torp?client` in a test\n\t\t// run compiles the component for the client, so that it can be\n\t\t// mounted, while other components stay SSR-compiled\n\t\tconst override = getOverride(getQuery(id));\n\t\tif (override === \"client\") {\n\t\t\ttransformOptions.server = false;\n\t\t} else if (override === \"server\") {\n\t\t\ttransformOptions.server = true;\n\t\t} else {\n\t\t\t// Vite can override user server options\n\t\t\tif (viteOptions && viteOptions.ssr !== undefined) {\n\t\t\t\ttransformOptions.server = viteOptions.ssr;\n\t\t\t}\n\n\t\t\t// But when testing we always generate for the server\n\t\t\tif (transformOptions.test) {\n\t\t\t\ttransformOptions.server = true;\n\t\t\t}\n\t\t}\n\n\t\t// A plain-JS module with an override query (a re-export barrel from a\n\t\t// torpor package) doesn't get compiled as a component; it only needs\n\t\t// the query passed on to the `.torp` files it imports\n\t\tif (!/\\.torp([?#]|$)/.test(id)) {\n\t\t\tif (!transformOptions.test || !override) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst rewritten = propagateOverride(code, override, path.dirname(cleanId(id)));\n\t\t\treturn rewritten === code ? undefined : { code: rewritten, map: null };\n\t\t}\n\n\t\t// Try to parse the code\n\t\tlet parsed = parse(code);\n\t\tif (parsed.ok && parsed.template) {\n\t\t\t// Transform for server or client\n\t\t\treturn transform(parsed.template, id, transformOptions, override);\n\t\t} else {\n\t\t\t// Show an error component\n\t\t\tlet name = id\n\t\t\t\t.split(/[\\\\/]/)\n\t\t\t\t.at(-1)\n\t\t\t\t.replace(/\\.torp.*$/, \"\")!;\n\t\t\tlet errorMessages = parsed.errors.map(\n\t\t\t\t(e) => `${e.startLine + 1},${e.startChar}: ${e.message}`,\n\t\t\t);\n\t\t\tconsole.log(`\\nERRORS: ${id}\\n======\\n${errorMessages.join(\"\\n\")}`);\n\t\t\tlet errorCode = `\nexport default function Error() {\n\t@render {\n\t\t<div style=\"background-color: #222; color: #f44\"; font-size: 15px; line-height: 1.5;\">\n\t\t\t<p style=\"margin: 0; padding: 0;\">\n\t\t\t\t<strong>Error${parsed.errors.length === 1 ? \"\" : \"s\"} in ${name}:</strong>\n\t\t\t</p>\n\t\t\t<ul style=\"margin: 0; padding: 0 20px\">\n\t\t\t\t${errorMessages.map((e) => `<li>${e}</li>`).join(\"\\n\")}\n\t\t\t</ul>\n\t\t</div>\n\t}\n}`;\n\t\t\tlet errorParsed = parse(errorCode);\n\t\t\tif (errorParsed.ok && errorParsed.template) {\n\t\t\t\treturn transform(errorParsed.template, id, transformOptions, override);\n\t\t\t}\n\t\t\t// This should never be reached, but just in case...\n\t\t\tthrow new Error(`Parse failed for ${id}, ${errorMessages.join(\"\\n\")}`);\n\t\t}\n\t},\n});\n\n/**\n * Gets the query string of a module id (e.g. `client` for `Foo.torp?client`)\n */\nfunction getQuery(id: string): URLSearchParams | undefined {\n\tconst queryStart = id.lastIndexOf(\"?\");\n\tif (queryStart === -1) {\n\t\treturn undefined;\n\t}\n\treturn new URLSearchParams(id.substring(queryStart + 1));\n}\n\n/**\n * Gets the client/server override from a module id's query, if any\n */\nfunction getOverride(query: URLSearchParams | undefined): \"client\" | \"server\" | undefined {\n\tif (query?.has(\"client\")) {\n\t\treturn \"client\";\n\t}\n\tif (query?.has(\"server\")) {\n\t\treturn \"server\";\n\t}\n\treturn undefined;\n}\n\n/**\n * Gets the id without its query string (e.g. `Foo.torp` for `Foo.torp?client`)\n */\nfunction cleanId(id: string): string {\n\treturn id.replace(/[?#].*$/, \"\");\n}\n\nfunction transform(\n\ttemplate: Template,\n\tid: string,\n\toptions?: Options,\n\toverride?: \"client\" | \"server\",\n) {\n\tconst built = build(template, options);\n\tlet transformed = built.code;\n\n\t// When a component is compiled with a ?client/?server override in a test\n\t// run, any child components it imports must be compiled the same way --\n\t// otherwise mounting the parent would try to render SSR children (which\n\t// don't show anything). Pass the override on to imported components:\n\t// relative `.torp` imports, and bare imports into packages that ship\n\t// `.torp` files (e.g. `@torpor/ui/*` or `phosphor-torpor/*`), whose\n\t// re-export barrels would otherwise be compiled for the default side\n\tif (options?.test && override) {\n\t\ttransformed = propagateOverride(transformed, override, path.dirname(cleanId(id)));\n\t}\n\n\tif (built.styles) {\n\t\tfor (let style of built.styles) {\n\t\t\t// Add a dynamic import for the component's CSS with a name from\n\t\t\t// the hash and add the styles to a map. Then resolveId will\n\t\t\t// pass the CSS id onto load, which will load the the actual CSS\n\t\t\t// from the map\n\t\t\ttransformed = `import '${style.hash}.css';\\n` + transformed;\n\t\t\tstyles.set(style.hash + \".css\", style.style);\n\t\t}\n\t}\n\n\t//printTransformed(transformed);\n\n\t// TODO: Compile typescript only if script lang=\"ts\" or config.lang=\"ts\"\n\treturn transformWithOxc(transformed, id.replace(/\\.torp.*$/, \".ts\"));\n}\n\n/**\n * Rewrites a component's imports so that the components it imports get the\n * same `?client`/`?server` override:\n *\n * - relative `.torp` imports get the query appended\n * - bare package imports that resolve into a torpor package (one that ships\n * `.torp` files, e.g. `@torpor/ui/Progress` or `phosphor-torpor/lib/Camera`)\n * get the query appended too -- their specifiers don't end in `.torp`, but\n * they resolve to `.torp` files (directly or through plain-JS re-export\n * barrels), so without the query they'd be compiled for the default side\n */\nfunction propagateOverride(\n\tcode: string,\n\toverride: \"client\" | \"server\",\n\timporterDir: string,\n): string {\n\tlet result = code;\n\n\t// Relative (or bare-but-.torp-suffixed) imports without an existing query\n\tresult = result.replace(/(from\\s*['\"])([^'\"]+\\.torp)(['\"])/g, `$1$2?${override}$3`);\n\n\t// Bare package imports (e.g. `@torpor/ui/Progress`)\n\tresult = result.replace(\n\t\t/(from\\s*['\"])([^'\".#/][^'\"]*)(['\"])/g,\n\t\t(match: string, pre: string, specifier: string, post: string) => {\n\t\t\tif (specifier.includes(\"?\")) {\n\t\t\t\treturn match;\n\t\t\t}\n\t\t\tif (!isTorporPackageImport(specifier, importerDir)) {\n\t\t\t\treturn match;\n\t\t\t}\n\t\t\treturn `${pre}${specifier}?${override}${post}`;\n\t\t},\n\t);\n\n\treturn result;\n}\n\n/**\n * Cache of whether a package (per importing directory) is a torpor package\n */\nconst torporPackages = new Map<string, boolean>();\n\n/**\n * Whether a bare import specifier (e.g. `@torpor/ui/Progress`) resolves into a\n * package that ships `.torp` files, and so needs override queries passed on\n * to it\n */\nfunction isTorporPackageImport(specifier: string, importerDir: string): boolean {\n\t// The package name, without any subpath (`pkg` or `@scope/pkg`)\n\tconst pkgName = /^(@[^/]+\\/[^/]+|[^@./][^/]*)/.exec(specifier)?.[0];\n\tif (!pkgName) {\n\t\treturn false;\n\t}\n\n\tconst cacheKey = `${importerDir}\\0${pkgName}`;\n\tlet isTorpor = torporPackages.get(cacheKey);\n\tif (isTorpor === undefined) {\n\t\tisTorpor = packageShipsTorp(pkgName, importerDir);\n\t\ttorporPackages.set(cacheKey, isTorpor);\n\t}\n\treturn isTorpor;\n}\n\n/**\n * Whether a package is a torpor package: it declares a `torpor` entry in its\n * package.json (usually `true`, or a path mirroring the `svelte` field\n * convention), or any of its export targets is a `.torp` file\n */\nfunction packageShipsTorp(pkgName: string, importerDir: string): boolean {\n\tconst packageJsonPath = findPackageJson(pkgName, importerDir);\n\tif (!packageJsonPath) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(packageJsonPath, \"utf8\"));\n\t\tif (pkg.torpor === true || typeof pkg.torpor === \"string\") {\n\t\t\treturn true;\n\t\t}\n\t\treturn exportsIncludeTorp(pkg.exports);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Finds a package's package.json, either by resolving it through the\n * package's own exports map, or (when the exports map doesn't expose\n * `./package.json`) through a node_modules walk-up from the importing file\n */\nfunction findPackageJson(pkgName: string, importerDir: string): string | undefined {\n\ttry {\n\t\tconst require = createRequire(path.join(importerDir, \"index.js\"));\n\t\tconst packageJson = require.resolve(`${pkgName}/package.json`);\n\t\tif (path.isAbsolute(packageJson)) {\n\t\t\treturn packageJson;\n\t\t}\n\t} catch {\n\t\t// Fall through to the node_modules walk-up below\n\t}\n\n\tlet dir = importerDir;\n\twhile (true) {\n\t\tconst packageJson = path.join(dir, \"node_modules\", ...pkgName.split(\"/\"), \"package.json\");\n\t\tif (existsSync(packageJson)) {\n\t\t\treturn packageJson;\n\t\t}\n\t\tconst parent = path.dirname(dir);\n\t\tif (parent === dir) {\n\t\t\treturn undefined;\n\t\t}\n\t\tdir = parent;\n\t}\n}\n\nfunction exportsIncludeTorp(exports: unknown): boolean {\n\tif (typeof exports === \"string\") {\n\t\treturn exports.endsWith(\".torp\");\n\t}\n\tif (Array.isArray(exports)) {\n\t\treturn exports.some(exportsIncludeTorp);\n\t}\n\tif (exports && typeof exports === \"object\") {\n\t\treturn Object.values(exports).some(exportsIncludeTorp);\n\t}\n\treturn false;\n}\n\n/*\nfunction printTransformed(transformed: string) {\n\tconsole.log(\n\t\ttransformed\n\t\t\t.split(\"\\n\")\n\t\t\t.map((l, i) => `${(i + 1).toString().padEnd(3)} ${l}`)\n\t\t\t.join(\"\\n\"),\n\t);\n}\n*/\n\nexport const unplugin: UnpluginInstance<Options | undefined, boolean> =\n\t/* #__PURE__ */ createUnplugin(unpluginFactory);\n\nexport default unplugin;\n"],"mappings":"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"}
package/dist/types.d.mts CHANGED
@@ -14,7 +14,10 @@ interface Options {
14
14
  * and assert on the rendered HTML. To mount a component client-side in
15
15
  * the same test project, import it with a `?client` query (e.g.
16
16
  * `Component.torp?client`); the override is passed on to any components
17
- * it imports. A `?server` query does the reverse.
17
+ * it imports, including components from packages that ship `.torp` files
18
+ * (e.g. `@torpor/ui/*` or `phosphor-torpor/*`), whose re-export barrels
19
+ * get the query passed through as well. A `?server` query does the
20
+ * reverse.
18
21
  */
19
22
  test?: boolean;
20
23
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";UAAyB;;;;EAIxB;;;;EAIA;;;;;;;;;EASA;;;;;EAKA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";UAAyB;;;;EAIxB;;;;EAIA;;;;;;;;;;;;EAYA;;;;;EAKA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torpor/unplugin",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Unplugin package to compile Torpor components for Vite, Rollup, and other bundlers",
5
5
  "keywords": [
6
6
  "torpor",
@@ -110,7 +110,9 @@
110
110
  "typescript": "^7.0.2",
111
111
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
112
112
  "vite-plus": "0.3.0",
113
- "webpack": "^5.109.2"
113
+ "webpack": "^5.109.2",
114
+ "@torpor/ui": "^1.1.6",
115
+ "torp-lib": "1.0.0"
114
116
  },
115
117
  "scripts": {
116
118
  "check": "tsgo --noEmit && pnpm dlx oxlint --type-aware",