fix(server-renderer): respect compilerOptions during runtime template compilation (#4631)
This commit is contained in:
parent
e4ae1fc3d0
commit
50d9d34360
148
packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts
Normal file
148
packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* @jest-environment node
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { renderToString } from '../src/renderToString'
|
||||||
|
|
||||||
|
describe('ssr: compiler options', () => {
|
||||||
|
test('config.isCustomElement (deprecated)', async () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div><x-button/></div>`
|
||||||
|
})
|
||||||
|
app.config.isCustomElement = tag => tag.startsWith('x-')
|
||||||
|
expect(await renderToString(app)).toBe(`<div><x-button></x-button></div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('config.compilerOptions.isCustomElement', async () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div><x-panel/></div>`
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('x-')
|
||||||
|
expect(await renderToString(app)).toBe(`<div><x-panel></x-panel></div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('component.compilerOptions.isCustomElement', async () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div><x-card/><y-child/></div>`,
|
||||||
|
compilerOptions: {
|
||||||
|
isCustomElement: (tag: string) => tag.startsWith('x-')
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
YChild: {
|
||||||
|
template: `<div><y-button/></div>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('y-')
|
||||||
|
expect(await renderToString(app)).toBe(
|
||||||
|
`<div><x-card></x-card><div><y-button></y-button></div></div>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('component.delimiters (deprecated)', async () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div>[[ 1 + 1 ]]</div>`,
|
||||||
|
delimiters: ['[[', ']]']
|
||||||
|
})
|
||||||
|
expect(await renderToString(app)).toBe(`<div>2</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('config.compilerOptions.delimiters', async () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div>[( 1 + 1 )]</div>`
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.delimiters = ['[(', ')]']
|
||||||
|
expect(await renderToString(app)).toBe(`<div>2</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('component.compilerOptions.delimiters', async () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div>[[ 1 + 1 ]]<ChildComponent/></div>`,
|
||||||
|
compilerOptions: {
|
||||||
|
delimiters: ['[[', ']]']
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ChildComponent: {
|
||||||
|
template: `<div>(( 2 + 2 ))</div>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.delimiters = ['((', '))']
|
||||||
|
expect(await renderToString(app)).toBe(`<div>2<div>4</div></div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compilerOptions.whitespace', async () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div><span>Hello world</span><ChildComponent/></div>`,
|
||||||
|
compilerOptions: {
|
||||||
|
whitespace: 'condense'
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ChildComponent: {
|
||||||
|
template: `<span>Hello world</span>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.whitespace = 'preserve'
|
||||||
|
expect(await renderToString(app)).toBe(
|
||||||
|
`<div><span>Hello world</span><span>Hello world</span></div>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('caching with compilerOptions', async () => {
|
||||||
|
const template = `<div>{{1 + 1}} [[1 + 1]]</div>`
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div><ChildOne/><ChildTwo/><ChildThree/></div>`,
|
||||||
|
components: {
|
||||||
|
ChildOne: {
|
||||||
|
template
|
||||||
|
},
|
||||||
|
ChildTwo: {
|
||||||
|
template,
|
||||||
|
compilerOptions: {
|
||||||
|
whitespace: 'preserve'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChildThree: {
|
||||||
|
template,
|
||||||
|
compilerOptions: {
|
||||||
|
delimiters: ['[[', ']]']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(await renderToString(app)).toBe(
|
||||||
|
`<div><div>2 [[1 + 1]]</div><div>2 [[1 + 1]]</div><div>{{1 + 1}} 2</div></div>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('caching with isCustomElement', async () => {
|
||||||
|
const template = `<div><MyChild/></div>`
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
template,
|
||||||
|
// No compilerOptions on the root
|
||||||
|
components: {
|
||||||
|
MyChild: {
|
||||||
|
template,
|
||||||
|
compilerOptions: {
|
||||||
|
isCustomElement: tag => tag.startsWith('x-')
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MyChild: {
|
||||||
|
template,
|
||||||
|
compilerOptions: {
|
||||||
|
isCustomElement: tag => tag.startsWith('My')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(await renderToString(app)).toBe(
|
||||||
|
`<div><div><div><MyChild></MyChild></div></div></div>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,7 +1,7 @@
|
|||||||
import { ComponentInternalInstance, warn } from 'vue'
|
import { ComponentInternalInstance, ComponentOptions, warn } from 'vue'
|
||||||
import { compile } from '@vue/compiler-ssr'
|
import { compile } from '@vue/compiler-ssr'
|
||||||
import { generateCodeFrame, NO } from '@vue/shared'
|
import { extend, generateCodeFrame, isFunction, NO } from '@vue/shared'
|
||||||
import { CompilerError } from '@vue/compiler-core'
|
import { CompilerError, CompilerOptions } from '@vue/compiler-core'
|
||||||
import { PushFn } from '../render'
|
import { PushFn } from '../render'
|
||||||
|
|
||||||
type SSRRenderFunction = (
|
type SSRRenderFunction = (
|
||||||
@ -24,29 +24,57 @@ export function ssrCompile(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cached = compileCache[template]
|
// TODO: This is copied from runtime-core/src/component.ts and should probably be refactored
|
||||||
|
const Component = instance.type as ComponentOptions
|
||||||
|
const { isCustomElement, compilerOptions } = instance.appContext.config
|
||||||
|
const { delimiters, compilerOptions: componentCompilerOptions } = Component
|
||||||
|
|
||||||
|
const finalCompilerOptions: CompilerOptions = extend(
|
||||||
|
extend(
|
||||||
|
{
|
||||||
|
isCustomElement,
|
||||||
|
delimiters
|
||||||
|
},
|
||||||
|
compilerOptions
|
||||||
|
),
|
||||||
|
componentCompilerOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
finalCompilerOptions.isCustomElement =
|
||||||
|
finalCompilerOptions.isCustomElement || NO
|
||||||
|
finalCompilerOptions.isNativeTag = finalCompilerOptions.isNativeTag || NO
|
||||||
|
|
||||||
|
const cacheKey = JSON.stringify(
|
||||||
|
{
|
||||||
|
template,
|
||||||
|
compilerOptions: finalCompilerOptions
|
||||||
|
},
|
||||||
|
(key, value) => {
|
||||||
|
return isFunction(value) ? value.toString() : value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const cached = compileCache[cacheKey]
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
|
|
||||||
const { code } = compile(template, {
|
finalCompilerOptions.onError = (err: CompilerError) => {
|
||||||
isCustomElement: instance.appContext.config.isCustomElement || NO,
|
if (__DEV__) {
|
||||||
isNativeTag: instance.appContext.config.isNativeTag || NO,
|
const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
|
||||||
onError(err: CompilerError) {
|
const codeFrame =
|
||||||
if (__DEV__) {
|
err.loc &&
|
||||||
const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
|
generateCodeFrame(
|
||||||
const codeFrame =
|
template as string,
|
||||||
err.loc &&
|
err.loc.start.offset,
|
||||||
generateCodeFrame(
|
err.loc.end.offset
|
||||||
template as string,
|
)
|
||||||
err.loc.start.offset,
|
warn(codeFrame ? `${message}\n${codeFrame}` : message)
|
||||||
err.loc.end.offset
|
} else {
|
||||||
)
|
throw err
|
||||||
warn(codeFrame ? `${message}\n${codeFrame}` : message)
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
return (compileCache[template] = Function('require', code)(require))
|
|
||||||
|
const { code } = compile(template, finalCompilerOptions)
|
||||||
|
return (compileCache[cacheKey] = Function('require', code)(require))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user