mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 07:32:01 +00:00
fix(nuxt): move v-if
to wrapper in islands transform (#26386)
This commit is contained in:
parent
79787c8123
commit
8911c0d0dd
@ -34,6 +34,7 @@ const HAS_SLOT_OR_CLIENT_RE = /(<slot[^>]*>)|(nuxt-client)/
|
|||||||
const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
|
const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
|
||||||
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
|
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
|
||||||
const IMPORT_CODE = '\nimport { vforToArray as __vforToArray } from \'#app/components/utils\'' + '\nimport NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'' + '\nimport NuxtTeleportSsrSlot from \'#app/components/nuxt-teleport-island-slot\''
|
const IMPORT_CODE = '\nimport { vforToArray as __vforToArray } from \'#app/components/utils\'' + '\nimport NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'' + '\nimport NuxtTeleportSsrSlot from \'#app/components/nuxt-teleport-island-slot\''
|
||||||
|
const EXTRACTED_ATTRS_RE = /v-(?:if|else-if|else)(="[^"]*")?/g
|
||||||
|
|
||||||
function wrapWithVForDiv (code: string, vfor: string): string {
|
function wrapWithVForDiv (code: string, vfor: string): string {
|
||||||
return `<div v-for="${vfor}" style="display: contents;">${code}</div>`
|
return `<div v-for="${vfor}" style="display: contents;">${code}</div>`
|
||||||
@ -79,29 +80,31 @@ export const islandsTransform = createUnplugin((options: ServerOnlyComponentTran
|
|||||||
if (node.name === 'slot') {
|
if (node.name === 'slot') {
|
||||||
const { attributes, children, loc } = node
|
const { attributes, children, loc } = node
|
||||||
|
|
||||||
// pass slot fallback to NuxtTeleportSsrSlot fallback
|
|
||||||
if (children.length) {
|
|
||||||
const attrString = Object.entries(attributes).map(([name, value]) => name ? `${name}="${value}" ` : value).join(' ')
|
|
||||||
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(/:?key="[^"]"/g, '')
|
|
||||||
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[1].end, `<slot ${attrString} /><template #fallback>${attributes['v-for'] ? wrapWithVForDiv(slice, attributes['v-for']) : slice}</template>`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const slotName = attributes.name ?? 'default'
|
const slotName = attributes.name ?? 'default'
|
||||||
let vfor: [string, string] | undefined
|
let vfor: string | undefined
|
||||||
if (attributes['v-for']) {
|
if (attributes['v-for']) {
|
||||||
vfor = attributes['v-for'].split(' in ').map((v: string) => v.trim()) as [string, string]
|
vfor = attributes['v-for']
|
||||||
}
|
}
|
||||||
delete attributes['v-for']
|
delete attributes['v-for']
|
||||||
|
|
||||||
if (attributes.name) { delete attributes.name }
|
if (attributes.name) { delete attributes.name }
|
||||||
if (attributes['v-bind']) {
|
if (attributes['v-bind']) {
|
||||||
attributes._bind = attributes['v-bind']
|
attributes._bind = extractAttributes(attributes, ['v-bind'])['v-bind']
|
||||||
delete attributes['v-bind']
|
|
||||||
}
|
}
|
||||||
const bindings = getPropsToString(attributes, vfor)
|
const teleportAttributes = extractAttributes(attributes, ['v-if', 'v-else-if', 'v-else'])
|
||||||
|
const bindings = getPropsToString(attributes, vfor?.split(' in ').map((v: string) => v.trim()) as [string, string])
|
||||||
// add the wrapper
|
// add the wrapper
|
||||||
s.appendLeft(startingIndex + loc[0].start, `<NuxtTeleportSsrSlot name="${slotName}" :props="${bindings}">`)
|
s.appendLeft(startingIndex + loc[0].start, `<NuxtTeleportSsrSlot${attributeToString(teleportAttributes)} name="${slotName}" :props="${bindings}">`)
|
||||||
|
|
||||||
|
if (children.length) {
|
||||||
|
// pass slot fallback to NuxtTeleportSsrSlot fallback
|
||||||
|
const attrString = attributeToString(attributes)
|
||||||
|
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(/:?key="[^"]"/g, '')
|
||||||
|
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[1].end, `<slot${attrString.replaceAll(EXTRACTED_ATTRS_RE, '')}/><template #fallback>${vfor ? wrapWithVForDiv(slice, vfor) : slice}</template>`)
|
||||||
|
} else {
|
||||||
|
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[0].end, code.slice(startingIndex + loc[0].start, startingIndex + loc[0].end).replaceAll(EXTRACTED_ATTRS_RE, ''))
|
||||||
|
}
|
||||||
|
|
||||||
s.appendRight(startingIndex + loc[1].end, '</NuxtTeleportSsrSlot>')
|
s.appendRight(startingIndex + loc[1].end, '</NuxtTeleportSsrSlot>')
|
||||||
} else if (options.selectiveClient && ('nuxt-client' in node.attributes || ':nuxt-client' in node.attributes)) {
|
} else if (options.selectiveClient && ('nuxt-client' in node.attributes || ':nuxt-client' in node.attributes)) {
|
||||||
hasNuxtClient = true
|
hasNuxtClient = true
|
||||||
@ -132,13 +135,31 @@ export const islandsTransform = createUnplugin((options: ServerOnlyComponentTran
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extract attributes from a node
|
||||||
|
*/
|
||||||
|
function extractAttributes (attributes: Record<string, string>, names: string[]) {
|
||||||
|
const extracted:Record<string, string> = {}
|
||||||
|
for (const name of names) {
|
||||||
|
if (name in attributes) {
|
||||||
|
extracted[name] = attributes[name]
|
||||||
|
delete attributes[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extracted
|
||||||
|
}
|
||||||
|
|
||||||
|
function attributeToString (attributes: Record<string, string>) {
|
||||||
|
return Object.entries(attributes).map(([name, value]) => value ? ` ${name}="${value}"` : ` ${name}`).join('')
|
||||||
|
}
|
||||||
|
|
||||||
function isBinding (attr: string): boolean {
|
function isBinding (attr: string): boolean {
|
||||||
return attr.startsWith(':')
|
return attr.startsWith(':')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPropsToString (bindings: Record<string, string>, vfor?: [string, string]): string {
|
function getPropsToString (bindings: Record<string, string>, vfor?: [string, string]): string {
|
||||||
if (Object.keys(bindings).length === 0) { return 'undefined' }
|
if (Object.keys(bindings).length === 0) { return 'undefined' }
|
||||||
const content = Object.entries(bindings).filter(b => b[0] && b[0] !== '_bind').map(([name, value]) => isBinding(name) ? `${name.slice(1)}: ${value}` : `${name}: \`${value}\``).join(',')
|
const content = Object.entries(bindings).filter(b => b[0] && b[0] !== '_bind').map(([name, value]) => isBinding(name) ? `[\`${name.slice(1)}\`]: ${value}` : `[\`${name}\`]: \`${value}\``).join(',')
|
||||||
const data = bindings._bind ? `mergeProps(${bindings._bind}, { ${content} })` : `{ ${content} }`
|
const data = bindings._bind ? `mergeProps(${bindings._bind}, { ${content} })` : `{ ${content} }`
|
||||||
if (!vfor) {
|
if (!vfor) {
|
||||||
return `[${data}]`
|
return `[${data}]`
|
||||||
|
@ -65,8 +65,8 @@ describe('islandTransform - server and island components', () => {
|
|||||||
<div>
|
<div>
|
||||||
<NuxtTeleportSsrSlot name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
<NuxtTeleportSsrSlot name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
||||||
|
|
||||||
<NuxtTeleportSsrSlot name="named" :props="[{ some-data: someData }]"><slot name="named" :some-data="someData" /></NuxtTeleportSsrSlot>
|
<NuxtTeleportSsrSlot name="named" :props="[{ [\`some-data\`]: someData }]"><slot name="named" :some-data="someData" /></NuxtTeleportSsrSlot>
|
||||||
<NuxtTeleportSsrSlot name="other" :props="[{ some-data: someData }]"><slot
|
<NuxtTeleportSsrSlot name="other" :props="[{ [\`some-data\`]: someData }]"><slot
|
||||||
name="other"
|
name="other"
|
||||||
:some-data="someData"
|
:some-data="someData"
|
||||||
/></NuxtTeleportSsrSlot>
|
/></NuxtTeleportSsrSlot>
|
||||||
@ -99,7 +99,7 @@ describe('islandTransform - server and island components', () => {
|
|||||||
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
||||||
"<template>
|
"<template>
|
||||||
<div>
|
<div>
|
||||||
<NuxtTeleportSsrSlot name="default" :props="[{ some-data: someData }]"><slot :some-data="someData" /><template #fallback>
|
<NuxtTeleportSsrSlot name="default" :props="[{ [\`some-data\`]: someData }]"><slot :some-data="someData"/><template #fallback>
|
||||||
<div>fallback</div>
|
<div>fallback</div>
|
||||||
</template></NuxtTeleportSsrSlot>
|
</template></NuxtTeleportSsrSlot>
|
||||||
</div>
|
</div>
|
||||||
@ -158,7 +158,7 @@ describe('islandTransform - server and island components', () => {
|
|||||||
<p>message: {{ message }}</p>
|
<p>message: {{ message }}</p>
|
||||||
<p>Below is the slot I want to be hydrated on the client</p>
|
<p>Below is the slot I want to be hydrated on the client</p>
|
||||||
<div>
|
<div>
|
||||||
<NuxtTeleportSsrSlot name="default" :props="undefined"><slot /><template #fallback>
|
<NuxtTeleportSsrSlot name="default" :props="undefined"><slot/><template #fallback>
|
||||||
This is the default content of the slot, I should not see this after
|
This is the default content of the slot, I should not see this after
|
||||||
the client loading has completed.
|
the client loading has completed.
|
||||||
</template></NuxtTeleportSsrSlot>
|
</template></NuxtTeleportSsrSlot>
|
||||||
@ -183,6 +183,33 @@ describe('islandTransform - server and island components', () => {
|
|||||||
"
|
"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('expect v-if/v-else/v-else-if to be set in teleport component wrapper', async () => {
|
||||||
|
const result = await viteTransform(`<script setup lang="ts">
|
||||||
|
const foo = true;
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<slot v-if="foo" />
|
||||||
|
<slot v-else-if="test" />
|
||||||
|
<slot v-else />
|
||||||
|
</template>
|
||||||
|
`, 'WithVif.vue', false, true)
|
||||||
|
|
||||||
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
||||||
|
"<script setup lang="ts">
|
||||||
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
||||||
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
||||||
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
||||||
|
const foo = true;
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<NuxtTeleportSsrSlot v-if="foo" name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
||||||
|
<NuxtTeleportSsrSlot v-else-if="test" name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
||||||
|
<NuxtTeleportSsrSlot v-else name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
||||||
|
</template>
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('nuxt-client', () => {
|
describe('nuxt-client', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user