--- /dev/null
+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(`<div v-noop/>`)
+ 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)
+ })
+})
foo(dir) {
_dir = dir
return {
- props: [createObjectProperty(dir.arg!, dir.exp!)],
- needRuntime: false
+ props: [createObjectProperty(dir.arg!, dir.exp!)]
}
}
}
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'
export interface TransformOptions {
nodeTransforms?: NodeTransform[]
- directiveTransforms?: { [name: string]: DirectiveTransform | undefined }
+ directiveTransforms?: Record<string, DirectiveTransform | undefined>
+ ssrDirectiveTransforms?: Record<string, DirectiveTransform | undefined>
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
export interface DirectiveTransformResult {
props: Property[]
- needRuntime: boolean | symbol
+ needRuntime?: boolean | symbol
}
// A structural directive transform is a technically a NodeTransform;
cacheHandlers = false,
nodeTransforms = [],
directiveTransforms = {},
+ ssrDirectiveTransforms = {},
isBuiltInComponent = NOOP,
ssr = false,
onError = defaultOnError
cacheHandlers,
nodeTransforms,
directiveTransforms,
+ ssrDirectiveTransforms,
isBuiltInComponent,
ssr,
onError,
--- /dev/null
+import { DirectiveTransform } from '../transform'
+
+export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })
return {
props: [
createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
- ],
- needRuntime: false
+ ]
}
}
}
function createTransformProps(props: Property[] = []) {
- return { props, needRuntime: false }
+ return { props }
}
eventName,
exp || createSimpleExpression(`() => {}`, false, loc)
)
- ],
- needRuntime: false
+ ]
}
// apply extended compiler augmentor
+++ /dev/null
-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(`<div v-cloak/>`)
- 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)
- })
-})
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 } = {
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'
...options,
nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
directiveTransforms: {
- cloak: transformCloak,
+ cloak: noopDirectiveTransform,
html: transformVHtml,
text: transformVText,
model: transformModel, // override compiler-core
})
}
+export { DOMErrorCodes } from './errors'
export * from '@vue/compiler-core'
+++ /dev/null
-import { DirectiveTransform } from '@vue/compiler-core'
-
-export const transformCloak: DirectiveTransform = () => {
- return { props: [], needRuntime: false }
-}
createSimpleExpression(`innerHTML`, true, loc),
exp || createSimpleExpression('', true)
)
- ],
- needRuntime: false
+ ]
}
}
}
return {
- props: [createObjectProperty(key, handlerExp)],
- needRuntime: false
+ props: [createObjectProperty(key, handlerExp)]
}
})
}
createSimpleExpression(`textContent`, true, loc),
exp || createSimpleExpression('', true)
)
- ],
- needRuntime: false
+ ]
}
}
--- /dev/null
+import { compile } from '../src'
+
+describe('ssr: v-bind', () => {
+ test('basic', () => {
+ expect(compile(`<div :id="id"/>`).code).toMatchInlineSnapshot(`
+ "const { _renderAttr } = require(\\"vue\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`<div\${_renderAttr(\\"id\\", _ctx.id)}></div>\`)
+ }"
+ `)
+ })
+})
--- /dev/null
+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.`
+}
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',
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
}
})
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 (
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
) {
node.children = [createInterpolation(prop.exp, prop.loc)]
// TODO handle <textrea> 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 {
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}` +
-// 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
+ )
+ ]
+ }
+ }
+}
-// TODO
+import { DirectiveTransform } from '@vue/compiler-dom'
+
+export const ssrVModel: DirectiveTransform = (dir, node, context) => {
+ return {
+ props: []
+ }
+}
-// TODO
+import { DirectiveTransform } from '@vue/compiler-dom'
+
+export const ssrVShow: DirectiveTransform = (dir, node, context) => {
+ return {
+ props: []
+ }
+}