wip(ssr): v-bind basic usage
This commit is contained in:
parent
7f38c1e0ff
commit
6a5ed49ea9
@ -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(`<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)
|
||||
})
|
||||
})
|
@ -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!)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -28,7 +28,8 @@ export interface ParserOptions {
|
||||
|
||||
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
|
||||
|
@ -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,
|
||||
|
@ -0,0 +1,3 @@
|
||||
import { DirectiveTransform } from '../transform'
|
||||
|
||||
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })
|
@ -30,7 +30,6 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
|
||||
return {
|
||||
props: [
|
||||
createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -101,5 +101,5 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
}
|
||||
|
||||
function createTransformProps(props: Property[] = []) {
|
||||
return { props, needRuntime: false }
|
||||
return { props }
|
||||
}
|
||||
|
@ -99,8 +99,7 @@ export const transformOn: DirectiveTransform = (
|
||||
eventName,
|
||||
exp || createSimpleExpression(`() => {}`, false, loc)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
|
||||
// apply extended compiler augmentor
|
||||
|
@ -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(`<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)
|
||||
})
|
||||
})
|
@ -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 } = {
|
||||
|
@ -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'
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { DirectiveTransform } from '@vue/compiler-core'
|
||||
|
||||
export const transformCloak: DirectiveTransform = () => {
|
||||
return { props: [], needRuntime: false }
|
||||
}
|
@ -24,7 +24,6 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
|
||||
createSimpleExpression(`innerHTML`, true, loc),
|
||||
exp || createSimpleExpression('', true)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -102,8 +102,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||
}
|
||||
|
||||
return {
|
||||
props: [createObjectProperty(key, handlerExp)],
|
||||
needRuntime: false
|
||||
props: [createObjectProperty(key, handlerExp)]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
|
||||
createSimpleExpression(`textContent`, true, loc),
|
||||
exp || createSimpleExpression('', true)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
13
packages/compiler-ssr/__tests__/ssrVBind.spec.ts
Normal file
13
packages/compiler-ssr/__tests__/ssrVBind.spec.ts
Normal file
@ -0,0 +1,13 @@
|
||||
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>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
25
packages/compiler-ssr/src/errors.ts
Normal file
25
packages/compiler-ssr/src/errors.ts
Normal file
@ -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.`
|
||||
}
|
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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 <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 {
|
||||
@ -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}` +
|
||||
|
@ -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
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
// TODO
|
@ -1 +1,7 @@
|
||||
// TODO
|
||||
import { DirectiveTransform } from '@vue/compiler-dom'
|
||||
|
||||
export const ssrVModel: DirectiveTransform = (dir, node, context) => {
|
||||
return {
|
||||
props: []
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
// TODO
|
@ -1 +1,7 @@
|
||||
// TODO
|
||||
import { DirectiveTransform } from '@vue/compiler-dom'
|
||||
|
||||
export const ssrVShow: DirectiveTransform = (dir, node, context) => {
|
||||
return {
|
||||
props: []
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user