wip: more consistent compiler-sfc usage + inline mode for ssr

This commit is contained in:
Evan You 2020-11-20 14:22:51 -05:00
parent 58227e88e9
commit 8ac2241b22
18 changed files with 213 additions and 93 deletions

View File

@ -9,7 +9,7 @@ const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"hello\\", -1 /*
const _hoisted_2 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */)
_popScopeId()
export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1,
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
@ -22,7 +22,7 @@ exports[`scopeId compiler support should wrap default slot 1`] = `
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, {
@ -38,7 +38,7 @@ exports[`scopeId compiler support should wrap dynamic slots 1`] = `
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 2 }, [
@ -66,7 +66,7 @@ exports[`scopeId compiler support should wrap named slots 1`] = `
"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, {
@ -85,7 +85,7 @@ exports[`scopeId compiler support should wrap render function 1`] = `
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
return (_openBlock(), _createBlock(\\"div\\"))
})"
`;

View File

@ -22,7 +22,7 @@ describe('scopeId compiler support', () => {
expect(ast.helpers).toContain(WITH_SCOPE_ID)
expect(code).toMatch(`const _withId = /*#__PURE__*/_withScopeId("test")`)
expect(code).toMatch(
`export const render = /*#__PURE__*/_withId(function render(`
`export const render = /*#__PURE__*/_withId((_ctx, _cache) => {`
)
expect(code).toMatchSnapshot()
})

View File

@ -203,7 +203,7 @@ export function generate(
const hasHelpers = ast.helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module'
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
const isSetupInlined = !!options.inline
const isSetupInlined = !__BROWSER__ && !!options.inline
// preambles
// in setup() inline mode, the preamble is generated in a sub context
@ -217,6 +217,8 @@ export function generate(
genFunctionPreamble(ast, preambleContext)
}
// enter render function
const functionName = ssr ? `ssrRender` : `render`
const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
// binding optimization args
@ -226,24 +228,18 @@ export function generate(
!__BROWSER__ && options.isTS
? args.map(arg => `${arg}: any`).join(',')
: args.join(', ')
// enter render function
if (!ssr) {
if (genScopeId) {
if (isSetupInlined) {
if (genScopeId) {
push(`${PURE_ANNOTATION}_withId(`)
}
push(`(${signature}) => {`)
push(`${PURE_ANNOTATION}_withId(`)
} else {
if (genScopeId) {
push(`const render = ${PURE_ANNOTATION}_withId(`)
}
push(`function render(${signature}) {`)
push(`const ${functionName} = ${PURE_ANNOTATION}_withId(`)
}
}
if (isSetupInlined || genScopeId) {
push(`(${signature}) => {`)
} else {
if (genScopeId) {
push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
}
push(`function ssrRender(${signature}) {`)
push(`function ${functionName}(${signature}) {`)
}
indent()

View File

@ -201,6 +201,40 @@ return (_ctx, _cache) => {
}"
`;
exports[`SFC compile <script setup> inlineTemplate mode ssr codegen 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from \\"@vue/server-renderer\\"
import { ref } from 'vue'
export default {
expose: [],
__ssrInlineRender: true,
setup(__props) {
_useCssVars(_ctx => ({
\\"xxxxxxxx-count\\": (count.value)
}))
const count = ref(0)
return (_ctx, _push, _parent, _attrs) => {
const _cssVars = { style: {
\\"xxxxxxxx-count\\": (count.value)
}}
_push(\`<!--[--><div\${
_ssrRenderAttrs(_cssVars)
}>\${
_ssrInterpolate(count.value)
}</div><div\${
_ssrRenderAttrs(_cssVars)
}>static</div><!--]-->\`)
}
}
}"
`;
exports[`SFC compile <script setup> inlineTemplate mode template assignment expression codegen 1`] = `
"import { createVNode as _createVNode, isRef as _isRef, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"

View File

@ -1,4 +1,4 @@
import { BindingTypes } from '@vue/compiler-dom/src'
import { BindingTypes } from '@vue/compiler-dom'
import { compileSFCScript as compile, assertCode } from './utils'
describe('SFC compile <script setup>', () => {
@ -297,6 +297,34 @@ const bar = 1
expect(content).toMatch(`{ lett: lett } = val`)
assertCode(content)
})
test('ssr codegen', () => {
const { content } = compile(
`
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<div>{{ count }}</div>
<div>static</div>
</template>
<style>
div { color: v-bind(count) }
</style>
`,
{
inlineTemplate: true,
templateOptions: {
ssr: true
}
}
)
expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
expect(content).toMatch(`return (_ctx, _push`)
expect(content).toMatch(`ssrInterpolate`)
assertCode(content)
})
})
describe('with TypeScript', () => {

View File

@ -16,7 +16,7 @@ export function compileScoped(
const res = compileStyle({
source,
filename: 'test.css',
id: 'test',
id: 'data-v-test',
scoped: true,
...options
})
@ -32,61 +32,61 @@ export function compileScoped(
describe('SFC scoped CSS', () => {
test('simple selectors', () => {
expect(compileScoped(`h1 { color: red; }`)).toMatch(
`h1[test] { color: red;`
`h1[data-v-test] { color: red;`
)
expect(compileScoped(`.foo { color: red; }`)).toMatch(
`.foo[test] { color: red;`
`.foo[data-v-test] { color: red;`
)
})
test('descendent selector', () => {
expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
`h1 .foo[test] { color: red;`
`h1 .foo[data-v-test] { color: red;`
)
})
test('multiple selectors', () => {
expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
`h1 .foo[test], .bar[test], .baz[test] { color: red;`
`h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`
)
})
test('pseudo class', () => {
expect(compileScoped(`.foo:after { color: red; }`)).toMatch(
`.foo[test]:after { color: red;`
`.foo[data-v-test]:after { color: red;`
)
})
test('pseudo element', () => {
expect(compileScoped(`::selection { display: none; }`)).toMatch(
'[test]::selection {'
'[data-v-test]::selection {'
)
})
test('spaces before pseudo element', () => {
const code = compileScoped(`.abc, ::selection { color: red; }`)
expect(code).toMatch('.abc[test],')
expect(code).toMatch('[test]::selection {')
expect(code).toMatch('.abc[data-v-test],')
expect(code).toMatch('[data-v-test]::selection {')
})
test('::v-deep', () => {
expect(compileScoped(`:deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
"[test] .foo { color: red;
"[data-v-test] .foo { color: red;
}"
`)
expect(compileScoped(`::v-deep(.foo) { color: red; }`))
.toMatchInlineSnapshot(`
"[test] .foo { color: red;
"[data-v-test] .foo { color: red;
}"
`)
expect(compileScoped(`::v-deep(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
"[test] .foo .bar { color: red;
"[data-v-test] .foo .bar { color: red;
}"
`)
expect(compileScoped(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".baz .qux[test] .foo .bar { color: red;
".baz .qux[data-v-test] .foo .bar { color: red;
}"
`)
})
@ -94,22 +94,22 @@ describe('SFC scoped CSS', () => {
test('::v-slotted', () => {
expect(compileScoped(`:slotted(.foo) { color: red; }`))
.toMatchInlineSnapshot(`
".foo[test-s] { color: red;
".foo[data-v-test-s] { color: red;
}"
`)
expect(compileScoped(`::v-slotted(.foo) { color: red; }`))
.toMatchInlineSnapshot(`
".foo[test-s] { color: red;
".foo[data-v-test-s] { color: red;
}"
`)
expect(compileScoped(`::v-slotted(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".foo .bar[test-s] { color: red;
".foo .bar[data-v-test-s] { color: red;
}"
`)
expect(compileScoped(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".baz .qux .foo .bar[test-s] { color: red;
".baz .qux .foo .bar[data-v-test-s] { color: red;
}"
`)
})
@ -142,7 +142,7 @@ describe('SFC scoped CSS', () => {
expect(compileScoped(`@media print { .foo { color: red }}`))
.toMatchInlineSnapshot(`
"@media print {
.foo[test] { color: red
.foo[data-v-test] { color: red
}}"
`)
})
@ -151,7 +151,7 @@ describe('SFC scoped CSS', () => {
expect(compileScoped(`@supports(display: grid) { .foo { display: grid }}`))
.toMatchInlineSnapshot(`
"@supports(display: grid) {
.foo[test] { display: grid
.foo[data-v-test] { display: grid
}}"
`)
})
@ -222,7 +222,7 @@ describe('SFC scoped CSS', () => {
// vue-loader/#1370
test('spaces after selector', () => {
expect(compileScoped(`.foo , .bar { color: red; }`)).toMatchInlineSnapshot(`
".foo[test], .bar[test] { color: red;
".foo[data-v-test], .bar[data-v-test] { color: red;
}"
`)
})
@ -231,12 +231,12 @@ describe('SFC scoped CSS', () => {
test('::v-deep as combinator', () => {
expect(compileScoped(`::v-deep .foo { color: red; }`))
.toMatchInlineSnapshot(`
"[test] .foo { color: red;
"[data-v-test] .foo { color: red;
}"
`)
expect(compileScoped(`.bar ::v-deep .foo { color: red; }`))
.toMatchInlineSnapshot(`
".bar[test] .foo { color: red;
".bar[data-v-test] .foo { color: red;
}"
`)
expect(
@ -247,7 +247,7 @@ describe('SFC scoped CSS', () => {
test('>>> (deprecated syntax)', () => {
const code = compileScoped(`>>> .foo { color: red; }`)
expect(code).toMatchInlineSnapshot(`
"[test] .foo { color: red;
"[data-v-test] .foo { color: red;
}"
`)
expect(
@ -258,7 +258,7 @@ describe('SFC scoped CSS', () => {
test('/deep/ (deprecated syntax)', () => {
const code = compileScoped(`/deep/ .foo { color: red; }`)
expect(code).toMatchInlineSnapshot(`
"[test] .foo { color: red;
"[data-v-test] .foo { color: red;
}"
`)
expect(

View File

@ -1,10 +1,20 @@
import { compileTemplate } from '../src/compileTemplate'
import {
compileTemplate,
SFCTemplateCompileOptions
} from '../src/compileTemplate'
import { parse, SFCTemplateBlock } from '../src/parse'
function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) {
return compileTemplate({
...opts,
id: ''
})
}
test('should work', () => {
const source = `<div><p>{{ render }}</p></div>`
const result = compileTemplate({ filename: 'example.vue', source })
const result = compile({ filename: 'example.vue', source })
expect(result.errors.length).toBe(0)
expect(result.source).toBe(source)
@ -25,7 +35,7 @@ body
{ filename: 'example.vue', sourceMap: true }
).descriptor.template as SFCTemplateBlock
const result = compileTemplate({
const result = compile({
filename: 'example.vue',
source: template.content,
preprocessLang: template.lang
@ -40,7 +50,7 @@ test('warn missing preprocessor', () => {
sourceMap: true
}).descriptor.template as SFCTemplateBlock
const result = compileTemplate({
const result = compile({
filename: 'example.vue',
source: template.content,
preprocessLang: template.lang
@ -52,7 +62,7 @@ test('warn missing preprocessor', () => {
test('transform asset url options', () => {
const input = { source: `<foo bar="~baz"/>`, filename: 'example.vue' }
// Object option
const { code: code1 } = compileTemplate({
const { code: code1 } = compile({
...input,
transformAssetUrls: {
tags: { foo: ['bar'] }
@ -61,7 +71,7 @@ test('transform asset url options', () => {
expect(code1).toMatch(`import _imports_0 from 'baz'\n`)
// legacy object option (direct tags config)
const { code: code2 } = compileTemplate({
const { code: code2 } = compile({
...input,
transformAssetUrls: {
foo: ['bar']
@ -70,7 +80,7 @@ test('transform asset url options', () => {
expect(code2).toMatch(`import _imports_0 from 'baz'\n`)
// false option
const { code: code3 } = compileTemplate({
const { code: code3 } = compile({
...input,
transformAssetUrls: false
})
@ -87,7 +97,7 @@ test('source map', () => {
{ filename: 'example.vue', sourceMap: true }
).descriptor.template as SFCTemplateBlock
const result = compileTemplate({
const result = compile({
filename: 'example.vue',
source: template.content
})
@ -96,7 +106,7 @@ test('source map', () => {
})
test('template errors', () => {
const result = compileTemplate({
const result = compile({
filename: 'example.vue',
source: `<div :foo
:bar="a[" v-model="baz"/>`
@ -114,7 +124,7 @@ test('preprocessor errors', () => {
{ filename: 'example.vue', sourceMap: true }
).descriptor.template as SFCTemplateBlock
const result = compileTemplate({
const result = compile({
filename: 'example.vue',
source: template.content,
preprocessLang: template.lang

View File

@ -167,6 +167,7 @@ export function compileScript(
let optionsArg: ObjectExpression | undefined
let optionsType: TSTypeLiteral | undefined
let hasAwait = false
let hasInlinedSsrRenderFn = false
// context types to generate
let propsType = `{}`
let emitType = `(e: string, ...args: any[]) => void`
@ -820,15 +821,24 @@ export function compileScript(
// 10. generate return statement
let returned
if (options.inlineTemplate) {
if (sfc.template) {
if (sfc.template && !sfc.template.src) {
if (options.templateOptions && options.templateOptions.ssr) {
hasInlinedSsrRenderFn = true
}
// inline render function mode - we are going to compile the template and
// inline it right here
const { code, ast, preamble, tips, errors } = compileTemplate({
...options.templateOptions,
filename,
source: sfc.template.content,
inMap: sfc.template.map,
...options.templateOptions,
id: scopeId,
scoped: sfc.styles.some(s => s.scoped),
isProd: options.isProd,
ssrCssVars: sfc.cssVars,
compilerOptions: {
...(options.templateOptions &&
options.templateOptions.compilerOptions),
inline: true,
isTS,
bindingMetadata
@ -883,6 +893,9 @@ export function compileScript(
// 11. finalize default export
// expose: [] makes <script setup> components "closed" by default.
let runtimeOptions = `\n expose: [],`
if (hasInlinedSsrRenderFn) {
runtimeOptions += `\n __ssrInlineRender: true,`
}
if (optionsArg) {
runtimeOptions += `\n ${scriptSetup.content
.slice(optionsArg.start! + 1, optionsArg.end! - 1)

View File

@ -20,15 +20,19 @@ export interface SFCStyleCompileOptions {
source: string
filename: string
id: string
map?: RawSourceMap
scoped?: boolean
trim?: boolean
isProd?: boolean
inMap?: RawSourceMap
preprocessLang?: PreprocessLang
preprocessOptions?: any
preprocessCustomRequire?: (id: string) => any
postcssOptions?: any
postcssPlugins?: any[]
/**
* @deprecated
*/
map?: RawSourceMap
}
export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
@ -92,16 +96,21 @@ export function doCompileStyle(
} = options
const preprocessor = preprocessLang && processors[preprocessLang]
const preProcessedSource = preprocessor && preprocess(options, preprocessor)
const map = preProcessedSource ? preProcessedSource.map : options.map
const map = preProcessedSource
? preProcessedSource.map
: options.inMap || options.map
const source = preProcessedSource ? preProcessedSource.code : options.source
const shortId = id.replace(/^data-v-/, '')
const longId = `data-v-${shortId}`
const plugins = (postcssPlugins || []).slice()
plugins.unshift(cssVarsPlugin({ id, isProd }))
plugins.unshift(cssVarsPlugin({ id: shortId, isProd }))
if (trim) {
plugins.push(trimPlugin())
}
if (scoped) {
plugins.push(scopedPlugin(id))
plugins.push(scopedPlugin(longId))
}
let cssModules: Record<string, string> | undefined
if (modules) {

View File

@ -23,6 +23,7 @@ import * as CompilerDOM from '@vue/compiler-dom'
import * as CompilerSSR from '@vue/compiler-ssr'
import consolidate from 'consolidate'
import { warnOnce } from './warn'
import { genCssVarsFromList } from './cssVars'
export interface TemplateCompiler {
compile(template: string, options: CompilerOptions): CodegenResult
@ -42,7 +43,11 @@ export interface SFCTemplateCompileResults {
export interface SFCTemplateCompileOptions {
source: string
filename: string
id: string
scoped?: boolean
isProd?: boolean
ssr?: boolean
ssrCssVars?: string[]
inMap?: RawSourceMap
compiler?: TemplateCompiler
compilerOptions?: CompilerOptions
@ -151,9 +156,13 @@ export function compileTemplate(
function doCompileTemplate({
filename,
id,
scoped,
inMap,
source,
ssr = false,
ssrCssVars,
isProd = false,
compiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM,
compilerOptions = {},
transformAssetUrls
@ -171,19 +180,30 @@ function doCompileTemplate({
nodeTransforms = [transformAssetUrl, transformSrcset]
}
if (ssr && compilerOptions.ssrCssVars == null) {
if (ssr && !ssrCssVars) {
warnOnce(
`compileTemplate is called with \`ssr: true\` but no ` +
`corresponding \`ssrCssVars\` option. The value can be generated by ` +
`calling \`generateCssVars(sfcDescriptor, scopeId, isProduction)\`.`
`corresponding \`cssVars\` option.\`.`
)
}
if (!id) {
warnOnce(`compileTemplate now requires the \`id\` option.\`.`)
id = ''
}
const shortId = id.replace(/^data-v-/, '')
const longId = `data-v-${shortId}`
let { code, ast, preamble, map } = compiler.compile(source, {
mode: 'module',
prefixIdentifiers: true,
hoistStatic: true,
cacheHandlers: true,
ssrCssVars:
ssr && ssrCssVars && ssrCssVars.length
? genCssVarsFromList(ssrCssVars, shortId, isProd)
: '',
scopeId: scoped ? longId : undefined,
...compilerOptions,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,

View File

@ -16,26 +16,13 @@ import hash from 'hash-sum'
export const CSS_VARS_HELPER = `useCssVars`
export const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g
/**
* Given an SFC descriptor, generate the CSS variables object string that can be
* passed to `compileTemplate` as `compilerOptions.ssrCssVars`.
* @public
*/
export function generateCssVars(
sfc: SFCDescriptor,
id: string,
isProd: boolean
): string {
return sfc.cssVars.length ? genCssVarsFromList(sfc.cssVars, id, isProd) : ''
}
function genCssVarsFromList(
export function genCssVarsFromList(
vars: string[],
id: string,
isProd: boolean
): string {
return `{\n ${vars
.map(v => `"${genVarName(id, v, isProd)}": (${v})`)
.map(key => `"${genVarName(id, key, isProd)}": (${key})`)
.join(',\n ')}\n}`
}
@ -68,12 +55,11 @@ export const cssVarsPlugin = postcss.plugin<CssVarsPluginOptions>(
'vue-scoped',
opts => (root: Root) => {
const { id, isProd } = opts!
const shortId = id.replace(/^data-v-/, '')
root.walkDecls(decl => {
// rewrite CSS variables
if (cssVarRE.test(decl.value)) {
decl.value = decl.value.replace(cssVarRE, (_, $1, $2, $3) => {
return `var(--${genVarName(shortId, $1 || $2 || $3, isProd)})`
return `var(--${genVarName(id, $1 || $2 || $3, isProd)})`
})
}
})

View File

@ -5,7 +5,6 @@ export { compileStyle, compileStyleAsync } from './compileStyle'
export { compileScript } from './compileScript'
export { rewriteDefault } from './rewriteDefault'
export { generateCodeFrame } from '@vue/compiler-core'
export { generateCssVars } from './cssVars'
// Types
export {

View File

@ -14,7 +14,7 @@ describe('ssr: scopeId', () => {
import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
_push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
})"
`)
@ -32,7 +32,7 @@ describe('ssr: scopeId', () => {
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, {
@ -62,7 +62,7 @@ describe('ssr: scopeId', () => {
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, {
@ -92,7 +92,7 @@ describe('ssr: scopeId', () => {
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
const _component_foo = _resolveComponent(\\"foo\\")
const _component_bar = _resolveComponent(\\"bar\\")

View File

@ -223,6 +223,11 @@ export interface ComponentInternalInstance {
* @internal
*/
render: InternalRenderFunction | null
/**
* SSR render function
* @internal
*/
ssrRender?: Function | null
/**
* Object containing values this component provides for its descendents
* @internal
@ -610,7 +615,13 @@ export function handleSetupResult(
) {
if (isFunction(setupResult)) {
// setup returned an inline render function
instance.render = setupResult as InternalRenderFunction
if (!__BROWSER__ && (instance.type as ComponentOptions).__ssrInlineRender) {
// when the function's name is `ssrRender` (compiled by SFC inline mode),
// set it as ssrRender instead.
instance.ssrRender = setupResult
} else {
instance.render = setupResult as InternalRenderFunction
}
} else if (isObject(setupResult)) {
if (__DEV__ && isVNode(setupResult)) {
warn(

View File

@ -121,7 +121,6 @@ export interface ComponentOptionsBase<
/**
* SSR only. This is produced by compiler-ssr and attached in compiler-sfc
* not user facing, so the typing is lax and for test only.
*
* @internal
*/
ssrRender?: (
@ -136,6 +135,13 @@ export interface ComponentOptionsBase<
$options: ComponentInternalInstance['ctx']
) => void
/**
* Only generated by compiler-sfc to mark a ssr render function inlined and
* returned from setup()
* @internal
*/
__ssrInlineRender?: boolean
/**
* marker for AsyncComponentWrapper
* @internal

View File

@ -14,6 +14,8 @@ import { ShapeFlags } from '@vue/shared'
* @private
*/
export function useCssVars(getter: (ctx: any) => Record<string, string>) {
if (!__BROWSER__ && !__TEST__) return
const instance = getCurrentInstance()
/* istanbul ignore next */
if (!instance) {

View File

@ -115,11 +115,17 @@ function renderComponentSubTree(
instance
)
} else {
if (!instance.render && !comp.ssrRender && isString(comp.template)) {
if (
!instance.render &&
!instance.ssrRender &&
!comp.ssrRender &&
isString(comp.template)
) {
comp.ssrRender = ssrCompile(comp.template, instance)
}
if (comp.ssrRender) {
const ssrRender = instance.ssrRender || comp.ssrRender
if (ssrRender) {
// optimized
// resolve fallthrough attrs
let attrs =
@ -138,7 +144,7 @@ function renderComponentSubTree(
// set current rendering instance for asset resolution
setCurrentRenderingInstance(instance)
comp.ssrRender(
ssrRender(
instance.proxy,
push,
instance,

View File

@ -129,7 +129,7 @@ function createConfig(format, output, plugins = []) {
[
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
...['path', 'url'] // for @vue/compiler-sfc
...['path', 'url', 'stream'] // for @vue/compiler-sfc / server-renderer
]
// the browser builds of @vue/compiler-sfc requires postcss to be available