Skip to content

Commit 50d9d34

Browse files
fix(server-renderer): respect compilerOptions during runtime template compilation (#4631)
1 parent e4ae1fc commit 50d9d34

File tree

2 files changed

+199
-23
lines changed

2 files changed

+199
-23
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
5+
import { createApp } from 'vue'
6+
import { renderToString } from '../src/renderToString'
7+
8+
describe('ssr: compiler options', () => {
9+
test('config.isCustomElement (deprecated)', async () => {
10+
const app = createApp({
11+
template: `<div><x-button/></div>`
12+
})
13+
app.config.isCustomElement = tag => tag.startsWith('x-')
14+
expect(await renderToString(app)).toBe(`<div><x-button></x-button></div>`)
15+
})
16+
17+
test('config.compilerOptions.isCustomElement', async () => {
18+
const app = createApp({
19+
template: `<div><x-panel/></div>`
20+
})
21+
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('x-')
22+
expect(await renderToString(app)).toBe(`<div><x-panel></x-panel></div>`)
23+
})
24+
25+
test('component.compilerOptions.isCustomElement', async () => {
26+
const app = createApp({
27+
template: `<div><x-card/><y-child/></div>`,
28+
compilerOptions: {
29+
isCustomElement: (tag: string) => tag.startsWith('x-')
30+
},
31+
components: {
32+
YChild: {
33+
template: `<div><y-button/></div>`
34+
}
35+
}
36+
})
37+
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('y-')
38+
expect(await renderToString(app)).toBe(
39+
`<div><x-card></x-card><div><y-button></y-button></div></div>`
40+
)
41+
})
42+
43+
test('component.delimiters (deprecated)', async () => {
44+
const app = createApp({
45+
template: `<div>[[ 1 + 1 ]]</div>`,
46+
delimiters: ['[[', ']]']
47+
})
48+
expect(await renderToString(app)).toBe(`<div>2</div>`)
49+
})
50+
51+
test('config.compilerOptions.delimiters', async () => {
52+
const app = createApp({
53+
template: `<div>[( 1 + 1 )]</div>`
54+
})
55+
app.config.compilerOptions.delimiters = ['[(', ')]']
56+
expect(await renderToString(app)).toBe(`<div>2</div>`)
57+
})
58+
59+
test('component.compilerOptions.delimiters', async () => {
60+
const app = createApp({
61+
template: `<div>[[ 1 + 1 ]]<ChildComponent/></div>`,
62+
compilerOptions: {
63+
delimiters: ['[[', ']]']
64+
},
65+
components: {
66+
ChildComponent: {
67+
template: `<div>(( 2 + 2 ))</div>`
68+
}
69+
}
70+
})
71+
app.config.compilerOptions.delimiters = ['((', '))']
72+
expect(await renderToString(app)).toBe(`<div>2<div>4</div></div>`)
73+
})
74+
75+
test('compilerOptions.whitespace', async () => {
76+
const app = createApp({
77+
template: `<div><span>Hello world</span><ChildComponent/></div>`,
78+
compilerOptions: {
79+
whitespace: 'condense'
80+
},
81+
components: {
82+
ChildComponent: {
83+
template: `<span>Hello world</span>`
84+
}
85+
}
86+
})
87+
app.config.compilerOptions.whitespace = 'preserve'
88+
expect(await renderToString(app)).toBe(
89+
`<div><span>Hello world</span><span>Hello world</span></div>`
90+
)
91+
})
92+
93+
test('caching with compilerOptions', async () => {
94+
const template = `<div>{{1 + 1}} [[1 + 1]]</div>`
95+
96+
const app = createApp({
97+
template: `<div><ChildOne/><ChildTwo/><ChildThree/></div>`,
98+
components: {
99+
ChildOne: {
100+
template
101+
},
102+
ChildTwo: {
103+
template,
104+
compilerOptions: {
105+
whitespace: 'preserve'
106+
}
107+
},
108+
ChildThree: {
109+
template,
110+
compilerOptions: {
111+
delimiters: ['[[', ']]']
112+
}
113+
}
114+
}
115+
})
116+
expect(await renderToString(app)).toBe(
117+
`<div><div>2 [[1 + 1]]</div><div>2 [[1 + 1]]</div><div>{{1 + 1}} 2</div></div>`
118+
)
119+
})
120+
121+
test('caching with isCustomElement', async () => {
122+
const template = `<div><MyChild/></div>`
123+
124+
const app = createApp({
125+
template,
126+
// No compilerOptions on the root
127+
components: {
128+
MyChild: {
129+
template,
130+
compilerOptions: {
131+
isCustomElement: tag => tag.startsWith('x-')
132+
},
133+
components: {
134+
MyChild: {
135+
template,
136+
compilerOptions: {
137+
isCustomElement: tag => tag.startsWith('My')
138+
}
139+
}
140+
}
141+
}
142+
}
143+
})
144+
expect(await renderToString(app)).toBe(
145+
`<div><div><div><MyChild></MyChild></div></div></div>`
146+
)
147+
})
148+
})
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ComponentInternalInstance, warn } from 'vue'
1+
import { ComponentInternalInstance, ComponentOptions, warn } from 'vue'
22
import { compile } from '@vue/compiler-ssr'
3-
import { generateCodeFrame, NO } from '@vue/shared'
4-
import { CompilerError } from '@vue/compiler-core'
3+
import { extend, generateCodeFrame, isFunction, NO } from '@vue/shared'
4+
import { CompilerError, CompilerOptions } from '@vue/compiler-core'
55
import { PushFn } from '../render'
66

77
type SSRRenderFunction = (
@@ -24,29 +24,57 @@ export function ssrCompile(
2424
)
2525
}
2626

27-
const cached = compileCache[template]
27+
// TODO: This is copied from runtime-core/src/component.ts and should probably be refactored
28+
const Component = instance.type as ComponentOptions
29+
const { isCustomElement, compilerOptions } = instance.appContext.config
30+
const { delimiters, compilerOptions: componentCompilerOptions } = Component
31+
32+
const finalCompilerOptions: CompilerOptions = extend(
33+
extend(
34+
{
35+
isCustomElement,
36+
delimiters
37+
},
38+
compilerOptions
39+
),
40+
componentCompilerOptions
41+
)
42+
43+
finalCompilerOptions.isCustomElement =
44+
finalCompilerOptions.isCustomElement || NO
45+
finalCompilerOptions.isNativeTag = finalCompilerOptions.isNativeTag || NO
46+
47+
const cacheKey = JSON.stringify(
48+
{
49+
template,
50+
compilerOptions: finalCompilerOptions
51+
},
52+
(key, value) => {
53+
return isFunction(value) ? value.toString() : value
54+
}
55+
)
56+
57+
const cached = compileCache[cacheKey]
2858
if (cached) {
2959
return cached
3060
}
3161

32-
const { code } = compile(template, {
33-
isCustomElement: instance.appContext.config.isCustomElement || NO,
34-
isNativeTag: instance.appContext.config.isNativeTag || NO,
35-
onError(err: CompilerError) {
36-
if (__DEV__) {
37-
const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
38-
const codeFrame =
39-
err.loc &&
40-
generateCodeFrame(
41-
template as string,
42-
err.loc.start.offset,
43-
err.loc.end.offset
44-
)
45-
warn(codeFrame ? `${message}\n${codeFrame}` : message)
46-
} else {
47-
throw err
48-
}
62+
finalCompilerOptions.onError = (err: CompilerError) => {
63+
if (__DEV__) {
64+
const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
65+
const codeFrame =
66+
err.loc &&
67+
generateCodeFrame(
68+
template as string,
69+
err.loc.start.offset,
70+
err.loc.end.offset
71+
)
72+
warn(codeFrame ? `${message}\n${codeFrame}` : message)
73+
} else {
74+
throw err
4975
}
50-
})
51-
return (compileCache[template] = Function('require', code)(require))
76+
}
77+
78+
const { code } = compile(template, finalCompilerOptions)
79+
return (compileCache[cacheKey] = Function('require', code)(require))
5280
}

0 commit comments

Comments
 (0)