From 6a5ed49ea9e51173e336382a8828163178b5f1c2 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 4 Feb 2020 12:20:51 -0500 Subject: [PATCH] wip(ssr): v-bind basic usage --- .../transforms/noopDirectiveTransform.spec.ts | 26 ++++++++++ .../transforms/transformElement.spec.ts | 3 +- packages/compiler-core/src/index.ts | 1 + packages/compiler-core/src/options.ts | 3 +- packages/compiler-core/src/transform.ts | 4 +- .../src/transforms/noopDirectiveTransform.ts | 3 ++ .../compiler-core/src/transforms/vBind.ts | 3 +- .../compiler-core/src/transforms/vModel.ts | 2 +- packages/compiler-core/src/transforms/vOn.ts | 3 +- .../__tests__/transforms/vCloak.spec.ts | 30 ------------ packages/compiler-dom/src/errors.ts | 3 +- packages/compiler-dom/src/index.ts | 7 +-- .../compiler-dom/src/transforms/vCloak.ts | 5 -- packages/compiler-dom/src/transforms/vHtml.ts | 3 +- packages/compiler-dom/src/transforms/vOn.ts | 3 +- packages/compiler-dom/src/transforms/vText.ts | 3 +- .../compiler-ssr/__tests__/ssrVBind.spec.ts | 13 +++++ packages/compiler-ssr/src/errors.ts | 25 ++++++++++ packages/compiler-ssr/src/index.ts | 24 ++++++---- .../src/transforms/ssrTransformElement.ts | 47 ++++++++++++++++--- .../compiler-ssr/src/transforms/ssrVBind.ts | 19 +++++++- .../compiler-ssr/src/transforms/ssrVCloak.ts | 1 - .../compiler-ssr/src/transforms/ssrVModel.ts | 8 +++- .../compiler-ssr/src/transforms/ssrVOn.ts | 1 - .../compiler-ssr/src/transforms/ssrVShow.ts | 8 +++- 25 files changed, 173 insertions(+), 75 deletions(-) create mode 100644 packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts create mode 100644 packages/compiler-core/src/transforms/noopDirectiveTransform.ts delete mode 100644 packages/compiler-dom/__tests__/transforms/vCloak.spec.ts delete mode 100644 packages/compiler-dom/src/transforms/vCloak.ts create mode 100644 packages/compiler-ssr/__tests__/ssrVBind.spec.ts create mode 100644 packages/compiler-ssr/src/errors.ts delete mode 100644 packages/compiler-ssr/src/transforms/ssrVCloak.ts delete mode 100644 packages/compiler-ssr/src/transforms/ssrVOn.ts 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 00000000..f48d3e89 --- /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 f411f4ff..dc581f0f 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 c9c80e57..bb4a23d4 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 f6dde0fa..6ec92e7f 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 35c81e8b..940e7fe4 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 00000000..5975069b --- /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 39582af5..1e5d6a98 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 04348949..8e13cc10 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 a43f5cbe..d6ecd145 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 03d7f716..00000000 --- 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 7dd83fce..87579a8c 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 ba9aa872..6013ba93 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 3a552b1f..00000000 --- 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 9f77539d..e42451a5 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 8c4972e0..dd6696b8 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 e28ca9a8..0969ce95 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 00000000..83c049fb --- /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 00000000..375bea68 --- /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 3d4f5d95..b26053f8 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 2f37023b..d80860ca 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 70b786d1..072d54c3 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 70b786d1..00000000 --- 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 70b786d1..b7e59be7 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 70b786d1..00000000 --- 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 70b786d1..eced5115 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: [] + } +}