diff --git a/packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts b/packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts new file mode 100644 index 00000000000..2ff588a5c77 --- /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(`
2
4
`) + }) + + 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 39fd6c09ba6..a44feb5fc0d 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)) }