From 50d9d3436079419f91231351f20f69062a01505c Mon Sep 17 00:00:00 2001
From: skirtle <65301168+skirtles-code@users.noreply.github.com>
Date: Sat, 25 Sep 2021 19:40:32 +0100
Subject: [PATCH] fix(server-renderer): respect compilerOptions during runtime
template compilation (#4631)
---
.../__tests__/ssrCompilerOptions.spec.ts | 148 ++++++++++++++++++
.../server-renderer/src/helpers/ssrCompile.ts | 74 ++++++---
2 files changed, 199 insertions(+), 23 deletions(-)
create mode 100644 packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts
diff --git a/packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts b/packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts
new file mode 100644
index 00000000..2ff588a5
--- /dev/null
+++ b/packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts
@@ -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: `
`
+ })
+ app.config.isCustomElement = tag => tag.startsWith('x-')
+ expect(await renderToString(app)).toBe(`
`)
+ })
+
+ test('config.compilerOptions.isCustomElement', async () => {
+ const app = createApp({
+ template: `
`
+ })
+ app.config.compilerOptions.isCustomElement = tag => tag.startsWith('x-')
+ expect(await renderToString(app)).toBe(`
`)
+ })
+
+ test('component.compilerOptions.isCustomElement', async () => {
+ const app = createApp({
+ template: `
`,
+ compilerOptions: {
+ isCustomElement: (tag: string) => tag.startsWith('x-')
+ },
+ components: {
+ YChild: {
+ template: `
`
+ }
+ }
+ })
+ app.config.compilerOptions.isCustomElement = tag => tag.startsWith('y-')
+ expect(await renderToString(app)).toBe(
+ ``
+ )
+ })
+
+ test('component.delimiters (deprecated)', async () => {
+ const app = createApp({
+ template: `[[ 1 + 1 ]]
`,
+ delimiters: ['[[', ']]']
+ })
+ expect(await renderToString(app)).toBe(`2
`)
+ })
+
+ test('config.compilerOptions.delimiters', async () => {
+ const app = createApp({
+ template: `[( 1 + 1 )]
`
+ })
+ app.config.compilerOptions.delimiters = ['[(', ')]']
+ expect(await renderToString(app)).toBe(`2
`)
+ })
+
+ test('component.compilerOptions.delimiters', async () => {
+ const app = createApp({
+ template: `[[ 1 + 1 ]]
`,
+ compilerOptions: {
+ delimiters: ['[[', ']]']
+ },
+ components: {
+ ChildComponent: {
+ template: `(( 2 + 2 ))
`
+ }
+ }
+ })
+ app.config.compilerOptions.delimiters = ['((', '))']
+ expect(await renderToString(app)).toBe(``)
+ })
+
+ test('compilerOptions.whitespace', async () => {
+ const app = createApp({
+ template: `Hello world
`,
+ compilerOptions: {
+ whitespace: 'condense'
+ },
+ components: {
+ ChildComponent: {
+ template: `Hello world`
+ }
+ }
+ })
+ app.config.compilerOptions.whitespace = 'preserve'
+ expect(await renderToString(app)).toBe(
+ `Hello worldHello world
`
+ )
+ })
+
+ test('caching with compilerOptions', async () => {
+ const template = `{{1 + 1}} [[1 + 1]]
`
+
+ const app = createApp({
+ template: `
`,
+ components: {
+ ChildOne: {
+ template
+ },
+ ChildTwo: {
+ template,
+ compilerOptions: {
+ whitespace: 'preserve'
+ }
+ },
+ ChildThree: {
+ template,
+ compilerOptions: {
+ delimiters: ['[[', ']]']
+ }
+ }
+ }
+ })
+ expect(await renderToString(app)).toBe(
+ `2 [[1 + 1]]
2 [[1 + 1]]
{{1 + 1}} 2
`
+ )
+ })
+
+ test('caching with isCustomElement', async () => {
+ const template = `
`
+
+ 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(
+ ``
+ )
+ })
+})
diff --git a/packages/server-renderer/src/helpers/ssrCompile.ts b/packages/server-renderer/src/helpers/ssrCompile.ts
index 39fd6c09..a44feb5f 100644
--- a/packages/server-renderer/src/helpers/ssrCompile.ts
+++ b/packages/server-renderer/src/helpers/ssrCompile.ts
@@ -1,7 +1,7 @@
-import { ComponentInternalInstance, warn } from 'vue'
+import { ComponentInternalInstance, ComponentOptions, warn } from 'vue'
import { compile } from '@vue/compiler-ssr'
-import { generateCodeFrame, NO } from '@vue/shared'
-import { CompilerError } from '@vue/compiler-core'
+import { extend, generateCodeFrame, isFunction, NO } from '@vue/shared'
+import { CompilerError, CompilerOptions } from '@vue/compiler-core'
import { PushFn } from '../render'
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) {
return cached
}
- const { code } = compile(template, {
- isCustomElement: instance.appContext.config.isCustomElement || NO,
- isNativeTag: instance.appContext.config.isNativeTag || NO,
- onError(err: CompilerError) {
- if (__DEV__) {
- const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
- const codeFrame =
- err.loc &&
- generateCodeFrame(
- template as string,
- err.loc.start.offset,
- err.loc.end.offset
- )
- warn(codeFrame ? `${message}\n${codeFrame}` : message)
- } else {
- throw err
- }
+ finalCompilerOptions.onError = (err: CompilerError) => {
+ if (__DEV__) {
+ const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
+ const codeFrame =
+ err.loc &&
+ generateCodeFrame(
+ template as string,
+ err.loc.start.offset,
+ err.loc.end.offset
+ )
+ 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))
}