@vizejs/nuxt 0.21.0 → 0.23.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.
Files changed (2) hide show
  1. package/dist/index.js +287 -41
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -2,7 +2,278 @@ import fs from "node:fs";
2
2
  import { addVitePlugin, defineNuxtModule } from "@nuxt/kit";
3
3
  import vize from "@vizejs/vite-plugin";
4
4
  import { musea } from "@vizejs/vite-plugin-musea";
5
+ import { createRequire } from "node:module";
6
+ import path from "node:path";
5
7
 
8
+ //#region src/components.ts
9
+ const COMPONENT_CALL_RE = /_?resolveComponent\s*\(\s*["'`]([^"'`]+)["'`]\s*(?:,\s*[^)]+)?\)/g;
10
+ const COMPONENT_EXT_RE = /\.(?:[cm]?js|ts|vue)$/;
11
+ const DTS_COMPONENT_RE = /^export const (\w+): (?:LazyComponent<)?typeof import\((["'])(.+?)\2\)(?:\.([A-Za-z_$][\w$]*)|\[['"]([A-Za-z_$][\w$]*)['"]\])>?/;
12
+ const DTS_EXT_RE = /\.d\.ts$/;
13
+ const FILE_EXTS = [
14
+ ".js",
15
+ ".mjs",
16
+ ".ts",
17
+ ".vue"
18
+ ];
19
+ const CLIENT_COMPONENT_RE = /\.client\.(?:[cm]?js|ts|vue)$/;
20
+ const SERVER_COMPONENT_RE = /\.server\.(?:[cm]?js|ts|vue)$/;
21
+ const RUNTIME_COMPONENT_DIRS = [
22
+ "dist/runtime/components",
23
+ "dist/runtime/components/nuxt4",
24
+ "runtime/components"
25
+ ];
26
+ function toKebabCase(name) {
27
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
28
+ }
29
+ function toPascalCase(name) {
30
+ return name.split(/[-_.]/g).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join("");
31
+ }
32
+ function addComponentAlias(map, name, resolved) {
33
+ if (!name || map.has(name)) return;
34
+ map.set(name, resolved);
35
+ const kebabName = toKebabCase(name);
36
+ if (!map.has(kebabName)) map.set(kebabName, resolved);
37
+ const pascalName = toPascalCase(name);
38
+ if (!map.has(pascalName)) map.set(pascalName, resolved);
39
+ }
40
+ function addLazyComponentAlias(map, name, resolved) {
41
+ if (!name || name.startsWith("Lazy")) return;
42
+ addComponentAlias(map, `Lazy${toPascalCase(name)}`, {
43
+ ...resolved,
44
+ lazy: true
45
+ });
46
+ }
47
+ function resolveImportPath(importPath) {
48
+ if (fs.existsSync(importPath)) return importPath;
49
+ for (const ext of FILE_EXTS) {
50
+ const withExt = importPath + ext;
51
+ if (fs.existsSync(withExt)) return withExt;
52
+ }
53
+ return importPath;
54
+ }
55
+ function detectComponentMode(filePath) {
56
+ if (CLIENT_COMPONENT_RE.test(filePath)) return "client";
57
+ if (SERVER_COMPONENT_RE.test(filePath)) return "server";
58
+ return void 0;
59
+ }
60
+ function createComponentImport(filePath, exportName, lazy) {
61
+ const componentImport = {
62
+ exportName,
63
+ filePath
64
+ };
65
+ if (lazy) componentImport.lazy = true;
66
+ const mode = detectComponentMode(filePath);
67
+ if (mode) componentImport.mode = mode;
68
+ return componentImport;
69
+ }
70
+ function getNuxtComponentDtsFiles(rootDir, buildDir) {
71
+ const candidates = [
72
+ path.join(buildDir, "components.d.ts"),
73
+ path.join(buildDir, "types", "components.d.ts"),
74
+ path.join(rootDir, ".nuxt", "components.d.ts"),
75
+ path.join(rootDir, ".nuxt", "types", "components.d.ts"),
76
+ path.join(rootDir, "node_modules", ".cache", "nuxt", ".nuxt", "components.d.ts"),
77
+ path.join(rootDir, "node_modules", ".cache", "nuxt", ".nuxt", "types", "components.d.ts")
78
+ ];
79
+ return Array.from(new Set(candidates.filter((candidate) => fs.existsSync(candidate))));
80
+ }
81
+ function loadDtsComponents(rootDir, buildDir) {
82
+ const resolved = new Map();
83
+ for (const filePath of getNuxtComponentDtsFiles(rootDir, buildDir)) {
84
+ const lines = fs.readFileSync(filePath, "utf-8").split("\n");
85
+ for (const line of lines) {
86
+ const match = line.match(DTS_COMPONENT_RE);
87
+ if (!match) continue;
88
+ const [, name, , importPath, exportNameDot, exportNameBracket] = match;
89
+ const exportName = exportNameDot || exportNameBracket;
90
+ if (!exportName) continue;
91
+ const absoluteImportPath = resolveImportPath(path.resolve(path.dirname(filePath), importPath));
92
+ const componentImport = createComponentImport(absoluteImportPath, exportName, name.startsWith("Lazy"));
93
+ addComponentAlias(resolved, name, componentImport);
94
+ addLazyComponentAlias(resolved, name, componentImport);
95
+ }
96
+ }
97
+ return resolved;
98
+ }
99
+ function getProjectPackageNames(moduleNames) {
100
+ const packageNames = new Set(["nuxt"]);
101
+ for (const name of moduleNames || []) packageNames.add(name);
102
+ return Array.from(packageNames);
103
+ }
104
+ function walkRuntimeComponentDir(resolved, dir) {
105
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
106
+ const entryPath = path.join(dir, entry.name);
107
+ if (entry.isDirectory()) {
108
+ walkRuntimeComponentDir(resolved, entryPath);
109
+ continue;
110
+ }
111
+ if (!COMPONENT_EXT_RE.test(entry.name) || DTS_EXT_RE.test(entry.name)) continue;
112
+ const baseName = entry.name.replace(COMPONENT_EXT_RE, "");
113
+ const componentName = baseName === "index" ? path.basename(path.dirname(entryPath)) : baseName;
114
+ if (!/[A-Z]/.test(componentName)) continue;
115
+ addComponentAlias(resolved, componentName, { ...createComponentImport(entryPath, "default") });
116
+ addLazyComponentAlias(resolved, componentName, { ...createComponentImport(entryPath, "default") });
117
+ }
118
+ }
119
+ function loadRuntimeComponents(rootDir, moduleNames) {
120
+ const resolved = new Map();
121
+ const requireFromRoot = createRequire(path.join(rootDir, "package.json"));
122
+ for (const packageName of getProjectPackageNames(moduleNames)) {
123
+ let packageJsonPath = "";
124
+ try {
125
+ packageJsonPath = requireFromRoot.resolve(`${packageName}/package.json`);
126
+ } catch {
127
+ continue;
128
+ }
129
+ const packageDir = path.dirname(packageJsonPath);
130
+ for (const runtimeDir of RUNTIME_COMPONENT_DIRS) {
131
+ const runtimePath = path.join(packageDir, runtimeDir);
132
+ if (fs.existsSync(runtimePath)) walkRuntimeComponentDir(resolved, runtimePath);
133
+ }
134
+ }
135
+ return resolved;
136
+ }
137
+ function createNuxtComponentResolver(options) {
138
+ const registered = new Map();
139
+ let dtsResolved = null;
140
+ let runtimeResolved = null;
141
+ function getDtsResolved() {
142
+ if (!dtsResolved) dtsResolved = loadDtsComponents(options.rootDir, options.buildDir);
143
+ return dtsResolved;
144
+ }
145
+ function getRuntimeResolved() {
146
+ if (!runtimeResolved) runtimeResolved = loadRuntimeComponents(options.rootDir, options.moduleNames);
147
+ return runtimeResolved;
148
+ }
149
+ return {
150
+ register(components) {
151
+ for (const component of components) {
152
+ const resolved = createComponentImport(component.filePath, component.export || "default");
153
+ addComponentAlias(registered, component.pascalName, resolved);
154
+ addComponentAlias(registered, component.kebabName, resolved);
155
+ addComponentAlias(registered, component.name, resolved);
156
+ addLazyComponentAlias(registered, component.pascalName, resolved);
157
+ addLazyComponentAlias(registered, component.kebabName, resolved);
158
+ addLazyComponentAlias(registered, component.name, resolved);
159
+ }
160
+ },
161
+ resolve(name) {
162
+ const normalizedName = name.trim();
163
+ const directResolved = registered.get(normalizedName) ?? getDtsResolved().get(normalizedName);
164
+ if (directResolved) return directResolved;
165
+ if (!/[A-Z]/.test(normalizedName)) return null;
166
+ return getRuntimeResolved().get(normalizedName) ?? null;
167
+ }
168
+ };
169
+ }
170
+ function injectNuxtComponentImports(code, resolveComponentImport) {
171
+ const componentImports = [];
172
+ const importedComponents = new Map();
173
+ let counter = 0;
174
+ let needsDefineAsyncComponent = false;
175
+ let needsCreateClientOnly = false;
176
+ const nextCode = code.replace(COMPONENT_CALL_RE, (match, name) => {
177
+ const resolved = resolveComponentImport(name);
178
+ if (!resolved) return match;
179
+ const importKey = `${resolved.exportName}\u0000${resolved.filePath}\u0000${resolved.lazy ? "lazy" : "eager"}\u0000${resolved.mode ?? "default"}`;
180
+ let variableName = importedComponents.get(importKey);
181
+ if (!variableName) {
182
+ variableName = `__nuxt_component_${counter++}`;
183
+ importedComponents.set(importKey, variableName);
184
+ if (resolved.lazy) {
185
+ needsDefineAsyncComponent = true;
186
+ const exportAccessor = resolved.exportName === "default" ? "module.default" : `module[${JSON.stringify(resolved.exportName)}]`;
187
+ if (resolved.mode === "client") {
188
+ needsCreateClientOnly = true;
189
+ componentImports.push(`const ${variableName} = __nuxt_define_async_component(() => import(${JSON.stringify(resolved.filePath)}).then((module) => __nuxt_create_client_only(${exportAccessor})));`);
190
+ } else componentImports.push(`const ${variableName} = __nuxt_define_async_component(() => import(${JSON.stringify(resolved.filePath)}).then((module) => ${exportAccessor}));`);
191
+ } else if (resolved.exportName === "default") if (resolved.mode === "client") {
192
+ needsCreateClientOnly = true;
193
+ const rawVariableName = `${variableName}_raw`;
194
+ componentImports.push(`import ${rawVariableName} from ${JSON.stringify(resolved.filePath)};`);
195
+ componentImports.push(`const ${variableName} = __nuxt_create_client_only(${rawVariableName});`);
196
+ } else componentImports.push(`import ${variableName} from ${JSON.stringify(resolved.filePath)};`);
197
+ else if (resolved.mode === "client") {
198
+ needsCreateClientOnly = true;
199
+ const rawVariableName = `${variableName}_raw`;
200
+ componentImports.push(`import { ${resolved.exportName} as ${rawVariableName} } from ${JSON.stringify(resolved.filePath)};`);
201
+ componentImports.push(`const ${variableName} = __nuxt_create_client_only(${rawVariableName});`);
202
+ } else componentImports.push(`import { ${resolved.exportName} as ${variableName} } from ${JSON.stringify(resolved.filePath)};`);
203
+ }
204
+ return variableName;
205
+ });
206
+ if (componentImports.length === 0) return code;
207
+ const preamble = [
208
+ ...needsDefineAsyncComponent ? ["import { defineAsyncComponent as __nuxt_define_async_component } from \"vue\";"] : [],
209
+ ...needsCreateClientOnly ? ["import { createClientOnly as __nuxt_create_client_only } from \"#app/components/client-only\";"] : [],
210
+ ...componentImports
211
+ ];
212
+ return preamble.join("\n") + "\n" + nextCode;
213
+ }
214
+
215
+ //#endregion
216
+ //#region src/i18n.ts
217
+ const I18N_FN_MAP = {
218
+ $t: "t: $t",
219
+ $rt: "rt: $rt",
220
+ $d: "d: $d",
221
+ $n: "n: $n",
222
+ $tm: "tm: $tm",
223
+ $te: "te: $te"
224
+ };
225
+ const I18N_FN_RE = /(?<![.\w])\$([tdn]|rt|tm|te)\s*\(/g;
226
+ const SETUP_FN_RE = /setup\s*\(__props[\s\S]*?\)\s*\{/;
227
+ const USE_I18N_DESTRUCTURE_RE = /const\s*\{([^}]*)\}\s*=\s*useI18n\s*\(\s*\)\s*;?/;
228
+ function getLocalAlias(specifier) {
229
+ const colon = specifier.indexOf(":");
230
+ return (colon === -1 ? specifier : specifier.slice(colon + 1)).trim();
231
+ }
232
+ function collectUsedI18nSpecifiers(code) {
233
+ const used = new Set();
234
+ for (const match of code.matchAll(I18N_FN_RE)) {
235
+ const fnName = `$${match[1]}`;
236
+ const specifier = I18N_FN_MAP[fnName];
237
+ if (specifier) used.add(specifier);
238
+ }
239
+ return Array.from(used);
240
+ }
241
+ function collectDestructuredLocalNames(destructure) {
242
+ const locals = new Set();
243
+ for (const rawPart of destructure.split(",")) {
244
+ const part = rawPart.trim();
245
+ if (!part) continue;
246
+ const withoutDefault = (part.split("=")[0] ?? part).trim();
247
+ const aliasMatch = withoutDefault.match(/^(?:\.\.\.)?[^:]+:\s*(.+)$/);
248
+ const localName = (aliasMatch ? aliasMatch[1] : withoutDefault).trim();
249
+ if (localName) locals.add(localName);
250
+ }
251
+ return locals;
252
+ }
253
+ function injectNuxtI18nHelpers(code) {
254
+ const usedSpecifiers = collectUsedI18nSpecifiers(code);
255
+ if (usedSpecifiers.length === 0) return code;
256
+ const setupMatch = code.match(SETUP_FN_RE);
257
+ if (!setupMatch || setupMatch.index === void 0) return code;
258
+ const setupBodyStart = setupMatch.index + setupMatch[0].length;
259
+ const setupBody = code.slice(setupBodyStart);
260
+ const existingMatch = setupBody.match(USE_I18N_DESTRUCTURE_RE);
261
+ if (existingMatch && existingMatch.index !== void 0) {
262
+ const existingLocals = collectDestructuredLocalNames(existingMatch[1]);
263
+ const missingSpecifiers = usedSpecifiers.filter((specifier) => {
264
+ return !existingLocals.has(getLocalAlias(specifier));
265
+ });
266
+ if (missingSpecifiers.length === 0) return code;
267
+ const merged = existingMatch[1].trim();
268
+ const nextDestructure = merged ? `${merged}, ${missingSpecifiers.join(", ")}` : missingSpecifiers.join(", ");
269
+ const matchStart = setupBodyStart + existingMatch.index;
270
+ const matchEnd = matchStart + existingMatch[0].length;
271
+ return code.slice(0, matchStart) + `const { ${nextDestructure} } = useI18n();` + code.slice(matchEnd);
272
+ }
273
+ return code.slice(0, setupBodyStart) + `\nconst { ${usedSpecifiers.join(", ")} } = useI18n();\n` + code.slice(setupBodyStart);
274
+ }
275
+
276
+ //#endregion
6
277
  //#region src/index.ts
7
278
  var src_default = defineNuxtModule({
8
279
  meta: {
@@ -32,9 +303,13 @@ var src_default = defineNuxtModule({
32
303
  nuxt.hook("imports:context", (ctx) => {
33
304
  unimportCtx = ctx;
34
305
  });
35
- let nuxtComponents = [];
306
+ const nuxtComponentResolver = createNuxtComponentResolver({
307
+ buildDir: nuxt.options.buildDir,
308
+ moduleNames: nuxt.options.modules.filter((moduleName) => typeof moduleName === "string"),
309
+ rootDir: nuxt.options.rootDir
310
+ });
36
311
  nuxt.hook("components:extend", (comps) => {
37
- nuxtComponents = comps;
312
+ nuxtComponentResolver.register(comps);
38
313
  });
39
314
  addVitePlugin({
40
315
  name: "vizejs:nuxt-transform-bridge",
@@ -43,46 +318,17 @@ var src_default = defineNuxtModule({
43
318
  if (!id.startsWith("\0") || !id.endsWith(".vue.ts")) return;
44
319
  let result = code;
45
320
  let changed = false;
46
- if (nuxtComponents.length > 0) {
47
- const compImports = [];
48
- let counter = 0;
49
- result = result.replace(/_?resolveComponent\s*\(\s*["'`]([^"'`]+)["'`]\s*(?:,\s*[^)]+)?\)/g, (match, name) => {
50
- const comp = nuxtComponents.find((c) => c.pascalName === name || c.kebabName === name || c.name === name);
51
- if (comp) {
52
- const varName = `__nuxt_component_${counter++}`;
53
- const exportName = comp.export || "default";
54
- if (exportName === "default") compImports.push(`import ${varName} from ${JSON.stringify(comp.filePath)};`);
55
- else compImports.push(`import { ${exportName} as ${varName} } from ${JSON.stringify(comp.filePath)};`);
56
- return varName;
57
- }
58
- return match;
59
- });
60
- if (compImports.length > 0) {
61
- result = compImports.join("\n") + "\n" + result;
62
- changed = true;
63
- }
321
+ const nextComponentResult = injectNuxtComponentImports(result, (name) => {
322
+ return nuxtComponentResolver.resolve(name);
323
+ });
324
+ if (nextComponentResult !== result) {
325
+ result = nextComponentResult;
326
+ changed = true;
64
327
  }
65
- const i18nFnRe = /(?<![.\w])\$([tdn]|rt|tm|te)\s*\(/;
66
- if (i18nFnRe.test(result) && !result.includes("useI18n")) {
67
- const i18nMap = {
68
- $t: "t: $t",
69
- $rt: "rt: $rt",
70
- $d: "d: $d",
71
- $n: "n: $n",
72
- $tm: "tm: $tm",
73
- $te: "te: $te"
74
- };
75
- const usedFns = [];
76
- for (const [fn, destructure] of Object.entries(i18nMap)) if (new RegExp(`(?<![.\\w])\\${fn}\\s*\\(`).test(result)) usedFns.push(destructure);
77
- if (usedFns.length > 0) {
78
- const setupMatch = result.match(/setup\s*\(__props[\s\S]*?\)\s*\{/);
79
- if (setupMatch && setupMatch.index !== void 0) {
80
- const insertPos = setupMatch.index + setupMatch[0].length;
81
- const injection = `\nconst { ${usedFns.join(", ")} } = useI18n();\n`;
82
- result = result.slice(0, insertPos) + injection + result.slice(insertPos);
83
- changed = true;
84
- }
85
- }
328
+ const nextResult = injectNuxtI18nHelpers(result);
329
+ if (nextResult !== result) {
330
+ result = nextResult;
331
+ changed = true;
86
332
  }
87
333
  if (unimportCtx) try {
88
334
  const injected = await unimportCtx.injectImports(result, id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizejs/nuxt",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "Nuxt module for Vize - compiler, musea gallery, linter, and type checker",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,10 +34,10 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@nuxt/kit": "^4.0.0",
37
- "@vizejs/vite-plugin-musea": "0.21.0",
38
- "@vizejs/musea-nuxt": "0.21.0",
39
- "@vizejs/vite-plugin": "0.21.0",
40
- "vize": "0.21.0"
37
+ "@vizejs/vite-plugin": "0.23.0",
38
+ "@vizejs/musea-nuxt": "0.23.0",
39
+ "vize": "0.23.0",
40
+ "@vizejs/vite-plugin-musea": "0.23.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "tsdown": "^0.9.0",