@dxup/nuxt 0.2.2 → 0.3.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 CHANGED
@@ -11,21 +11,23 @@ This is a TypeScript plugin that improves Nuxt DX.
11
11
 
12
12
  ## Installation
13
13
 
14
- ```bash
15
- pnpm i -D @dxup/nuxt
16
- ```
14
+ *No installation is required if you are using Nuxt 4.2 or above.*
17
15
 
18
16
  ## Usage
19
17
 
20
- Add the following to your `nuxt.config.ts`:
18
+ 1. Have `@dxup/unimport` installed as a dependency if you haven't enabled the `shamefullyHoist` option with pnpm workspace.
21
19
 
22
- ```ts
23
- export default defineNuxtConfig({
24
- modules: [
25
- "@dxup/nuxt",
26
- ],
27
- });
28
- ```
20
+ 2. Add the following to your `nuxt.config.ts`:
21
+
22
+ ```ts
23
+ export default defineNuxtConfig({
24
+ experimental: {
25
+ typescriptPlugin: true,
26
+ },
27
+ });
28
+ ```
29
+
30
+ 3. Run `nuxt prepare` and restart the tsserver.
29
31
 
30
32
  ## Features
31
33
 
@@ -71,6 +73,27 @@ Go to definition for runtime config.
71
73
  </template>
72
74
  ```
73
75
 
74
- ### 5. unimport
76
+ ### 5. typedPages
77
+
78
+ Go to definition for typed pages.
79
+
80
+ ```vue
81
+ <template>
82
+ <nuxt-link :to="{ name: `about` }"/>
83
+ <!-- ^^^^^^^ -->
84
+ </template>
85
+ ```
86
+
87
+ It can be triggered on the `name` property of an object literal constrained by the `RouteLocationRaw` type.
88
+
89
+ ### 6. unimport
90
+
91
+ Find references for SFC on `<template>`.
92
+
93
+ ```vue
94
+ ....<template>
95
+ <!-- ^^^^^^^^ -->
96
+ </template>
97
+ ```
75
98
 
76
99
  Please refer to the [@dxup/unimport](/packages/unimport) package for more details.
package/dist/module.d.mts CHANGED
@@ -23,13 +23,32 @@ interface ModuleOptions {
23
23
  * @default true
24
24
  */
25
25
  runtimeConfig?: boolean;
26
+ /**
27
+ * Whether to enable Go to Definition for typed pages.
28
+ * @default true
29
+ */
30
+ typedPages?: boolean;
26
31
  /**
27
32
  * Whether to enable enhanced navigation for auto imported APIs.
28
33
  * @default true
29
34
  */
30
- unimport?: boolean;
35
+ unimport?: boolean | {
36
+ /**
37
+ * Whether to enable Find References for SFC on `<template>`.
38
+ */
39
+ componentReferences: boolean;
40
+ };
31
41
  };
32
42
  }
33
- declare const _default: _nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
43
+ declare const _default: _nuxt_schema0.NuxtModule<ModuleOptions, {
44
+ features: {
45
+ components: true;
46
+ importGlob: true;
47
+ nitroRoutes: true;
48
+ runtimeConfig: true;
49
+ typedPages: true;
50
+ unimport: true;
51
+ };
52
+ }, true>;
34
53
  //#endregion
35
54
  export { ModuleOptions, _default as default };
package/dist/module.mjs CHANGED
@@ -64,7 +64,7 @@ async function onComponentsRename(nuxt, { fileName, references }) {
64
64
 
65
65
  //#endregion
66
66
  //#region src/module/index.ts
67
- var module_default = defineNuxtModule({
67
+ var module_default = defineNuxtModule().with({
68
68
  meta: {
69
69
  name,
70
70
  configKey: "dxup"
@@ -74,6 +74,7 @@ var module_default = defineNuxtModule({
74
74
  importGlob: true,
75
75
  nitroRoutes: true,
76
76
  runtimeConfig: true,
77
+ typedPages: true,
77
78
  unimport: true
78
79
  } },
79
80
  async setup(options, nuxt) {
@@ -86,14 +87,21 @@ var module_default = defineNuxtModule({
86
87
  addTemplate({
87
88
  filename: "dxup/data.json",
88
89
  write: true,
89
- getContents({ nuxt: nuxt$1 }) {
90
- const nitro = useNitro();
90
+ getContents({ nuxt: nuxt$1, app }) {
91
+ const nitroRoutes = useNitro().scannedHandlers.reduce((acc, item) => {
92
+ if (item.route && item.method) (acc[item.route] ??= {})[item.method] = item.handler;
93
+ return acc;
94
+ }, {});
91
95
  const data = {
92
96
  buildDir: nuxt$1.options.buildDir,
93
97
  publicDir: nuxt$1.options.dir.public,
94
98
  configFiles: [...nuxt$1.options._nuxtConfigFiles, ...nuxt$1.options._layers.map((layer) => layer._configFile).filter(Boolean)],
95
- nitroRoutes: Object.fromEntries(nitro.scannedHandlers.filter((item) => item.route).map((item) => [`${item.route}+${item.method ?? "get"}`, item.handler])),
96
- features: options.features
99
+ nitroRoutes,
100
+ typedPages: Object.fromEntries(app.pages?.map((page) => [page.name, page.file]) ?? []),
101
+ features: {
102
+ ...options.features,
103
+ unimport: { componentReferences: typeof options.features.unimport === "object" ? options.features.unimport.componentReferences : options.features.unimport }
104
+ }
97
105
  };
98
106
  return JSON.stringify(data, null, 2);
99
107
  }
@@ -1,3 +1,20 @@
1
+ //#region rolldown:runtime
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (all, symbols) => {
4
+ let target = {};
5
+ for (var name in all) {
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ }
11
+ if (symbols) {
12
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ }
14
+ return target;
15
+ };
16
+
17
+ //#endregion
1
18
  let node_fs_promises = require("node:fs/promises");
2
19
  let pathe = require("pathe");
3
20
  let tinyglobby = require("tinyglobby");
@@ -20,11 +37,14 @@ const initialValue = {
20
37
  publicDir: "",
21
38
  configFiles: [],
22
39
  nitroRoutes: {},
40
+ typedPages: {},
23
41
  features: {
24
42
  components: true,
25
43
  importGlob: true,
26
44
  nitroRoutes: true,
27
- runtimeConfig: true
45
+ runtimeConfig: true,
46
+ typedPages: true,
47
+ unimport: { componentReferences: true }
28
48
  }
29
49
  };
30
50
  const callbacks = {};
@@ -46,12 +66,94 @@ function createData(ts, info) {
46
66
  return data;
47
67
  }
48
68
 
69
+ //#endregion
70
+ //#region src/typescript/utils.ts
71
+ function createModuleDefinition(ts, path) {
72
+ return {
73
+ fileName: path,
74
+ textSpan: {
75
+ start: 0,
76
+ length: 0
77
+ },
78
+ kind: ts.ScriptElementKind.moduleElement,
79
+ name: `"${path}"`,
80
+ containerKind: ts.ScriptElementKind.unknown,
81
+ containerName: ""
82
+ };
83
+ }
84
+ function isVueVirtualCode(code) {
85
+ return code?.languageId === "vue";
86
+ }
87
+ function toSourceSpan(language, fileName, textSpan) {
88
+ const sourceScript = language?.scripts.get(fileName);
89
+ if (!sourceScript?.generated) return;
90
+ const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
91
+ if (!serviceScript) return;
92
+ const map = language.maps.get(serviceScript.code, sourceScript);
93
+ const leadingOffset = sourceScript.snapshot.getLength();
94
+ for (const [start, end] of map.toSourceRange(textSpan.start - leadingOffset, textSpan.start + textSpan.length - leadingOffset, false)) return {
95
+ start,
96
+ length: end - start
97
+ };
98
+ }
99
+ function withVirtualOffset(language, sourceScript, position, method) {
100
+ const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
101
+ if (!serviceScript) return;
102
+ const map = language.maps.get(serviceScript.code, sourceScript);
103
+ const leadingOffset = sourceScript.snapshot.getLength();
104
+ const offset = 1145141919810;
105
+ const mapping = {
106
+ sourceOffsets: [offset],
107
+ generatedOffsets: [position - leadingOffset],
108
+ lengths: [0],
109
+ data: {
110
+ completion: true,
111
+ navigation: true,
112
+ semantic: true,
113
+ verification: true
114
+ }
115
+ };
116
+ const original = map.toGeneratedLocation;
117
+ map.toGeneratedLocation = function* (sourceOffset, ...args) {
118
+ if (sourceOffset === offset) yield [mapping.generatedOffsets[0], mapping];
119
+ yield* original.call(this, sourceOffset, ...args);
120
+ };
121
+ try {
122
+ return method(offset);
123
+ } finally {
124
+ map.toGeneratedLocation = original;
125
+ }
126
+ }
127
+
128
+ //#endregion
129
+ //#region src/typescript/features/findReferences.ts
130
+ var findReferences_exports = /* @__PURE__ */ __export({ postprocess: () => postprocess$1 });
131
+ function postprocess$1(context, language, findReferences) {
132
+ const { ts, info } = context;
133
+ return (...args) => {
134
+ const result = findReferences(...args);
135
+ if (!result?.length) {
136
+ const sourceScript = language.scripts.get(args[0]);
137
+ const root = sourceScript?.generated?.root;
138
+ if (!isVueVirtualCode(root)) return;
139
+ const start = (root.sfc.template?.start ?? Infinity) + 1;
140
+ if (args[1] < start || args[1] > start + 8) return;
141
+ const sourceFile = info.languageService.getProgram().getSourceFile(args[0]);
142
+ if (!sourceFile) return;
143
+ for (const statement of sourceFile.statements) if (ts.isExportAssignment(statement)) return withVirtualOffset(language, sourceScript, statement.getChildAt(1).getStart(sourceFile), (position) => findReferences(args[0], position));
144
+ return;
145
+ }
146
+ return result;
147
+ };
148
+ }
149
+
49
150
  //#endregion
50
151
  //#region src/typescript/features/findRenameLocations.ts
51
- function findRenameLocations(context, findRenameLocations$1) {
152
+ var findRenameLocations_exports = /* @__PURE__ */ __export({ preprocess: () => preprocess$2 });
153
+ function preprocess$2(context, findRenameLocations) {
52
154
  const { data } = context;
53
155
  return (...args) => {
54
- return findRenameLocations$1(...args)?.filter((edit) => {
156
+ return findRenameLocations(...args)?.filter((edit) => {
55
157
  return !edit.fileName.startsWith(data.buildDir);
56
158
  });
57
159
  };
@@ -87,26 +189,58 @@ function isTextSpanWithin(node, textSpan, sourceFile) {
87
189
 
88
190
  //#endregion
89
191
  //#region src/typescript/features/getDefinitionAndBoundSpan.ts
192
+ var getDefinitionAndBoundSpan_exports = /* @__PURE__ */ __export({
193
+ postprocess: () => postprocess,
194
+ preprocess: () => preprocess$1
195
+ });
90
196
  const fetchFunctions = new Set([
91
197
  "$fetch",
92
198
  "useFetch",
93
199
  "useLazyFetch"
94
200
  ]);
95
- function getDefinitionAndBoundSpan(context, getDefinitionAndBoundSpan$1) {
201
+ function postprocess(context, language, getDefinitionAndBoundSpan) {
202
+ const { ts } = context;
203
+ return (...args) => {
204
+ const result = getDefinitionAndBoundSpan(...args);
205
+ if (!result?.definitions?.length) {
206
+ const root = language.scripts.get(args[0])?.generated?.root;
207
+ if (!isVueVirtualCode(root)) return result;
208
+ const textSpan = {
209
+ start: (root.sfc.template?.start ?? Infinity) + 1,
210
+ length: 8
211
+ };
212
+ if (args[1] >= textSpan.start && args[1] <= textSpan.start + textSpan.length) return {
213
+ textSpan,
214
+ definitions: [{
215
+ fileName: args[0],
216
+ textSpan,
217
+ kind: ts.ScriptElementKind.memberVariableElement,
218
+ name: "default",
219
+ containerKind: ts.ScriptElementKind.unknown,
220
+ containerName: args[0]
221
+ }]
222
+ };
223
+ return result;
224
+ }
225
+ return result;
226
+ };
227
+ }
228
+ function preprocess$1(context, getDefinitionAndBoundSpan) {
96
229
  const { ts, info, data } = context;
97
230
  return (...args) => {
98
- const result = getDefinitionAndBoundSpan$1(...args);
231
+ const result = getDefinitionAndBoundSpan(...args);
99
232
  if (!result) {
100
233
  const program$1 = info.languageService.getProgram();
101
234
  const sourceFile = program$1.getSourceFile(args[0]);
102
235
  if (!sourceFile) return;
103
236
  const checker = program$1.getTypeChecker();
104
- let res;
237
+ let result$1;
105
238
  for (const node of forEachTouchingNode(ts, sourceFile, args[1])) {
106
- if (data.features.importGlob) res ??= visitImportGlob(ts, info, sourceFile, node, args[1]);
107
- if (data.features.nitroRoutes) res ??= visitNitroRoutes(ts, data, checker, sourceFile, node, args[1]);
239
+ if (data.features.importGlob) result$1 ??= visitImportGlob(ts, info, sourceFile, node, args[1]);
240
+ if (data.features.nitroRoutes) result$1 ??= visitNitroRoutes(ts, data, checker, sourceFile, node, args[1]);
241
+ if (data.features.typedPages) result$1 ??= visitTypedPages(ts, data, checker, sourceFile, node, args[1]);
108
242
  }
109
- if (res) return res;
243
+ if (result$1) return result$1;
110
244
  }
111
245
  if (!result?.definitions?.length) return result;
112
246
  const program = info.languageService.getProgram();
@@ -114,7 +248,7 @@ function getDefinitionAndBoundSpan(context, getDefinitionAndBoundSpan$1) {
114
248
  for (const definition of result.definitions) {
115
249
  const sourceFile = program.getSourceFile(definition.fileName);
116
250
  if (!sourceFile) continue;
117
- let result$1 = [];
251
+ let result$1;
118
252
  if (data.features.runtimeConfig && definition.fileName.endsWith("runtime-config.d.ts")) result$1 = visitRuntimeConfig(context, sourceFile, definition);
119
253
  if (result$1?.length) {
120
254
  for (const definition$1 of result$1) definitions.add(definition$1);
@@ -153,17 +287,7 @@ function visitImportGlob(ts, info, sourceFile, node, position) {
153
287
  start,
154
288
  length: end - start
155
289
  },
156
- definitions: fileNames.map((fileName) => ({
157
- fileName,
158
- textSpan: {
159
- start: 0,
160
- length: 0
161
- },
162
- kind: ts.ScriptElementKind.unknown,
163
- name: fileName,
164
- containerKind: ts.ScriptElementKind.unknown,
165
- containerName: ""
166
- }))
290
+ definitions: fileNames.map((fileName) => createModuleDefinition(ts, fileName))
167
291
  };
168
292
  }
169
293
  function visitNitroRoutes(ts, data, checker, sourceFile, node, position) {
@@ -187,8 +311,11 @@ function visitNitroRoutes(ts, data, checker, sourceFile, node, position) {
187
311
  }
188
312
  const paths = [];
189
313
  if (routeType?.isStringLiteral()) {
190
- for (const type of methodType?.isUnion() ? methodType.types : [methodType]) if (type?.isStringLiteral()) {
191
- const path = data.nitroRoutes[`${routeType.value}+${type.value}`];
314
+ const alternatives = data.nitroRoutes[routeType.value] ?? {};
315
+ const methods = [];
316
+ for (const type of methodType?.isUnion() ? methodType.types : [methodType]) if (type?.isStringLiteral()) methods.push(type.value);
317
+ for (const method of methods.length ? methods : Object.keys(alternatives)) {
318
+ const path = alternatives[method];
192
319
  if (path !== void 0) paths.push(path);
193
320
  }
194
321
  }
@@ -201,17 +328,23 @@ function visitNitroRoutes(ts, data, checker, sourceFile, node, position) {
201
328
  start,
202
329
  length: end - start
203
330
  },
204
- definitions: paths.map((path) => ({
205
- fileName: path,
206
- textSpan: {
207
- start: 0,
208
- length: 0
209
- },
210
- kind: ts.ScriptElementKind.scriptElement,
211
- name: path,
212
- containerKind: ts.ScriptElementKind.unknown,
213
- containerName: ""
214
- }))
331
+ definitions: paths.map((path) => createModuleDefinition(ts, path))
332
+ };
333
+ }
334
+ function visitTypedPages(ts, data, checker, sourceFile, node, position) {
335
+ if (!ts.isPropertyAssignment(node) || !ts.isIdentifier(node.name) || node.name.text !== "name" || !ts.isStringLiteralLike(node.initializer)) return;
336
+ const start = node.initializer.getStart(sourceFile);
337
+ const end = node.initializer.getEnd();
338
+ if (position < start || position > end) return;
339
+ if (checker.getContextualType(node.parent)?.getNonNullableType().aliasSymbol?.name !== "RouteLocationRaw") return;
340
+ const path = data.typedPages[node.initializer.text];
341
+ if (path === void 0) return;
342
+ return {
343
+ textSpan: {
344
+ start,
345
+ length: end - start
346
+ },
347
+ definitions: [createModuleDefinition(ts, path)]
215
348
  };
216
349
  }
217
350
  function visitRuntimeConfig(context, sourceFile, definition) {
@@ -289,27 +422,13 @@ function* forwardRuntimeConfig(context, definition, path) {
289
422
  }
290
423
  }
291
424
 
292
- //#endregion
293
- //#region src/typescript/utils.ts
294
- function toSourceSpan(language, fileName, textSpan) {
295
- const sourceScript = language?.scripts.get(fileName);
296
- if (!sourceScript?.generated) return;
297
- const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
298
- if (!serviceScript) return;
299
- const map = language.maps.get(serviceScript.code, sourceScript);
300
- const leadingOffset = sourceScript.snapshot.getLength();
301
- for (const [start, end] of map.toSourceRange(textSpan.start - leadingOffset, textSpan.start + textSpan.length - leadingOffset, false)) return {
302
- start,
303
- length: end - start
304
- };
305
- }
306
-
307
425
  //#endregion
308
426
  //#region src/typescript/features/getEditsForFileRename.ts
309
- function getEditsForFileRename(context, getEditsForFileRename$1) {
427
+ var getEditsForFileRename_exports = /* @__PURE__ */ __export({ preprocess: () => preprocess });
428
+ function preprocess(context, getEditsForFileRename) {
310
429
  const { ts, info, data, server } = context;
311
430
  return (...args) => {
312
- const result = getEditsForFileRename$1(...args);
431
+ const result = getEditsForFileRename(...args);
313
432
  if (!result?.length) return result;
314
433
  const program = info.languageService.getProgram();
315
434
  const references = {};
@@ -346,22 +465,38 @@ function getEditsForFileRename(context, getEditsForFileRename$1) {
346
465
  const plugin = (module$1) => {
347
466
  const { typescript: ts } = module$1;
348
467
  return { create(info) {
468
+ const data = createData(ts, info);
349
469
  const context = {
350
470
  ts,
351
471
  info,
352
- data: createData(ts, info),
472
+ data,
353
473
  server: createEventServer(info)
354
474
  };
355
- setTimeout(() => {
356
- context.language = (info.project.__vue__ ?? info.project["program"]?.__vue__)?.language;
357
- }, 500);
475
+ queueMicrotask(() => {
476
+ context.language = info.project.__vue__?.language;
477
+ if (!context.language || !data.features.unimport.componentReferences) return;
478
+ const languageService = info.project.getLanguageService();
479
+ const methods = {};
480
+ for (const [key, method] of [["findReferences", findReferences_exports], ["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan_exports]]) {
481
+ const original = languageService[key];
482
+ methods[key] = method.postprocess(context, context.language, original);
483
+ }
484
+ info.project["languageService"] = new Proxy(languageService, {
485
+ get(target, p, receiver) {
486
+ return methods[p] ?? Reflect.get(target, p, receiver);
487
+ },
488
+ set(...args) {
489
+ return Reflect.set(...args);
490
+ }
491
+ });
492
+ });
358
493
  for (const [key, method] of [
359
- ["findRenameLocations", findRenameLocations],
360
- ["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan],
361
- ["getEditsForFileRename", getEditsForFileRename]
494
+ ["findRenameLocations", findRenameLocations_exports],
495
+ ["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan_exports],
496
+ ["getEditsForFileRename", getEditsForFileRename_exports]
362
497
  ]) {
363
498
  const original = info.languageService[key];
364
- info.languageService[key] = method(context, original);
499
+ info.languageService[key] = method.preprocess(context, original);
365
500
  }
366
501
  return info.languageService;
367
502
  } };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dxup/nuxt",
3
3
  "type": "module",
4
- "version": "0.2.2",
4
+ "version": "0.3.1",
5
5
  "description": "TypeScript plugin for Nuxt",
6
6
  "author": "KazariEX",
7
7
  "license": "MIT",
@@ -16,18 +16,18 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@nuxt/kit": "^4.2.1",
20
- "chokidar": "^4.0.3",
19
+ "@nuxt/kit": "^4.2.2",
20
+ "chokidar": "^5.0.0",
21
21
  "pathe": "^2.0.3",
22
22
  "tinyglobby": "^0.2.15",
23
23
  "@dxup/unimport": "^0.1.2"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@dxup/shared": "",
27
- "@volar/language-core": "^2.4.23",
28
- "@volar/typescript": "^2.4.23",
29
- "@vue/language-core": "^3.1.3",
30
- "nuxt": "^4.2.1",
27
+ "@volar/language-core": "^2.4.26",
28
+ "@volar/typescript": "^2.4.26",
29
+ "@vue/language-core": "^3.1.8",
30
+ "nuxt": "^4.2.2",
31
31
  "typescript": "^5.9.3"
32
32
  },
33
33
  "scripts": {