@dxup/nuxt 0.1.0 → 0.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.
package/README.md CHANGED
@@ -9,6 +9,7 @@ This is a TypeScript plugin that improves Nuxt DX.
9
9
  ## Features
10
10
 
11
11
  - Update references when renaming auto imported component files
12
+ - Go to definition for dynamic imports with glob patterns
12
13
  - Go to definition for nitro routes in data fetching methods
13
14
  - Go to definition for runtime config
14
15
  - [@dxup/unimport](/packages/unimport)
package/dist/module.d.ts CHANGED
@@ -2,10 +2,33 @@ import * as _nuxt_schema0 from "@nuxt/schema";
2
2
 
3
3
  //#region src/module/index.d.ts
4
4
  interface ModuleOptions {
5
- components?: boolean;
6
- nitroRoutes?: boolean;
7
- runtimeConfig?: boolean;
8
- unimport?: boolean;
5
+ features?: {
6
+ /**
7
+ * Whether to update references when renaming auto imported component files.
8
+ * @default true
9
+ */
10
+ components?: boolean;
11
+ /**
12
+ * Whether to enable Go to Definition for dynamic imports with glob patterns.
13
+ * @default true
14
+ */
15
+ importGlob?: boolean;
16
+ /**
17
+ * Whether to enable Go to Definition for nitro routes in data fetching methods.
18
+ * @default true
19
+ */
20
+ nitroRoutes?: boolean;
21
+ /**
22
+ * Whether to enable Go to Definition for runtime config.
23
+ * @default true
24
+ */
25
+ runtimeConfig?: boolean;
26
+ /**
27
+ * Whether to enable enhanced navigation for auto imported APIs.
28
+ * @default true
29
+ */
30
+ unimport?: boolean;
31
+ };
9
32
  }
10
33
  declare const _default: _nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
11
34
  //#endregion
package/dist/module.js CHANGED
@@ -1,4 +1,4 @@
1
- import { addTemplate, defineNuxtModule } from "@nuxt/kit";
1
+ import { addTemplate, defineNuxtModule, useNitro } from "@nuxt/kit";
2
2
  import { Buffer } from "node:buffer";
3
3
  import EventEmitter from "node:events";
4
4
  import { mkdir, open, readFile, writeFile } from "node:fs/promises";
@@ -69,15 +69,16 @@ var module_default = defineNuxtModule({
69
69
  name,
70
70
  configKey: "dxup"
71
71
  },
72
- defaults: {
72
+ defaults: { features: {
73
73
  components: true,
74
+ importGlob: true,
74
75
  nitroRoutes: true,
75
76
  runtimeConfig: true,
76
77
  unimport: true
77
- },
78
+ } },
78
79
  async setup(options, nuxt) {
79
80
  const pluginsTs = [{ name: "@dxup/nuxt" }];
80
- if (options.unimport) pluginsTs.unshift({ name: "@dxup/unimport" });
81
+ if (options.features?.unimport) pluginsTs.unshift({ name: "@dxup/unimport" });
81
82
  append(pluginsTs, nuxt.options, "typescript", "tsConfig", "compilerOptions");
82
83
  append(pluginsTs, nuxt.options.nitro, "typescript", "tsConfig", "compilerOptions");
83
84
  append(pluginsTs, nuxt.options, "typescript", "sharedTsConfig", "compilerOptions");
@@ -85,14 +86,16 @@ var module_default = defineNuxtModule({
85
86
  addTemplate({
86
87
  filename: "dxup/data.json",
87
88
  write: true,
88
- getContents() {
89
+ getContents({ nuxt: nuxt$1 }) {
90
+ const nitro = useNitro();
91
+ const nitroRoutes = options.features?.nitroRoutes && Object.fromEntries(nitro.scannedHandlers.filter((item) => item.route).map((item) => [`${item.route}+${item.method ?? "get"}`, item.handler]));
89
92
  const data = {
90
- buildDir: nuxt.options.buildDir,
91
- serverDir: nuxt.options.serverDir,
92
- configFiles: [...nuxt.options._nuxtConfigFiles, ...nuxt.options._layers.map((layer) => layer._configFile).filter(Boolean)],
93
- components: options.components,
94
- nitroRoutes: options.nitroRoutes,
95
- runtimeConfig: options.runtimeConfig
93
+ buildDir: nuxt$1.options.buildDir,
94
+ configFiles: [...nuxt$1.options._nuxtConfigFiles, ...nuxt$1.options._layers.map((layer) => layer._configFile).filter(Boolean)],
95
+ components: options.features?.components,
96
+ importGlob: options.features?.importGlob,
97
+ nitroRoutes,
98
+ runtimeConfig: options.features?.runtimeConfig
96
99
  };
97
100
  return JSON.stringify(data, null, 2);
98
101
  }
@@ -25,6 +25,8 @@ let pathe = require("pathe");
25
25
  pathe = __toESM(pathe);
26
26
  let node_fs_promises = require("node:fs/promises");
27
27
  node_fs_promises = __toESM(node_fs_promises);
28
+ let tinyglobby = require("tinyglobby");
29
+ tinyglobby = __toESM(tinyglobby);
28
30
 
29
31
  //#region src/event/server.ts
30
32
  function createEventServer(info) {
@@ -63,10 +65,8 @@ function* binaryVisit(ts, sourceFile, node, position) {
63
65
  while (left <= right) {
64
66
  const mid = Math.floor((left + right) / 2);
65
67
  const node$1 = nodes[mid];
66
- const start = node$1.getStart(sourceFile);
67
- const end = node$1.getEnd();
68
- if (position < start) right = mid - 1;
69
- else if (position > end) left = mid + 1;
68
+ if (position > node$1.getEnd()) left = mid + 1;
69
+ else if (position < node$1.getStart(sourceFile)) right = mid - 1;
70
70
  else {
71
71
  yield node$1;
72
72
  yield* binaryVisit(ts, sourceFile, node$1, position);
@@ -74,6 +74,9 @@ function* binaryVisit(ts, sourceFile, node, position) {
74
74
  }
75
75
  }
76
76
  }
77
+ function isTextSpanEqual(node, textSpan, sourceFile) {
78
+ return textSpan.start + textSpan.length === node.getEnd() && textSpan.start === node.getStart(sourceFile);
79
+ }
77
80
 
78
81
  //#endregion
79
82
  //#region src/typescript/features/getDefinitionAndBoundSpan.ts
@@ -82,49 +85,21 @@ const fetchFunctions = new Set([
82
85
  "useFetch",
83
86
  "useLazyFetch"
84
87
  ]);
85
- const nonApiRE = /^(?!\/api\/)/;
86
88
  function getDefinitionAndBoundSpan(context, getDefinitionAndBoundSpan$1) {
87
89
  const { ts, info, data } = context;
88
90
  return (...args) => {
89
91
  const result = getDefinitionAndBoundSpan$1(...args);
90
- if (!result && data.nitroRoutes) {
92
+ if (!result) {
91
93
  const program$1 = info.languageService.getProgram();
92
94
  const sourceFile = program$1.getSourceFile(args[0]);
93
95
  if (!sourceFile) return;
94
96
  const checker = program$1.getTypeChecker();
97
+ let res;
95
98
  for (const node of forEachTouchingNode(ts, sourceFile, args[1])) {
96
- if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression) || !fetchFunctions.has(node.expression.text) || !node.arguments.length) continue;
97
- const firstArg = node.arguments[0];
98
- const start = firstArg.getStart(sourceFile);
99
- const end = firstArg.getEnd();
100
- if (args[1] < start || args[1] > end) continue;
101
- const resolvedSignature = checker.getResolvedSignature(node);
102
- if (!resolvedSignature) break;
103
- const typeArguments = checker.getTypeArgumentsForResolvedSignature(resolvedSignature);
104
- const [routeType, methodType] = (node.expression.text === "$fetch" ? typeArguments?.[2] && checker.getTypeArguments(typeArguments?.[2]) : typeArguments?.slice(2)) ?? [];
105
- if (!routeType?.isStringLiteral() || !methodType?.isStringLiteral()) break;
106
- const route = routeType.value.replace(nonApiRE, "/routes");
107
- const method = methodType.value;
108
- const path = (0, pathe.join)(data.serverDir, `${route}.${method}.ts`);
109
- return {
110
- textSpan: {
111
- start,
112
- length: end - start
113
- },
114
- definitions: [{
115
- fileName: path,
116
- textSpan: {
117
- start: 0,
118
- length: 0
119
- },
120
- kind: ts.ScriptElementKind.scriptElement,
121
- name: path,
122
- containerKind: ts.ScriptElementKind.unknown,
123
- containerName: ""
124
- }]
125
- };
99
+ if (data.importGlob) res ??= visitImportGlob(ts, info, sourceFile, node, args[1]);
100
+ if (data.nitroRoutes) res ??= visitNitroRoutes(ts, checker, sourceFile, node, args[1], data.nitroRoutes);
126
101
  }
127
- return;
102
+ if (res) return res;
128
103
  }
129
104
  if (!result?.definitions?.length) return result;
130
105
  const program = info.languageService.getProgram();
@@ -145,6 +120,88 @@ function getDefinitionAndBoundSpan(context, getDefinitionAndBoundSpan$1) {
145
120
  };
146
121
  };
147
122
  }
123
+ function visitImportGlob(ts, info, sourceFile, node, position) {
124
+ if (!ts.isCallExpression(node) || !node.arguments.length) return;
125
+ const firstArg = node.arguments[0];
126
+ const start = firstArg.getStart(sourceFile);
127
+ const end = firstArg.getEnd();
128
+ if (position < start || position > end) return;
129
+ let pattern;
130
+ const callText = node.expression.getText(sourceFile);
131
+ if (callText === "import" && ts.isTemplateExpression(firstArg)) pattern = [firstArg.head.text, ...firstArg.templateSpans.map((span) => span.literal.text)].join("*");
132
+ else if (callText === "import.meta.glob" && ts.isStringLiteral(firstArg)) pattern = firstArg.text;
133
+ if (pattern === void 0) return;
134
+ const resolved = ts.resolveModuleName(pattern, sourceFile.fileName, info.languageServiceHost.getCompilationSettings(), {
135
+ fileExists: () => true,
136
+ readFile: () => ""
137
+ });
138
+ if (!resolved?.resolvedModule) return;
139
+ const extension = (0, pathe.extname)(pattern);
140
+ const arbitrary = `.d${extension}.ts`;
141
+ pattern = resolved.resolvedModule.resolvedFileName;
142
+ if (resolved.resolvedModule.extension === arbitrary) pattern = pattern.slice(0, -arbitrary.length) + extension;
143
+ const fileNames = (0, tinyglobby.globSync)(pattern, { absolute: true });
144
+ return {
145
+ textSpan: {
146
+ start,
147
+ length: end - start
148
+ },
149
+ definitions: fileNames.map((fileName) => ({
150
+ fileName,
151
+ textSpan: {
152
+ start: 0,
153
+ length: 0
154
+ },
155
+ kind: ts.ScriptElementKind.unknown,
156
+ name: fileName,
157
+ containerKind: ts.ScriptElementKind.unknown,
158
+ containerName: ""
159
+ }))
160
+ };
161
+ }
162
+ function visitNitroRoutes(ts, checker, sourceFile, node, position, nitroRoutes) {
163
+ if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression) || !fetchFunctions.has(node.expression.text) || !node.arguments.length) return;
164
+ const firstArg = node.arguments[0];
165
+ const start = firstArg.getStart(sourceFile);
166
+ const end = firstArg.getEnd();
167
+ if (position < start || position > end) return;
168
+ const resolvedSignature = checker.getResolvedSignature(node);
169
+ if (!resolvedSignature) return;
170
+ const typeArguments = checker.getTypeArgumentsForResolvedSignature(resolvedSignature);
171
+ let routeType;
172
+ let methodType;
173
+ if (node.expression.text === "$fetch") {
174
+ routeType = typeArguments?.[1];
175
+ const symbol = typeArguments?.[2].getProperty("method");
176
+ methodType = symbol ? checker.getTypeOfSymbol(symbol) : void 0;
177
+ } else {
178
+ routeType = typeArguments?.[2];
179
+ methodType = typeArguments?.[3];
180
+ }
181
+ if (!routeType?.isStringLiteral()) return;
182
+ const paths = [];
183
+ for (const type of methodType?.isUnion() ? methodType.types : [methodType]) if (type?.isStringLiteral()) {
184
+ const path = nitroRoutes[`${routeType.value}+${type.value}`];
185
+ if (path !== void 0) paths.push(path);
186
+ }
187
+ return {
188
+ textSpan: {
189
+ start,
190
+ length: end - start
191
+ },
192
+ definitions: paths.map((path) => ({
193
+ fileName: path,
194
+ textSpan: {
195
+ start: 0,
196
+ length: 0
197
+ },
198
+ kind: ts.ScriptElementKind.scriptElement,
199
+ name: path,
200
+ containerKind: ts.ScriptElementKind.unknown,
201
+ containerName: ""
202
+ }))
203
+ };
204
+ }
148
205
  function visitRuntimeConfig(context, sourceFile, definition) {
149
206
  const { ts } = context;
150
207
  let definitions = [];
@@ -154,10 +211,7 @@ function visitRuntimeConfig(context, sourceFile, definition) {
154
211
  if (ts.isInterfaceDeclaration(node) && ts.isIdentifier(node.name)) key = node.name.text;
155
212
  else if (ts.isPropertySignature(node) && ts.isIdentifier(node.name)) {
156
213
  key = node.name.text;
157
- const { textSpan } = definition;
158
- const start = node.name.getStart(sourceFile);
159
- const end = node.name.getEnd();
160
- if (start === textSpan.start && end - start === textSpan.length) {
214
+ if (isTextSpanEqual(node.name, definition.textSpan, sourceFile)) {
161
215
  path.push(key);
162
216
  definitions = [...forwardRuntimeConfig(context, definition, path)];
163
217
  break;
@@ -280,36 +334,49 @@ function getEditsForFileRename(context, getEditsForFileRename$1) {
280
334
  const plugin = (module$1) => {
281
335
  const { typescript: ts } = module$1;
282
336
  return { create(info) {
283
- const currentDirectory = info.languageServiceHost.getCurrentDirectory();
284
- const path = (0, pathe.join)(currentDirectory, "dxup/data.json");
285
337
  const context = {
286
338
  ts,
287
339
  info,
288
- data: {
289
- buildDir: currentDirectory,
290
- configFiles: [],
291
- components: true,
292
- nitroRoutes: true,
293
- runtimeConfig: true,
294
- ...JSON.parse(ts.sys.readFile(path) ?? "{}")
295
- },
340
+ data: createData(ts, info),
296
341
  server: createEventServer(info)
297
342
  };
298
343
  setTimeout(() => {
299
344
  context.language = (info.project.__vue__ ?? info.project["program"]?.__vue__)?.language;
300
345
  }, 500);
301
346
  for (const [key, method] of [
302
- ["findRenameLocations", findRenameLocations.bind(null, context)],
303
- ["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan.bind(null, context)],
304
- ["getEditsForFileRename", getEditsForFileRename.bind(null, context)]
347
+ ["findRenameLocations", findRenameLocations],
348
+ ["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan],
349
+ ["getEditsForFileRename", getEditsForFileRename]
305
350
  ]) {
306
351
  const original = info.languageService[key];
307
- info.languageService[key] = method(original);
352
+ info.languageService[key] = method(context, original);
308
353
  }
309
354
  return info.languageService;
310
355
  } };
311
356
  };
312
357
  var typescript_default = plugin;
358
+ function createData(ts, info) {
359
+ const initialValue = {
360
+ buildDir: "",
361
+ configFiles: [],
362
+ components: true,
363
+ importGlob: true,
364
+ nitroRoutes: {},
365
+ runtimeConfig: true
366
+ };
367
+ const path = (0, pathe.join)(info.languageServiceHost.getCurrentDirectory(), "dxup/data.json");
368
+ const data = {};
369
+ update();
370
+ ts.sys.watchFile?.(path, update);
371
+ return data;
372
+ function update() {
373
+ const text = ts.sys.readFile(path);
374
+ Object.assign(data, {
375
+ ...initialValue,
376
+ ...text ? JSON.parse(text) : {}
377
+ });
378
+ }
379
+ }
313
380
 
314
381
  //#endregion
315
382
  module.exports = typescript_default;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dxup/nuxt",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "TypeScript plugin for Nuxt",
6
6
  "author": "KazariEX",
7
7
  "license": "MIT",
@@ -19,6 +19,7 @@
19
19
  "@nuxt/kit": "^4.1.3",
20
20
  "chokidar": "^4.0.3",
21
21
  "pathe": "2.0.3",
22
+ "tinyglobby": "^0.2.15",
22
23
  "@dxup/unimport": "^0.1.0"
23
24
  },
24
25
  "devDependencies": {