mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-31 07:40:33 +00:00
fix(nuxt): handle auto-importing named components (#26556)
This commit is contained in:
parent
cbb4a1cb61
commit
1019ed9fba
@ -6,6 +6,7 @@ import { createUnplugin } from 'unplugin'
|
|||||||
import { parseURL } from 'ufo'
|
import { parseURL } from 'ufo'
|
||||||
import { parseQuery } from 'vue-router'
|
import { parseQuery } from 'vue-router'
|
||||||
import { normalize, resolve } from 'pathe'
|
import { normalize, resolve } from 'pathe'
|
||||||
|
import { genImport } from 'knitwork'
|
||||||
import { distDir } from '../dirs'
|
import { distDir } from '../dirs'
|
||||||
import type { getComponentsT } from './module'
|
import type { getComponentsT } from './module'
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ export function createTransformPlugin (nuxt: Nuxt, getComponents: getComponentsT
|
|||||||
const components = getComponents(mode)
|
const components = getComponents(mode)
|
||||||
return components.flatMap((c): Import[] => {
|
return components.flatMap((c): Import[] => {
|
||||||
const withMode = (mode: string | undefined) => mode
|
const withMode = (mode: string | undefined) => mode
|
||||||
? `${c.filePath}${c.filePath.includes('?') ? '&' : '?'}nuxt_component=${mode}&nuxt_component_name=${c.pascalName}`
|
? `${c.filePath}${c.filePath.includes('?') ? '&' : '?'}nuxt_component=${mode}&nuxt_component_name=${c.pascalName}&nuxt_component_export=${c.export || 'default'}`
|
||||||
: c.filePath
|
: c.filePath
|
||||||
|
|
||||||
const mode = !c._raw && c.mode && ['client', 'server'].includes(c.mode) ? c.mode : undefined
|
const mode = !c._raw && c.mode && ['client', 'server'].includes(c.mode) ? c.mode : undefined
|
||||||
@ -60,20 +61,22 @@ export function createTransformPlugin (nuxt: Nuxt, getComponents: getComponentsT
|
|||||||
const query = parseQuery(search)
|
const query = parseQuery(search)
|
||||||
const mode = query.nuxt_component
|
const mode = query.nuxt_component
|
||||||
const bare = id.replace(/\?.*/, '')
|
const bare = id.replace(/\?.*/, '')
|
||||||
|
const componentExport = query.nuxt_component_export as string || 'default'
|
||||||
|
const exportWording = componentExport === 'default' ? 'export default' : `export const ${componentExport} =`
|
||||||
if (mode === 'async') {
|
if (mode === 'async') {
|
||||||
return {
|
return {
|
||||||
code: [
|
code: [
|
||||||
'import { defineAsyncComponent } from "vue"',
|
'import { defineAsyncComponent } from "vue"',
|
||||||
`export default defineAsyncComponent(() => import(${JSON.stringify(bare)}).then(r => r.default))`
|
`${exportWording} defineAsyncComponent(() => import(${JSON.stringify(bare)}).then(r => r[${JSON.stringify(componentExport)}] || r.default || r))`
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
map: null
|
map: null
|
||||||
}
|
}
|
||||||
} else if (mode === 'client') {
|
} else if (mode === 'client') {
|
||||||
return {
|
return {
|
||||||
code: [
|
code: [
|
||||||
`import __component from ${JSON.stringify(bare)}`,
|
genImport(bare, [{ name: componentExport, as: '__component' }]),
|
||||||
'import { createClientOnly } from "#app/components/client-only"',
|
'import { createClientOnly } from "#app/components/client-only"',
|
||||||
'export default createClientOnly(__component)'
|
`${exportWording} createClientOnly(__component)`
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
map: null
|
map: null
|
||||||
}
|
}
|
||||||
@ -82,7 +85,7 @@ export function createTransformPlugin (nuxt: Nuxt, getComponents: getComponentsT
|
|||||||
code: [
|
code: [
|
||||||
'import { defineAsyncComponent } from "vue"',
|
'import { defineAsyncComponent } from "vue"',
|
||||||
'import { createClientOnly } from "#app/components/client-only"',
|
'import { createClientOnly } from "#app/components/client-only"',
|
||||||
`export default defineAsyncComponent(() => import(${JSON.stringify(bare)}).then(r => createClientOnly(r.default)))`
|
`${exportWording} defineAsyncComponent(() => import(${JSON.stringify(bare)}).then(r => createClientOnly(r[${JSON.stringify(componentExport)}] || r.default || r)))`
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
map: null
|
map: null
|
||||||
}
|
}
|
||||||
@ -91,7 +94,7 @@ export function createTransformPlugin (nuxt: Nuxt, getComponents: getComponentsT
|
|||||||
return {
|
return {
|
||||||
code: [
|
code: [
|
||||||
`import { createServerComponent } from ${JSON.stringify(serverComponentRuntime)}`,
|
`import { createServerComponent } from ${JSON.stringify(serverComponentRuntime)}`,
|
||||||
`export default createServerComponent(${JSON.stringify(name)})`
|
`${exportWording} createServerComponent(${JSON.stringify(name)})`
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
map: null
|
map: null
|
||||||
}
|
}
|
||||||
|
112
packages/nuxt/test/components-transform.test.ts
Normal file
112
packages/nuxt/test/components-transform.test.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import type { Component, Nuxt } from '@nuxt/schema'
|
||||||
|
import { kebabCase } from 'scule'
|
||||||
|
|
||||||
|
import { createTransformPlugin } from '../src/components/transform'
|
||||||
|
|
||||||
|
describe('components:transform', () => {
|
||||||
|
it('should transform #components imports', async () => {
|
||||||
|
const transform = createTransformer([
|
||||||
|
createComponent('Foo'),
|
||||||
|
createComponent('Bar', { export: 'Bar' })
|
||||||
|
])
|
||||||
|
|
||||||
|
const code = await transform('import { Foo, Bar } from \'#components\'', '/app.vue')
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"import Foo from '/Foo.vue';
|
||||||
|
import { Bar } from '/Bar.vue';
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly resolve server-only components', async () => {
|
||||||
|
const transform = createTransformer([
|
||||||
|
createComponent('Foo', { mode: 'server' })
|
||||||
|
])
|
||||||
|
|
||||||
|
const code = await transform('import { Foo, LazyFoo } from \'#components\'', '/app.vue')
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"import Foo from '/Foo.vue?nuxt_component=server&nuxt_component_name=Foo&nuxt_component_export=default';
|
||||||
|
import LazyFoo from '/Foo.vue?nuxt_component=server,async&nuxt_component_name=Foo&nuxt_component_export=default';
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await transform('', '/Foo.vue?nuxt_component=server&nuxt_component_name=Foo&nuxt_component_export=default')).toMatchInlineSnapshot(`
|
||||||
|
"import { createServerComponent } from "<repo>/nuxt/src/components/runtime/server-component"
|
||||||
|
export default createServerComponent("Foo")"
|
||||||
|
`)
|
||||||
|
expect(await transform('', '/Foo.vue?nuxt_component=server,async&nuxt_component_name=Foo&nuxt_component_export=default')).toMatchInlineSnapshot(`
|
||||||
|
"import { createServerComponent } from "<repo>/nuxt/src/components/runtime/server-component"
|
||||||
|
export default createServerComponent("Foo")"
|
||||||
|
`)
|
||||||
|
expect(await transform('', '/Foo.vue?nuxt_component=server&nuxt_component_name=Foo&nuxt_component_export=Foo')).toMatchInlineSnapshot(`
|
||||||
|
"import { createServerComponent } from "<repo>/nuxt/src/components/runtime/server-component"
|
||||||
|
export const Foo = createServerComponent("Foo")"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly resolve client-only components', async () => {
|
||||||
|
const transform = createTransformer([
|
||||||
|
createComponent('Foo', { mode: 'client' })
|
||||||
|
])
|
||||||
|
|
||||||
|
const code = await transform('import { Foo, LazyFoo } from \'#components\'', '/app.vue')
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"import Foo from '/Foo.vue?nuxt_component=client&nuxt_component_name=Foo&nuxt_component_export=default';
|
||||||
|
import LazyFoo from '/Foo.vue?nuxt_component=client,async&nuxt_component_name=Foo&nuxt_component_export=default';
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await transform('', '/Foo.vue?nuxt_component=client&nuxt_component_name=Foo&nuxt_component_export=default')).toMatchInlineSnapshot(`
|
||||||
|
"import { default as __component } from "/Foo.vue";
|
||||||
|
import { createClientOnly } from "#app/components/client-only"
|
||||||
|
export default createClientOnly(__component)"
|
||||||
|
`)
|
||||||
|
expect(await transform('', '/Foo.vue?nuxt_component=client,async&nuxt_component_name=Foo&nuxt_component_export=default')).toMatchInlineSnapshot(`
|
||||||
|
"import { defineAsyncComponent } from "vue"
|
||||||
|
import { createClientOnly } from "#app/components/client-only"
|
||||||
|
export default defineAsyncComponent(() => import("/Foo.vue").then(r => createClientOnly(r["default"] || r.default || r)))"
|
||||||
|
`)
|
||||||
|
expect(await transform('', '/Foo.vue?nuxt_component=client,async&nuxt_component_name=Foo&nuxt_component_export=Foo')).toMatchInlineSnapshot(`
|
||||||
|
"import { defineAsyncComponent } from "vue"
|
||||||
|
import { createClientOnly } from "#app/components/client-only"
|
||||||
|
export const Foo = defineAsyncComponent(() => import("/Foo.vue").then(r => createClientOnly(r["Foo"] || r.default || r)))"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const rootDir = fileURLToPath(new URL('../..', import.meta.url))
|
||||||
|
|
||||||
|
function createTransformer (components: Component[], mode: 'client' | 'server' | 'all' = 'all') {
|
||||||
|
const stubNuxt = {
|
||||||
|
options: {
|
||||||
|
buildDir: '/',
|
||||||
|
sourcemap: {
|
||||||
|
server: false,
|
||||||
|
client: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as Nuxt
|
||||||
|
const plugin = createTransformPlugin(stubNuxt, () => components, mode).vite()
|
||||||
|
|
||||||
|
return async (code: string, id: string) => {
|
||||||
|
const result = await (plugin as any).transform!(code, id)
|
||||||
|
return (typeof result === 'string' ? result : result?.code)?.replaceAll(rootDir, '<repo>/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createComponent (pascalName: string, options: Partial<Component> = {}) {
|
||||||
|
return {
|
||||||
|
filePath: `/${pascalName}.vue`,
|
||||||
|
pascalName,
|
||||||
|
export: 'default',
|
||||||
|
chunkName: `components/${pascalName.toLowerCase()}`,
|
||||||
|
kebabName: kebabCase(pascalName),
|
||||||
|
mode: 'all',
|
||||||
|
prefetch: false,
|
||||||
|
preload: false,
|
||||||
|
shortPath: `components/${pascalName}.vue`,
|
||||||
|
...options
|
||||||
|
} satisfies Component
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user