From: Evan You Date: Tue, 4 Feb 2020 17:20:51 +0000 (-0500) Subject: wip(ssr): v-bind basic usage X-Git-Tag: v3.0.0-alpha.5~117 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6a5ed49ea9e51173e336382a8828163178b5f1c2;p=thirdparty%2Fvuejs%2Fcore.git wip(ssr): v-bind basic usage --- diff --git a/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts b/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts new file mode 100644 index 0000000000..f48d3e8995 --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts @@ -0,0 +1,26 @@ +import { + baseParse as parse, + transform, + ElementNode, + CallExpression, + noopDirectiveTransform +} from '../../src' +import { transformElement } from '../../src/transforms/transformElement' + +describe('compiler: noop directive transform', () => { + test('should add no props to DOM', () => { + const ast = parse(`
`) + transform(ast, { + nodeTransforms: [transformElement], + directiveTransforms: { + noop: noopDirectiveTransform + } + }) + const node = ast.children[0] as ElementNode + const codegenArgs = (node.codegenNode as CallExpression).arguments + + // As v-noop adds no properties the codegen should be identical to + // rendering a div with no props or reactive data (so just the tag as the arg) + expect(codegenArgs.length).toBe(1) + }) +}) diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index f411f4ff8e..dc581f0f09 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -405,8 +405,7 @@ describe('compiler: element transform', () => { foo(dir) { _dir = dir return { - props: [createObjectProperty(dir.arg!, dir.exp!)], - needRuntime: false + props: [createObjectProperty(dir.arg!, dir.exp!)] } } } diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index c9c80e5737..bb4a23d400 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -26,6 +26,7 @@ export { export * from './ast' export * from './utils' export { registerRuntimeHelpers } from './runtimeHelpers' +export { noopDirectiveTransform } from './transforms/noopDirectiveTransform' // expose transforms so higher-order compilers can import and extend them export { transformModel } from './transforms/vModel' diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index f6dde0fa64..6ec92e7f80 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -28,7 +28,8 @@ export interface ParserOptions { export interface TransformOptions { nodeTransforms?: NodeTransform[] - directiveTransforms?: { [name: string]: DirectiveTransform | undefined } + directiveTransforms?: Record + ssrDirectiveTransforms?: Record isBuiltInComponent?: (tag: string) => symbol | void // Transform expressions like {{ foo }} to `_ctx.foo`. // If this option is false, the generated code will be wrapped in a diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 35c81e8be6..940e7fe4eb 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -61,7 +61,7 @@ export type DirectiveTransform = ( export interface DirectiveTransformResult { props: Property[] - needRuntime: boolean | symbol + needRuntime?: boolean | symbol } // A structural directive transform is a technically a NodeTransform; @@ -114,6 +114,7 @@ function createTransformContext( cacheHandlers = false, nodeTransforms = [], directiveTransforms = {}, + ssrDirectiveTransforms = {}, isBuiltInComponent = NOOP, ssr = false, onError = defaultOnError @@ -126,6 +127,7 @@ function createTransformContext( cacheHandlers, nodeTransforms, directiveTransforms, + ssrDirectiveTransforms, isBuiltInComponent, ssr, onError, diff --git a/packages/compiler-core/src/transforms/noopDirectiveTransform.ts b/packages/compiler-core/src/transforms/noopDirectiveTransform.ts new file mode 100644 index 0000000000..5975069be0 --- /dev/null +++ b/packages/compiler-core/src/transforms/noopDirectiveTransform.ts @@ -0,0 +1,3 @@ +import { DirectiveTransform } from '../transform' + +export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] }) diff --git a/packages/compiler-core/src/transforms/vBind.ts b/packages/compiler-core/src/transforms/vBind.ts index 39582af5cc..1e5d6a98dd 100644 --- a/packages/compiler-core/src/transforms/vBind.ts +++ b/packages/compiler-core/src/transforms/vBind.ts @@ -30,7 +30,6 @@ export const transformBind: DirectiveTransform = (dir, node, context) => { return { props: [ createObjectProperty(arg!, exp || createSimpleExpression('', true, loc)) - ], - needRuntime: false + ] } } diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts index 0434894911..8e13cc10a3 100644 --- a/packages/compiler-core/src/transforms/vModel.ts +++ b/packages/compiler-core/src/transforms/vModel.ts @@ -101,5 +101,5 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { } function createTransformProps(props: Property[] = []) { - return { props, needRuntime: false } + return { props } } diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index a43f5cbe85..d6ecd14587 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -99,8 +99,7 @@ export const transformOn: DirectiveTransform = ( eventName, exp || createSimpleExpression(`() => {}`, false, loc) ) - ], - needRuntime: false + ] } // apply extended compiler augmentor diff --git a/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts b/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts deleted file mode 100644 index 03d7f7169f..0000000000 --- a/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - baseParse as parse, - transform, - ElementNode, - CallExpression -} from '@vue/compiler-core' -import { transformCloak } from '../../src/transforms/vCloak' -import { transformElement } from '../../../compiler-core/src/transforms/transformElement' - -function transformWithCloak(template: string) { - const ast = parse(template) - transform(ast, { - nodeTransforms: [transformElement], - directiveTransforms: { - cloak: transformCloak - } - }) - return ast.children[0] as ElementNode -} - -describe('compiler: v-cloak transform', () => { - test('should add no props to DOM', () => { - const node = transformWithCloak(`
`) - const codegenArgs = (node.codegenNode as CallExpression).arguments - - // As v-cloak adds no properties the codegen should be identical to - // rendering a div with no props or reactive data (so just the tag as the arg) - expect(codegenArgs.length).toBe(1) - }) -}) diff --git a/packages/compiler-dom/src/errors.ts b/packages/compiler-dom/src/errors.ts index 7dd83fce3d..87579a8cdb 100644 --- a/packages/compiler-dom/src/errors.ts +++ b/packages/compiler-dom/src/errors.ts @@ -28,7 +28,8 @@ export const enum DOMErrorCodes { X_V_MODEL_ON_INVALID_ELEMENT, X_V_MODEL_ARG_ON_ELEMENT, X_V_MODEL_ON_FILE_INPUT_ELEMENT, - X_V_SHOW_NO_EXPRESSION + X_V_SHOW_NO_EXPRESSION, + __EXTEND_POINT__ } export const DOMErrorMessages: { [code: number]: string } = { diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index ba9aa8727b..6013ba931d 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -5,12 +5,12 @@ import { CodegenResult, isBuiltInType, ParserOptions, - RootNode + RootNode, + noopDirectiveTransform } from '@vue/compiler-core' import { parserOptionsMinimal } from './parserOptionsMinimal' import { parserOptionsStandard } from './parserOptionsStandard' import { transformStyle } from './transforms/transformStyle' -import { transformCloak } from './transforms/vCloak' import { transformVHtml } from './transforms/vHtml' import { transformVText } from './transforms/vText' import { transformModel } from './transforms/vModel' @@ -31,7 +31,7 @@ export function compile( ...options, nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])], directiveTransforms: { - cloak: transformCloak, + cloak: noopDirectiveTransform, html: transformVHtml, text: transformVText, model: transformModel, // override compiler-core @@ -56,4 +56,5 @@ export function parse(template: string, options: ParserOptions = {}): RootNode { }) } +export { DOMErrorCodes } from './errors' export * from '@vue/compiler-core' diff --git a/packages/compiler-dom/src/transforms/vCloak.ts b/packages/compiler-dom/src/transforms/vCloak.ts deleted file mode 100644 index 3a552b1f1f..0000000000 --- a/packages/compiler-dom/src/transforms/vCloak.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { DirectiveTransform } from '@vue/compiler-core' - -export const transformCloak: DirectiveTransform = () => { - return { props: [], needRuntime: false } -} diff --git a/packages/compiler-dom/src/transforms/vHtml.ts b/packages/compiler-dom/src/transforms/vHtml.ts index 9f77539daa..e42451a58c 100644 --- a/packages/compiler-dom/src/transforms/vHtml.ts +++ b/packages/compiler-dom/src/transforms/vHtml.ts @@ -24,7 +24,6 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => { createSimpleExpression(`innerHTML`, true, loc), exp || createSimpleExpression('', true) ) - ], - needRuntime: false + ] } } diff --git a/packages/compiler-dom/src/transforms/vOn.ts b/packages/compiler-dom/src/transforms/vOn.ts index 8c4972e0af..dd6696b824 100644 --- a/packages/compiler-dom/src/transforms/vOn.ts +++ b/packages/compiler-dom/src/transforms/vOn.ts @@ -102,8 +102,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => { } return { - props: [createObjectProperty(key, handlerExp)], - needRuntime: false + props: [createObjectProperty(key, handlerExp)] } }) } diff --git a/packages/compiler-dom/src/transforms/vText.ts b/packages/compiler-dom/src/transforms/vText.ts index e28ca9a8ed..0969ce95ec 100644 --- a/packages/compiler-dom/src/transforms/vText.ts +++ b/packages/compiler-dom/src/transforms/vText.ts @@ -24,7 +24,6 @@ export const transformVText: DirectiveTransform = (dir, node, context) => { createSimpleExpression(`textContent`, true, loc), exp || createSimpleExpression('', true) ) - ], - needRuntime: false + ] } } diff --git a/packages/compiler-ssr/__tests__/ssrVBind.spec.ts b/packages/compiler-ssr/__tests__/ssrVBind.spec.ts new file mode 100644 index 0000000000..83c049fb19 --- /dev/null +++ b/packages/compiler-ssr/__tests__/ssrVBind.spec.ts @@ -0,0 +1,13 @@ +import { compile } from '../src' + +describe('ssr: v-bind', () => { + test('basic', () => { + expect(compile(`
`).code).toMatchInlineSnapshot(` + "const { _renderAttr } = require(\\"vue\\") + + return function ssrRender(_ctx, _push, _parent) { + _push(\`
\`) + }" + `) + }) +}) diff --git a/packages/compiler-ssr/src/errors.ts b/packages/compiler-ssr/src/errors.ts new file mode 100644 index 0000000000..375bea6856 --- /dev/null +++ b/packages/compiler-ssr/src/errors.ts @@ -0,0 +1,25 @@ +import { + SourceLocation, + CompilerError, + createCompilerError, + DOMErrorCodes +} from '@vue/compiler-dom' + +export interface SSRCompilerError extends CompilerError { + code: SSRErrorCodes +} + +export function createSSRCompilerError( + code: SSRErrorCodes, + loc?: SourceLocation +): SSRCompilerError { + return createCompilerError(code, loc, SSRErrorMessages) +} + +export const enum SSRErrorCodes { + X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__ +} + +export const SSRErrorMessages: { [code: number]: string } = { + [SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM]: `Custom directive is missing corresponding SSR transform and will be ignored.` +} diff --git a/packages/compiler-ssr/src/index.ts b/packages/compiler-ssr/src/index.ts index 3d4f5d95fb..b26053f8e2 100644 --- a/packages/compiler-ssr/src/index.ts +++ b/packages/compiler-ssr/src/index.ts @@ -7,20 +7,22 @@ import { CompilerOptions, transformExpression, trackVForSlotScopes, - trackSlotScopes + trackSlotScopes, + noopDirectiveTransform } from '@vue/compiler-dom' import { ssrCodegenTransform } from './ssrCodegenTransform' -import { ssrTransformIf } from './transforms/ssrVIf' -import { ssrTransformFor } from './transforms/ssrVFor' import { ssrTransformElement } from './transforms/ssrTransformElement' import { ssrTransformComponent } from './transforms/ssrTransformComponent' import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet' - -export interface SSRCompilerOptions extends CompilerOptions {} +import { ssrTransformIf } from './transforms/ssrVIf' +import { ssrTransformFor } from './transforms/ssrVFor' +import { ssrVBind } from './transforms/ssrVBind' +import { ssrVModel } from './transforms/ssrVModel' +import { ssrVShow } from './transforms/ssrVShow' export function compile( template: string, - options: SSRCompilerOptions = {} + options: CompilerOptions = {} ): CodegenResult { options = { mode: 'cjs', @@ -50,9 +52,13 @@ export function compile( trackSlotScopes, ...(options.nodeTransforms || []) // user transforms ], - directiveTransforms: { - // TODO server-side directive transforms - ...(options.directiveTransforms || {}) // user transforms + ssrDirectiveTransforms: { + on: noopDirectiveTransform, + cloak: noopDirectiveTransform, + bind: ssrVBind, + model: ssrVModel, + show: ssrVShow, + ...(options.ssrDirectiveTransforms || {}) // user transforms } }) diff --git a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts index 2f37023b53..d80860cae1 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts @@ -4,9 +4,12 @@ import { ElementTypes, TemplateLiteral, createTemplateLiteral, - createInterpolation + createInterpolation, + createCallExpression } from '@vue/compiler-dom' import { escapeHtml } from '@vue/shared' +import { createSSRCompilerError, SSRErrorCodes } from '../errors' +import { SSR_RENDER_ATTR } from '../runtimeHelpers' export const ssrTransformElement: NodeTransform = (node, context) => { if ( @@ -19,10 +22,25 @@ export const ssrTransformElement: NodeTransform = (node, context) => { const openTag: TemplateLiteral['elements'] = [`<${node.tag}`] let rawChildren + // v-bind="obj" or v-bind:[key] can potentially overwrite other static + // attrs and can affect final rendering result, so when they are present + // we need to bail out to full `renderAttrs` + const hasDynamicVBind = node.props.some( + p => + p.type === NodeTypes.DIRECTIVE && + p.name === 'bind' && + (!p.arg || // v-bind="obj" + p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo] + !p.arg.isStatic) // v-bind:[foo] + ) + + if (hasDynamicVBind) { + } + for (let i = 0; i < node.props.length; i++) { const prop = node.props[i] + // special cases with children override if (prop.type === NodeTypes.DIRECTIVE) { - // special cases with children override if (prop.name === 'html' && prop.exp) { node.children = [] rawChildren = prop.exp @@ -40,13 +58,28 @@ export const ssrTransformElement: NodeTransform = (node, context) => { ) { node.children = [createInterpolation(prop.exp, prop.loc)] // TODO handle with dynamic v-bind - } else { - const directiveTransform = context.directiveTransforms[prop.name] + } else if (!hasDynamicVBind) { + // Directive transforms. + const directiveTransform = context.ssrDirectiveTransforms[prop.name] if (directiveTransform) { - // TODO directive transforms + const { props } = directiveTransform(prop, node, context) + for (let j = 0; j < props.length; j++) { + const { key, value } = props[i] + openTag.push( + createCallExpression(context.helper(SSR_RENDER_ATTR), [ + key, + value + ]) + ) + } } else { // no corresponding ssr directive transform found. - // TODO emit error + context.onError( + createSSRCompilerError( + SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM, + prop.loc + ) + ) } } } else { @@ -54,7 +87,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => { if (node.tag === 'textarea' && prop.name === 'value' && prop.value) { node.children = [] rawChildren = escapeHtml(prop.value.content) - } else { + } else if (!hasDynamicVBind) { // static prop openTag.push( ` ${prop.name}` + diff --git a/packages/compiler-ssr/src/transforms/ssrVBind.ts b/packages/compiler-ssr/src/transforms/ssrVBind.ts index 70b786d12e..072d54c3db 100644 --- a/packages/compiler-ssr/src/transforms/ssrVBind.ts +++ b/packages/compiler-ssr/src/transforms/ssrVBind.ts @@ -1 +1,18 @@ -// TODO +import { DirectiveTransform, createObjectProperty } from '@vue/compiler-dom' + +export const ssrVBind: DirectiveTransform = (dir, node, context) => { + if (!dir.exp) { + // error + return { props: [] } + } else { + // TODO modifiers + return { + props: [ + createObjectProperty( + dir.arg!, // v-bind="obj" is handled separately + dir.exp + ) + ] + } + } +} diff --git a/packages/compiler-ssr/src/transforms/ssrVCloak.ts b/packages/compiler-ssr/src/transforms/ssrVCloak.ts deleted file mode 100644 index 70b786d12e..0000000000 --- a/packages/compiler-ssr/src/transforms/ssrVCloak.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVModel.ts b/packages/compiler-ssr/src/transforms/ssrVModel.ts index 70b786d12e..b7e59be7f4 100644 --- a/packages/compiler-ssr/src/transforms/ssrVModel.ts +++ b/packages/compiler-ssr/src/transforms/ssrVModel.ts @@ -1 +1,7 @@ -// TODO +import { DirectiveTransform } from '@vue/compiler-dom' + +export const ssrVModel: DirectiveTransform = (dir, node, context) => { + return { + props: [] + } +} diff --git a/packages/compiler-ssr/src/transforms/ssrVOn.ts b/packages/compiler-ssr/src/transforms/ssrVOn.ts deleted file mode 100644 index 70b786d12e..0000000000 --- a/packages/compiler-ssr/src/transforms/ssrVOn.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVShow.ts b/packages/compiler-ssr/src/transforms/ssrVShow.ts index 70b786d12e..eced51154c 100644 --- a/packages/compiler-ssr/src/transforms/ssrVShow.ts +++ b/packages/compiler-ssr/src/transforms/ssrVShow.ts @@ -1 +1,7 @@ -// TODO +import { DirectiveTransform } from '@vue/compiler-dom' + +export const ssrVShow: DirectiveTransform = (dir, node, context) => { + return { + props: [] + } +}