@dxup/nuxt 0.2.1 → 0.3.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.
package/README.md CHANGED
@@ -6,31 +6,40 @@
6
6
 
7
7
  This is a TypeScript plugin that improves Nuxt DX.
8
8
 
9
+ > [!note]
10
+ > It's now an experimental builtin feature of Nuxt. Please refer to the [documentation](https://nuxt.com/docs/4.x/guide/going-further/experimental-features#typescriptplugin) for more details.
11
+
9
12
  ## Installation
10
13
 
11
- ```bash
12
- pnpm i -D @dxup/nuxt
13
- ```
14
+ *No installation is required if you are using Nuxt 4.2 or above.*
14
15
 
15
16
  ## Usage
16
17
 
17
- 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.
18
19
 
19
- ```ts
20
- export default defineNuxtConfig({
21
- modules: [
22
- "@dxup/nuxt",
23
- ],
24
- });
25
- ```
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.
26
31
 
27
32
  ## Features
28
33
 
29
- ### components
34
+ ### 1. components
30
35
 
31
36
  Update references when renaming auto imported component files.
32
37
 
33
- ### importGlob
38
+ For example, when renaming `components/foo/bar.vue` to `components/foo/baz.vue`, all usages of `<FooBar />` will be updated to `<FooBaz />`.
39
+
40
+ It only works when the dev server is active.
41
+
42
+ ### 2. importGlob
34
43
 
35
44
  Go to definition for dynamic imports with glob patterns.
36
45
 
@@ -41,7 +50,7 @@ import.meta.glob("~/assets/*.webp");
41
50
  // ^^^^^^^^^^^^^^^^^
42
51
  ```
43
52
 
44
- ### nitroRoutes
53
+ ### 3. nitroRoutes
45
54
 
46
55
  Go to definition for nitro routes in data fetching methods.
47
56
 
@@ -53,7 +62,7 @@ useFetch("/api/foo");
53
62
 
54
63
  It will fallback to resolve the URL from your `public` directory when no nitro routes match.
55
64
 
56
- ### runtimeConfig
65
+ ### 4. runtimeConfig
57
66
 
58
67
  Go to definition for runtime config.
59
68
 
@@ -64,6 +73,14 @@ Go to definition for runtime config.
64
73
  </template>
65
74
  ```
66
75
 
67
- ### unimport
76
+ ### 5. unimport
77
+
78
+ Find references for SFC on `<template>`.
79
+
80
+ ```vue
81
+ ....<template>
82
+ <!-- ^^^^^^^^ -->
83
+ </template>
84
+ ```
68
85
 
69
86
  Please refer to the [@dxup/unimport](/packages/unimport) package for more details.
@@ -27,9 +27,22 @@ interface ModuleOptions {
27
27
  * Whether to enable enhanced navigation for auto imported APIs.
28
28
  * @default true
29
29
  */
30
- unimport?: boolean;
30
+ unimport?: boolean | {
31
+ /**
32
+ * Whether to enable Find References for SFC on `<template>`.
33
+ */
34
+ componentReferences: boolean;
35
+ };
31
36
  };
32
37
  }
33
- declare const _default: _nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
38
+ declare const _default: _nuxt_schema0.NuxtModule<ModuleOptions, {
39
+ features: {
40
+ components: true;
41
+ importGlob: true;
42
+ nitroRoutes: true;
43
+ runtimeConfig: true;
44
+ unimport: true;
45
+ };
46
+ }, true>;
34
47
  //#endregion
35
48
  export { ModuleOptions, _default as default };
@@ -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"
@@ -87,18 +87,24 @@ var module_default = defineNuxtModule({
87
87
  filename: "dxup/data.json",
88
88
  write: true,
89
89
  getContents({ nuxt: nuxt$1 }) {
90
- const nitro = useNitro();
90
+ const nitroRoutes = useNitro().scannedHandlers.reduce((acc, item) => {
91
+ if (item.route && item.method) (acc[item.route] ??= {})[item.method] = item.handler;
92
+ return acc;
93
+ }, {});
91
94
  const data = {
92
95
  buildDir: nuxt$1.options.buildDir,
93
96
  publicDir: nuxt$1.options.dir.public,
94
97
  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
98
+ nitroRoutes,
99
+ features: {
100
+ ...options.features,
101
+ unimport: { componentReferences: typeof options.features.unimport === "object" ? options.features.unimport.componentReferences : options.features.unimport }
102
+ }
97
103
  };
98
104
  return JSON.stringify(data, null, 2);
99
105
  }
100
106
  });
101
- (await createEventClient(nuxt)).on("components:rename", (data) => onComponentsRename(nuxt, data));
107
+ if (nuxt.options.dev) (await createEventClient(nuxt)).on("components:rename", (data) => onComponentsRename(nuxt, data));
102
108
  }
103
109
  });
104
110
  function append(plugins, target, ...keys) {
@@ -1,32 +1,23 @@
1
1
  //#region rolldown:runtime
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
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
14
9
  });
15
10
  }
16
- return to;
11
+ if (symbols) {
12
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ }
14
+ return target;
17
15
  };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
16
 
23
17
  //#endregion
24
- let pathe = require("pathe");
25
- pathe = __toESM(pathe);
26
18
  let node_fs_promises = require("node:fs/promises");
27
- node_fs_promises = __toESM(node_fs_promises);
19
+ let pathe = require("pathe");
28
20
  let tinyglobby = require("tinyglobby");
29
- tinyglobby = __toESM(tinyglobby);
30
21
 
31
22
  //#region src/event/server.ts
32
23
  function createEventServer(info) {
@@ -39,12 +30,115 @@ function createEventServer(info) {
39
30
  return { write };
40
31
  }
41
32
 
33
+ //#endregion
34
+ //#region src/typescript/data.ts
35
+ const initialValue = {
36
+ buildDir: "",
37
+ publicDir: "",
38
+ configFiles: [],
39
+ nitroRoutes: {},
40
+ features: {
41
+ components: true,
42
+ importGlob: true,
43
+ nitroRoutes: true,
44
+ runtimeConfig: true,
45
+ unimport: { componentReferences: true }
46
+ }
47
+ };
48
+ const callbacks = {};
49
+ function createData(ts, info) {
50
+ const path = (0, pathe.join)(info.languageServiceHost.getCurrentDirectory(), "dxup/data.json");
51
+ const data = {};
52
+ const updates = callbacks[path] ??= (ts.sys.watchFile?.(path, () => {
53
+ const text$1 = ts.sys.readFile(path);
54
+ for (const update of updates) update(text$1);
55
+ }), []);
56
+ updates.push((text$1) => {
57
+ Object.assign(data, {
58
+ ...initialValue,
59
+ ...text$1 ? JSON.parse(text$1) : {}
60
+ });
61
+ });
62
+ const text = ts.sys.readFile(path);
63
+ updates.at(-1)(text);
64
+ return data;
65
+ }
66
+
67
+ //#endregion
68
+ //#region src/typescript/utils.ts
69
+ function isVueVirtualCode(code) {
70
+ return code?.languageId === "vue";
71
+ }
72
+ function toSourceSpan(language, fileName, textSpan) {
73
+ const sourceScript = language?.scripts.get(fileName);
74
+ if (!sourceScript?.generated) return;
75
+ const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
76
+ if (!serviceScript) return;
77
+ const map = language.maps.get(serviceScript.code, sourceScript);
78
+ const leadingOffset = sourceScript.snapshot.getLength();
79
+ for (const [start, end] of map.toSourceRange(textSpan.start - leadingOffset, textSpan.start + textSpan.length - leadingOffset, false)) return {
80
+ start,
81
+ length: end - start
82
+ };
83
+ }
84
+ function withVirtualOffset(language, sourceScript, position, method) {
85
+ const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
86
+ if (!serviceScript) return;
87
+ const map = language.maps.get(serviceScript.code, sourceScript);
88
+ const leadingOffset = sourceScript.snapshot.getLength();
89
+ const offset = 1145141919810;
90
+ const mapping = {
91
+ sourceOffsets: [offset],
92
+ generatedOffsets: [position - leadingOffset],
93
+ lengths: [0],
94
+ data: {
95
+ completion: true,
96
+ navigation: true,
97
+ semantic: true,
98
+ verification: true
99
+ }
100
+ };
101
+ const original = map.toGeneratedLocation;
102
+ map.toGeneratedLocation = function* (sourceOffset, ...args) {
103
+ if (sourceOffset === offset) yield [mapping.generatedOffsets[0], mapping];
104
+ yield* original.call(this, sourceOffset, ...args);
105
+ };
106
+ try {
107
+ return method(offset);
108
+ } finally {
109
+ map.toGeneratedLocation = original;
110
+ }
111
+ }
112
+
113
+ //#endregion
114
+ //#region src/typescript/features/findReferences.ts
115
+ var findReferences_exports = /* @__PURE__ */ __export({ postprocess: () => postprocess$1 });
116
+ function postprocess$1(context, language, findReferences) {
117
+ const { ts, info } = context;
118
+ return (...args) => {
119
+ const result = findReferences(...args);
120
+ if (!result?.length) {
121
+ const sourceScript = language.scripts.get(args[0]);
122
+ const root = sourceScript?.generated?.root;
123
+ if (!isVueVirtualCode(root)) return;
124
+ const start = (root.sfc.template?.start ?? Infinity) + 1;
125
+ if (args[1] < start || args[1] > start + 8) return;
126
+ const sourceFile = info.languageService.getProgram().getSourceFile(args[0]);
127
+ if (!sourceFile) return;
128
+ for (const statement of sourceFile.statements) if (ts.isExportAssignment(statement)) return withVirtualOffset(language, sourceScript, statement.getChildAt(1).getStart(sourceFile), (position) => findReferences(args[0], position));
129
+ return;
130
+ }
131
+ return result;
132
+ };
133
+ }
134
+
42
135
  //#endregion
43
136
  //#region src/typescript/features/findRenameLocations.ts
44
- function findRenameLocations(context, findRenameLocations$1) {
137
+ var findRenameLocations_exports = /* @__PURE__ */ __export({ preprocess: () => preprocess$2 });
138
+ function preprocess$2(context, findRenameLocations) {
45
139
  const { data } = context;
46
140
  return (...args) => {
47
- return findRenameLocations$1(...args)?.filter((edit) => {
141
+ return findRenameLocations(...args)?.filter((edit) => {
48
142
  return !edit.fileName.startsWith(data.buildDir);
49
143
  });
50
144
  };
@@ -80,15 +174,46 @@ function isTextSpanWithin(node, textSpan, sourceFile) {
80
174
 
81
175
  //#endregion
82
176
  //#region src/typescript/features/getDefinitionAndBoundSpan.ts
177
+ var getDefinitionAndBoundSpan_exports = /* @__PURE__ */ __export({
178
+ postprocess: () => postprocess,
179
+ preprocess: () => preprocess$1
180
+ });
83
181
  const fetchFunctions = new Set([
84
182
  "$fetch",
85
183
  "useFetch",
86
184
  "useLazyFetch"
87
185
  ]);
88
- function getDefinitionAndBoundSpan(context, getDefinitionAndBoundSpan$1) {
186
+ function postprocess(context, language, getDefinitionAndBoundSpan) {
187
+ const { ts } = context;
188
+ return (...args) => {
189
+ const result = getDefinitionAndBoundSpan(...args);
190
+ if (!result?.definitions?.length) {
191
+ const root = language.scripts.get(args[0])?.generated?.root;
192
+ if (!isVueVirtualCode(root)) return result;
193
+ const textSpan = {
194
+ start: (root.sfc.template?.start ?? Infinity) + 1,
195
+ length: 8
196
+ };
197
+ if (args[1] >= textSpan.start && args[1] <= textSpan.start + textSpan.length) return {
198
+ textSpan,
199
+ definitions: [{
200
+ fileName: args[0],
201
+ textSpan,
202
+ kind: ts.ScriptElementKind.scriptElement,
203
+ name: args[0],
204
+ containerKind: ts.ScriptElementKind.unknown,
205
+ containerName: ""
206
+ }]
207
+ };
208
+ return result;
209
+ }
210
+ return result;
211
+ };
212
+ }
213
+ function preprocess$1(context, getDefinitionAndBoundSpan) {
89
214
  const { ts, info, data } = context;
90
215
  return (...args) => {
91
- const result = getDefinitionAndBoundSpan$1(...args);
216
+ const result = getDefinitionAndBoundSpan(...args);
92
217
  if (!result) {
93
218
  const program$1 = info.languageService.getProgram();
94
219
  const sourceFile = program$1.getSourceFile(args[0]);
@@ -180,8 +305,11 @@ function visitNitroRoutes(ts, data, checker, sourceFile, node, position) {
180
305
  }
181
306
  const paths = [];
182
307
  if (routeType?.isStringLiteral()) {
183
- for (const type of methodType?.isUnion() ? methodType.types : [methodType]) if (type?.isStringLiteral()) {
184
- const path = data.nitroRoutes[`${routeType.value}+${type.value}`];
308
+ const alternatives = data.nitroRoutes[routeType.value] ?? {};
309
+ const methods = [];
310
+ for (const type of methodType?.isUnion() ? methodType.types : [methodType]) if (type?.isStringLiteral()) methods.push(type.value);
311
+ for (const method of methods.length ? methods : Object.keys(alternatives)) {
312
+ const path = alternatives[method];
185
313
  if (path !== void 0) paths.push(path);
186
314
  }
187
315
  }
@@ -282,27 +410,13 @@ function* forwardRuntimeConfig(context, definition, path) {
282
410
  }
283
411
  }
284
412
 
285
- //#endregion
286
- //#region src/typescript/utils.ts
287
- function toSourceSpan(language, fileName, textSpan) {
288
- const sourceScript = language?.scripts.get(fileName);
289
- if (!sourceScript?.generated) return;
290
- const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
291
- if (!serviceScript) return;
292
- const map = language.maps.get(serviceScript.code, sourceScript);
293
- const leadingOffset = sourceScript.snapshot.getLength();
294
- for (const [start, end] of map.toSourceRange(textSpan.start - leadingOffset, textSpan.start + textSpan.length - leadingOffset, false)) return {
295
- start,
296
- length: end - start
297
- };
298
- }
299
-
300
413
  //#endregion
301
414
  //#region src/typescript/features/getEditsForFileRename.ts
302
- function getEditsForFileRename(context, getEditsForFileRename$1) {
415
+ var getEditsForFileRename_exports = /* @__PURE__ */ __export({ preprocess: () => preprocess });
416
+ function preprocess(context, getEditsForFileRename) {
303
417
  const { ts, info, data, server } = context;
304
418
  return (...args) => {
305
- const result = getEditsForFileRename$1(...args);
419
+ const result = getEditsForFileRename(...args);
306
420
  if (!result?.length) return result;
307
421
  const program = info.languageService.getProgram();
308
422
  const references = {};
@@ -339,53 +453,43 @@ function getEditsForFileRename(context, getEditsForFileRename$1) {
339
453
  const plugin = (module$1) => {
340
454
  const { typescript: ts } = module$1;
341
455
  return { create(info) {
456
+ const data = createData(ts, info);
342
457
  const context = {
343
458
  ts,
344
459
  info,
345
- data: createData(ts, info),
460
+ data,
346
461
  server: createEventServer(info)
347
462
  };
348
463
  setTimeout(() => {
349
- context.language = (info.project.__vue__ ?? info.project["program"]?.__vue__)?.language;
350
- }, 500);
464
+ context.language = info.project.__vue__?.language;
465
+ if (!context.language || !data.features.unimport.componentReferences) return;
466
+ const languageService = info.project.getLanguageService();
467
+ const methods = {};
468
+ for (const [key, method] of [["findReferences", findReferences_exports], ["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan_exports]]) {
469
+ const original = languageService[key];
470
+ methods[key] = method.postprocess(context, context.language, original);
471
+ }
472
+ info.project["languageService"] = new Proxy(languageService, {
473
+ get(target, p, receiver) {
474
+ return methods[p] ?? Reflect.get(target, p, receiver);
475
+ },
476
+ set(...args) {
477
+ return Reflect.set(...args);
478
+ }
479
+ });
480
+ }, 0);
351
481
  for (const [key, method] of [
352
- ["findRenameLocations", findRenameLocations],
353
- ["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan],
354
- ["getEditsForFileRename", getEditsForFileRename]
482
+ ["findRenameLocations", findRenameLocations_exports],
483
+ ["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan_exports],
484
+ ["getEditsForFileRename", getEditsForFileRename_exports]
355
485
  ]) {
356
486
  const original = info.languageService[key];
357
- info.languageService[key] = method(context, original);
487
+ info.languageService[key] = method.preprocess(context, original);
358
488
  }
359
489
  return info.languageService;
360
490
  } };
361
491
  };
362
492
  var typescript_default = plugin;
363
- function createData(ts, info) {
364
- const initialValue = {
365
- buildDir: "",
366
- publicDir: "",
367
- configFiles: [],
368
- nitroRoutes: {},
369
- features: {
370
- components: true,
371
- importGlob: true,
372
- nitroRoutes: true,
373
- runtimeConfig: true
374
- }
375
- };
376
- const path = (0, pathe.join)(info.languageServiceHost.getCurrentDirectory(), "dxup/data.json");
377
- const data = {};
378
- update();
379
- ts.sys.watchFile?.(path, update);
380
- return data;
381
- function update() {
382
- const text = ts.sys.readFile(path);
383
- Object.assign(data, {
384
- ...initialValue,
385
- ...text ? JSON.parse(text) : {}
386
- });
387
- }
388
- }
389
493
 
390
494
  //#endregion
391
495
  module.exports = typescript_default;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@dxup/nuxt",
3
3
  "type": "module",
4
- "version": "0.2.1",
4
+ "version": "0.3.0",
5
5
  "description": "TypeScript plugin for Nuxt",
6
6
  "author": "KazariEX",
7
7
  "license": "MIT",
8
8
  "repository": "KazariEX/dxup",
9
9
  "exports": {
10
- ".": "./dist/module.js",
10
+ ".": "./dist/module.mjs",
11
11
  "./package.json": "./package.json"
12
12
  },
13
13
  "main": "./dist/typescript.cjs",
@@ -16,18 +16,18 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@nuxt/kit": "^4.2.0",
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
- "@dxup/unimport": "^0.1.1"
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.0",
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": {