mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-28 08:32:06 +00:00
feat(nuxt): normalise component names to match nuxt pattern (#28745)
This commit is contained in:
parent
ed7f946ab8
commit
9cb34dd49b
@ -73,6 +73,7 @@ export default defineNuxtConfig({
|
||||
// resetAsyncDataToUndefined: true,
|
||||
// templateUtils: true,
|
||||
// relativeWatchPaths: true,
|
||||
// normalizeComponentNames: false
|
||||
// defaults: {
|
||||
// useAsyncData: {
|
||||
// deep: true
|
||||
@ -198,6 +199,43 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
#### Normalized Component Names
|
||||
|
||||
🚦 **Impact Level**: Moderate
|
||||
|
||||
Vue will now generate component names that match the Nuxt pattern for component naming.
|
||||
|
||||
##### What Changed
|
||||
|
||||
By default, if you haven't set it manually, Vue will assign a component name that matches
|
||||
the filename of the component.
|
||||
|
||||
```bash [Directory structure]
|
||||
├─ components/
|
||||
├─── SomeFolder/
|
||||
├───── MyComponent.vue
|
||||
```
|
||||
|
||||
In this case, the component name would be `MyComponent`, as far as Vue is concerned. If you wanted to use `<KeepAlive>` with it, or identify it in the Vue DevTools, you would need to use this name.
|
||||
|
||||
But in order to auto-import it, you would need to use `SomeFolderMyComponent`.
|
||||
|
||||
With this change, these two values will match, and Vue will generate a component name that matches the Nuxt pattern for component naming.
|
||||
|
||||
##### Migration Steps
|
||||
|
||||
Ensure that you use the updated name in any tests which use `findComponent` from `@vue/test-utils` and in any `<KeepAlive>` which depends on the name of your component.
|
||||
|
||||
Alternatively, for now, you can disable this behaviour with:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
experimental: {
|
||||
normalizeComponentNames: false
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Shared Prerender Data
|
||||
|
||||
🚦 **Impact Level**: Medium
|
||||
|
@ -390,3 +390,31 @@ In addition, any changes to files within `srcDir` will trigger a rebuild of the
|
||||
::note
|
||||
A maximum of 10 cache tarballs are kept.
|
||||
::
|
||||
|
||||
## normalizeComponentNames
|
||||
|
||||
Ensure that auto-generated Vue component names match the full component name
|
||||
you would use to auto-import the component.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
experimental: {
|
||||
normalizeComponentNames: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
By default, if you haven't set it manually, Vue will assign a component name that matches
|
||||
the filename of the component.
|
||||
|
||||
```bash [Directory structure]
|
||||
├─ components/
|
||||
├─── SomeFolder/
|
||||
├───── MyComponent.vue
|
||||
```
|
||||
|
||||
In this case, the component name would be `MyComponent`, as far as Vue is concerned. If you wanted to use `<KeepAlive>` with it, or identify it in the Vue DevTools, you would need to use this component.
|
||||
|
||||
But in order to auto-import it, you would need to use `SomeFolderMyComponent`.
|
||||
|
||||
By setting `experimental.normalizeComponentNames`, these two values match, and Vue will generate a component name that matches the Nuxt pattern for component naming.
|
||||
|
@ -61,9 +61,12 @@ export default defineNuxtConfig({
|
||||
app: 'app'
|
||||
},
|
||||
experimental: {
|
||||
sharedPrerenderData: false,
|
||||
compileTemplate: true,
|
||||
resetAsyncDataToUndefined: true,
|
||||
templateUtils: true,
|
||||
relativeWatchPaths: true,
|
||||
normalizeComponentNames: false
|
||||
defaults: {
|
||||
useAsyncData: {
|
||||
deep: true
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { existsSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
||||
import { addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, logger, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, logger, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit'
|
||||
import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema'
|
||||
|
||||
import { distDir } from '../dirs'
|
||||
import { clientFallbackAutoIdPlugin } from './client-fallback-auto-id'
|
||||
import { componentNamesTemplate, componentsIslandsTemplate, componentsMetadataTemplate, componentsPluginTemplate, componentsTypeTemplate } from './templates'
|
||||
import { scanComponents } from './scan'
|
||||
import { loaderPlugin } from './loader'
|
||||
import { TreeShakeTemplatePlugin } from './tree-shake'
|
||||
import { componentsChunkPlugin, islandsTransform } from './islandsTransform'
|
||||
import { createTransformPlugin } from './transform'
|
||||
|
||||
import { ClientFallbackAutoIdPlugin } from './plugins/client-fallback-auto-id'
|
||||
import { LoaderPlugin } from './plugins/loader'
|
||||
import { componentsChunkPlugin, islandsTransform } from './plugins/islands-transform'
|
||||
import { createTransformPlugin } from './plugins/transform'
|
||||
import { TreeShakeTemplatePlugin } from './plugins/tree-shake'
|
||||
import { ComponentNamePlugin } from './plugins/component-names'
|
||||
|
||||
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
|
||||
const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch { return false } }
|
||||
@ -42,6 +44,11 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
: context.components
|
||||
}
|
||||
|
||||
if (nuxt.options.experimental.normalizeComponentNames) {
|
||||
addBuildPlugin(ComponentNamePlugin({ sourcemap: !!nuxt.options.sourcemap.client, getComponents }), { server: false })
|
||||
addBuildPlugin(ComponentNamePlugin({ sourcemap: !!nuxt.options.sourcemap.server, getComponents }), { client: false })
|
||||
}
|
||||
|
||||
const normalizeDirs = (dir: any, cwd: string, options?: { priority?: number }): ComponentsDir[] => {
|
||||
if (Array.isArray(dir)) {
|
||||
return dir.map(dir => normalizeDirs(dir, cwd, options)).flat().sort(compareDirByPathLength)
|
||||
@ -127,14 +134,14 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
addTemplate(componentsMetadataTemplate)
|
||||
}
|
||||
|
||||
const unpluginServer = createTransformPlugin(nuxt, getComponents, 'server')
|
||||
const unpluginClient = createTransformPlugin(nuxt, getComponents, 'client')
|
||||
const TransformPluginServer = createTransformPlugin(nuxt, getComponents, 'server')
|
||||
const TransformPluginClient = createTransformPlugin(nuxt, getComponents, 'client')
|
||||
|
||||
addVitePlugin(() => unpluginServer.vite(), { server: true, client: false })
|
||||
addVitePlugin(() => unpluginClient.vite(), { server: false, client: true })
|
||||
addVitePlugin(() => TransformPluginServer.vite(), { server: true, client: false })
|
||||
addVitePlugin(() => TransformPluginClient.vite(), { server: false, client: true })
|
||||
|
||||
addWebpackPlugin(() => unpluginServer.webpack(), { server: true, client: false })
|
||||
addWebpackPlugin(() => unpluginClient.webpack(), { server: false, client: true })
|
||||
addWebpackPlugin(() => TransformPluginServer.webpack(), { server: true, client: false })
|
||||
addWebpackPlugin(() => TransformPluginClient.webpack(), { server: false, client: true })
|
||||
|
||||
// Do not prefetch global components chunks
|
||||
nuxt.hook('build:manifest', (manifest) => {
|
||||
@ -223,12 +230,12 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
}))
|
||||
}
|
||||
if (nuxt.options.experimental.clientFallback) {
|
||||
config.plugins.push(clientFallbackAutoIdPlugin.vite({
|
||||
config.plugins.push(ClientFallbackAutoIdPlugin.vite({
|
||||
sourcemap: !!nuxt.options.sourcemap[mode],
|
||||
rootDir: nuxt.options.rootDir,
|
||||
}))
|
||||
}
|
||||
config.plugins.push(loaderPlugin.vite({
|
||||
config.plugins.push(LoaderPlugin.vite({
|
||||
sourcemap: !!nuxt.options.sourcemap[mode],
|
||||
getComponents,
|
||||
mode,
|
||||
@ -292,12 +299,12 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
}))
|
||||
}
|
||||
if (nuxt.options.experimental.clientFallback) {
|
||||
config.plugins.push(clientFallbackAutoIdPlugin.webpack({
|
||||
config.plugins.push(ClientFallbackAutoIdPlugin.webpack({
|
||||
sourcemap: !!nuxt.options.sourcemap[mode],
|
||||
rootDir: nuxt.options.rootDir,
|
||||
}))
|
||||
}
|
||||
config.plugins.push(loaderPlugin.webpack({
|
||||
config.plugins.push(LoaderPlugin.webpack({
|
||||
sourcemap: !!nuxt.options.sourcemap[mode],
|
||||
getComponents,
|
||||
mode,
|
||||
|
@ -3,7 +3,7 @@ import type { ComponentsOptions } from '@nuxt/schema'
|
||||
import MagicString from 'magic-string'
|
||||
import { isAbsolute, relative } from 'pathe'
|
||||
import { hash } from 'ohash'
|
||||
import { isVue } from '../core/utils'
|
||||
import { isVue } from '../../core/utils'
|
||||
|
||||
interface LoaderOptions {
|
||||
sourcemap?: boolean
|
||||
@ -12,7 +12,7 @@ interface LoaderOptions {
|
||||
}
|
||||
const CLIENT_FALLBACK_RE = /<(?:NuxtClientFallback|nuxt-client-fallback)(?: [^>]*)?>/
|
||||
const CLIENT_FALLBACK_GLOBAL_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/g
|
||||
export const clientFallbackAutoIdPlugin = createUnplugin((options: LoaderOptions) => {
|
||||
export const ClientFallbackAutoIdPlugin = createUnplugin((options: LoaderOptions) => {
|
||||
const exclude = options.transform?.exclude || []
|
||||
const include = options.transform?.include || []
|
||||
|
46
packages/nuxt/src/components/plugins/component-names.ts
Normal file
46
packages/nuxt/src/components/plugins/component-names.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import MagicString from 'magic-string'
|
||||
import type { Component } from 'nuxt/schema'
|
||||
import { isVue } from '../../core/utils'
|
||||
|
||||
interface NameDevPluginOptions {
|
||||
sourcemap: boolean
|
||||
getComponents: () => Component[]
|
||||
}
|
||||
/**
|
||||
* Set the default name of components to their PascalCase name
|
||||
*/
|
||||
export const ComponentNamePlugin = (options: NameDevPluginOptions) => createUnplugin(() => {
|
||||
return {
|
||||
name: 'nuxt:component-name-plugin',
|
||||
enforce: 'post',
|
||||
transformInclude (id) {
|
||||
return isVue(id) || !!id.match(/\.[tj]sx$/)
|
||||
},
|
||||
transform (code, id) {
|
||||
const filename = id.match(/([^/\\]+)\.\w+$/)?.[1]
|
||||
if (!filename) {
|
||||
return
|
||||
}
|
||||
|
||||
const component = options.getComponents().find(c => c.filePath === id)
|
||||
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
|
||||
const NAME_RE = new RegExp(`__name:\\s*['"]${filename}['"]`)
|
||||
const s = new MagicString(code)
|
||||
s.replace(NAME_RE, `__name: ${JSON.stringify(component.pascalName)}`)
|
||||
|
||||
if (s.hasChanged()) {
|
||||
return {
|
||||
code: s.toString(),
|
||||
map: options.sourcemap
|
||||
? s.generateMap({ hires: true })
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
@ -9,7 +9,7 @@ import { ELEMENT_NODE, parse, walk } from 'ultrahtml'
|
||||
import { hash } from 'ohash'
|
||||
import { resolvePath } from '@nuxt/kit'
|
||||
import defu from 'defu'
|
||||
import { isVue } from '../core/utils'
|
||||
import { isVue } from '../../core/utils'
|
||||
|
||||
interface ServerOnlyComponentTransformPluginOptions {
|
||||
getComponents: () => Component[]
|
@ -6,8 +6,8 @@ import { resolve } from 'pathe'
|
||||
import type { Component, ComponentsOptions } from 'nuxt/schema'
|
||||
|
||||
import { logger, tryUseNuxt } from '@nuxt/kit'
|
||||
import { distDir } from '../dirs'
|
||||
import { isVue } from '../core/utils'
|
||||
import { distDir } from '../../dirs'
|
||||
import { isVue } from '../../core/utils'
|
||||
|
||||
interface LoaderOptions {
|
||||
getComponents (): Component[]
|
||||
@ -17,7 +17,7 @@ interface LoaderOptions {
|
||||
experimentalComponentIslands?: boolean
|
||||
}
|
||||
|
||||
export const loaderPlugin = createUnplugin((options: LoaderOptions) => {
|
||||
export const LoaderPlugin = createUnplugin((options: LoaderOptions) => {
|
||||
const exclude = options.transform?.exclude || []
|
||||
const include = options.transform?.include || []
|
||||
const serverComponentRuntime = resolve(distDir, 'components/runtime/server-component')
|
||||
@ -49,7 +49,7 @@ export const loaderPlugin = createUnplugin((options: LoaderOptions) => {
|
||||
// @ts-expect-error TODO: refactor to nuxi
|
||||
if (component._internal_install && tryUseNuxt()?.options.test === false) {
|
||||
// @ts-expect-error TODO: refactor to nuxi
|
||||
import('../core/features').then(({ installNuxtModule }) => installNuxtModule(component._internal_install))
|
||||
import('../../core/features').then(({ installNuxtModule }) => installNuxtModule(component._internal_install))
|
||||
}
|
||||
let identifier = map.get(component) || `__nuxt_component_${num++}`
|
||||
map.set(component, identifier)
|
@ -7,8 +7,8 @@ import { parseURL } from 'ufo'
|
||||
import { parseQuery } from 'vue-router'
|
||||
import { normalize, resolve } from 'pathe'
|
||||
import { genImport } from 'knitwork'
|
||||
import { distDir } from '../dirs'
|
||||
import type { getComponentsT } from './module'
|
||||
import { distDir } from '../../dirs'
|
||||
import type { getComponentsT } from '../module'
|
||||
|
||||
const COMPONENT_QUERY_RE = /[?&]nuxt_component=/
|
||||
|
@ -6,7 +6,7 @@ import type { AssignmentProperty, CallExpression, Identifier, Literal, MemberExp
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import type { Component } from '@nuxt/schema'
|
||||
import { resolve } from 'pathe'
|
||||
import { distDir } from '../dirs'
|
||||
import { distDir } from '../../dirs'
|
||||
|
||||
interface TreeShakeTemplatePluginOptions {
|
||||
sourcemap?: boolean
|
@ -4,7 +4,7 @@ import type { Component, Nuxt } from '@nuxt/schema'
|
||||
import { kebabCase } from 'scule'
|
||||
import { normalize } from 'pathe'
|
||||
|
||||
import { createTransformPlugin } from '../src/components/transform'
|
||||
import { createTransformPlugin } from '../src/components/plugins/transform'
|
||||
|
||||
describe('components:transform', () => {
|
||||
it('should transform #components imports', async () => {
|
||||
|
@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import type { Plugin } from 'vite'
|
||||
import type { Component } from '@nuxt/schema'
|
||||
import type { UnpluginOptions } from 'unplugin'
|
||||
import { islandsTransform } from '../src/components/islandsTransform'
|
||||
import { islandsTransform } from '../src/components/plugins/islands-transform'
|
||||
import { normalizeLineEndings } from './utils'
|
||||
|
||||
const getComponents = () => [{
|
||||
|
@ -6,7 +6,7 @@ import type { Plugin } from 'vite'
|
||||
import { Parser } from 'acorn'
|
||||
import type { Options } from '@vitejs/plugin-vue'
|
||||
import _vuePlugin from '@vitejs/plugin-vue'
|
||||
import { TreeShakeTemplatePlugin } from '../src/components/tree-shake'
|
||||
import { TreeShakeTemplatePlugin } from '../src/components/plugins/tree-shake'
|
||||
import { fixtureDir, normalizeLineEndings } from './utils'
|
||||
|
||||
// mock due to differences of results between windows and linux
|
||||
|
@ -389,5 +389,15 @@ export default defineUntypedSchema({
|
||||
* This only works for source files within `srcDir` and `serverDir` for the Vue/Nitro parts of your app.
|
||||
*/
|
||||
buildCache: false,
|
||||
|
||||
/**
|
||||
* Ensure that auto-generated Vue component names match the full component name
|
||||
* you would use to auto-import the component.
|
||||
*/
|
||||
normalizeComponentNames: {
|
||||
$resolve: async (val, get) => {
|
||||
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user