@nx/nuxt 23.0.0-beta.23 → 23.0.0-beta.24
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/migrations.json +6 -0
- package/package.json +8 -8
- package/src/migrations/update-22-2-0/ai-instructions-for-nuxt-4.md +531 -0
- package/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.d.ts +10 -0
- package/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.d.ts.map +1 -0
- package/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.js +134 -0
- package/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md +25 -0
package/migrations.json
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
"version": "22.2.0-beta.0",
|
|
5
5
|
"description": "Create AI Instructions to help migrate workspaces to Nuxt 4.",
|
|
6
6
|
"prompt": "./src/migrations/update-22-2-0/ai-instructions-for-nuxt-4.md"
|
|
7
|
+
},
|
|
8
|
+
"update-23-0-0-migrate-create-nodes-v2-import": {
|
|
9
|
+
"version": "23.0.0-beta.24",
|
|
10
|
+
"description": "Rename imports of `createNodesV2` from `@nx/nuxt/plugin` to the canonical `createNodes` export.",
|
|
11
|
+
"implementation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes",
|
|
12
|
+
"documentation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md"
|
|
7
13
|
}
|
|
8
14
|
},
|
|
9
15
|
"packageJsonUpdates": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/nuxt",
|
|
3
|
-
"version": "23.0.0-beta.
|
|
3
|
+
"version": "23.0.0-beta.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Nuxt plugin for Nx contains executors and generators for managing Nuxt applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Vitest, Playwright, Cypress, and Storybook.\n\n- Generators for applications, libraries, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
|
6
6
|
"repository": {
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"tslib": "^2.3.0",
|
|
47
|
-
"@nx/devkit": "23.0.0-beta.
|
|
48
|
-
"@nx/js": "23.0.0-beta.
|
|
49
|
-
"@nx/eslint": "23.0.0-beta.
|
|
50
|
-
"@nx/vue": "23.0.0-beta.
|
|
51
|
-
"@nx/vite": "23.0.0-beta.
|
|
52
|
-
"@nx/vitest": "23.0.0-beta.
|
|
47
|
+
"@nx/devkit": "23.0.0-beta.24",
|
|
48
|
+
"@nx/js": "23.0.0-beta.24",
|
|
49
|
+
"@nx/eslint": "23.0.0-beta.24",
|
|
50
|
+
"@nx/vue": "23.0.0-beta.24",
|
|
51
|
+
"@nx/vite": "23.0.0-beta.24",
|
|
52
|
+
"@nx/vitest": "23.0.0-beta.24",
|
|
53
53
|
"semver": "^7.6.3"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"nx": "23.0.0-beta.
|
|
56
|
+
"nx": "23.0.0-beta.24"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"nuxt": ">=3.0.0 <5.0.0",
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
# Nuxt 3 to Nuxt 4 Migration Instructions
|
|
2
|
+
|
|
3
|
+
This document provides instructions for an AI Agent to assist with migrating Nuxt 3 projects to Nuxt 4.
|
|
4
|
+
|
|
5
|
+
## Pre-Migration Checklist
|
|
6
|
+
|
|
7
|
+
Before starting the migration, run these commands to understand the scope:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# List all Nuxt projects in the workspace
|
|
11
|
+
nx show projects --with-target build | xargs -I {} sh -c 'cat {}/nuxt.config.ts 2>/dev/null && echo "Project: {}"' | grep -B1 "Project:"
|
|
12
|
+
|
|
13
|
+
# Find all files that may need updates
|
|
14
|
+
find . -name "*.vue" -o -name "*.ts" | head -50
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Section 1: Configuration Updates
|
|
20
|
+
|
|
21
|
+
### 1.1 Directory Structure (Optional but Recommended)
|
|
22
|
+
|
|
23
|
+
**Search Pattern**: Check `nuxt.config.ts` for `srcDir` configuration
|
|
24
|
+
|
|
25
|
+
Nuxt 4 introduces a new default directory structure using `app/` instead of `src/`.
|
|
26
|
+
|
|
27
|
+
**If adopting new structure:**
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// Before (Nuxt 3 with srcDir)
|
|
31
|
+
export default defineNuxtConfig({
|
|
32
|
+
srcDir: 'src',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// After (Nuxt 4 - remove srcDir, move files to app/)
|
|
36
|
+
export default defineNuxtConfig({
|
|
37
|
+
// srcDir removed - app/ is now the default
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Action Items:**
|
|
42
|
+
|
|
43
|
+
- [ ] Move `src/` contents to `app/` directory
|
|
44
|
+
- [ ] Keep `server/`, `public/`, `layers/`, `modules/` at project root
|
|
45
|
+
- [ ] Remove `srcDir` from `nuxt.config.ts`
|
|
46
|
+
|
|
47
|
+
**OR to keep existing structure:**
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
export default defineNuxtConfig({
|
|
51
|
+
srcDir: '.',
|
|
52
|
+
dir: { app: 'app' },
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 1.2 Remove Deprecated `generate` Configuration
|
|
57
|
+
|
|
58
|
+
**Search Pattern**: `grep -r "generate:" --include="nuxt.config.ts"`
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Before
|
|
62
|
+
export default defineNuxtConfig({
|
|
63
|
+
generate: {
|
|
64
|
+
exclude: ['/admin'],
|
|
65
|
+
routes: ['/sitemap.xml'],
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// After
|
|
70
|
+
export default defineNuxtConfig({
|
|
71
|
+
nitro: {
|
|
72
|
+
prerender: {
|
|
73
|
+
ignore: ['/admin'],
|
|
74
|
+
routes: ['/sitemap.xml'],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 1.3 TypeScript Configuration
|
|
81
|
+
|
|
82
|
+
**Search Pattern**: Check `tsconfig.json` files
|
|
83
|
+
|
|
84
|
+
Nuxt 4 sets `noUncheckedIndexedAccess: true` by default.
|
|
85
|
+
|
|
86
|
+
**To override if needed:**
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
export default defineNuxtConfig({
|
|
90
|
+
typescript: {
|
|
91
|
+
tsConfig: {
|
|
92
|
+
compilerOptions: {
|
|
93
|
+
noUncheckedIndexedAccess: false,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Section 2: Data Fetching Updates
|
|
103
|
+
|
|
104
|
+
### 2.1 Default Values Changed from `null` to `undefined`
|
|
105
|
+
|
|
106
|
+
**Search Pattern**: `grep -rn "!== null\|=== null" --include="*.vue" --include="*.ts"`
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Before (Nuxt 3)
|
|
110
|
+
const { data } = await useAsyncData('key', () => fetch('/api/data'));
|
|
111
|
+
if (data.value !== null) {
|
|
112
|
+
/* ... */
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// After (Nuxt 4)
|
|
116
|
+
const { data } = await useAsyncData('key', () => fetch('/api/data'));
|
|
117
|
+
if (data.value !== undefined) {
|
|
118
|
+
/* ... */
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Automation available:**
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx codemod@latest nuxt/4/default-data-error-value
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 2.2 `getCachedData` Context Parameter
|
|
129
|
+
|
|
130
|
+
**Search Pattern**: `grep -rn "getCachedData" --include="*.vue" --include="*.ts"`
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Before
|
|
134
|
+
getCachedData: (key, nuxtApp) => cachedData[key];
|
|
135
|
+
|
|
136
|
+
// After
|
|
137
|
+
getCachedData: (key, nuxtApp, ctx) => {
|
|
138
|
+
// ctx.cause: 'initial' | 'refresh:hook' | 'refresh:manual' | 'watch'
|
|
139
|
+
if (ctx.cause === 'refresh:manual') return undefined;
|
|
140
|
+
return cachedData[key];
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 2.3 Shallow Data Reactivity
|
|
145
|
+
|
|
146
|
+
**Search Pattern**: `grep -rn "useAsyncData\|useFetch" --include="*.vue" --include="*.ts"`
|
|
147
|
+
|
|
148
|
+
Data from `useAsyncData`/`useFetch` is now `shallowRef` (not deep reactive).
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// If deep reactivity is needed:
|
|
152
|
+
const { data } = useFetch('/api/test', { deep: true });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Automation available:**
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx codemod@latest nuxt/4/shallow-function-reactivity
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 2.4 Removed `dedupe` Boolean Values
|
|
162
|
+
|
|
163
|
+
**Search Pattern**: `grep -rn "dedupe: true\|dedupe: false" --include="*.vue" --include="*.ts"`
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// Before
|
|
167
|
+
refresh({ dedupe: true });
|
|
168
|
+
refresh({ dedupe: false });
|
|
169
|
+
|
|
170
|
+
// After
|
|
171
|
+
refresh({ dedupe: 'cancel' });
|
|
172
|
+
refresh({ dedupe: 'defer' });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Automation available:**
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npx codemod@latest nuxt/4/deprecated-dedupe-value
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 2.5 Unique Keys for Shared Prerender Data
|
|
182
|
+
|
|
183
|
+
**Search Pattern**: Check dynamic route files `[*.vue` in `pages/`
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// Before (unsafe for dynamic routes)
|
|
187
|
+
const { data } = await useAsyncData(async () =>
|
|
188
|
+
$fetch(`/api/page/${route.params.slug}`)
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// After (safe - key includes slug)
|
|
192
|
+
const { data } = await useAsyncData(route.params.slug, async () =>
|
|
193
|
+
$fetch(`/api/page/${route.params.slug}`)
|
|
194
|
+
);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Section 3: Unhead v2 Migration
|
|
200
|
+
|
|
201
|
+
### 3.1 Remove Deprecated Props
|
|
202
|
+
|
|
203
|
+
**Search Pattern**: `grep -rn "hid:\|vmid:\|children:\|body:" --include="*.vue" --include="*.ts"`
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Before
|
|
207
|
+
useHead({
|
|
208
|
+
meta: [{ name: 'description', hid: 'description', content: 'My page' }],
|
|
209
|
+
script: [{ children: 'console.log("hello")' }],
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// After
|
|
213
|
+
useHead({
|
|
214
|
+
meta: [{ name: 'description', content: 'My page' }],
|
|
215
|
+
script: [{ innerHTML: 'console.log("hello")' }],
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Section 4: Component and Routing Changes
|
|
222
|
+
|
|
223
|
+
### 4.1 Normalized Component Names
|
|
224
|
+
|
|
225
|
+
**Search Pattern**: Check test files using `findComponent` and templates with `<KeepAlive>`
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// Component in SomeFolder/MyComponent.vue
|
|
229
|
+
// Before: findComponent({ name: 'MyComponent' })
|
|
230
|
+
// After: findComponent({ name: 'SomeFolderMyComponent' })
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**To disable if needed:**
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
export default defineNuxtConfig({
|
|
237
|
+
experimental: {
|
|
238
|
+
normalizeComponentNames: false,
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 4.2 Route Metadata Deduplication
|
|
244
|
+
|
|
245
|
+
**Search Pattern**: `grep -rn "route.meta.name\|route.meta.path" --include="*.vue" --include="*.ts"`
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Before
|
|
249
|
+
const name = route.meta.name;
|
|
250
|
+
|
|
251
|
+
// After
|
|
252
|
+
const name = route.name;
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Section 5: Removed Experimental Flags
|
|
258
|
+
|
|
259
|
+
These flags are now hardcoded and cannot be configured:
|
|
260
|
+
|
|
261
|
+
- `experimental.treeshakeClientOnly` → always `true`
|
|
262
|
+
- `experimental.configSchema` → always `true`
|
|
263
|
+
- `experimental.polyfillVueUseHead` → always `false`
|
|
264
|
+
- `experimental.respectNoSSRHeader` → always `false`
|
|
265
|
+
|
|
266
|
+
**Action Items:**
|
|
267
|
+
|
|
268
|
+
- [ ] Remove these from `nuxt.config.ts` if present
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Section 6: Error Handling
|
|
273
|
+
|
|
274
|
+
### 6.1 Parsed `error.data`
|
|
275
|
+
|
|
276
|
+
**Search Pattern**: `grep -rn "JSON.parse.*error.data\|error.data.*JSON.parse" --include="*.vue" --include="*.ts"`
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Before
|
|
280
|
+
const data = JSON.parse(error.data);
|
|
281
|
+
|
|
282
|
+
// After (data is already parsed)
|
|
283
|
+
const data = error.data;
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Section 7: Module and Build Changes
|
|
289
|
+
|
|
290
|
+
### 7.1 Absolute Watch Paths in `builder:watch`
|
|
291
|
+
|
|
292
|
+
**Search Pattern**: Check custom modules using `builder:watch` hook
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { relative, resolve } from 'node:fs';
|
|
296
|
+
|
|
297
|
+
nuxt.hook('builder:watch', async (event, path) => {
|
|
298
|
+
// Convert to relative path for backward/forward compatibility
|
|
299
|
+
path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path));
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### 7.2 Template Compilation Changes
|
|
304
|
+
|
|
305
|
+
**Search Pattern**: Check modules using `addTemplate` with EJS syntax
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Before (using lodash template)
|
|
309
|
+
addTemplate({
|
|
310
|
+
fileName: 'plugin.js',
|
|
311
|
+
src: './runtime/plugin.ejs',
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// After (using getContents)
|
|
315
|
+
import { template } from 'es-toolkit/compat';
|
|
316
|
+
|
|
317
|
+
addTemplate({
|
|
318
|
+
fileName: 'plugin.js',
|
|
319
|
+
getContents({ options }) {
|
|
320
|
+
const contents = readFileSync('./runtime/plugin.ejs', 'utf-8');
|
|
321
|
+
return template(contents)({ options });
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Section 8: ESLint Configuration (Flat Config Only)
|
|
329
|
+
|
|
330
|
+
> **Note:** This section only applies if your workspace uses ESLint flat config (`eslint.config.js`, `eslint.config.mjs`, or `eslint.config.cjs`). If you're using legacy `.eslintrc.json`, no changes are required.
|
|
331
|
+
|
|
332
|
+
### 8.1 Migrate to `createConfigForNuxt`
|
|
333
|
+
|
|
334
|
+
**Search Pattern**: Check for `eslint.config.js`, `eslint.config.mjs`, or `eslint.config.cjs` in Nuxt project directories
|
|
335
|
+
|
|
336
|
+
For workspaces using ESLint flat config, Nuxt 4 requires updating to `@nuxt/eslint-config` version `^1.10.0` and using `createConfigForNuxt` from `@nuxt/eslint-config/flat`.
|
|
337
|
+
|
|
338
|
+
**Before (Nuxt 3 flat config):**
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
342
|
+
|
|
343
|
+
export default [
|
|
344
|
+
...baseConfig,
|
|
345
|
+
{
|
|
346
|
+
files: ['**/*.vue'],
|
|
347
|
+
languageOptions: {
|
|
348
|
+
parserOptions: { parser: '@typescript-eslint/parser' },
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
ignores: ['.nuxt/**', '.output/**', 'node_modules'],
|
|
353
|
+
},
|
|
354
|
+
];
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**After (Nuxt 4 flat config - eslint.config.mjs):**
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
import { createConfigForNuxt } from '@nuxt/eslint-config/flat';
|
|
361
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
362
|
+
|
|
363
|
+
export default createConfigForNuxt({
|
|
364
|
+
features: {
|
|
365
|
+
typescript: true,
|
|
366
|
+
},
|
|
367
|
+
})
|
|
368
|
+
.prepend(...baseConfig)
|
|
369
|
+
.append(
|
|
370
|
+
{
|
|
371
|
+
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],
|
|
372
|
+
rules: {},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
ignores: ['.nuxt/**', '.output/**', 'node_modules'],
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**For CJS (eslint.config.cjs):**
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
const { createConfigForNuxt } = require('@nuxt/eslint-config/flat');
|
|
384
|
+
const baseConfig = require('../../eslint.config.cjs');
|
|
385
|
+
|
|
386
|
+
module.exports = createConfigForNuxt({
|
|
387
|
+
features: {
|
|
388
|
+
typescript: true,
|
|
389
|
+
},
|
|
390
|
+
})
|
|
391
|
+
.prepend(...baseConfig)
|
|
392
|
+
.append(
|
|
393
|
+
{
|
|
394
|
+
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],
|
|
395
|
+
rules: {},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
ignores: ['.nuxt/**', '.output/**', 'node_modules'],
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Action Items (Flat Config Only):**
|
|
404
|
+
|
|
405
|
+
- [ ] Update `@nuxt/eslint-config` to `^1.10.0` in `package.json`
|
|
406
|
+
- [ ] Replace manual Vue/TypeScript parser config with `createConfigForNuxt`
|
|
407
|
+
- [ ] Use `features.typescript: true` option for TypeScript support
|
|
408
|
+
- [ ] Remove `@typescript-eslint/parser` from devDependencies (handled automatically)
|
|
409
|
+
- [ ] Use `.prepend()` for base configs and `.append()` for project-specific rules/ignores
|
|
410
|
+
|
|
411
|
+
### 8.2 Understanding the New Config Structure
|
|
412
|
+
|
|
413
|
+
The `createConfigForNuxt` function returns a chainable config builder:
|
|
414
|
+
|
|
415
|
+
- **`features.typescript: true`** - Enables TypeScript support with proper Vue file parsing
|
|
416
|
+
- **`.prepend(...configs)`** - Adds configs at the beginning (useful for workspace base configs)
|
|
417
|
+
- **`.append(...configs)`** - Adds configs at the end (for project-specific rules and ignores)
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Post-Migration Validation
|
|
422
|
+
|
|
423
|
+
After completing the migration, run these commands:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# 1. Install updated dependencies
|
|
427
|
+
npm install
|
|
428
|
+
|
|
429
|
+
# 2. Run Nuxt prepare
|
|
430
|
+
npx nuxi prepare
|
|
431
|
+
|
|
432
|
+
# 3. Type check
|
|
433
|
+
npx nuxi typecheck
|
|
434
|
+
|
|
435
|
+
# 4. Build the application
|
|
436
|
+
nx build <project-name>
|
|
437
|
+
|
|
438
|
+
# 5. Run tests
|
|
439
|
+
nx test <project-name>
|
|
440
|
+
|
|
441
|
+
# 6. Start dev server to verify
|
|
442
|
+
nx serve <project-name>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Quick Migration Commands
|
|
448
|
+
|
|
449
|
+
Nuxt provides codemods to automate many changes:
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
# Run the full migration recipe
|
|
453
|
+
npx codemod@0.18.7 nuxt/4/migration-recipe
|
|
454
|
+
|
|
455
|
+
# Or run individual codemods:
|
|
456
|
+
npx codemod@latest nuxt/4/file-structure
|
|
457
|
+
npx codemod@latest nuxt/4/default-data-error-value
|
|
458
|
+
npx codemod@latest nuxt/4/shallow-function-reactivity
|
|
459
|
+
npx codemod@latest nuxt/4/deprecated-dedupe-value
|
|
460
|
+
npx codemod@latest nuxt/4/template-compilation-changes
|
|
461
|
+
npx codemod@latest nuxt/4/absolute-watch-path
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Common Issues and Solutions
|
|
467
|
+
|
|
468
|
+
### Issue: White flash on initial load
|
|
469
|
+
|
|
470
|
+
**Solution:** This is expected behavior - SPA loading template now renders outside `#__nuxt`. To revert:
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
experimental: {
|
|
474
|
+
spaLoadingTemplateLocation: 'within';
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Issue: Global CSS not inlined
|
|
479
|
+
|
|
480
|
+
**Solution:** Only component CSS is inlined by default. To inline all CSS:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
features: {
|
|
484
|
+
inlineStyles: true;
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Issue: Middleware index files being registered
|
|
489
|
+
|
|
490
|
+
**Solution:** Filter unwanted middleware:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
hooks: {
|
|
494
|
+
'app:resolve'(app) {
|
|
495
|
+
app.middleware = app.middleware.filter(mw => !/\/index\.[^/]+$/.test(mw.path))
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Files to Review
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# Find all Vue files
|
|
506
|
+
find . -name "*.vue" -not -path "./node_modules/*"
|
|
507
|
+
|
|
508
|
+
# Find all nuxt config files
|
|
509
|
+
find . -name "nuxt.config.*" -not -path "./node_modules/*"
|
|
510
|
+
|
|
511
|
+
# Find composables using data fetching
|
|
512
|
+
grep -rn "useAsyncData\|useFetch\|getCachedData" --include="*.vue" --include="*.ts" | grep -v node_modules
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Notes for AI Agent
|
|
518
|
+
|
|
519
|
+
1. **Work systematically** through each section
|
|
520
|
+
2. **Run codemods first** where available, then manually fix remaining issues
|
|
521
|
+
3. **Test incrementally** - run `nx build` and `nx test` after each major change
|
|
522
|
+
4. **Document changes** as you make them for user review
|
|
523
|
+
5. **Handle errors gracefully** - if a file doesn't exist or a pattern isn't found, continue to the next item
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## References
|
|
528
|
+
|
|
529
|
+
- [Official Nuxt 4 Upgrade Guide](https://nuxt.com/docs/4.x/getting-started/upgrade)
|
|
530
|
+
- [Nuxt 4 Announcement Blog](https://nuxt.com/blog/v4)
|
|
531
|
+
- [Nuxt GitHub Repository](https://github.com/nuxt/nuxt)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Tree } from '@nx/devkit';
|
|
2
|
+
export default function migrateCreateNodesV2ToCreateNodes(tree: Tree): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Rewrites named imports and re-exports of `createNodesV2` to `createNodes`
|
|
5
|
+
* when they come from one of the given module specifiers. Only the named
|
|
6
|
+
* bindings are touched — the module specifier, the `import`/`export` keyword,
|
|
7
|
+
* any `type` modifier, and any default import are left untouched.
|
|
8
|
+
*/
|
|
9
|
+
export declare function rewriteCreateNodesV2Imports(source: string, specifiers: ReadonlySet<string>): string;
|
|
10
|
+
//# sourceMappingURL=migrate-create-nodes-v2-to-create-nodes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-create-nodes-v2-to-create-nodes.d.ts","sourceRoot":"","sources":["../../../../../../packages/nuxt/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,IAAI,EAEV,MAAM,YAAY,CAAC;AAsBpB,wBAA8B,iCAAiC,CAC7D,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B,MAAM,CAoBR"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = migrateCreateNodesV2ToCreateNodes;
|
|
4
|
+
exports.rewriteCreateNodesV2Imports = rewriteCreateNodesV2Imports;
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const TS_EXTENSIONS = ['.ts', '.tsx', '.cts', '.mts'];
|
|
7
|
+
const DEPRECATED_NAME = 'createNodesV2';
|
|
8
|
+
const CANONICAL_NAME = 'createNodes';
|
|
9
|
+
// Module specifiers from which `@nx/nuxt` publicly exposes `createNodesV2`.
|
|
10
|
+
// A named import or re-export of `createNodesV2` from one of these is rewritten
|
|
11
|
+
// to the canonical `createNodes` export.
|
|
12
|
+
const TARGET_SPECIFIERS = new Set(['@nx/nuxt/plugin']);
|
|
13
|
+
let ts;
|
|
14
|
+
async function migrateCreateNodesV2ToCreateNodes(tree) {
|
|
15
|
+
let touchedCount = 0;
|
|
16
|
+
(0, devkit_1.visitNotIgnoredFiles)(tree, '.', (filePath) => {
|
|
17
|
+
if (!TS_EXTENSIONS.some((ext) => filePath.endsWith(ext))) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const original = tree.read(filePath, 'utf-8');
|
|
21
|
+
if (!original || !original.includes(DEPRECATED_NAME)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const updated = rewriteCreateNodesV2Imports(original, TARGET_SPECIFIERS);
|
|
25
|
+
if (updated !== original) {
|
|
26
|
+
tree.write(filePath, updated);
|
|
27
|
+
touchedCount += 1;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
if (touchedCount > 0) {
|
|
31
|
+
devkit_1.logger.info(`Renamed \`${DEPRECATED_NAME}\` imports to \`${CANONICAL_NAME}\` in ${touchedCount} file(s).`);
|
|
32
|
+
}
|
|
33
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Rewrites named imports and re-exports of `createNodesV2` to `createNodes`
|
|
37
|
+
* when they come from one of the given module specifiers. Only the named
|
|
38
|
+
* bindings are touched — the module specifier, the `import`/`export` keyword,
|
|
39
|
+
* any `type` modifier, and any default import are left untouched.
|
|
40
|
+
*/
|
|
41
|
+
function rewriteCreateNodesV2Imports(source, specifiers) {
|
|
42
|
+
ts ??= (0, devkit_1.ensurePackage)('typescript', '*');
|
|
43
|
+
const sourceFile = ts.createSourceFile('tmp.ts', source, ts.ScriptTarget.Latest,
|
|
44
|
+
/* setParentNodes */ true, ts.ScriptKind.TSX);
|
|
45
|
+
const changes = [];
|
|
46
|
+
for (const stmt of sourceFile.statements) {
|
|
47
|
+
if (ts.isImportDeclaration(stmt)) {
|
|
48
|
+
collectImportRewrite(sourceFile, stmt, specifiers, changes);
|
|
49
|
+
}
|
|
50
|
+
else if (ts.isExportDeclaration(stmt)) {
|
|
51
|
+
collectExportRewrite(sourceFile, stmt, specifiers, changes);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return changes.length > 0 ? (0, devkit_1.applyChangesToString)(source, changes) : source;
|
|
55
|
+
}
|
|
56
|
+
function isTargetSpecifier(node, specifiers) {
|
|
57
|
+
return ts.isStringLiteral(node) && specifiers.has(node.text);
|
|
58
|
+
}
|
|
59
|
+
function collectImportRewrite(sourceFile, stmt, specifiers, changes) {
|
|
60
|
+
if (!isTargetSpecifier(stmt.moduleSpecifier, specifiers)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
64
|
+
// Only `import { ... }` carries renameable named bindings. `import x`,
|
|
65
|
+
// `import * as ns`, and side-effect imports reference the module wholesale
|
|
66
|
+
// and keep working through the `createNodesV2` runtime alias, so we leave
|
|
67
|
+
// them be. A mixed `import def, { createNodesV2 }` still has its named
|
|
68
|
+
// bindings rewritten below — the default binding is untouched.
|
|
69
|
+
if (!namedBindings || !ts.isNamedImports(namedBindings)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
rewriteNamedBindings(sourceFile, namedBindings, changes);
|
|
73
|
+
}
|
|
74
|
+
function collectExportRewrite(sourceFile, stmt, specifiers, changes) {
|
|
75
|
+
if (!stmt.moduleSpecifier ||
|
|
76
|
+
!isTargetSpecifier(stmt.moduleSpecifier, specifiers)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// `export { ... } from '...'` can be rewritten; `export * from '...'` has no
|
|
80
|
+
// named bindings to rename.
|
|
81
|
+
if (!stmt.exportClause || !ts.isNamedExports(stmt.exportClause)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
rewriteNamedBindings(sourceFile, stmt.exportClause, changes);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Re-renders the `{ ... }` of a named import/export, renaming any
|
|
88
|
+
* `createNodesV2` specifier to `createNodes`. If renaming would collide with a
|
|
89
|
+
* `createNodes` that is already present (e.g. `{ createNodes, createNodesV2 }`),
|
|
90
|
+
* the duplicate is dropped. Returns without recording a change when the binding
|
|
91
|
+
* list contains no `createNodesV2`.
|
|
92
|
+
*/
|
|
93
|
+
function rewriteNamedBindings(sourceFile, namedBindings, changes) {
|
|
94
|
+
const elements = namedBindings.elements;
|
|
95
|
+
const hasDeprecated = elements.some((el) => (el.propertyName ?? el.name).text === DEPRECATED_NAME);
|
|
96
|
+
if (!hasDeprecated) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
const rendered = [];
|
|
101
|
+
for (const el of elements) {
|
|
102
|
+
const text = renderSpecifier(el);
|
|
103
|
+
if (!seen.has(text)) {
|
|
104
|
+
seen.add(text);
|
|
105
|
+
rendered.push(text);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const start = namedBindings.getStart(sourceFile);
|
|
109
|
+
changes.push({
|
|
110
|
+
type: devkit_1.ChangeType.Delete,
|
|
111
|
+
start,
|
|
112
|
+
length: namedBindings.getEnd() - start,
|
|
113
|
+
}, {
|
|
114
|
+
type: devkit_1.ChangeType.Insert,
|
|
115
|
+
index: start,
|
|
116
|
+
text: `{ ${rendered.join(', ')} }`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function renderSpecifier(el) {
|
|
120
|
+
const typePrefix = el.isTypeOnly ? 'type ' : '';
|
|
121
|
+
const rename = (name) => name === DEPRECATED_NAME ? CANONICAL_NAME : name;
|
|
122
|
+
// `{ name }` — no alias, so the local binding follows the rename.
|
|
123
|
+
if (!el.propertyName) {
|
|
124
|
+
return `${typePrefix}${rename(el.name.text)}`;
|
|
125
|
+
}
|
|
126
|
+
// `{ propertyName as name }` — only the imported (left) side is renamed; the
|
|
127
|
+
// local alias is preserved. A now-redundant alias such as
|
|
128
|
+
// `createNodesV2 as createNodes` collapses to `createNodes`.
|
|
129
|
+
const canonicalImported = rename(el.propertyName.text);
|
|
130
|
+
const localName = el.name.text;
|
|
131
|
+
return canonicalImported === localName
|
|
132
|
+
? `${typePrefix}${localName}`
|
|
133
|
+
: `${typePrefix}${canonicalImported} as ${localName}`;
|
|
134
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#### Rename `createNodesV2` imports to `createNodes`
|
|
2
|
+
|
|
3
|
+
`@nx/nuxt` renamed its primary inferred-plugin export from `createNodesV2` to `createNodes`. The `createNodesV2` name is preserved as a deprecated alias for now, but new code should use `createNodes`.
|
|
4
|
+
|
|
5
|
+
This migration scans every `.ts`, `.tsx`, `.cts`, and `.mts` file in your workspace and rewrites named imports and re-exports of `createNodesV2` from `@nx/nuxt/plugin` to `createNodes`.
|
|
6
|
+
|
|
7
|
+
#### Sample Code Changes
|
|
8
|
+
|
|
9
|
+
##### Before
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createNodesV2 } from '@nx/nuxt/plugin';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
##### After
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createNodes } from '@nx/nuxt/plugin';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Aliases are preserved (`createNodesV2 as cn` becomes `createNodes as cn`), and if a file already imports both names (`{ createNodes, createNodesV2 }`) the redundant binding is dropped.
|
|
22
|
+
|
|
23
|
+
#### What is not rewritten
|
|
24
|
+
|
|
25
|
+
Only static `import`/`export` named bindings from `@nx/nuxt/plugin` are rewritten. Namespace imports, dynamic `import(...)`, `require(...)` destructuring, and property access such as `plugin.createNodesV2` are left untouched — they keep working through the `createNodesV2` runtime alias. Update those by hand if you want to drop the deprecated name everywhere.
|