Skip to content

Commit

Permalink
fix(server-renderer): respect compilerOptions during runtime template…
Browse files Browse the repository at this point in the history
… compilation (#4631)
  • Loading branch information
skirtles-code authored Sep 25, 2021
1 parent e4ae1fc commit 50d9d34
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 23 deletions.
148 changes: 148 additions & 0 deletions packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts
Original file line number Diff line number Diff line change
@@ -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>`
)
})
})
74 changes: 51 additions & 23 deletions packages/server-renderer/src/helpers/ssrCompile.ts
Original file line number Diff line number Diff line change
@@ -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 = (
Expand All @@ -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))
}

0 comments on commit 50d9d34

Please sign in to comment.