From: Evan You Date: Wed, 5 Feb 2020 19:23:03 +0000 (-0500) Subject: wip(compiler-ssr): v-model static types + textarea X-Git-Tag: v3.0.0-alpha.5~105 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c952321fcf63c20bddd7f8b8bb08d5d8d5c21e96;p=thirdparty%2Fvuejs%2Fcore.git wip(compiler-ssr): v-model static types + textarea --- diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 97d64b2039..cd605832e1 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -204,6 +204,7 @@ export interface CompoundExpressionNode extends Node { type: NodeTypes.COMPOUND_EXPRESSION children: ( | SimpleExpressionNode + | CompoundExpressionNode | InterpolationNode | TextNode | string diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts index 8e13cc10a3..7211334c3d 100644 --- a/packages/compiler-core/src/transforms/vModel.ts +++ b/packages/compiler-core/src/transforms/vModel.ts @@ -56,11 +56,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { // "onUpdate:modelValue": $event => (foo = $event) createObjectProperty( eventName, - createCompoundExpression([ - `$event => (`, - ...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children), - ` = $event)` - ]) + createCompoundExpression([`$event => (`, exp, ` = $event)`]) ) ] @@ -82,12 +78,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { const modifiersKey = arg ? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic ? `${arg.content}Modifiers` - : createCompoundExpression([ - ...(arg.type === NodeTypes.SIMPLE_EXPRESSION - ? [arg] - : arg.children), - ' + "Modifiers"' - ]) + : createCompoundExpression([arg, ' + "Modifiers"']) : `modelModifiers` props.push( createObjectProperty( diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index d6ecd14587..c56b300c01 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -87,7 +87,7 @@ export const transformOn: DirectiveTransform = ( // wrap inline statement in a function expression exp = createCompoundExpression([ `$event => ${hasMultipleStatements ? `{` : `(`}`, - ...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children), + exp, hasMultipleStatements ? `}` : `)` ]) } diff --git a/packages/compiler-dom/src/errors.ts b/packages/compiler-dom/src/errors.ts index 87579a8cdb..8ead252604 100644 --- a/packages/compiler-dom/src/errors.ts +++ b/packages/compiler-dom/src/errors.ts @@ -28,6 +28,7 @@ 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_MODEL_UNNECESSARY_VALUE, X_V_SHOW_NO_EXPRESSION, __EXTEND_POINT__ } @@ -40,5 +41,6 @@ export const DOMErrorMessages: { [code: number]: string } = { [DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT]: `v-model can only be used on , `).code) + .toMatchInlineSnapshot(` + "const { _interpolate } = require(\\"@vue/server-renderer\\") + + return function ssrRender(_ctx, _push, _parent) { + _push(\`\`) + }" + `) + }) +}) diff --git a/packages/compiler-ssr/src/runtimeHelpers.ts b/packages/compiler-ssr/src/runtimeHelpers.ts index 392b0eadc7..87fd6b35f1 100644 --- a/packages/compiler-ssr/src/runtimeHelpers.ts +++ b/packages/compiler-ssr/src/runtimeHelpers.ts @@ -9,6 +9,8 @@ export const SSR_RENDER_ATTRS = Symbol(`renderAttrs`) export const SSR_RENDER_ATTR = Symbol(`renderAttr`) export const SSR_RENDER_DYNAMIC_ATTR = Symbol(`renderDynamicAttr`) export const SSR_RENDER_LIST = Symbol(`renderList`) +export const SSR_LOOSE_EQUAL = Symbol(`looseEqual`) +export const SSR_LOOSE_CONTAIN = Symbol(`looseContain`) export const ssrHelpers = { [SSR_INTERPOLATE]: `_interpolate`, @@ -19,7 +21,9 @@ export const ssrHelpers = { [SSR_RENDER_ATTRS]: `_renderAttrs`, [SSR_RENDER_ATTR]: `_renderAttr`, [SSR_RENDER_DYNAMIC_ATTR]: `_renderDynamicAttr`, - [SSR_RENDER_LIST]: `_renderList` + [SSR_RENDER_LIST]: `_renderList`, + [SSR_LOOSE_EQUAL]: `_looseEqual`, + [SSR_LOOSE_CONTAIN]: `_looseContain` } // Note: these are helpers imported from @vue/server-renderer diff --git a/packages/compiler-ssr/src/transforms/ssrVModel.ts b/packages/compiler-ssr/src/transforms/ssrVModel.ts index 077991d830..8d4c0ad6df 100644 --- a/packages/compiler-ssr/src/transforms/ssrVModel.ts +++ b/packages/compiler-ssr/src/transforms/ssrVModel.ts @@ -1,7 +1,123 @@ -import { DirectiveTransform } from '@vue/compiler-dom' +import { + DirectiveTransform, + ElementTypes, + transformModel, + findProp, + NodeTypes, + createDOMCompilerError, + DOMErrorCodes, + Property, + createObjectProperty, + createSimpleExpression, + createCallExpression, + PlainElementNode, + ExpressionNode, + createConditionalExpression, + createInterpolation +} from '@vue/compiler-dom' +import { SSR_LOOSE_EQUAL, SSR_LOOSE_CONTAIN } from '../runtimeHelpers' export const ssrTransformModel: DirectiveTransform = (dir, node, context) => { - return { - props: [] + const model = dir.exp! + + function checkDuplicatedValue() { + const value = findProp(node, 'value') + if (value) { + context.onError( + createDOMCompilerError( + DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE, + value.loc + ) + ) + } + } + + if (node.tagType === ElementTypes.ELEMENT) { + let props: Property[] = [] + const defaultProps = [ + // default value binding for text type inputs + createObjectProperty(createSimpleExpression(`value`, true), model) + ] + if (node.tag === 'input') { + const type = findProp(node, 'type') + if (type) { + if (type.type === NodeTypes.DIRECTIVE) { + // dynamic type + // TODO + } else if (type.value) { + // static type + switch (type.value.content) { + case 'radio': + props = [ + createObjectProperty( + createSimpleExpression(`checked`, true), + createCallExpression(context.helper(SSR_LOOSE_EQUAL), [ + model, + findValueBinding(node) + ]) + ) + ] + break + case 'checkbox': + const value = findValueBinding(node) + props = [ + createObjectProperty( + createSimpleExpression(`checked`, true), + createConditionalExpression( + createCallExpression(`Array.isArray`, [model]), + createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [ + model, + value + ]), + model + ) + ) + ] + break + case 'file': + context.onError( + createDOMCompilerError( + DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT, + dir.loc + ) + ) + break + default: + checkDuplicatedValue() + props = defaultProps + break + } + } + } else { + checkDuplicatedValue() + props = defaultProps + } + } else if (node.tag === 'textarea') { + checkDuplicatedValue() + node.children = [createInterpolation(model, model.loc)] + } else if (node.tag === 'select') { + // TODO + } else { + context.onError( + createDOMCompilerError( + DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT, + dir.loc + ) + ) + } + + return { props } + } else { + // component v-model + return transformModel(dir, node, context) } } + +function findValueBinding(node: PlainElementNode): ExpressionNode { + const valueBinding = findProp(node, 'value') + return valueBinding + ? valueBinding.type === NodeTypes.DIRECTIVE + ? valueBinding.exp! + : createSimpleExpression(valueBinding.value!.content, true) + : createSimpleExpression(`null`, false) +} diff --git a/packages/compiler-ssr/src/transforms/ssrVShow.ts b/packages/compiler-ssr/src/transforms/ssrVShow.ts index d46cac737c..314d8a234c 100644 --- a/packages/compiler-ssr/src/transforms/ssrVShow.ts +++ b/packages/compiler-ssr/src/transforms/ssrVShow.ts @@ -1,16 +1,18 @@ import { DirectiveTransform, - createCompilerError, DOMErrorCodes, createObjectProperty, createSimpleExpression, createConditionalExpression, - createObjectExpression + createObjectExpression, + createDOMCompilerError } from '@vue/compiler-dom' export const ssrTransformShow: DirectiveTransform = (dir, node, context) => { if (!dir.exp) { - context.onError(createCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION)) + context.onError( + createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION) + ) } return { props: [ diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 8cc9ea7b8b..8b1343944f 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -5,7 +5,7 @@ import { warn } from '@vue/runtime-core' import { addEventListener } from '../modules/events' -import { isArray, isObject } from '@vue/shared' +import { isArray, looseEqual, looseIndexOf } from '@vue/shared' const getModelAssigner = (vnode: VNode): ((value: any) => void) => vnode.props!['onUpdate:modelValue'] @@ -182,47 +182,6 @@ function setSelected(el: HTMLSelectElement, value: any) { } } -function looseEqual(a: any, b: any): boolean { - if (a === b) return true - const isObjectA = isObject(a) - const isObjectB = isObject(b) - if (isObjectA && isObjectB) { - try { - const isArrayA = isArray(a) - const isArrayB = isArray(b) - if (isArrayA && isArrayB) { - return ( - a.length === b.length && - a.every((e: any, i: any) => looseEqual(e, b[i])) - ) - } else if (a instanceof Date && b instanceof Date) { - return a.getTime() === b.getTime() - } else if (!isArrayA && !isArrayB) { - const keysA = Object.keys(a) - const keysB = Object.keys(b) - return ( - keysA.length === keysB.length && - keysA.every(key => looseEqual(a[key], b[key])) - ) - } else { - /* istanbul ignore next */ - return false - } - } catch (e) { - /* istanbul ignore next */ - return false - } - } else if (!isObjectA && !isObjectB) { - return String(a) === String(b) - } else { - return false - } -} - -function looseIndexOf(arr: any[], val: any): number { - return arr.findIndex(item => looseEqual(item, val)) -} - // retrieve raw value set via :value bindings function getValue(el: HTMLOptionElement | HTMLInputElement) { return '_value' in el ? (el as any)._value : el.value diff --git a/packages/server-renderer/src/index.ts b/packages/server-renderer/src/index.ts index 39e5c3e330..099a2e0348 100644 --- a/packages/server-renderer/src/index.ts +++ b/packages/server-renderer/src/index.ts @@ -15,3 +15,9 @@ export { } from './helpers/renderAttrs' export { interpolate as _interpolate } from './helpers/interpolate' export { renderList as _renderList } from './helpers/renderList' + +// v-model helpers +import { looseEqual, looseIndexOf } from '@vue/shared' +export const _looseEqual = looseEqual as (a: unknown, b: unknown) => boolean +export const _looseContain = (arr: unknown[], value: unknown): boolean => + looseIndexOf(arr, value) > -1 diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 3a1c07f2d6..4ec5d8d91b 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -9,6 +9,7 @@ export * from './normalizeProp' export * from './domTagConfig' export * from './domAttrConfig' export * from './escapeHtml' +export * from './looseEqual' export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__ ? Object.freeze({}) diff --git a/packages/shared/src/looseEqual.ts b/packages/shared/src/looseEqual.ts new file mode 100644 index 0000000000..bd58332ddd --- /dev/null +++ b/packages/shared/src/looseEqual.ts @@ -0,0 +1,42 @@ +import { isObject, isArray } from './' + +export function looseEqual(a: any, b: any): boolean { + if (a === b) return true + const isObjectA = isObject(a) + const isObjectB = isObject(b) + if (isObjectA && isObjectB) { + try { + const isArrayA = isArray(a) + const isArrayB = isArray(b) + if (isArrayA && isArrayB) { + return ( + a.length === b.length && + a.every((e: any, i: any) => looseEqual(e, b[i])) + ) + } else if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime() + } else if (!isArrayA && !isArrayB) { + const keysA = Object.keys(a) + const keysB = Object.keys(b) + return ( + keysA.length === keysB.length && + keysA.every(key => looseEqual(a[key], b[key])) + ) + } else { + /* istanbul ignore next */ + return false + } + } catch (e) { + /* istanbul ignore next */ + return false + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } +} + +export function looseIndexOf(arr: any[], val: any): number { + return arr.findIndex(item => looseEqual(item, val)) +} diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index f79b76960e..2cc408889a 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -47,6 +47,8 @@ function compileToFunction( err.loc.end.offset ) warn(codeFrame ? `${message}\n${codeFrame}` : message) + } else { + throw err } }, ...options