From b4e1dfe3f8a58ce06117d4f95416d198c5d095b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E6=9D=89?= Date: Mon, 13 Jun 2022 10:58:27 +0800 Subject: [PATCH 0001/1409] chore(sfc-playground): hide versions when click iframe & set color-scheme to dark (#6003) --- packages/sfc-playground/src/App.vue | 4 ++++ packages/sfc-playground/src/Header.vue | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/packages/sfc-playground/src/App.vue b/packages/sfc-playground/src/App.vue index 2e123531b40..33ed68c1043 100644 --- a/packages/sfc-playground/src/App.vue +++ b/packages/sfc-playground/src/App.vue @@ -82,6 +82,10 @@ function toggleSSR() { ` + ) + expect(content).toMatch( + `export default {\n setup(__props, { expose }) {\n expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))` + ) + }) }) }) From ddba46ae6d48bd705c671850636c7ab1300137ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E9=9B=BE=E4=B8=89=E8=AF=AD?= <32354856+baiwusanyu-c@users.noreply.github.com> Date: Tue, 28 Mar 2023 18:27:09 +0800 Subject: [PATCH 0352/1409] chore: update snapshots by #7766 (#7970) --- packages/compiler-sfc/__tests__/cssVars.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler-sfc/__tests__/cssVars.spec.ts b/packages/compiler-sfc/__tests__/cssVars.spec.ts index 05e5f689ac9..5b01d73d772 100644 --- a/packages/compiler-sfc/__tests__/cssVars.spec.ts +++ b/packages/compiler-sfc/__tests__/cssVars.spec.ts @@ -269,7 +269,7 @@ describe('CSS vars injection', () => { ` ) expect(content).toMatch( - `export default {\n setup(__props, { expose }) {\n expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))` + `export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))` ) }) }) From 1bde9fbc913a947ed4e5bd1f426530b401697ced Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 28 Mar 2023 21:21:54 +0800 Subject: [PATCH 0353/1409] chore: fix accidentally replaced comments [ci skip] --- packages/compiler-sfc/src/compileScript.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index aacc8c6273f..6a4ac52b1ea 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -387,7 +387,7 @@ export function compileScript( isFromSetup: boolean, needTemplateUsageCheck: boolean ) { - // template usage check is only needed in non-inline mode, so we can UNKNOWN + // template usage check is only needed in non-inline mode, so we can skip // the work if inlineTemplate is true. let isUsedInTemplate = needTemplateUsageCheck if ( @@ -1109,7 +1109,7 @@ export function compileScript( // check if user has manually specified `name` or 'render` option in // export default - // if has name, UNKNOWN name inference + // if has name, skip name inference // if has render and no template, generate return object instead of // empty render function (#4980) let optionProperties @@ -1586,7 +1586,7 @@ export function compileScript( !userImports[key].source.endsWith('.vue') ) { // generate getter for import bindings - // UNKNOWN vue imports since we know they will never change + // skip vue imports since we know they will never change returned += `get ${key}() { return ${key} }, ` } else if (bindingMetadata[key] === BindingTypes.SETUP_LET) { // local let binding, also add setter From 63ad77f6f65751780aa52f817387165b4773cfe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Tue, 28 Mar 2023 22:29:54 +0800 Subject: [PATCH 0354/1409] feat(runtime-core): add skipCheck for prop (#7548) --- .../__snapshots__/compileScript.spec.ts.snap | 4 +++- .../compiler-sfc/__tests__/compileScript.spec.ts | 14 ++++++++++++-- packages/compiler-sfc/src/compileScript.ts | 16 ++++++++++++---- .../__tests__/componentProps.spec.ts | 9 +++++++-- packages/runtime-core/src/componentProps.ts | 5 +++-- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 9655e7385c4..6dcac98204e 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1715,7 +1715,9 @@ export default /*#__PURE__*/_defineComponent({ foo: { type: [Function, null], required: true }, unknown: { type: null, required: true }, unknownUnion: { type: null, required: true }, - unknownIntersection: { type: Object, required: true } + unknownIntersection: { type: Object, required: true }, + unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true }, + unknownUnionWithFunction: { type: Function, required: true, skipCheck: true } }, setup(__props: any, { expose: __expose }) { __expose(); diff --git a/packages/compiler-sfc/__tests__/compileScript.spec.ts b/packages/compiler-sfc/__tests__/compileScript.spec.ts index 8aa5951104a..8e3ef4e63ea 100644 --- a/packages/compiler-sfc/__tests__/compileScript.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript.spec.ts @@ -1042,6 +1042,8 @@ const emit = defineEmits(['a', 'b']) unknown: UnknownType unknownUnion: UnknownType | string unknownIntersection: UnknownType & Object + unknownUnionWithBoolean: UnknownType | boolean + unknownUnionWithFunction: UnknownType | (() => any) }>() `) assertCode(content) @@ -1093,7 +1095,13 @@ const emit = defineEmits(['a', 'b']) expect(content).toMatch(`unknownUnion: { type: null, required: true }`) // intersection containing unknown type: narrow to the known types expect(content).toMatch( - `unknownIntersection: { type: Object, required: true }` + `unknownIntersection: { type: Object, required: true },` + ) + expect(content).toMatch( + `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },` + ) + expect(content).toMatch( + `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }` ) expect(bindings).toStrictEqual({ string: BindingTypes.PROPS, @@ -1131,7 +1139,9 @@ const emit = defineEmits(['a', 'b']) nonNull: BindingTypes.PROPS, unknown: BindingTypes.PROPS, unknownUnion: BindingTypes.PROPS, - unknownIntersection: BindingTypes.PROPS + unknownIntersection: BindingTypes.PROPS, + unknownUnionWithBoolean: BindingTypes.PROPS, + unknownUnionWithFunction: BindingTypes.PROPS }) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 6a4ac52b1ea..3ec54e8010a 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -888,11 +888,11 @@ export function compileScript( } } - const { type, required } = props[key] + const { type, required, skipCheck } = props[key] if (!isProd) { return `${key}: { type: ${toRuntimeTypeString( type - )}, required: ${required}${ + )}, required: ${required}${skipCheck ? ', skipCheck: true' : ''}${ defaultString ? `, ${defaultString}` : `` } }` } else if ( @@ -1964,6 +1964,7 @@ interface PropTypeData { key: string type: string[] required: boolean + skipCheck: boolean } function recordType(node: Node, declaredTypes: Record) { @@ -1993,19 +1994,26 @@ function extractRuntimeProps( m.key.type === 'Identifier' ) { let type: string[] | undefined + let skipCheck = false if (m.type === 'TSMethodSignature') { type = ['Function'] } else if (m.typeAnnotation) { type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes) // skip check for result containing unknown types if (type.includes(UNKNOWN_TYPE)) { - type = [`null`] + if (type.includes('Boolean') || type.includes('Function')) { + type = type.filter(t => t !== UNKNOWN_TYPE) + skipCheck = true + } else { + type = ['null'] + } } } props[m.key.name] = { key: m.key.name, required: !m.optional, - type: type || [`null`] + type: type || [`null`], + skipCheck } } } diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts index df46da6807a..071a24f138d 100644 --- a/packages/runtime-core/__tests__/componentProps.spec.ts +++ b/packages/runtime-core/__tests__/componentProps.spec.ts @@ -335,7 +335,8 @@ describe('component props', () => { arr: { type: Array }, obj: { type: Object }, cls: { type: MyClass }, - fn: { type: Function } + fn: { type: Function }, + skipCheck: { type: [Boolean, Function], skipCheck: true } }, setup() { return () => null @@ -349,7 +350,8 @@ describe('component props', () => { arr: {}, obj: 'false', cls: {}, - fn: true + fn: true, + skipCheck: 'foo' }), nodeOps.createElement('div') ) @@ -374,6 +376,9 @@ describe('component props', () => { expect( `Invalid prop: type check failed for prop "cls". Expected MyClass, got Object` ).toHaveBeenWarned() + expect( + `Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".` + ).not.toHaveBeenWarned() }) // #3495 diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index db8bf73a9a1..03c83990c57 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -58,6 +58,7 @@ export interface PropOptions { required?: boolean default?: D | DefaultFactory | null | undefined | object validator?(value: unknown): boolean + skipCheck?: boolean } export type PropType = PropConstructor | PropConstructor[] @@ -608,7 +609,7 @@ function validateProp( prop: PropOptions, isAbsent: boolean ) { - const { type, required, validator } = prop + const { type, required, validator, skipCheck } = prop // required! if (required && isAbsent) { warn('Missing required prop: "' + name + '"') @@ -619,7 +620,7 @@ function validateProp( return } // type check - if (type != null && type !== true) { + if (type != null && type !== true && !skipCheck) { let isValid = false const types = isArray(type) ? type : [type] const expectedTypes = [] From 77686cf4765e7e345bef364c0b03739e3c2da91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 29 Mar 2023 09:02:16 +0800 Subject: [PATCH 0355/1409] fix(compiler-core): check if expression is constant (#7974) close #7973 --- .../transforms/transformExpressions.spec.ts | 25 ++++++++++++++++++- .../src/transforms/transformExpression.ts | 10 +++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index b9593c4a4be..40070778b2a 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -13,6 +13,7 @@ import { } from '../../src' import { transformIf } from '../../src/transforms/vIf' import { transformExpression } from '../../src/transforms/transformExpression' +import { PatchFlagNames, PatchFlags } from '../../../shared/src' function parseWithExpressionTransform( template: string, @@ -494,7 +495,9 @@ describe('compiler: expression transform', () => { setup: BindingTypes.SETUP_MAYBE_REF, setupConst: BindingTypes.SETUP_CONST, data: BindingTypes.DATA, - options: BindingTypes.OPTIONS + options: BindingTypes.OPTIONS, + reactive: BindingTypes.SETUP_REACTIVE_CONST, + literal: BindingTypes.LITERAL_CONST } function compileWithBindingMetadata( @@ -532,5 +535,25 @@ describe('compiler: expression transform', () => { expect(code).toMatch(`_ctx.options`) expect(code).toMatchSnapshot() }) + + test('literal const handling', () => { + const { code } = compileWithBindingMetadata(`
{{ literal }}
`, { + inline: true + }) + // #7973 should skip patch for literal const + expect(code).not.toMatch( + `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */` + ) + }) + + test('reactive const handling', () => { + const { code } = compileWithBindingMetadata(`
{{ reactive }}
`, { + inline: true + }) + // #7973 should not skip patch for reactive const + expect(code).toMatch( + `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */` + ) + }) }) }) diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 112dc63cb7a..080d61d739f 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -128,7 +128,11 @@ export function processExpression( const isDestructureAssignment = parent && isInDestructureAssignment(parent, parentStack) - if (isConst(type) || localVars[raw]) { + if ( + isConst(type) || + type === BindingTypes.SETUP_REACTIVE_CONST || + localVars[raw] + ) { return raw } else if (type === BindingTypes.SETUP_REF) { return `${raw}.value` @@ -371,8 +375,6 @@ export function stringifyExpression(exp: ExpressionNode | string): string { function isConst(type: unknown) { return ( - type === BindingTypes.SETUP_CONST || - type === BindingTypes.LITERAL_CONST || - type === BindingTypes.SETUP_REACTIVE_CONST + type === BindingTypes.SETUP_CONST || type === BindingTypes.LITERAL_CONST ) } From 0f73f394dafd709298bd8c71107a323bf322a1d2 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Wed, 29 Mar 2023 16:15:08 +0800 Subject: [PATCH 0356/1409] fix(types/jsx): jsx-runtime types for global JSX namespace registration (#7978) --- packages/vue/jsx.d.ts | 39 +++++++++++++++++++++------- packages/vue/types/jsx-register.d.ts | 2 ++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/vue/jsx.d.ts b/packages/vue/jsx.d.ts index 4057d6afc9c..947a9904419 100644 --- a/packages/vue/jsx.d.ts +++ b/packages/vue/jsx.d.ts @@ -1,15 +1,36 @@ // global JSX namespace registration -import { JSX as JSXInternal } from './jsx-runtime' +// somehow we have to copy=pase the jsx-runtime types here to make TypeScript happy +import { VNode, VNodeRef } from '@vue/runtime-dom' +import { IntrinsicElementAttributes } from './jsx-runtime/dom' + +export * from './jsx-runtime/dom' + +export type ReservedProps = { + key?: string | number | symbol + ref?: VNodeRef + ref_for?: boolean + ref_key?: string +} + +export type NativeElements = { + [K in keyof IntrinsicElementAttributes]: IntrinsicElementAttributes[K] & + ReservedProps +} declare global { namespace JSX { - interface Element extends JSXInternal.Element {} - interface ElementClass extends JSXInternal.ElementClass {} - interface ElementAttributesProperty - extends JSXInternal.ElementAttributesProperty {} - interface IntrinsicElements extends JSXInternal.IntrinsicElements {} - interface IntrinsicAttributes extends JSXInternal.IntrinsicAttributes {} + export interface Element extends VNode {} + export interface ElementClass { + $props: {} + } + export interface ElementAttributesProperty { + $props: {} + } + export interface IntrinsicElements extends NativeElements { + // allow arbitrary elements + // @ts-ignore suppress ts:2374 = Duplicate string index signature. + [name: string]: any + } + export interface IntrinsicAttributes extends ReservedProps {} } } - -export {} diff --git a/packages/vue/types/jsx-register.d.ts b/packages/vue/types/jsx-register.d.ts index a626f798c2a..af5d5f29023 100644 --- a/packages/vue/types/jsx-register.d.ts +++ b/packages/vue/types/jsx-register.d.ts @@ -2,3 +2,5 @@ // imports the global JSX namespace registration for compat. // TODO: remove in 3.4 import '../jsx' + +export * from '../jsx-runtime/dom' From ff60b933ae4e02422393664ee7818cffadf9b58b Mon Sep 17 00:00:00 2001 From: Leonardo Piccioni de Almeida Date: Wed, 29 Mar 2023 05:17:34 -0300 Subject: [PATCH 0357/1409] fix(jsx-runtime): handle keys (#7976) --- packages/vue/jsx-runtime/index.js | 7 ++++++- packages/vue/jsx-runtime/index.mjs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/vue/jsx-runtime/index.js b/packages/vue/jsx-runtime/index.js index 255fb4490df..73b137d5261 100644 --- a/packages/vue/jsx-runtime/index.js +++ b/packages/vue/jsx-runtime/index.js @@ -1,6 +1,11 @@ const { h, Fragment } = require('vue') -function jsx(type, { children, ...props }) { +function jsx(type, props, key) { + const { children } = props + delete props.children + if (arguments.length > 2) { + props.key = key + } return h(type, props, children) } diff --git a/packages/vue/jsx-runtime/index.mjs b/packages/vue/jsx-runtime/index.mjs index 92bb8a9d4ad..57dd60af68f 100644 --- a/packages/vue/jsx-runtime/index.mjs +++ b/packages/vue/jsx-runtime/index.mjs @@ -1,6 +1,11 @@ import { h, Fragment } from 'vue' -function jsx(type, { children, ...props }) { +function jsx(type, props, key) { + const { children } = props + delete props.children + if (arguments.length > 2) { + props.key = key + } return h(type, props, children) } From ffe679c490986b69956daec7166f1ab6d9f23073 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Wed, 29 Mar 2023 20:22:29 +0800 Subject: [PATCH 0358/1409] fix(types/jsx): move JSX DOM types back to `@vue/runtime-dom` (#7979) --- packages/runtime-dom/src/index.ts | 2 ++ .../dom.d.ts => runtime-dom/src/jsx.ts} | 14 +++++++++++ packages/vue/jsx-runtime/index.d.ts | 20 +++++----------- packages/vue/jsx.d.ts | 22 +++++------------- packages/vue/types/jsx-register.d.ts | 2 -- rollup.dts.config.js | 23 ++++++++++++++++--- 6 files changed, 48 insertions(+), 35 deletions(-) rename packages/{vue/jsx-runtime/dom.d.ts => runtime-dom/src/jsx.ts} (99%) diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index 19b2ce51abe..354cc4c5bf5 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -252,3 +252,5 @@ export const initDirectivesForSSR = __SSR__ // re-export everything from core // h, Component, reactivity API, nextTick, flags & types export * from '@vue/runtime-core' + +export * from './jsx' diff --git a/packages/vue/jsx-runtime/dom.d.ts b/packages/runtime-dom/src/jsx.ts similarity index 99% rename from packages/vue/jsx-runtime/dom.d.ts rename to packages/runtime-dom/src/jsx.ts index c4da2cdd08a..d103278c6e6 100644 --- a/packages/vue/jsx-runtime/dom.d.ts +++ b/packages/runtime-dom/src/jsx.ts @@ -1319,3 +1319,17 @@ type EventHandlers = { ? E[K] : (payload: E[K]) => void } + +import { VNodeRef } from '@vue/runtime-core' + +export type ReservedProps = { + key?: string | number | symbol + ref?: VNodeRef + ref_for?: boolean + ref_key?: string +} + +export type NativeElements = { + [K in keyof IntrinsicElementAttributes]: IntrinsicElementAttributes[K] & + ReservedProps +} diff --git a/packages/vue/jsx-runtime/index.d.ts b/packages/vue/jsx-runtime/index.d.ts index a05a7293da0..a44382cfbb1 100644 --- a/packages/vue/jsx-runtime/index.d.ts +++ b/packages/vue/jsx-runtime/index.d.ts @@ -1,17 +1,9 @@ -import { VNode, VNodeRef } from '@vue/runtime-dom' -import { IntrinsicElementAttributes } from './dom' - -export type ReservedProps = { - key?: string | number | symbol - ref?: VNodeRef - ref_for?: boolean - ref_key?: string -} - -export type NativeElements = { - [K in keyof IntrinsicElementAttributes]: IntrinsicElementAttributes[K] & - ReservedProps -} +import type { + VNode, + IntrinsicElementAttributes, + ReservedProps, + NativeElements +} from '@vue/runtime-dom' /** * JSX namespace for usage with @jsxImportsSource directive diff --git a/packages/vue/jsx.d.ts b/packages/vue/jsx.d.ts index 947a9904419..afc1039e46d 100644 --- a/packages/vue/jsx.d.ts +++ b/packages/vue/jsx.d.ts @@ -1,21 +1,11 @@ // global JSX namespace registration // somehow we have to copy=pase the jsx-runtime types here to make TypeScript happy -import { VNode, VNodeRef } from '@vue/runtime-dom' -import { IntrinsicElementAttributes } from './jsx-runtime/dom' - -export * from './jsx-runtime/dom' - -export type ReservedProps = { - key?: string | number | symbol - ref?: VNodeRef - ref_for?: boolean - ref_key?: string -} - -export type NativeElements = { - [K in keyof IntrinsicElementAttributes]: IntrinsicElementAttributes[K] & - ReservedProps -} +import type { + VNode, + IntrinsicElementAttributes, + ReservedProps, + NativeElements +} from '@vue/runtime-dom' declare global { namespace JSX { diff --git a/packages/vue/types/jsx-register.d.ts b/packages/vue/types/jsx-register.d.ts index af5d5f29023..a626f798c2a 100644 --- a/packages/vue/types/jsx-register.d.ts +++ b/packages/vue/types/jsx-register.d.ts @@ -2,5 +2,3 @@ // imports the global JSX namespace registration for compat. // TODO: remove in 3.4 import '../jsx' - -export * from '../jsx-runtime/dom' diff --git a/rollup.dts.config.js b/rollup.dts.config.js index 84a6138b44b..ca811d349d5 100644 --- a/rollup.dts.config.js +++ b/rollup.dts.config.js @@ -88,7 +88,21 @@ function patchTypes(pkg) { return false } + const isExported = new Set() const shouldRemoveExport = new Set() + + // pass 0: check all exported types + for (const node of ast.program.body) { + if (node.type === 'ExportNamedDeclaration' && !node.source) { + for (let i = 0; i < node.specifiers.length; i++) { + const spec = node.specifiers[i] + if (spec.type === 'ExportSpecifier') { + isExported.add(spec.local.name) + } + } + } + } + // pass 1: remove internals + add exports for (const node of ast.program.body) { if ( @@ -96,10 +110,13 @@ function patchTypes(pkg) { node.type === 'TSInterfaceDeclaration') && !node.id.name.startsWith(`_`) ) { - shouldRemoveExport.add(node.id.name) + const name = node.id.name + shouldRemoveExport.add(name) if (!removeInternal(node)) { - // @ts-ignore - s.prependLeft(node.start, `export `) + if (isExported.has(name)) { + // @ts-ignore + s.prependLeft(node.start, `export `) + } // traverse further for internal properties if (node.type === 'TSInterfaceDeclaration') { node.body.body.forEach(removeInternal) From a94072dd2ca1aca4ce1fbe5da51ca2a9a07a4637 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 29 Mar 2023 20:31:25 +0800 Subject: [PATCH 0359/1409] fix(compiler-sfc): fix defineExpose() codegen regression from #7949 --- .../__tests__/__snapshots__/compileScript.spec.ts.snap | 4 ++-- packages/compiler-sfc/__tests__/compileScript.spec.ts | 2 +- packages/compiler-sfc/src/compileScript.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 6dcac98204e..2d6df5607a3 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -645,7 +645,7 @@ exports[`SFC compile `) // literals can be used as-is, non-literals are always returned from a // function - expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar'], { + // functions need to be marked with a skip marker + expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar', 'baz'], { foo: 1, - bar: () => ({}) + bar: () => ({}), + func: () => {}, __skip_func: true +})`) + assertCode(content) + }) + + test('default values w/ object runtime declaration', () => { + const { content } = compile(` + + `) + // literals can be used as-is, non-literals are always returned from a + // function + // functions need to be marked with a skip marker since we cannot always + // safely infer whether runtime type is Function (e.g. if the runtime decl + // is imported, or spreads another object) + expect(content) + .toMatch(`props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, { + foo: 1, + bar: () => ({}), + func: () => {}, __skip_func: true, + ext: x, __skip_ext: true })`) assertCode(content) }) @@ -87,14 +110,15 @@ describe('sfc props transform', () => { test('default values w/ type declaration', () => { const { content } = compile(` `) // literals can be used as-is, non-literals are always returned from a // function expect(content).toMatch(`props: { foo: { type: Number, required: false, default: 1 }, - bar: { type: Object, required: false, default: () => ({}) } + bar: { type: Object, required: false, default: () => ({}) }, + func: { type: Function, required: false, default: () => {} } }`) assertCode(content) }) @@ -116,7 +140,7 @@ describe('sfc props transform', () => { baz: null, boola: { type: Boolean }, boolb: { type: [Boolean, Number] }, - func: { type: Function, default: () => (() => {}) } + func: { type: Function, default: () => {} } }`) assertCode(content) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index af54641f4de..d9c59ab665e 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -862,9 +862,11 @@ export function compileScript( ${keys .map(key => { let defaultString: string | undefined - const destructured = genDestructuredDefaultValue(key) + const destructured = genDestructuredDefaultValue(key, props[key].type) if (destructured) { - defaultString = `default: ${destructured}` + defaultString = `default: ${destructured.valueString}${ + destructured.needSkipFactory ? `, skipFactory: true` : `` + }` } else if (hasStaticDefaults) { const prop = propsRuntimeDefaults!.properties.find(node => { if (node.type === 'SpreadElement') return false @@ -925,15 +927,38 @@ export function compileScript( return `\n props: ${propsDecls},` } - function genDestructuredDefaultValue(key: string): string | undefined { + function genDestructuredDefaultValue( + key: string, + inferredType?: string[] + ): + | { + valueString: string + needSkipFactory: boolean + } + | undefined { const destructured = propsDestructuredBindings[key] - if (destructured && destructured.default) { + const defaultVal = destructured && destructured.default + if (defaultVal) { const value = scriptSetup!.content.slice( - destructured.default.start!, - destructured.default.end! + defaultVal.start!, + defaultVal.end! ) - const isLiteral = isLiteralNode(destructured.default) - return isLiteral ? value : `() => (${value})` + const unwrapped = unwrapTSNode(defaultVal) + // If the default value is a function or is an identifier referencing + // external value, skip factory wrap. This is needed when using + // destructure w/ runtime declaration since we cannot safely infer + // whether tje expected runtime prop type is `Function`. + const needSkipFactory = + !inferredType && + (isFunctionType(unwrapped) || unwrapped.type === 'Identifier') + const needFactoryWrap = + !needSkipFactory && + !isLiteralNode(unwrapped) && + !inferredType?.includes('Function') + return { + valueString: needFactoryWrap ? `() => (${value})` : value, + needSkipFactory + } } } @@ -1693,7 +1718,12 @@ export function compileScript( const defaults: string[] = [] for (const key in propsDestructuredBindings) { const d = genDestructuredDefaultValue(key) - if (d) defaults.push(`${key}: ${d}`) + if (d) + defaults.push( + `${key}: ${d.valueString}${ + d.needSkipFactory ? `, __skip_${key}: true` : `` + }` + ) } if (defaults.length) { declCode = `${helper( diff --git a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts index aa4e04e80f4..65fb325f044 100644 --- a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts +++ b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts @@ -114,6 +114,17 @@ describe('SFC - `) - expect(content).toMatch(`const __props_foo = _toRef(__props, 'foo')`) - expect(content).toMatch(`const __props_bar = _toRef(__props, 'bar')`) - expect(content).toMatch(`console.log((__props_foo))`) - expect(content).toMatch(`console.log((__props_bar))`) - expect(content).toMatch(`({ foo: __props_foo, baz: __props_bar })`) - assertCode(content) - }) - // #6960 test('computed static key', () => { const { content, bindings } = compile(` @@ -292,7 +274,7 @@ describe('sfc props transform', () => { ).toThrow(`cannot reference locally declared variables`) }) - test('should error if assignment to constant variable', () => { + test('should error if assignment to destructured prop binding', () => { expect(() => compile( `` ) - ).toThrow(`Assignment to constant variable.`) + ).toThrow(`Cannot assign to destructured props`) + + expect(() => + compile( + `` + ) + ).toThrow(`Cannot assign to destructured props`) + }) + + test('should error when watching destructured prop', () => { + expect(() => + compile( + `` + ) + ).toThrow(`"foo" is a destructured prop and cannot be directly watched.`) + + expect(() => + compile( + `` + ) + ).toThrow(`"foo" is a destructured prop and cannot be directly watched.`) + }) + + // not comprehensive, but should help for most common cases + test('should error if default value type does not match declared type', () => { + expect(() => + compile( + `` + ) + ).toThrow(`Default value of prop "foo" does not match declared type.`) }) }) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index d9c59ab665e..96ea7df18bc 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -11,7 +11,8 @@ import { isFunctionType, walkIdentifiers, getImportedName, - unwrapTSNode + unwrapTSNode, + isCallOf } from '@vue/compiler-dom' import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse' import { @@ -59,6 +60,7 @@ import { warnOnce } from './warn' import { rewriteDefaultAST } from './rewriteDefault' import { createCache } from './cache' import { shouldTransform, transformAST } from '@vue/reactivity-transform' +import { transformDestructuredProps } from './compileScriptPropsDestructure' // Special compiler macros const DEFINE_PROPS = 'defineProps' @@ -132,6 +134,14 @@ export interface ImportBinding { isUsedInTemplate: boolean } +export type PropsDestructureBindings = Record< + string, // public prop key + { + local: string // local identifier, may be different + default?: Expression + } +> + type FromNormalScript = T & { __fromNormalScript?: boolean | null } type PropsDeclType = FromNormalScript type EmitsDeclType = FromNormalScript< @@ -151,7 +161,6 @@ export function compileScript( // feature flags // TODO remove support for deprecated options when out of experimental const enableReactivityTransform = !!options.reactivityTransform - const enablePropsTransform = !!options.reactivityTransform const isProd = !!options.isProd const genSourceMap = options.sourceMap !== false const hoistStatic = options.hoistStatic !== false && !script @@ -310,14 +319,8 @@ export function compileScript( // record declared types for runtime props type generation const declaredTypes: Record = {} // props destructure data - const propsDestructuredBindings: Record< - string, // public prop key - { - local: string // local identifier, may be different - default?: Expression - isConst: boolean - } - > = Object.create(null) + const propsDestructuredBindings: PropsDestructureBindings = + Object.create(null) // magic-string state const s = new MagicString(source) @@ -410,11 +413,7 @@ export function compileScript( } } - function processDefineProps( - node: Node, - declId?: LVal, - declKind?: VariableDeclaration['kind'] - ): boolean { + function processDefineProps(node: Node, declId?: LVal): boolean { if (!isCallOf(node, DEFINE_PROPS)) { return false } @@ -452,10 +451,9 @@ export function compileScript( } if (declId) { - const isConst = declKind === 'const' - if (enablePropsTransform && declId.type === 'ObjectPattern') { + // handle props destructure + if (declId.type === 'ObjectPattern') { propsDestructureDecl = declId - // props destructure - handle compilation sugar for (const prop of declId.properties) { if (prop.type === 'ObjectProperty') { const propKey = resolveObjectKey(prop.key, prop.computed) @@ -479,14 +477,12 @@ export function compileScript( // store default value propsDestructuredBindings[propKey] = { local: left.name, - default: right, - isConst + default: right } } else if (prop.value.type === 'Identifier') { // simple destructure propsDestructuredBindings[propKey] = { - local: prop.value.name, - isConst + local: prop.value.name } } else { error( @@ -515,7 +511,12 @@ export function compileScript( if (!isCallOf(node, WITH_DEFAULTS)) { return false } - if (processDefineProps(node.arguments[0], declId, declKind)) { + warnOnce( + `withDefaults() has been deprecated. ` + + `Props destructure is now reactive by default - ` + + `use destructure with default values instead.` + ) + if (processDefineProps(node.arguments[0], declId)) { if (propsRuntimeDecl) { error( `${WITH_DEFAULTS} can only be used with type-based ` + @@ -943,7 +944,23 @@ export function compileScript( defaultVal.start!, defaultVal.end! ) + const unwrapped = unwrapTSNode(defaultVal) + + if ( + inferredType && + inferredType.length && + !inferredType.includes(UNKNOWN_TYPE) + ) { + const valueType = inferValueType(unwrapped) + if (valueType && !inferredType.includes(valueType)) { + error( + `Default value of prop "${key}" does not match declared type.`, + unwrapped + ) + } + } + // If the default value is a function or is an identifier referencing // external value, skip factory wrap. This is needed when using // destructure w/ runtime declaration since we cannot safely infer @@ -951,10 +968,12 @@ export function compileScript( const needSkipFactory = !inferredType && (isFunctionType(unwrapped) || unwrapped.type === 'Identifier') + const needFactoryWrap = !needSkipFactory && !isLiteralNode(unwrapped) && !inferredType?.includes('Function') + return { valueString: needFactoryWrap ? `() => (${value})` : value, needSkipFactory @@ -1220,6 +1239,7 @@ export function compileScript( } // apply reactivity transform + // TODO remove in 3.4 if (enableReactivityTransform && shouldTransform(script.content)) { const { rootRefs, importedHelpers } = transformAST( scriptAst, @@ -1300,7 +1320,7 @@ export function compileScript( // defineProps / defineEmits const isDefineProps = - processDefineProps(init, decl.id, node.kind) || + processDefineProps(init, decl.id) || processWithDefaults(init, decl.id, node.kind) const isDefineEmits = processDefineEmits(init, decl.id) if (isDefineProps || isDefineEmits) { @@ -1416,19 +1436,30 @@ export function compileScript( } } - // 3. Apply reactivity transform + // 3.1 props destructure transform + if (propsDestructureDecl) { + transformDestructuredProps( + scriptSetupAst, + s, + startOffset, + propsDestructuredBindings, + error, + vueImportAliases.watch + ) + } + + // 3.2 Apply reactivity transform + // TODO remove in 3.4 if ( - (enableReactivityTransform && - // normal `, + { + genDefaultAs: '_sfc_' + } + ) + expect(content).not.toMatch('export default') + expect(content).toMatch(`const _sfc_ = {}`) + assertCode(content) + }) + + test('normal + `, + { + genDefaultAs: '_sfc_' + } + ) + expect(content).not.toMatch('export default') + expect(content).not.toMatch('__default__') + expect(content).toMatch(`const _sfc_ = {}`) + assertCode(content) + }) + + test(' + `, + { + genDefaultAs: '_sfc_' + } + ) + expect(content).not.toMatch('export default') + expect(content).toMatch( + `const _sfc_ = /*#__PURE__*/Object.assign(__default__` + ) + assertCode(content) + }) + + test(' + `, + { + genDefaultAs: '_sfc_' + } + ) + expect(content).not.toMatch('export default') + expect(content).toMatch( + `const _sfc_ = /*#__PURE__*/Object.assign(__default__` + ) + assertCode(content) + }) + + test('`, + { + genDefaultAs: '_sfc_' + } + ) + expect(content).not.toMatch('export default') + expect(content).toMatch(`const _sfc_ = {\n setup`) + assertCode(content) + }) + + test('`, + { + genDefaultAs: '_sfc_' + } + ) + expect(content).not.toMatch('export default') + expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`) + assertCode(content) + }) + + test(' + `, + { + genDefaultAs: '_sfc_' + } + ) + expect(content).not.toMatch('export default') + expect(content).toMatch( + `const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__` + ) + assertCode(content) + }) +}) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 61b4b7c0ee1..9a5e19c84d4 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -69,9 +69,6 @@ const DEFINE_EXPOSE = 'defineExpose' const WITH_DEFAULTS = 'withDefaults' const DEFINE_OPTIONS = 'defineOptions' -// constants -const DEFAULT_VAR = `__default__` - const isBuiltInDir = makeMap( `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is` ) @@ -110,6 +107,12 @@ export interface SFCScriptCompileOptions { * from being hot-reloaded separately from component state. */ inlineTemplate?: boolean + /** + * Generate the final component as a variable instead of default export. + * This is useful in e.g. @vitejs/plugin-vue where the script needs to be + * placed inside the main module. + */ + genDefaultAs?: string /** * Options for template compilation when inlining. Note these are options that * would normally be passed to `compiler-sfc`'s own `compileTemplate()`, not @@ -178,6 +181,10 @@ export function compileScript( const cssVars = sfc.cssVars const scriptLang = script && script.lang const scriptSetupLang = scriptSetup && scriptSetup.lang + const genDefaultAs = options.genDefaultAs + ? `const ${options.genDefaultAs} =` + : `export default` + const normalScriptDefaultVar = `__default__` const isJS = scriptLang === 'js' || scriptLang === 'jsx' || @@ -216,6 +223,7 @@ export function compileScript( // do not process non js/ts script blocks return script } + // normal + `) + expect(content).toMatch(`emits: ["foo", "bar"]`) + assertCode(content) + }) + test('runtime Enum', () => { const { content, bindings } = compile( ``).content ) }) + + test('mixed usage of tuple / call signature in defineEmits', () => { + expect(() => + compile(``) + ).toThrow( + `defineEmits() type cannot mixed call signature and property syntax.` + ) + }) }) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 9a5e19c84d4..2c2505740a7 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1486,7 +1486,7 @@ export function compileScript( extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes) } if (emitsTypeDecl) { - extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits) + extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits, error) } // 5. check macro args to make sure it doesn't reference setup scope @@ -2289,15 +2289,32 @@ function inferValueType(node: Node): string | undefined { function extractRuntimeEmits( node: TSFunctionType | TSTypeLiteral | TSInterfaceBody, - emits: Set + emits: Set, + error: (msg: string, node: Node) => never ) { if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') { const members = node.type === 'TSTypeLiteral' ? node.members : node.body + let hasCallSignature = false + let hasProperty = false for (let t of members) { if (t.type === 'TSCallSignatureDeclaration') { extractEventNames(t.parameters[0], emits) + hasCallSignature = true + } + if (t.type === 'TSPropertySignature') { + if (t.key.type !== 'Identifier' || t.computed) { + error(`defineEmits() type cannot use computed keys.`, t.key) + } + emits.add(t.key.name) + hasProperty = true } } + if (hasCallSignature && hasProperty) { + error( + `defineEmits() type cannot mixed call signature and property syntax.`, + node + ) + } return } else { extractEventNames(node.parameters[0], emits) From f5971468e53683d8a54d9cd11f73d0b95c0e0fb7 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 30 Mar 2023 20:06:11 +0800 Subject: [PATCH 0371/1409] refactor(compiler-sfc): remove unnecessary emits type codegen This is no longer necessary as we no longer recommend type checking generated code --- .../__snapshots__/compileScript.spec.ts.snap | 34 +++++++++---------- .../__tests__/compileScript.spec.ts | 14 ++------ packages/compiler-sfc/src/compileScript.ts | 12 +------ 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index da223439c57..082bfc884de 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1388,7 +1388,7 @@ export interface Emits { (e: 'foo' | 'bar'): void } export default /*#__PURE__*/_defineComponent({ emits: [\\"foo\\", \\"bar\\"], - setup(__props, { expose: __expose, emit }: { emit: ({ (e: 'foo' | 'bar'): void }), expose: any, slots: any, attrs: any }) { + setup(__props, { expose: __expose, emit }) { __expose(); @@ -1405,7 +1405,7 @@ export type Emits = { (e: 'foo' | 'bar'): void } export default /*#__PURE__*/_defineComponent({ emits: [\\"foo\\", \\"bar\\"], - setup(__props, { expose: __expose, emit }: { emit: ({ (e: 'foo' | 'bar'): void }), expose: any, slots: any, attrs: any }) { + setup(__props, { expose: __expose, emit }) { __expose(); @@ -1439,7 +1439,7 @@ interface Emits { (e: 'foo' | 'bar'): void } export default /*#__PURE__*/_defineComponent({ emits: [\\"foo\\", \\"bar\\"], - setup(__props, { expose: __expose, emit }: { emit: ({ (e: 'foo' | 'bar'): void }), expose: any, slots: any, attrs: any }) { + setup(__props, { expose: __expose, emit }) { __expose(); @@ -1450,13 +1450,12 @@ return { emit } })" `; -exports[`SFC compile `) assertCode(content) - expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`) expect(content).toMatch(`emits: ["foo", "bar"]`) }) @@ -1437,7 +1436,6 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`emit: (${type}),`) expect(content).toMatch(`emits: ["foo", "bar", "baz"]`) }) @@ -1449,7 +1447,6 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`) expect(content).toMatch(`emits: ["foo", "bar"]`) }) @@ -1461,7 +1458,6 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`) expect(content).toMatch(`emits: ["foo", "bar"]`) }) @@ -1475,7 +1471,6 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`) expect(content).toMatch(`emits: ["foo", "bar"]`) }) @@ -1487,7 +1482,6 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`) expect(content).toMatch(`emits: ["foo", "bar"]`) }) @@ -1499,7 +1493,6 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`) expect(content).toMatch(`emits: ["foo", "bar"]`) }) @@ -1511,7 +1504,6 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`) expect(content).toMatch(`emits: ["foo", "bar"]`) }) @@ -1523,7 +1515,6 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`) expect(content).toMatch(`emits: ["foo", "bar"]`) }) @@ -1536,11 +1527,10 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) - expect(content).toMatch(`setup(__props, { expose: __expose, emit }) {`) expect(content).toMatch(`emits: ['foo']`) }) - test('defineEmits w/ type (tuple syntax)', () => { + test('defineEmits w/ type (property syntax)', () => { const { content } = compile(` + `) + assertCode(content) + expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) + expect(content).toMatch( + ` + _mergeDefaults({ + foo: { type: String, required: false }, + bar: { type: Number, required: false }, + baz: { type: Boolean, required: true } + }, defaults)`.trim() + ) + }) + // #7111 test('withDefaults (dynamic) w/ production mode', () => { const { content } = compile( diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 3eeee3a6623..8f78ca90392 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -313,7 +313,7 @@ export function compileScript( let hasDefaultExportRender = false let hasDefineOptionsCall = false let propsRuntimeDecl: Node | undefined - let propsRuntimeDefaults: ObjectExpression | undefined + let propsRuntimeDefaults: Node | undefined let propsDestructureDecl: Node | undefined let propsDestructureRestId: string | undefined let propsTypeDecl: PropsDeclType | undefined @@ -534,15 +534,9 @@ export function compileScript( node.callee ) } - propsRuntimeDefaults = node.arguments[1] as ObjectExpression - if ( - !propsRuntimeDefaults || - propsRuntimeDefaults.type !== 'ObjectExpression' - ) { - error( - `The 2nd argument of ${WITH_DEFAULTS} must be an object literal.`, - propsRuntimeDefaults || node - ) + propsRuntimeDefaults = node.arguments[1] + if (!propsRuntimeDefaults) { + error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node) } } else { error( @@ -872,7 +866,9 @@ export function compileScript( destructured.needSkipFactory ? `, skipFactory: true` : `` }` } else if (hasStaticDefaults) { - const prop = propsRuntimeDefaults!.properties.find(node => { + const prop = ( + propsRuntimeDefaults as ObjectExpression + ).properties.find(node => { if (node.type === 'SpreadElement') return false return resolveObjectKey(node.key, node.computed) === key }) as ObjectProperty | ObjectMethod @@ -1001,7 +997,7 @@ export function compileScript( m.key.type === 'Identifier' ) { if ( - propsRuntimeDefaults!.properties.some(p => { + (propsRuntimeDefaults as ObjectExpression).properties.some(p => { if (p.type === 'SpreadElement') return false return ( resolveObjectKey(p.key, p.computed) === From 482f2e3434a1edc47a181890354838e206d08922 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 31 Mar 2023 09:08:23 +0800 Subject: [PATCH 0375/1409] fix(compiler-sfc): use dynamic defaults merging for methods with computed keys ref #7113 --- .../__snapshots__/compileScript.spec.ts.snap | 24 +++++++++++++++++++ .../__tests__/compileScript.spec.ts | 22 +++++++++++++++++ packages/compiler-sfc/src/compileScript.ts | 5 ++-- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index b1f33f5ff04..f59a7407c25 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -2001,6 +2001,30 @@ const props = __props as { foo: () => void, bar: boolean, baz: boolean | (() => +return { props } +} + +})" +`; + +exports[`SFC compile + `) + assertCode(content) + expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) + expect(content).toMatch( + ` + _mergeDefaults({ + foo: { type: Function, required: false } + }, { + ['fo' + 'o']() { return 'foo' } + })`.trim() + ) + }) + test('defineEmits w/ type', () => { const { content } = compile(` ` ) - ).toThrow(`"foo" is a destructured prop and cannot be directly watched.`) + ).toThrow( + `"foo" is a destructured prop and should not be passed directly to watch().` + ) expect(() => compile( @@ -313,7 +315,33 @@ describe('sfc props transform', () => { w(foo, () => {}) ` ) - ).toThrow(`"foo" is a destructured prop and cannot be directly watched.`) + ).toThrow( + `"foo" is a destructured prop and should not be passed directly to watch().` + ) + + expect(() => + compile( + `` + ) + ).toThrow( + `"foo" is a destructured prop and should not be passed directly to toRef().` + ) + + expect(() => + compile( + `` + ) + ).toThrow( + `"foo" is a destructured prop and should not be passed directly to toRef().` + ) }) // not comprehensive, but should help for most common cases diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index de9e11d071f..ec476c4ad16 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1442,7 +1442,7 @@ export function compileScript( startOffset, propsDestructuredBindings, error, - vueImportAliases.watch + vueImportAliases ) } diff --git a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts b/packages/compiler-sfc/src/compileScriptPropsDestructure.ts index bc38912653e..4ee09070d76 100644 --- a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts +++ b/packages/compiler-sfc/src/compileScriptPropsDestructure.ts @@ -32,7 +32,7 @@ export function transformDestructuredProps( offset = 0, knownProps: PropsDestructureBindings, error: (msg: string, node: Node, end?: number) => never, - watchMethodName = 'watch' + vueImportAliases: Record ) { const rootScope: Scope = {} const scopeStack: Scope[] = [rootScope] @@ -152,6 +152,19 @@ export function transformDestructuredProps( return false } + function checkUsage(node: Node, method: string, alias = method) { + if (isCallOf(node, alias)) { + const arg = unwrapTSNode(node.arguments[0]) + if (arg.type === 'Identifier') { + error( + `"${arg.name}" is a destructured prop and should not be passed directly to ${method}(). ` + + `Pass a getter () => ${arg.name} instead.`, + arg + ) + } + } + } + // check root scope first walkScope(ast, true) ;(walk as any)(ast, { @@ -169,16 +182,8 @@ export function transformDestructuredProps( return this.skip() } - if (isCallOf(node, watchMethodName)) { - const arg = unwrapTSNode(node.arguments[0]) - if (arg.type === 'Identifier') { - error( - `"${arg.name}" is a destructured prop and cannot be directly watched. ` + - `Use a getter () => ${arg.name} instead.`, - arg - ) - } - } + checkUsage(node, 'watch', vueImportAliases.watch) + checkUsage(node, 'toRef', vueImportAliases.toRef) // function scopes if (isFunctionType(node)) { diff --git a/packages/dts-test/ref.test-d.ts b/packages/dts-test/ref.test-d.ts index dbf54de09c8..bbcde45ddda 100644 --- a/packages/dts-test/ref.test-d.ts +++ b/packages/dts-test/ref.test-d.ts @@ -7,10 +7,15 @@ import { reactive, proxyRefs, toRef, + toValue, toRefs, ToRefs, shallowReactive, - readonly + readonly, + MaybeRef, + MaybeRefOrGetter, + ComputedRef, + computed } from 'vue' import { expectType, describe } from './utils' @@ -26,6 +31,8 @@ function plainType(arg: number | Ref) { // ref unwrapping expectType(unref(arg)) + expectType(toValue(arg)) + expectType(toValue(() => 123)) // ref inner type should be unwrapped const nestedRef = ref({ @@ -203,6 +210,13 @@ expectType>(p2.obj.k) // Should not distribute Refs over union expectType>(toRef(obj, 'c')) + expectType>(toRef(() => 123)) + expectType>(toRef(() => obj.c)) + + const r = toRef(() => 123) + // @ts-expect-error + r.value = 234 + // toRefs expectType<{ a: Ref @@ -319,3 +333,58 @@ describe('reactive in shallow ref', () => { expectType(x.value.a.b) }) + +describe('toRef <-> toValue', () => { + function foo( + a: MaybeRef, + b: () => string, + c: MaybeRefOrGetter, + d: ComputedRef + ) { + const r = toRef(a) + expectType>(r) + // writable + r.value = 'foo' + + const rb = toRef(b) + expectType>>(rb) + // @ts-expect-error ref created from getter should be readonly + rb.value = 'foo' + + const rc = toRef(c) + expectType | Ref>>(rc) + // @ts-expect-error ref created from MaybeReadonlyRef should be readonly + rc.value = 'foo' + + const rd = toRef(d) + expectType>(rd) + // @ts-expect-error ref created from computed ref should be readonly + rd.value = 'foo' + + expectType(toValue(a)) + expectType(toValue(b)) + expectType(toValue(c)) + expectType(toValue(d)) + + return { + r: toValue(r), + rb: toValue(rb), + rc: toValue(rc), + rd: toValue(rd) + } + } + + expectType<{ + r: string + rb: string + rc: string + rd: string + }>( + foo( + 'foo', + () => 'bar', + ref('baz'), + computed(() => 'hi') + ) + ) +}) diff --git a/packages/reactivity/__tests__/ref.spec.ts b/packages/reactivity/__tests__/ref.spec.ts index 646cc6e6791..718b2bc61b8 100644 --- a/packages/reactivity/__tests__/ref.spec.ts +++ b/packages/reactivity/__tests__/ref.spec.ts @@ -11,7 +11,12 @@ import { } from '../src/index' import { computed } from '@vue/runtime-dom' import { shallowRef, unref, customRef, triggerRef } from '../src/ref' -import { isShallow, readonly, shallowReactive } from '../src/reactive' +import { + isReadonly, + isShallow, + readonly, + shallowReactive +} from '../src/reactive' describe('reactivity/ref', () => { it('should hold a value', () => { @@ -275,6 +280,15 @@ describe('reactivity/ref', () => { expect(toRef(r, 'x')).toBe(r.x) }) + test('toRef on array', () => { + const a = reactive(['a', 'b']) + const r = toRef(a, 1) + expect(r.value).toBe('b') + r.value = 'c' + expect(r.value).toBe('c') + expect(a[1]).toBe('c') + }) + test('toRef default value', () => { const a: { x: number | undefined } = { x: undefined } const x = toRef(a, 'x', 1) @@ -287,6 +301,17 @@ describe('reactivity/ref', () => { expect(x.value).toBe(1) }) + test('toRef getter', () => { + const x = toRef(() => 1) + expect(x.value).toBe(1) + expect(isRef(x)).toBe(true) + expect(unref(x)).toBe(1) + //@ts-expect-error + expect(() => (x.value = 123)).toThrow() + + expect(isReadonly(x)).toBe(true) + }) + test('toRefs', () => { const a = reactive({ x: 1, diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index 60707febef4..ee4da5b1935 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -3,12 +3,15 @@ export { shallowRef, isRef, toRef, + toValue, toRefs, unref, proxyRefs, customRef, triggerRef, type Ref, + type MaybeRef, + type MaybeRefOrGetter, type ToRef, type ToRefs, type UnwrapRef, diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 85a19802d4f..5dd31a9f8ca 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -6,7 +6,7 @@ import { triggerEffects } from './effect' import { TrackOpTypes, TriggerOpTypes } from './operations' -import { isArray, hasChanged, IfAny } from '@vue/shared' +import { isArray, hasChanged, IfAny, isFunction, isObject } from '@vue/shared' import { isProxy, toRaw, @@ -87,9 +87,7 @@ export function isRef(r: any): r is Ref { * @param value - The object to wrap in the ref. * @see {@link https://vuejs.org/api/reactivity-core.html#ref} */ -export function ref( - value: T -): [T] extends [Ref] ? T : Ref> +export function ref(value: T): T export function ref(value: T): Ref> export function ref(): Ref export function ref(value?: unknown) { @@ -191,6 +189,9 @@ export function triggerRef(ref: Ref) { triggerRefValue(ref, __DEV__ ? ref.value : void 0) } +export type MaybeRef = T | Ref +export type MaybeRefOrGetter = MaybeRef | (() => T) + /** * Returns the inner value if the argument is a ref, otherwise return the * argument itself. This is a sugar function for @@ -207,10 +208,30 @@ export function triggerRef(ref: Ref) { * @param ref - Ref or plain value to be converted into the plain value. * @see {@link https://vuejs.org/api/reactivity-utilities.html#unref} */ -export function unref(ref: T | Ref): T { +export function unref(ref: MaybeRef): T { return isRef(ref) ? (ref.value as any) : ref } +/** + * Normalizes values / refs / getters to values. + * This is similar to {@link unref()}, except that it also normalizes getters. + * If the argument is a getter, it will be invoked and its return value will + * be returned. + * + * @example + * ```js + * toValue(1) // 1 + * toValue(ref(1)) // 1 + * toValue(() => 1) // 1 + * ``` + * + * @param source - A getter, an existing ref, or a non-function value. + * @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue} + */ +export function toValue(source: MaybeRefOrGetter): T { + return isFunction(source) ? source() : unref(source) +} + const shallowUnwrapHandlers: ProxyHandler = { get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)), set: (target, key, value, receiver) => { @@ -305,7 +326,7 @@ export function toRefs(object: T): ToRefs { } const ret: any = isArray(object) ? new Array(object.length) : {} for (const key in object) { - ret[key] = toRef(object, key) + ret[key] = propertyToRef(object, key) } return ret } @@ -333,12 +354,36 @@ class ObjectRefImpl { } } +class GetterRefImpl { + public readonly __v_isRef = true + public readonly __v_isReadonly = true + constructor(private readonly _getter: () => T) {} + get value() { + return this._getter() + } +} + export type ToRef = IfAny, [T] extends [Ref] ? T : Ref> /** - * Can be used to create a ref for a property on a source reactive object. The - * created ref is synced with its source property: mutating the source property - * will update the ref, and vice-versa. + * Used to normalize values / refs / getters into refs. + * + * @example + * ```js + * // returns existing refs as-is + * toRef(existingRef) + * + * // creates a ref that calls the getter on .value access + * toRef(() => props.foo) + * + * // creates normal refs from non-function values + * // equivalent to ref(1) + * toRef(1) + * ``` + * + * Can also be used to create a ref for a property on a source reactive object. + * The created ref is synced with its source property: mutating the source + * property will update the ref, and vice-versa. * * @example * ```js @@ -358,10 +403,18 @@ export type ToRef = IfAny, [T] extends [Ref] ? T : Ref> * console.log(fooRef.value) // 3 * ``` * - * @param object - The reactive object containing the desired property. - * @param key - Name of the property in the reactive object. + * @param source - A getter, an existing ref, a non-function value, or a + * reactive object to create a property ref from. + * @param [key] - (optional) Name of the property in the reactive object. * @see {@link https://vuejs.org/api/reactivity-utilities.html#toref} */ +export function toRef( + value: T +): T extends () => infer R + ? Readonly> + : T extends Ref + ? T + : Ref> export function toRef( object: T, key: K @@ -371,15 +424,31 @@ export function toRef( key: K, defaultValue: T[K] ): ToRef> -export function toRef( - object: T, - key: K, - defaultValue?: T[K] -): ToRef { - const val = object[key] +export function toRef( + source: Record | MaybeRef, + key?: string, + defaultValue?: unknown +): Ref { + if (isRef(source)) { + return source + } else if (isFunction(source)) { + return new GetterRefImpl(source as () => unknown) as any + } else if (isObject(source) && arguments.length > 1) { + return propertyToRef(source, key!, defaultValue) + } else { + return ref(source) + } +} + +function propertyToRef(source: object, key: string, defaultValue?: unknown) { + const val = (source as any)[key] return isRef(val) ? val - : (new ObjectRefImpl(object, key, defaultValue) as any) + : (new ObjectRefImpl( + source as Record, + key, + defaultValue + ) as any) } // corner case when use narrows type diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 06f9a2affd4..936d6ca3565 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -11,6 +11,7 @@ export { proxyRefs, isRef, toRef, + toValue, toRefs, isProxy, isReactive, @@ -152,6 +153,8 @@ declare module '@vue/reactivity' { export { TrackOpTypes, TriggerOpTypes } from '@vue/reactivity' export type { Ref, + MaybeRef, + MaybeRefOrGetter, ToRef, ToRefs, UnwrapRef, From 5a2f5d59cffa36a99e6f2feab6b3ba7958b7362f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Mon, 3 Apr 2023 16:49:16 +0800 Subject: [PATCH 0380/1409] feat(types/slots): support slot presence / props type checks via `defineSlots` macro and `slots` option (#7982) --- .../__snapshots__/compileScript.spec.ts.snap | 45 ++++++++++++ .../__tests__/compileScript.spec.ts | 39 +++++++++++ packages/compiler-sfc/src/compileScript.ts | 42 ++++++++++- packages/dts-test/defineComponent.test-d.tsx | 69 ++++++++++++++++++- .../dts-test/functionalComponent.test-d.tsx | 28 +++++++- packages/dts-test/setupHelpers.test-d.ts | 25 ++++++- .../runtime-core/src/apiDefineComponent.ts | 29 ++++++-- packages/runtime-core/src/apiSetupHelpers.ts | 20 ++++-- packages/runtime-core/src/component.ts | 33 ++++++--- packages/runtime-core/src/componentOptions.ts | 31 ++++++++- .../src/componentPublicInstance.ts | 10 ++- packages/runtime-core/src/componentSlots.ts | 26 ++++++- packages/runtime-core/src/h.ts | 8 ++- packages/runtime-core/src/index.ts | 3 +- .../types/scriptSetupHelpers.d.ts | 2 + packages/runtime-dom/src/apiCustomElement.ts | 9 ++- 16 files changed, 380 insertions(+), 39 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index f59a7407c25..ee00977c131 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1785,6 +1785,51 @@ return { props, emit } })" `; +exports[`SFC compile + `) + assertCode(content) + expect(content).toMatch(`const slots = _useSlots()`) + expect(content).not.toMatch('defineSlots') + }) + + test('w/o return value', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).not.toMatch('defineSlots') + expect(content).not.toMatch(`_useSlots`) + }) + + test('w/o generic params', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`const slots = _useSlots()`) + expect(content).not.toMatch('defineSlots') + }) + }) + test('runtime Enum', () => { const { content, bindings } = compile( `` + ) + ).not.toThrowError() + }) }) }) diff --git a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts b/packages/compiler-sfc/src/compileScriptPropsDestructure.ts index 4ee09070d76..d0addf6fcbc 100644 --- a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts +++ b/packages/compiler-sfc/src/compileScriptPropsDestructure.ts @@ -17,7 +17,7 @@ import { isCallOf, unwrapTSNode } from '@vue/compiler-core' -import { hasOwn, genPropsAccessExp } from '@vue/shared' +import { genPropsAccessExp } from '@vue/shared' import { PropsDestructureBindings } from './compileScript' /** @@ -47,6 +47,15 @@ export function transformDestructuredProps( propsLocalToPublicMap[local] = key } + function pushScope() { + scopeStack.push((currentScope = Object.create(currentScope))) + } + + function popScope() { + scopeStack.pop() + currentScope = scopeStack[scopeStack.length - 1] || null + } + function registerLocalBinding(id: Identifier) { excludedIds.add(id) if (currentScope) { @@ -108,54 +117,41 @@ export function transformDestructuredProps( } } - function rewriteId( - scope: Scope, - id: Identifier, - parent: Node, - parentStack: Node[] - ): boolean { - if (hasOwn(scope, id.name)) { - const binding = scope[id.name] - - if (binding) { - if ( - (parent.type === 'AssignmentExpression' && id === parent.left) || - parent.type === 'UpdateExpression' - ) { - error(`Cannot assign to destructured props as they are readonly.`, id) - } + function rewriteId(id: Identifier, parent: Node, parentStack: Node[]) { + if ( + (parent.type === 'AssignmentExpression' && id === parent.left) || + parent.type === 'UpdateExpression' + ) { + error(`Cannot assign to destructured props as they are readonly.`, id) + } - if (isStaticProperty(parent) && parent.shorthand) { - // let binding used in a property shorthand - // skip for destructure patterns - if ( - !(parent as any).inPattern || - isInDestructureAssignment(parent, parentStack) - ) { - // { prop } -> { prop: __props.prop } - s.appendLeft( - id.end! + offset, - `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}` - ) - } - } else { - // x --> __props.x - s.overwrite( - id.start! + offset, - id.end! + offset, - genPropsAccessExp(propsLocalToPublicMap[id.name]) - ) - } + if (isStaticProperty(parent) && parent.shorthand) { + // let binding used in a property shorthand + // skip for destructure patterns + if ( + !(parent as any).inPattern || + isInDestructureAssignment(parent, parentStack) + ) { + // { prop } -> { prop: __props.prop } + s.appendLeft( + id.end! + offset, + `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}` + ) } - return true + } else { + // x --> __props.x + s.overwrite( + id.start! + offset, + id.end! + offset, + genPropsAccessExp(propsLocalToPublicMap[id.name]) + ) } - return false } function checkUsage(node: Node, method: string, alias = method) { if (isCallOf(node, alias)) { const arg = unwrapTSNode(node.arguments[0]) - if (arg.type === 'Identifier') { + if (arg.type === 'Identifier' && currentScope[arg.name]) { error( `"${arg.name}" is a destructured prop and should not be passed directly to ${method}(). ` + `Pass a getter () => ${arg.name} instead.`, @@ -187,7 +183,7 @@ export function transformDestructuredProps( // function scopes if (isFunctionType(node)) { - scopeStack.push((currentScope = {})) + pushScope() walkFunctionParams(node, registerLocalBinding) if (node.body.type === 'BlockStatement') { walkScope(node.body) @@ -197,7 +193,7 @@ export function transformDestructuredProps( // catch param if (node.type === 'CatchClause') { - scopeStack.push((currentScope = {})) + pushScope() if (node.param && node.param.type === 'Identifier') { registerLocalBinding(node.param) } @@ -207,7 +203,7 @@ export function transformDestructuredProps( // non-function block scopes if (node.type === 'BlockStatement' && !isFunctionType(parent!)) { - scopeStack.push((currentScope = {})) + pushScope() walkScope(node) return } @@ -217,12 +213,8 @@ export function transformDestructuredProps( isReferencedIdentifier(node, parent!, parentStack) && !excludedIds.has(node) ) { - // walk up the scope chain to check if id should be appended .value - let i = scopeStack.length - while (i--) { - if (rewriteId(scopeStack[i], node, parent!, parentStack)) { - return - } + if (currentScope[node.name]) { + rewriteId(node, parent!, parentStack) } } } @@ -233,8 +225,7 @@ export function transformDestructuredProps( (node.type === 'BlockStatement' && !isFunctionType(parent!)) || isFunctionType(node) ) { - scopeStack.pop() - currentScope = scopeStack[scopeStack.length - 1] || null + popScope() } } }) From 10317fa01e6d6adcaed20e6093bf9eb1382131a6 Mon Sep 17 00:00:00 2001 From: JayFate <48240828+JayFate@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:14:45 +0800 Subject: [PATCH 0383/1409] chore: add setupVitest to tsconfig (#8009) --- scripts/setupVitest.ts | 4 ++-- tsconfig.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/setupVitest.ts b/scripts/setupVitest.ts index 81a78d2985a..c555b0fa5e8 100644 --- a/scripts/setupVitest.ts +++ b/scripts/setupVitest.ts @@ -1,4 +1,4 @@ -import { vi } from 'vitest' +import { vi, type SpyInstance } from 'vitest' expect.extend({ toHaveBeenWarned(received: string) { @@ -65,7 +65,7 @@ expect.extend({ } }) -let warn +let warn: SpyInstance const asserted: Set = new Set() beforeEach(() => { diff --git a/tsconfig.json b/tsconfig.json index bbe12407bc1..e575f0c4c90 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,6 +33,7 @@ "packages/runtime-dom/types/jsx.d.ts", "packages/*/__tests__", "packages/dts-test", - "packages/vue/jsx-runtime" + "packages/vue/jsx-runtime", + "scripts/setupVitest.ts" ] } From 6003ef74c708154c0641b135358668d2908a58fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AD=8F?= <2553241022@qq.com> Date: Tue, 4 Apr 2023 18:20:56 +0800 Subject: [PATCH 0384/1409] chore: remove duplicate test (#8003) --- packages/runtime-core/__tests__/vnode.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts index 0324a70734f..130ea411b23 100644 --- a/packages/runtime-core/__tests__/vnode.spec.ts +++ b/packages/runtime-core/__tests__/vnode.spec.ts @@ -220,7 +220,6 @@ describe('vnode', () => { const node2 = createVNode({}, null, [node1]) const cloned2 = cloneVNode(node2) expect(cloned2).toEqual(node2) - expect(cloneVNode(node2)).toEqual(node2) expect(cloneVNode(node2)).toEqual(cloned2) }) From 036914c10b597de660a745d329e5e0cf252a05d6 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 4 Apr 2023 18:23:06 +0800 Subject: [PATCH 0385/1409] release: v3.3.0-alpha.8 --- CHANGELOG.md | 9 ++++ package.json | 2 +- packages/compiler-core/package.json | 4 +- packages/compiler-dom/package.json | 6 +-- packages/compiler-sfc/package.json | 12 ++--- packages/compiler-ssr/package.json | 6 +-- packages/dts-test/package.json | 2 +- packages/reactivity-transform/package.json | 6 +-- packages/reactivity/package.json | 4 +- packages/runtime-core/package.json | 6 +-- packages/runtime-dom/package.json | 6 +-- packages/runtime-test/package.json | 6 +-- packages/server-renderer/package.json | 8 ++-- packages/sfc-playground/package.json | 2 +- packages/shared/package.json | 2 +- packages/size-check/package.json | 2 +- packages/template-explorer/package.json | 2 +- packages/vue-compat/package.json | 4 +- packages/vue/package.json | 12 ++--- pnpm-lock.yaml | 52 +++++++++++----------- 20 files changed, 81 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34df3c7d0b7..1c7ba846f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [3.3.0-alpha.8](https://github.com/vuejs/core/compare/v3.3.0-alpha.7...v3.3.0-alpha.8) (2023-04-04) + + +### Bug Fixes + +* **compiler-sfc:** check binding is prop before erroring ([f3145a9](https://github.com/vuejs/core/commit/f3145a915aaec11c915f1df258c5209ae4782bcc)), closes [#8017](https://github.com/vuejs/core/issues/8017) + + + # [3.3.0-alpha.7](https://github.com/vuejs/core/compare/v3.3.0-alpha.6...v3.3.0-alpha.7) (2023-04-03) diff --git a/package.json b/package.json index dc81f6c9559..2f6db271c76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "packageManager": "pnpm@7.26.0", "type": "module", "scripts": { diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 682026df93f..152fc9ce6cf 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/compiler-core", "main": "index.js", "module": "dist/compiler-core.esm-bundler.js", @@ -33,7 +33,7 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme", "dependencies": { "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.0-alpha.7", + "@vue/shared": "3.3.0-alpha.8", "estree-walker": "^2.0.2", "source-map": "^0.6.1" }, diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index 92caea5ebe1..007568fec7c 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/compiler-dom", "main": "index.js", "module": "dist/compiler-dom.esm-bundler.js", @@ -37,7 +37,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.7", - "@vue/compiler-core": "3.3.0-alpha.7" + "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-core": "3.3.0-alpha.8" } } diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index 6328889699a..ce831788a9a 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", "module": "dist/compiler-sfc.esm-browser.js", @@ -33,11 +33,11 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.7", - "@vue/compiler-dom": "3.3.0-alpha.7", - "@vue/compiler-ssr": "3.3.0-alpha.7", - "@vue/reactivity-transform": "3.3.0-alpha.7", - "@vue/shared": "3.3.0-alpha.7", + "@vue/compiler-core": "3.3.0-alpha.8", + "@vue/compiler-dom": "3.3.0-alpha.8", + "@vue/compiler-ssr": "3.3.0-alpha.8", + "@vue/reactivity-transform": "3.3.0-alpha.8", + "@vue/shared": "3.3.0-alpha.8", "estree-walker": "^2.0.2", "magic-string": "^0.30.0", "postcss": "^8.1.10", diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index eac467fd7a8..b692b9fdd0f 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-ssr", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/compiler-ssr", "main": "dist/compiler-ssr.cjs.js", "types": "dist/compiler-ssr.d.ts", @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.7", - "@vue/compiler-dom": "3.3.0-alpha.7" + "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-dom": "3.3.0-alpha.8" } } diff --git a/packages/dts-test/package.json b/packages/dts-test/package.json index 8687e34428e..5d5acc0989d 100644 --- a/packages/dts-test/package.json +++ b/packages/dts-test/package.json @@ -4,5 +4,5 @@ "dependencies": { "vue": "workspace:*" }, - "version": "3.3.0-alpha.7" + "version": "3.3.0-alpha.8" } diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json index 057cba0e604..29f37dd6a49 100644 --- a/packages/reactivity-transform/package.json +++ b/packages/reactivity-transform/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity-transform", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/reactivity-transform", "main": "dist/reactivity-transform.cjs.js", "files": [ @@ -29,8 +29,8 @@ "homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.7", - "@vue/shared": "3.3.0-alpha.7", + "@vue/compiler-core": "3.3.0-alpha.8", + "@vue/shared": "3.3.0-alpha.8", "estree-walker": "^2.0.2", "magic-string": "^0.30.0" }, diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index e069619ca87..e07312dce31 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/reactivity", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", @@ -36,6 +36,6 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.7" + "@vue/shared": "3.3.0-alpha.8" } } diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index a09933bfc6c..a282b8d87b4 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-core", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/runtime-core", "main": "index.js", "module": "dist/runtime-core.esm-bundler.js", @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.7", - "@vue/reactivity": "3.3.0-alpha.7" + "@vue/shared": "3.3.0-alpha.8", + "@vue/reactivity": "3.3.0-alpha.8" } } diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json index ed871f75fb8..29cf32c048b 100644 --- a/packages/runtime-dom/package.json +++ b/packages/runtime-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-dom", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/runtime-dom", "main": "index.js", "module": "dist/runtime-dom.esm-bundler.js", @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.7", - "@vue/runtime-core": "3.3.0-alpha.7", + "@vue/shared": "3.3.0-alpha.8", + "@vue/runtime-core": "3.3.0-alpha.8", "csstype": "^3.1.1" } } diff --git a/packages/runtime-test/package.json b/packages/runtime-test/package.json index d95c02c8d96..4c778451a92 100644 --- a/packages/runtime-test/package.json +++ b/packages/runtime-test/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-test", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/runtime-test", "private": true, "main": "index.js", @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-test#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.7", - "@vue/runtime-core": "3.3.0-alpha.7" + "@vue/shared": "3.3.0-alpha.8", + "@vue/runtime-core": "3.3.0-alpha.8" } } diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json index d65afa63762..d6d43bf82f7 100644 --- a/packages/server-renderer/package.json +++ b/packages/server-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/server-renderer", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "@vue/server-renderer", "main": "index.js", "module": "dist/server-renderer.esm-bundler.js", @@ -32,10 +32,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme", "peerDependencies": { - "vue": "3.3.0-alpha.7" + "vue": "3.3.0-alpha.8" }, "dependencies": { - "@vue/shared": "3.3.0-alpha.7", - "@vue/compiler-ssr": "3.3.0-alpha.7" + "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-ssr": "3.3.0-alpha.8" } } diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index 8212d1b276d..ec5724190be 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -1,6 +1,6 @@ { "name": "@vue/sfc-playground", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "private": true, "scripts": { "dev": "vite", diff --git a/packages/shared/package.json b/packages/shared/package.json index 426cac33cce..727e24ee626 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vue/shared", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "internal utils shared across @vue packages", "main": "index.js", "module": "dist/shared.esm-bundler.js", diff --git a/packages/size-check/package.json b/packages/size-check/package.json index 314961bfbbd..94d49cc4e6a 100644 --- a/packages/size-check/package.json +++ b/packages/size-check/package.json @@ -1,6 +1,6 @@ { "name": "@vue/size-check", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "private": true, "scripts": { "build": "vite build" diff --git a/packages/template-explorer/package.json b/packages/template-explorer/package.json index b7bdd068e46..7b42a4c3ea6 100644 --- a/packages/template-explorer/package.json +++ b/packages/template-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/template-explorer", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "private": true, "buildOptions": { "formats": [ diff --git a/packages/vue-compat/package.json b/packages/vue-compat/package.json index dcc0631d6fe..aa04139c247 100644 --- a/packages/vue-compat/package.json +++ b/packages/vue-compat/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compat", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "Vue 3 compatibility build for Vue 2", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -43,6 +43,6 @@ "source-map": "^0.6.1" }, "peerDependencies": { - "vue": "3.3.0-alpha.7" + "vue": "3.3.0-alpha.8" } } diff --git a/packages/vue/package.json b/packages/vue/package.json index 7606ae73596..cb17e34700d 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "vue", - "version": "3.3.0-alpha.7", + "version": "3.3.0-alpha.8", "description": "The progressive JavaScript framework for building modern web UI.", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -81,10 +81,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.7", - "@vue/compiler-dom": "3.3.0-alpha.7", - "@vue/runtime-dom": "3.3.0-alpha.7", - "@vue/compiler-sfc": "3.3.0-alpha.7", - "@vue/server-renderer": "3.3.0-alpha.7" + "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-dom": "3.3.0-alpha.8", + "@vue/runtime-dom": "3.3.0-alpha.8", + "@vue/compiler-sfc": "3.3.0-alpha.8", + "@vue/server-renderer": "3.3.0-alpha.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71477814d57..0b889e640e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,7 +102,7 @@ importers: specifiers: '@babel/parser': ^7.21.3 '@babel/types': ^7.21.3 - '@vue/shared': 3.3.0-alpha.7 + '@vue/shared': 3.3.0-alpha.8 estree-walker: ^2.0.2 source-map: ^0.6.1 dependencies: @@ -115,8 +115,8 @@ importers: packages/compiler-dom: specifiers: - '@vue/compiler-core': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/compiler-core': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/compiler-core': link:../compiler-core '@vue/shared': link:../shared @@ -127,12 +127,12 @@ importers: '@babel/types': ^7.21.3 '@types/estree': ^0.0.48 '@types/lru-cache': ^5.1.0 - '@vue/compiler-core': 3.3.0-alpha.7 - '@vue/compiler-dom': 3.3.0-alpha.7 - '@vue/compiler-ssr': 3.3.0-alpha.7 + '@vue/compiler-core': 3.3.0-alpha.8 + '@vue/compiler-dom': 3.3.0-alpha.8 + '@vue/compiler-ssr': 3.3.0-alpha.8 '@vue/consolidate': ^0.17.3 - '@vue/reactivity-transform': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/reactivity-transform': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 estree-walker: ^2.0.2 hash-sum: ^2.0.0 lru-cache: ^5.1.1 @@ -170,8 +170,8 @@ importers: packages/compiler-ssr: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/compiler-dom': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/shared': link:../shared @@ -184,7 +184,7 @@ importers: packages/reactivity: specifiers: - '@vue/shared': 3.3.0-alpha.7 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/shared': link:../shared @@ -193,8 +193,8 @@ importers: '@babel/core': ^7.21.3 '@babel/parser': ^7.20.15 '@babel/types': ^7.21.3 - '@vue/compiler-core': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/compiler-core': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 estree-walker: ^2.0.2 magic-string: ^0.30.0 dependencies: @@ -209,16 +209,16 @@ importers: packages/runtime-core: specifiers: - '@vue/reactivity': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/reactivity': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/reactivity': link:../reactivity '@vue/shared': link:../shared packages/runtime-dom: specifiers: - '@vue/runtime-core': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/runtime-core': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 csstype: ^3.1.1 dependencies: '@vue/runtime-core': link:../runtime-core @@ -227,16 +227,16 @@ importers: packages/runtime-test: specifiers: - '@vue/runtime-core': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/runtime-core': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/runtime-core': link:../runtime-core '@vue/shared': link:../shared packages/server-renderer: specifiers: - '@vue/compiler-ssr': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/compiler-ssr': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/compiler-ssr': link:../compiler-ssr '@vue/shared': link:../shared @@ -277,11 +277,11 @@ importers: packages/vue: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.7 - '@vue/compiler-sfc': 3.3.0-alpha.7 - '@vue/runtime-dom': 3.3.0-alpha.7 - '@vue/server-renderer': 3.3.0-alpha.7 - '@vue/shared': 3.3.0-alpha.7 + '@vue/compiler-dom': 3.3.0-alpha.8 + '@vue/compiler-sfc': 3.3.0-alpha.8 + '@vue/runtime-dom': 3.3.0-alpha.8 + '@vue/server-renderer': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/compiler-sfc': link:../compiler-sfc From 05f94cf7b01dd05ed7d3170916a38b175d5df292 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 5 Apr 2023 11:10:21 +0800 Subject: [PATCH 0386/1409] fix(compiler-ssr): disable v-once transform in ssr vdom fallback branch fix #7644 --- .../compiler-core/src/transforms/vOnce.ts | 2 +- .../__tests__/ssrComponent.spec.ts | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/compiler-core/src/transforms/vOnce.ts b/packages/compiler-core/src/transforms/vOnce.ts index 1b58c6763b6..e1ec5e129df 100644 --- a/packages/compiler-core/src/transforms/vOnce.ts +++ b/packages/compiler-core/src/transforms/vOnce.ts @@ -7,7 +7,7 @@ const seen = new WeakSet() export const transformOnce: NodeTransform = (node, context) => { if (node.type === NodeTypes.ELEMENT && findDir(node, 'once', true)) { - if (seen.has(node) || context.inVOnce) { + if (seen.has(node) || context.inVOnce || context.inSSR) { return } seen.add(node) diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts index 83c5bcfc3a6..9391c01e37e 100644 --- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts @@ -286,6 +286,34 @@ describe('ssr: components', () => { `) }) + // #7644 + test('slot content with v-once', () => { + const { code } = compile(``) + expect(code).not.toMatch(`_cache`) + expect(compile(``).code).toMatchInlineSnapshot(` + "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\") + const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\") + + return function ssrRender(_ctx, _push, _parent, _attrs) { + const _component_foo = _resolveComponent(\\"foo\\") + const _component_bar = _resolveComponent(\\"bar\\") + + _push(_ssrRenderComponent(_component_foo, _attrs, { + default: _withCtx((_, _push, _parent, _scopeId) => { + if (_push) { + _push(_ssrRenderComponent(_component_bar, null, null, _parent, _scopeId)) + } else { + return [ + _createVNode(_component_bar) + ] + } + }), + _: 1 /* STABLE */ + }, _parent)) + }" + `) + }) + describe('built-in fallthroughs', () => { test('transition', () => { expect(compile(`
`).code) From 2a9e379655a1ed1cae5615f29498a9f0aa7aa9c6 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 5 Apr 2023 11:16:08 +0800 Subject: [PATCH 0387/1409] chore: remove unused args passed to ssrRender --- packages/server-renderer/src/render.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index a1f327b4320..d3ebf1fcf30 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -182,17 +182,7 @@ function renderComponentSubTree( // set current rendering instance for asset resolution const prev = setCurrentRenderingInstance(instance) try { - ssrRender( - instance.proxy, - push, - instance, - attrs, - // compiler-optimized bindings - instance.props, - instance.setupState, - instance.data, - instance.ctx - ) + ssrRender(instance.proxy, push, instance, attrs) } finally { setCurrentRenderingInstance(prev) } From 869f3fb93e61400be4fd925e0850c2b1564749e2 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 5 Apr 2023 09:18:13 +0200 Subject: [PATCH 0388/1409] feat(app): app.runWithContext() (#7451) --- .../__tests__/apiCreateApp.spec.ts | 16 +++++++++++++ packages/runtime-core/src/apiCreateApp.ts | 23 +++++++++++++++++++ packages/runtime-core/src/apiInject.ts | 12 ++++++---- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/runtime-core/__tests__/apiCreateApp.spec.ts b/packages/runtime-core/__tests__/apiCreateApp.spec.ts index 7cfce51c161..699d0be6d50 100644 --- a/packages/runtime-core/__tests__/apiCreateApp.spec.ts +++ b/packages/runtime-core/__tests__/apiCreateApp.spec.ts @@ -110,6 +110,22 @@ describe('api: createApp', () => { expect(`App already provides property with key "bar".`).toHaveBeenWarned() }) + test('runWithContext', () => { + const app = createApp({ + setup() { + provide('foo', 'should not be seen') + return () => h('div') + } + }) + app.provide('foo', 1) + + expect(app.runWithContext(() => inject('foo'))).toBe(1) + + // ensure the context is restored + inject('foo') + expect('inject() can only be used inside setup').toHaveBeenWarned() + }) + test('component', () => { const Root = { // local override diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 05c7ce31539..6b698ba26c2 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -51,6 +51,14 @@ export interface App { unmount(): void provide(key: InjectionKey | string, value: T): this + /** + * Runs a function with the app as active instance. This allows using of `inject()` within the function to get access + * to variables provided via `app.provide()`. + * + * @param fn - function to run with the app as active instance + */ + runWithContext(fn: () => T): T + // internal, but we need to expose these for the server-renderer and devtools _uid: number _component: ConcreteComponent @@ -370,6 +378,15 @@ export function createAppAPI( context.provides[key as string | symbol] = value return app + }, + + runWithContext(fn) { + currentApp = app + try { + return fn() + } finally { + currentApp = null + } } }) @@ -380,3 +397,9 @@ export function createAppAPI( return app } } + +/** + * @internal Used to identify the current app when using `inject()` within + * `app.runWithContext()`. + */ +export let currentApp: App | null = null diff --git a/packages/runtime-core/src/apiInject.ts b/packages/runtime-core/src/apiInject.ts index c5c47876cb3..6eedee88c09 100644 --- a/packages/runtime-core/src/apiInject.ts +++ b/packages/runtime-core/src/apiInject.ts @@ -1,6 +1,7 @@ import { isFunction } from '@vue/shared' import { currentInstance } from './component' import { currentRenderingInstance } from './componentRenderContext' +import { currentApp } from './apiCreateApp' import { warn } from './warning' export interface InjectionKey extends Symbol {} @@ -46,21 +47,24 @@ export function inject( // fallback to `currentRenderingInstance` so that this can be called in // a functional component const instance = currentInstance || currentRenderingInstance - if (instance) { + + // also support looking up from app-level provides w/ `app.runWithContext()` + if (instance || currentApp) { // #2400 // to support `app.use` plugins, // fallback to appContext's `provides` if the instance is at root - const provides = - instance.parent == null + const provides = instance + ? instance.parent == null ? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides + : currentApp!._context.provides if (provides && (key as string | symbol) in provides) { // TS doesn't allow symbol as index type return provides[key as string] } else if (arguments.length > 1) { return treatDefaultAsFactory && isFunction(defaultValue) - ? defaultValue.call(instance.proxy) + ? defaultValue.call(instance && instance.proxy) : defaultValue } else if (__DEV__) { warn(`injection "${String(key)}" not found.`) From b2c874e3b9b3bdcab43f99ea80c264c16b448473 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 5 Apr 2023 16:35:10 +0800 Subject: [PATCH 0389/1409] refactor(compiler-sfc): move related files into separate directories --- .../__tests__/templateTransformAssetUrl.spec.ts | 2 +- .../__tests__/templateTransformSrcset.spec.ts | 4 ++-- packages/compiler-sfc/__tests__/templateUtils.spec.ts | 2 +- packages/compiler-sfc/src/compileScript.ts | 4 ++-- packages/compiler-sfc/src/compileStyle.ts | 8 ++++---- packages/compiler-sfc/src/compileTemplate.ts | 6 +++--- packages/compiler-sfc/src/index.ts | 2 +- packages/compiler-sfc/src/parse.ts | 2 +- .../propsDestructure.ts} | 2 +- packages/compiler-sfc/src/{ => style}/cssVars.ts | 2 +- .../src/{stylePluginScoped.ts => style/pluginScoped.ts} | 2 +- .../src/{stylePluginTrim.ts => style/pluginTrim.ts} | 0 .../src/{stylePreprocessors.ts => style/preprocessors.ts} | 2 +- packages/compiler-sfc/src/{ => template}/templateUtils.ts | 0 .../transformAssetUrl.ts} | 0 .../transformSrcset.ts} | 5 +---- 16 files changed, 20 insertions(+), 23 deletions(-) rename packages/compiler-sfc/src/{compileScriptPropsDestructure.ts => script/propsDestructure.ts} (99%) rename packages/compiler-sfc/src/{ => style}/cssVars.ts (99%) rename packages/compiler-sfc/src/{stylePluginScoped.ts => style/pluginScoped.ts} (99%) rename packages/compiler-sfc/src/{stylePluginTrim.ts => style/pluginTrim.ts} (100%) rename packages/compiler-sfc/src/{stylePreprocessors.ts => style/preprocessors.ts} (98%) rename packages/compiler-sfc/src/{ => template}/templateUtils.ts (100%) rename packages/compiler-sfc/src/{templateTransformAssetUrl.ts => template/transformAssetUrl.ts} (100%) rename packages/compiler-sfc/src/{templateTransformSrcset.ts => template/transformSrcset.ts} (98%) diff --git a/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts b/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts index 0b0f138b8a8..f267e73ede0 100644 --- a/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts +++ b/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts @@ -9,7 +9,7 @@ import { createAssetUrlTransformWithOptions, AssetURLOptions, normalizeOptions -} from '../src/templateTransformAssetUrl' +} from '../src/template/transformAssetUrl' import { transformElement } from '../../compiler-core/src/transforms/transformElement' import { transformBind } from '../../compiler-core/src/transforms/vBind' import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStatic' diff --git a/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts b/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts index 8c21dd41656..174e3ca9f79 100644 --- a/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts +++ b/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts @@ -7,13 +7,13 @@ import { import { transformSrcset, createSrcsetTransformWithOptions -} from '../src/templateTransformSrcset' +} from '../src/template/transformSrcset' import { transformElement } from '../../compiler-core/src/transforms/transformElement' import { transformBind } from '../../compiler-core/src/transforms/vBind' import { AssetURLOptions, normalizeOptions -} from '../src/templateTransformAssetUrl' +} from '../src/template/transformAssetUrl' import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStatic' function compileWithSrcset( diff --git a/packages/compiler-sfc/__tests__/templateUtils.spec.ts b/packages/compiler-sfc/__tests__/templateUtils.spec.ts index a509657332a..7e20603848c 100644 --- a/packages/compiler-sfc/__tests__/templateUtils.spec.ts +++ b/packages/compiler-sfc/__tests__/templateUtils.spec.ts @@ -2,7 +2,7 @@ import { isRelativeUrl, isExternalUrl, isDataUrl -} from '../../compiler-sfc/src/templateUtils' +} from '../src/template/templateUtils' describe('compiler sfc:templateUtils isRelativeUrl', () => { test('should return true when The first character of the string path is .', () => { diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 1f1385b25fd..4902bea7a2c 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -53,13 +53,13 @@ import { CSS_VARS_HELPER, genCssVarsCode, genNormalScriptCssVarsCode -} from './cssVars' +} from './style/cssVars' import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate' import { warnOnce } from './warn' import { rewriteDefaultAST } from './rewriteDefault' import { createCache } from './cache' import { shouldTransform, transformAST } from '@vue/reactivity-transform' -import { transformDestructuredProps } from './compileScriptPropsDestructure' +import { transformDestructuredProps } from './script/propsDestructure' // Special compiler macros const DEFINE_PROPS = 'defineProps' diff --git a/packages/compiler-sfc/src/compileStyle.ts b/packages/compiler-sfc/src/compileStyle.ts index 8e02eaf6ca8..1885569635d 100644 --- a/packages/compiler-sfc/src/compileStyle.ts +++ b/packages/compiler-sfc/src/compileStyle.ts @@ -5,16 +5,16 @@ import postcss, { Message, LazyResult } from 'postcss' -import trimPlugin from './stylePluginTrim' -import scopedPlugin from './stylePluginScoped' +import trimPlugin from './style/pluginTrim' +import scopedPlugin from './style/pluginScoped' import { processors, StylePreprocessor, StylePreprocessorResults, PreprocessLang -} from './stylePreprocessors' +} from './style/preprocessors' import { RawSourceMap } from 'source-map' -import { cssVarsPlugin } from './cssVars' +import { cssVarsPlugin } from './style/cssVars' import postcssModules from 'postcss-modules' export interface SFCStyleCompileOptions { diff --git a/packages/compiler-sfc/src/compileTemplate.ts b/packages/compiler-sfc/src/compileTemplate.ts index 2654da105a7..9ada0e7557f 100644 --- a/packages/compiler-sfc/src/compileTemplate.ts +++ b/packages/compiler-sfc/src/compileTemplate.ts @@ -13,17 +13,17 @@ import { createAssetUrlTransformWithOptions, AssetURLTagConfig, normalizeOptions -} from './templateTransformAssetUrl' +} from './template/transformAssetUrl' import { transformSrcset, createSrcsetTransformWithOptions -} from './templateTransformSrcset' +} from './template/transformSrcset' import { generateCodeFrame, isObject } from '@vue/shared' import * as CompilerDOM from '@vue/compiler-dom' import * as CompilerSSR from '@vue/compiler-ssr' import consolidate from '@vue/consolidate' import { warnOnce } from './warn' -import { genCssVarsFromList } from './cssVars' +import { genCssVarsFromList } from './style/cssVars' export interface TemplateCompiler { compile(template: string, options: CompilerOptions): CodegenResult diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index c73276d9c91..6ba097b2466 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -52,7 +52,7 @@ export type { SFCScriptCompileOptions } from './compileScript' export type { AssetURLOptions, AssetURLTagConfig -} from './templateTransformAssetUrl' +} from './template/transformAssetUrl' export type { CompilerOptions, CompilerError, diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index 79065fc667e..d2b98756c49 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -9,7 +9,7 @@ import { import * as CompilerDOM from '@vue/compiler-dom' import { RawSourceMap, SourceMapGenerator } from 'source-map' import { TemplateCompiler } from './compileTemplate' -import { parseCssVars } from './cssVars' +import { parseCssVars } from './style/cssVars' import { createCache } from './cache' import { hmrShouldReload, ImportBinding } from './compileScript' diff --git a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts b/packages/compiler-sfc/src/script/propsDestructure.ts similarity index 99% rename from packages/compiler-sfc/src/compileScriptPropsDestructure.ts rename to packages/compiler-sfc/src/script/propsDestructure.ts index d0addf6fcbc..cd1fe36a2f9 100644 --- a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts +++ b/packages/compiler-sfc/src/script/propsDestructure.ts @@ -18,7 +18,7 @@ import { unwrapTSNode } from '@vue/compiler-core' import { genPropsAccessExp } from '@vue/shared' -import { PropsDestructureBindings } from './compileScript' +import { PropsDestructureBindings } from '../compileScript' /** * true -> prop binding diff --git a/packages/compiler-sfc/src/cssVars.ts b/packages/compiler-sfc/src/style/cssVars.ts similarity index 99% rename from packages/compiler-sfc/src/cssVars.ts rename to packages/compiler-sfc/src/style/cssVars.ts index 411e48cb76a..f232d09695d 100644 --- a/packages/compiler-sfc/src/cssVars.ts +++ b/packages/compiler-sfc/src/style/cssVars.ts @@ -7,7 +7,7 @@ import { SimpleExpressionNode, BindingMetadata } from '@vue/compiler-dom' -import { SFCDescriptor } from './parse' +import { SFCDescriptor } from '../parse' import { PluginCreator } from 'postcss' import hash from 'hash-sum' diff --git a/packages/compiler-sfc/src/stylePluginScoped.ts b/packages/compiler-sfc/src/style/pluginScoped.ts similarity index 99% rename from packages/compiler-sfc/src/stylePluginScoped.ts rename to packages/compiler-sfc/src/style/pluginScoped.ts index c4576009495..1dcc248ad71 100644 --- a/packages/compiler-sfc/src/stylePluginScoped.ts +++ b/packages/compiler-sfc/src/style/pluginScoped.ts @@ -1,6 +1,6 @@ import { PluginCreator, Rule, AtRule } from 'postcss' import selectorParser from 'postcss-selector-parser' -import { warn } from './warn' +import { warn } from '../warn' const animationNameRE = /^(-\w+-)?animation-name$/ const animationRE = /^(-\w+-)?animation$/ diff --git a/packages/compiler-sfc/src/stylePluginTrim.ts b/packages/compiler-sfc/src/style/pluginTrim.ts similarity index 100% rename from packages/compiler-sfc/src/stylePluginTrim.ts rename to packages/compiler-sfc/src/style/pluginTrim.ts diff --git a/packages/compiler-sfc/src/stylePreprocessors.ts b/packages/compiler-sfc/src/style/preprocessors.ts similarity index 98% rename from packages/compiler-sfc/src/stylePreprocessors.ts rename to packages/compiler-sfc/src/style/preprocessors.ts index 218ebfdb430..06122f2e6b3 100644 --- a/packages/compiler-sfc/src/stylePreprocessors.ts +++ b/packages/compiler-sfc/src/style/preprocessors.ts @@ -1,6 +1,6 @@ import merge from 'merge-source-map' import { RawSourceMap } from 'source-map' -import { SFCStyleCompileOptions } from './compileStyle' +import { SFCStyleCompileOptions } from '../compileStyle' import { isFunction } from '@vue/shared' export type StylePreprocessor = ( diff --git a/packages/compiler-sfc/src/templateUtils.ts b/packages/compiler-sfc/src/template/templateUtils.ts similarity index 100% rename from packages/compiler-sfc/src/templateUtils.ts rename to packages/compiler-sfc/src/template/templateUtils.ts diff --git a/packages/compiler-sfc/src/templateTransformAssetUrl.ts b/packages/compiler-sfc/src/template/transformAssetUrl.ts similarity index 100% rename from packages/compiler-sfc/src/templateTransformAssetUrl.ts rename to packages/compiler-sfc/src/template/transformAssetUrl.ts diff --git a/packages/compiler-sfc/src/templateTransformSrcset.ts b/packages/compiler-sfc/src/template/transformSrcset.ts similarity index 98% rename from packages/compiler-sfc/src/templateTransformSrcset.ts rename to packages/compiler-sfc/src/template/transformSrcset.ts index 9780f93b5c5..18b9d0b0b3b 100644 --- a/packages/compiler-sfc/src/templateTransformSrcset.ts +++ b/packages/compiler-sfc/src/template/transformSrcset.ts @@ -14,10 +14,7 @@ import { isExternalUrl, isDataUrl } from './templateUtils' -import { - AssetURLOptions, - defaultAssetUrlOptions -} from './templateTransformAssetUrl' +import { AssetURLOptions, defaultAssetUrlOptions } from './transformAssetUrl' const srcsetTags = ['img', 'source'] From 91a931ae8707b8d43f10216e1ce8e18b12158f99 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 5 Apr 2023 17:18:11 +0800 Subject: [PATCH 0390/1409] fix(types): improve defineProps return type with generic arguments --- packages/runtime-core/src/apiSetupHelpers.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts index 1927d13bbd4..1c60416c6ea 100644 --- a/packages/runtime-core/src/apiSetupHelpers.ts +++ b/packages/runtime-core/src/apiSetupHelpers.ts @@ -76,7 +76,7 @@ export function defineProps< PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions >(props: PP): Prettify>> // overload 3: typed-based declaration -export function defineProps(): ResolveProps +export function defineProps(): DefineProps // implementation export function defineProps() { if (__DEV__) { @@ -85,13 +85,9 @@ export function defineProps() { return null as any } -type ResolveProps> = Prettify< - Readonly< - T & { - [K in BooleanKeys]-?: boolean - } - > -> +type DefineProps = Readonly & { + readonly [K in BooleanKey]-?: boolean +} type BooleanKey = K extends any ? [T[K]] extends [boolean | undefined] From 955752951e1d31b90d817bd20830fe3f89018771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 5 Apr 2023 17:33:29 +0800 Subject: [PATCH 0391/1409] fix(compiler-sfc): skip empty `defineOptions` and support TypeScript type assertions (#8028) --- .../__snapshots__/compileScript.spec.ts.snap | 15 +++++- .../__tests__/compileScript.spec.ts | 54 +++++++++++++++++-- packages/compiler-sfc/src/compileScript.ts | 3 +- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index ee00977c131..818ea02e303 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -658,12 +658,25 @@ exports[`SFC compile - `) + + `) assertCode(content) // should remove defineOptions import and call expect(content).not.toMatch('defineOptions') @@ -218,6 +218,18 @@ defineOptions({ name: 'FooApp' }) ) }) + test('empty argument', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`export default {`) + // should remove defineOptions import and call + expect(content).not.toMatch('defineOptions') + }) + it('should emit an error with two defineProps', () => { expect(() => compile(` @@ -249,6 +261,26 @@ defineOptions({ name: 'FooApp' }) ).toThrowError( '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.' ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.' + ) }) it('should emit an error with type generic', () => { @@ -262,6 +294,18 @@ defineOptions({ name: 'FooApp' }) '[@vue/compiler-sfc] defineOptions() cannot accept type arguments' ) }) + + it('should emit an error with type assertion', () => { + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.' + ) + }) }) test('defineExpose()', () => { diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 4902bea7a2c..8d22d7e1348 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -702,9 +702,10 @@ export function compileScript( if (node.typeParameters) { error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node) } + if (!node.arguments[0]) return true hasDefineOptionsCall = true - optionsRuntimeDecl = node.arguments[0] + optionsRuntimeDecl = unwrapTSNode(node.arguments[0]) let propsOption = undefined let emitsOption = undefined From b117b8844881a732a021432066230ff2215049ea Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 5 Apr 2023 20:39:43 +0800 Subject: [PATCH 0392/1409] Revert "chore: remove unused args passed to ssrRender" This reverts commit 2a9e379655a1ed1cae5615f29498a9f0aa7aa9c6. --- packages/server-renderer/src/render.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index d3ebf1fcf30..a1f327b4320 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -182,7 +182,17 @@ function renderComponentSubTree( // set current rendering instance for asset resolution const prev = setCurrentRenderingInstance(instance) try { - ssrRender(instance.proxy, push, instance, attrs) + ssrRender( + instance.proxy, + push, + instance, + attrs, + // compiler-optimized bindings + instance.props, + instance.setupState, + instance.data, + instance.ctx + ) } finally { setCurrentRenderingInstance(prev) } From bdf557f6f233c039fff8007b1b16aec00c4e68aa Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 5 Apr 2023 22:30:50 +0800 Subject: [PATCH 0393/1409] fix(types): retain type parameters order for public types --- packages/dts-test/defineComponent.test-d.tsx | 2 +- .../runtime-core/src/apiDefineComponent.ts | 70 ++++++++++++++----- packages/runtime-core/src/componentOptions.ts | 6 +- .../src/componentPublicInstance.ts | 8 +-- packages/runtime-dom/src/apiCustomElement.ts | 24 +++---- 5 files changed, 72 insertions(+), 38 deletions(-) diff --git a/packages/dts-test/defineComponent.test-d.tsx b/packages/dts-test/defineComponent.test-d.tsx index 32912dd9a2a..edd8d17eb06 100644 --- a/packages/dts-test/defineComponent.test-d.tsx +++ b/packages/dts-test/defineComponent.test-d.tsx @@ -1494,9 +1494,9 @@ declare const MyButton: DefineComponent< ComponentOptionsMixin, EmitsOptions, string, - {}, VNodeProps & AllowedComponentProps & ComponentCustomProps, Readonly>, + {}, {} > ; diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 0adc9d29387..5e8e7a85735 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -34,6 +34,13 @@ export type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps +type ResolveProps = Readonly< + PropsOrPropOptions extends ComponentPropsOptions + ? ExtractPropTypes + : PropsOrPropOptions +> & + ({} extends E ? {} : EmitsToProps) + export type DefineComponent< PropsOrPropOptions = {}, RawBindings = {}, @@ -44,15 +51,10 @@ export type DefineComponent< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = {}, EE extends string = string, - S extends SlotsType = {}, PP = PublicProps, - Props = Readonly< - PropsOrPropOptions extends ComponentPropsOptions - ? ExtractPropTypes - : PropsOrPropOptions - > & - ({} extends E ? {} : EmitsToProps), - Defaults = ExtractDefaultPropTypes + Props = ResolveProps, + Defaults = ExtractDefaultPropTypes, + S extends SlotsType = {} > = ComponentPublicInstanceConstructor< CreateComponentPublicInstance< Props, @@ -152,11 +154,25 @@ export function defineComponent< Extends, E, EE, - S, I, - II + II, + S > -): DefineComponent +): DefineComponent< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE, + PublicProps, + ResolveProps, + ExtractDefaultPropTypes, + S +> // overload 3: object format with array props declaration // props inferred as { [key in PropNames]?: any } @@ -173,7 +189,8 @@ export function defineComponent< EE extends string = string, S extends SlotsType = {}, I extends ComponentInjectOptions = {}, - II extends string = string + II extends string = string, + Props = Readonly<{ [key in PropNames]?: any }> >( options: ComponentOptionsWithArrayProps< PropNames, @@ -185,12 +202,12 @@ export function defineComponent< Extends, E, EE, - S, I, - II + II, + S > ): DefineComponent< - Readonly<{ [key in PropNames]?: any }>, + Props, RawBindings, D, C, @@ -199,6 +216,9 @@ export function defineComponent< Extends, E, EE, + PublicProps, + ResolveProps, + ExtractDefaultPropTypes, S > @@ -230,11 +250,25 @@ export function defineComponent< Extends, E, EE, - S, I, - II + II, + S > -): DefineComponent +): DefineComponent< + PropsOptions, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE, + PublicProps, + ResolveProps, + ExtractDefaultPropTypes, + S +> // implementation, close to no-op export function defineComponent( diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 04c3839dd1d..cf459962c44 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -219,9 +219,9 @@ export type ComponentOptionsWithoutProps< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, - S extends SlotsType = {}, I extends ComponentInjectOptions = {}, II extends string = string, + S extends SlotsType = {}, PE = Props & EmitsToProps > = ComponentOptionsBase< PE, @@ -267,9 +267,9 @@ export type ComponentOptionsWithArrayProps< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, - S extends SlotsType = {}, I extends ComponentInjectOptions = {}, II extends string = string, + S extends SlotsType = {}, Props = Prettify>> > = ComponentOptionsBase< Props, @@ -315,9 +315,9 @@ export type ComponentOptionsWithObjectProps< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, - S extends SlotsType = {}, I extends ComponentInjectOptions = {}, II extends string = string, + S extends SlotsType = {}, Props = Prettify & EmitsToProps>>, Defaults = ExtractDefaultPropTypes > = ComponentOptionsBase< diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index 9997296b641..7ffa3ab2cf9 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -164,12 +164,12 @@ export type CreateComponentPublicInstance< PublicC, PublicM, E, - S, PublicProps, PublicDefaults, MakeDefaultsOptional, ComponentOptionsBase, - I + I, + S > // public properties exposed on the proxy, which is used as the render context @@ -181,12 +181,12 @@ export type ComponentPublicInstance< C extends ComputedOptions = {}, M extends MethodOptions = {}, E extends EmitsOptions = {}, - S extends SlotsType = {}, PublicProps = P, Defaults = {}, MakeDefaultsOptional extends boolean = false, Options = ComponentOptionsBase, - I extends ComponentInjectOptions = {} + I extends ComponentInjectOptions = {}, + S extends SlotsType = {} > = { $: ComponentInternalInstance $data: D diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 58bc48f64d6..1e551cc05da 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -52,9 +52,9 @@ export function defineCustomElement< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, - S extends SlotsType = {}, I extends ComponentInjectOptions = {}, - II extends string = string + II extends string = string, + S extends SlotsType = {} >( options: ComponentOptionsWithoutProps< Props, @@ -66,9 +66,9 @@ export function defineCustomElement< Extends, E, EE, - S, I, - II + II, + S > & { styles?: string[] } ): VueElementConstructor @@ -83,9 +83,9 @@ export function defineCustomElement< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = Record, EE extends string = string, - S extends SlotsType = {}, I extends ComponentInjectOptions = {}, - II extends string = string + II extends string = string, + S extends SlotsType = {} >( options: ComponentOptionsWithArrayProps< PropNames, @@ -97,9 +97,9 @@ export function defineCustomElement< Extends, E, EE, - S, I, - II + II, + S > & { styles?: string[] } ): VueElementConstructor<{ [K in PropNames]: any }> @@ -114,9 +114,9 @@ export function defineCustomElement< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = Record, EE extends string = string, - S extends SlotsType = {}, I extends ComponentInjectOptions = {}, - II extends string = string + II extends string = string, + S extends SlotsType = {} >( options: ComponentOptionsWithObjectProps< PropsOptions, @@ -128,9 +128,9 @@ export function defineCustomElement< Extends, E, EE, - S, I, - II + II, + S > & { styles?: string[] } ): VueElementConstructor> From af563bf428200367b6f5bb7944f690c85d810202 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 6 Apr 2023 11:12:49 +0800 Subject: [PATCH 0394/1409] fix(types): more public type argument order fix --- .../runtime-core/src/apiDefineComponent.ts | 11 +++++--- packages/runtime-core/src/componentOptions.ts | 28 +++++++++---------- .../src/componentPublicInstance.ts | 22 +++++++++++++-- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 5e8e7a85735..722a3693e3f 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -65,10 +65,11 @@ export type DefineComponent< Mixin, Extends, E, - S, PP & Props, Defaults, - true + true, + {}, + S > & Props > & @@ -82,8 +83,10 @@ export type DefineComponent< Extends, E, EE, - S, - Defaults + Defaults, + {}, + string, + S > & PP diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index cf459962c44..481c2adb67e 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -106,10 +106,10 @@ export interface ComponentOptionsBase< Extends extends ComponentOptionsMixin, E extends EmitsOptions, EE extends string = string, - S extends SlotsType = {}, Defaults = {}, I extends ComponentInjectOptions = {}, - II extends string = string + II extends string = string, + S extends SlotsType = {} > extends LegacyOptions, ComponentInternalOptions, ComponentCustomOptions { @@ -233,10 +233,10 @@ export type ComponentOptionsWithoutProps< Extends, E, EE, - S, {}, I, - II + II, + S > & { props?: undefined } & ThisType< @@ -249,11 +249,11 @@ export type ComponentOptionsWithoutProps< Mixin, Extends, E, - S, PE, {}, false, - I + I, + S > > @@ -281,10 +281,10 @@ export type ComponentOptionsWithArrayProps< Extends, E, EE, - S, {}, I, - II + II, + S > & { props: PropNames[] } & ThisType< @@ -297,11 +297,11 @@ export type ComponentOptionsWithArrayProps< Mixin, Extends, E, - S, Props, {}, false, - I + I, + S > > @@ -330,10 +330,10 @@ export type ComponentOptionsWithObjectProps< Extends, E, EE, - S, Defaults, I, - II + II, + S > & { props: PropsOptions & ThisType } & ThisType< @@ -346,11 +346,11 @@ export type ComponentOptionsWithObjectProps< Mixin, Extends, E, - S, Props, Defaults, false, - I + I, + S > > diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index 7ffa3ab2cf9..7b0ccf77ac9 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -89,8 +89,10 @@ type MixinToOptionTypes = T extends ComponentOptionsBase< infer Extends, any, any, + infer Defaults, any, - infer Defaults + any, + any > ? OptionTypesType

& IntersectionMixin & @@ -142,11 +144,11 @@ export type CreateComponentPublicInstance< Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = {}, - S extends SlotsType = {}, PublicProps = P, Defaults = {}, MakeDefaultsOptional extends boolean = false, I extends ComponentInjectOptions = {}, + S extends SlotsType = {}, PublicMixin = IntersectionMixin & IntersectionMixin, PublicP = UnwrapMixinsType & EnsureNonVoid

, PublicB = UnwrapMixinsType & EnsureNonVoid, @@ -167,7 +169,21 @@ export type CreateComponentPublicInstance< PublicProps, PublicDefaults, MakeDefaultsOptional, - ComponentOptionsBase, + ComponentOptionsBase< + P, + B, + D, + C, + M, + Mixin, + Extends, + E, + string, + Defaults, + {}, + string, + S + >, I, S > From 593da4069a0ac913371641e913bd8d64fbc5492f Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 6 Apr 2023 12:42:07 +0800 Subject: [PATCH 0395/1409] chore: update playground url --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/bug-repro-guidelines.md | 2 +- .github/contributing.md | 2 +- packages/sfc-playground/README.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 11853cec30c..95e0ca79c07 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -28,7 +28,7 @@ body: attributes: label: Link to minimal reproduction description: | - The easiest way to provide a reproduction is by showing the bug in [The SFC Playground](https://sfc.vuejs.org/). + The easiest way to provide a reproduction is by showing the bug in [The SFC Playground](https://play.vuejs.org/). If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue). If neither of these are suitable, you can always provide a GitHub repository. diff --git a/.github/bug-repro-guidelines.md b/.github/bug-repro-guidelines.md index 19e9a7e2f26..90458b30741 100644 --- a/.github/bug-repro-guidelines.md +++ b/.github/bug-repro-guidelines.md @@ -22,7 +22,7 @@ A minimal reproduction means it demonstrates the bug, and the bug only. It shoul ### How to create a repro -For Vue 3 core reproductions, try reproducing it in [The SFC Playground](https://sfc.vuejs.org/). +For Vue 3 core reproductions, try reproducing it in [The SFC Playground](https://play.vuejs.org/). If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue). diff --git a/.github/contributing.md b/.github/contributing.md index bb2a916c76a..c535aa7f4e6 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -244,7 +244,7 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set - `dts-test`: Contains type-only tests against generated dts files. - - `sfc-playground`: The playground continuously deployed at https://sfc.vuejs.org. To run the playground locally, use [`nr dev-sfc`](#nr-dev-sfc). + - `sfc-playground`: The playground continuously deployed at https://play.vuejs.org. To run the playground locally, use [`nr dev-sfc`](#nr-dev-sfc). - `template-explorer`: A development tool for debugging compiler output, continuously deployed at https://template-explorer.vuejs.org/. To run it locally, run [`nr dev-compiler`](#nr-dev-compiler). diff --git a/packages/sfc-playground/README.md b/packages/sfc-playground/README.md index df14d842233..8abaa81bd2a 100644 --- a/packages/sfc-playground/README.md +++ b/packages/sfc-playground/README.md @@ -1,6 +1,6 @@ # SFC Playground -This is continuously deployed at [https://sfc.vuejs.org](https://sfc.vuejs.org). +This is continuously deployed at [https://play.vuejs.org](https://play.vuejs.org). ## Run Locally in Dev From 4c022ccb01727378f89d28b27da237b8a0bd894e Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 6 Apr 2023 12:46:19 +0800 Subject: [PATCH 0396/1409] chore: enable cors for sfc as temporary fix --- packages/sfc-playground/public/_headers | 3 --- packages/sfc-playground/vercel.json | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 packages/sfc-playground/public/_headers create mode 100644 packages/sfc-playground/vercel.json diff --git a/packages/sfc-playground/public/_headers b/packages/sfc-playground/public/_headers deleted file mode 100644 index 9079d85b36c..00000000000 --- a/packages/sfc-playground/public/_headers +++ /dev/null @@ -1,3 +0,0 @@ -/assets/* - cache-control: max-age=31536000 - cache-control: immutable diff --git a/packages/sfc-playground/vercel.json b/packages/sfc-playground/vercel.json new file mode 100644 index 00000000000..b33ffef7315 --- /dev/null +++ b/packages/sfc-playground/vercel.json @@ -0,0 +1,8 @@ +{ + "headers": [ + { + "source": "/(.*).js", + "headers": [{ "key": "Access-Control-Allow-Origin", "value": "*" }] + } + ] +} From 6e540d6ac73924b6ea51adb605325e112eaf7a29 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 6 Apr 2023 12:46:19 +0800 Subject: [PATCH 0397/1409] chore: enable cors for sfc as temporary fix --- packages/sfc-playground/package.json | 2 +- packages/sfc-playground/vercel.json | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index ec5724190be..214e81cce27 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -12,7 +12,7 @@ "vite": "^4.2.0" }, "dependencies": { - "@vue/repl": "^1.3.0", + "@vue/repl": "^1.3.4", "file-saver": "^2.0.5", "jszip": "^3.6.0", "vue": "workspace:*" diff --git a/packages/sfc-playground/vercel.json b/packages/sfc-playground/vercel.json index b33ffef7315..5ef7ae0673f 100644 --- a/packages/sfc-playground/vercel.json +++ b/packages/sfc-playground/vercel.json @@ -1,8 +1,13 @@ { "headers": [ { - "source": "/(.*).js", - "headers": [{ "key": "Access-Control-Allow-Origin", "value": "*" }] + "source": "/assets/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "max-age=31536000, immutable" + } + ] } ] } From 5531ff4eb05d2eb56b3606643b371018b1127b37 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 6 Apr 2023 13:02:05 +0800 Subject: [PATCH 0398/1409] chore: bump repl to handle legacy playground import maps --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b889e640e6..5206450c2af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,13 +244,13 @@ importers: packages/sfc-playground: specifiers: '@vitejs/plugin-vue': ^4.1.0 - '@vue/repl': ^1.3.0 + '@vue/repl': ^1.3.4 file-saver: ^2.0.5 jszip: ^3.6.0 vite: ^4.2.0 vue: workspace:* dependencies: - '@vue/repl': 1.3.2_vue@packages+vue + '@vue/repl': 1.3.4_vue@packages+vue file-saver: 2.0.5 jszip: 3.10.1 vue: link:../vue @@ -1195,8 +1195,8 @@ packages: engines: {node: '>= 0.12.0'} dev: true - /@vue/repl/1.3.2_vue@packages+vue: - resolution: {integrity: sha512-5joGOuTFmjaugG3E1h/oP1EXSMcVXRUwLIoo8xvYQnqDrCT6g1SfsH1pfei5PpC5DUxMX1584CekZu6REgGYkQ==} + /@vue/repl/1.3.4_vue@packages+vue: + resolution: {integrity: sha512-jGjizCBG0onn3Xl3lKqKTvVE/RMza/QGLuCumDEx4kNZ7BEpWJNpXN9qaj4d/2/vr/+U0TaNl/YUwJuCDpLB6Q==} peerDependencies: vue: ^3.2.13 dependencies: From 3ccbea08e09217b50a410d7b49ebb138e0c4c1e7 Mon Sep 17 00:00:00 2001 From: -isum <17521736+equt@users.noreply.github.com> Date: Thu, 6 Apr 2023 17:13:34 +0800 Subject: [PATCH 0399/1409] fix(compiler-sfc): accept `StringLiteral` node in `defineEmit` tuple syntax (#8041) close #8040 --- .../__snapshots__/compileScript.spec.ts.snap | 16 ++++++++++++++++ .../compiler-sfc/__tests__/compileScript.spec.ts | 11 +++++++++++ packages/compiler-sfc/src/compileScript.ts | 10 +++++++--- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 818ea02e303..23a3741afb2 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1457,6 +1457,22 @@ export default /*#__PURE__*/_defineComponent({ +return { emit } +} + +})" +`; + +exports[`SFC compile + `) + expect(content).toMatch(`emits: ["foo:bar"]`) + assertCode(content) + }) + describe('defineSlots()', () => { test('basic usage', () => { const { content } = compile(` diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 8d22d7e1348..31114c52d8c 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -2316,11 +2316,15 @@ function extractRuntimeEmits( hasCallSignature = true } if (t.type === 'TSPropertySignature') { - if (t.key.type !== 'Identifier' || t.computed) { + if (t.key.type === 'Identifier' && !t.computed) { + emits.add(t.key.name) + hasProperty = true + } else if (t.key.type === 'StringLiteral' && !t.computed) { + emits.add(t.key.value) + hasProperty = true + } else { error(`defineEmits() type cannot use computed keys.`, t.key) } - emits.add(t.key.name) - hasProperty = true } } if (hasCallSignature && hasProperty) { From cbe3d42ed3fe961d01139255a992cf2d9b182592 Mon Sep 17 00:00:00 2001 From: Cloyd Lau <31238760+cloydlau@users.noreply.github.com> Date: Thu, 6 Apr 2023 17:14:16 +0800 Subject: [PATCH 0400/1409] chore: fix wrong placeholder in issue template (#7918) [ci skip] --- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9165eb4d291..6861fc26d86 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -34,6 +34,6 @@ body: label: What does the proposed API look like? description: | Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format your code blocks. - placeholder: Steps to reproduce + placeholder: Assumed API validations: required: true From cac15123903dc40dc9c45d2b3346b20cb5c785fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Thu, 6 Apr 2023 17:15:27 +0800 Subject: [PATCH 0401/1409] chore(reactive): remove unref type assertion (#8007) --- packages/reactivity/src/ref.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 5dd31a9f8ca..b04c393669b 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -209,7 +209,7 @@ export type MaybeRefOrGetter = MaybeRef | (() => T) * @see {@link https://vuejs.org/api/reactivity-utilities.html#unref} */ export function unref(ref: MaybeRef): T { - return isRef(ref) ? (ref.value as any) : ref + return isRef(ref) ? ref.value : ref } /** From f7f4624191bbdc09600dbb0eb048b947c3a4f761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 6 Apr 2023 17:19:00 +0800 Subject: [PATCH 0402/1409] fix(compiler-sfc): fix binding type for constants when hoistStatic is disabled (#8029) --- .../compileScriptHoistStatic.spec.ts.snap | 12 +++++++ .../__tests__/compileScript.spec.ts | 2 +- .../compileScriptHoistStatic.spec.ts | 17 ++++++++- packages/compiler-sfc/src/compileScript.ts | 35 +++++++++++++++---- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptHoistStatic.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptHoistStatic.spec.ts.snap index ed5301432e8..3b94955f2db 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptHoistStatic.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptHoistStatic.spec.ts.snap @@ -130,3 +130,15 @@ return () => {} }" `; + +exports[`sfc hoist static > should not hoist when disabled 1`] = ` +"export default { + setup(__props) { + + const foo = 'bar' + +return () => {} +} + +}" +`; diff --git a/packages/compiler-sfc/__tests__/compileScript.spec.ts b/packages/compiler-sfc/__tests__/compileScript.spec.ts index 1d8d20ec1a0..2da79cf8312 100644 --- a/packages/compiler-sfc/__tests__/compileScript.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript.spec.ts @@ -39,7 +39,7 @@ describe('SFC compile `) expect(bindings).toStrictEqual({ - foo: BindingTypes.LITERAL_CONST + foo: BindingTypes.SETUP_CONST + }) + assertCode(content) + }) + + test('should not hoist when disabled', () => { + const { content, bindings } = compile( + ` + + `, + { hoistStatic: false } + ) + expect(bindings).toStrictEqual({ + foo: BindingTypes.SETUP_CONST }) assertCode(content) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 31114c52d8c..b00c17799c0 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -803,7 +803,7 @@ export function compileScript( if (!node) return walkIdentifiers(node, id => { const binding = setupBindings[id.name] - if (binding && (binding !== BindingTypes.LITERAL_CONST || !hoistStatic)) { + if (binding && binding !== BindingTypes.LITERAL_CONST) { error( `\`${method}()\` in + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead' + ) + }) }) test('defineExpose()', () => { @@ -323,6 +365,109 @@ defineExpose({ foo: 123 }) expect(content).toMatch(/\b__expose\(\{ foo: 123 \}\)/) }) + describe('defineModel()', () => { + test('basic usage', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch('props: {') + expect(content).toMatch('"modelValue": { required: true },') + expect(content).toMatch('"count": {},') + expect(content).toMatch('emits: ["update:modelValue", "update:count"],') + expect(content).toMatch( + `const modelValue = _useModel(__props, "modelValue")` + ) + expect(content).toMatch(`const c = _useModel(__props, "count")`) + expect(content).toMatch(`return { modelValue, c }`) + expect(content).not.toMatch('defineModel') + + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + count: BindingTypes.PROPS, + c: BindingTypes.SETUP_REF + }) + }) + + test('w/ defineProps and defineEmits', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch(`props: _mergeModels({ foo: String }`) + expect(content).toMatch(`"modelValue": { default: 0 }`) + expect(content).toMatch(`const count = _useModel(__props, "modelValue")`) + expect(content).not.toMatch('defineModel') + expect(bindings).toStrictEqual({ + count: BindingTypes.SETUP_REF, + foo: BindingTypes.PROPS, + modelValue: BindingTypes.PROPS + }) + }) + + test('w/ array props', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch(`props: _mergeModels(['foo', 'bar'], { + "count": {}, + })`) + expect(content).toMatch(`const count = _useModel(__props, "count")`) + expect(content).not.toMatch('defineModel') + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.PROPS, + count: BindingTypes.SETUP_REF + }) + }) + + test('w/ local flag', () => { + const { content } = compile( + ``, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch( + `_useModel(__props, "modelValue", { local: true })` + ) + expect(content).toMatch(`_useModel(__props, "bar", { [key]: true })`) + expect(content).toMatch(`_useModel(__props, "baz", { ...x })`) + expect(content).toMatch(`_useModel(__props, "qux", x)`) + expect(content).toMatch(`_useModel(__props, "foo2", { local: true })`) + expect(content).toMatch(`_useModel(__props, "hoist", { local })`) + }) + }) + test(' + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch('"modelValue": { type: [Boolean, String] }') + expect(content).toMatch('"count": { type: Number }') + expect(content).toMatch( + '"disabled": { type: Number, ...{ required: false } }' + ) + expect(content).toMatch('"any": { type: Boolean, skipCheck: true }') + expect(content).toMatch( + 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]' + ) + + expect(content).toMatch( + `const modelValue = _useModel(__props, "modelValue")` + ) + expect(content).toMatch(`const count = _useModel(__props, "count")`) + expect(content).toMatch( + `const disabled = _useModel(__props, "disabled")` + ) + expect(content).toMatch(`const any = _useModel(__props, "any")`) + + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + count: BindingTypes.SETUP_REF, + disabled: BindingTypes.SETUP_REF, + any: BindingTypes.SETUP_REF + }) + }) + + test('w/ production mode', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true, isProd: true } + ) + assertCode(content) + expect(content).toMatch('"modelValue": { type: Boolean }') + expect(content).toMatch('"fn": {}') + expect(content).toMatch( + '"fnWithDefault": { type: Function, ...{ default: () => null } },' + ) + expect(content).toMatch('"str": {}') + expect(content).toMatch('"optional": { required: false }') + expect(content).toMatch( + 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]' + ) + expect(content).toMatch( + `const modelValue = _useModel(__props, "modelValue")` + ) + expect(content).toMatch(`const fn = _useModel(__props, "fn")`) + expect(content).toMatch(`const str = _useModel(__props, "str")`) + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + fn: BindingTypes.SETUP_REF, + fnWithDefault: BindingTypes.SETUP_REF, + str: BindingTypes.SETUP_REF, + optional: BindingTypes.SETUP_REF + }) + }) + }) + test('runtime Enum', () => { const { content, bindings } = compile( ` - `) - // should generate working code - assertCode(content) - // should analyze bindings - expect(bindings).toStrictEqual({ - foo: BindingTypes.PROPS, - bar: BindingTypes.LITERAL_CONST, - props: BindingTypes.SETUP_REACTIVE_CONST - }) - - // should remove defineOptions import and call - expect(content).not.toMatch('defineProps') - // should generate correct setup signature - expect(content).toMatch(`setup(__props, { expose: __expose }) {`) - // should assign user identifier to it - expect(content).toMatch(`const props = __props`) - // should include context options in default export - expect(content).toMatch(`export default { - props: { - foo: String -},`) - }) - - test('defineProps w/ external definition', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`export default { - props: propsModel,`) - }) - - // #4764 - test('defineProps w/ leading code', () => { - const { content } = compile(` - - `) - // props declaration should be inside setup, not moved along with the import - expect(content).not.toMatch(`const props = __props\nimport`) - assertCode(content) - }) - - test('defineEmits()', () => { - const { content, bindings } = compile(` - - `) - assertCode(content) - expect(bindings).toStrictEqual({ - myEmit: BindingTypes.SETUP_CONST - }) - // should remove defineEmits import and call - expect(content).not.toMatch('defineEmits') - // should generate correct setup signature - expect(content).toMatch( - `setup(__props, { expose: __expose, emit: myEmit }) {` - ) - // should include context options in default export - expect(content).toMatch(`export default { - emits: ['foo', 'bar'],`) - }) - test('defineProps/defineEmits in multi-variable declaration', () => { const { content } = compile(` - `) - assertCode(content) - // should remove defineOptions import and call - expect(content).not.toMatch('defineOptions') - // should include context options in default export - expect(content).toMatch( - `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, ` - ) - }) - - test('empty argument', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`export default {`) - // should remove defineOptions import and call - expect(content).not.toMatch('defineOptions') - }) - - it('should emit an error with two defineProps', () => { - expect(() => - compile(` - - `) - ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call') - }) - - it('should emit an error with props or emits property', () => { - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.' - ) - - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.' - ) - - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.' - ) - - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.' - ) - }) - - it('should emit an error with type generic', () => { - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot accept type arguments' - ) - }) - - it('should emit an error with type assertion', () => { - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.' - ) - }) - - it('should emit an error with declaring props/emits/slots/expose', () => { - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead' - ) - - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead' - ) - - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead' - ) - - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead' - ) - }) - }) - - test('defineExpose()', () => { - const { content } = compile(` - - `) - assertCode(content) - // should remove defineOptions import and call - expect(content).not.toMatch('defineExpose') - // should generate correct setup signature - expect(content).toMatch(`setup(__props, { expose: __expose }) {`) - // should replace callee - expect(content).toMatch(/\b__expose\(\{ foo: 123 \}\)/) - }) - - describe('defineModel()', () => { - test('basic usage', () => { - const { content, bindings } = compile( - ` - - `, - { defineModel: true } - ) - assertCode(content) - expect(content).toMatch('props: {') - expect(content).toMatch('"modelValue": { required: true },') - expect(content).toMatch('"count": {},') - expect(content).toMatch('emits: ["update:modelValue", "update:count"],') - expect(content).toMatch( - `const modelValue = _useModel(__props, "modelValue")` - ) - expect(content).toMatch(`const c = _useModel(__props, "count")`) - expect(content).toMatch(`return { modelValue, c }`) - expect(content).not.toMatch('defineModel') - - expect(bindings).toStrictEqual({ - modelValue: BindingTypes.SETUP_REF, - count: BindingTypes.PROPS, - c: BindingTypes.SETUP_REF - }) - }) - - test('w/ defineProps and defineEmits', () => { - const { content, bindings } = compile( - ` - - `, - { defineModel: true } - ) - assertCode(content) - expect(content).toMatch(`props: _mergeModels({ foo: String }`) - expect(content).toMatch(`"modelValue": { default: 0 }`) - expect(content).toMatch(`const count = _useModel(__props, "modelValue")`) - expect(content).not.toMatch('defineModel') - expect(bindings).toStrictEqual({ - count: BindingTypes.SETUP_REF, - foo: BindingTypes.PROPS, - modelValue: BindingTypes.PROPS - }) - }) - - test('w/ array props', () => { - const { content, bindings } = compile( - ` - - `, - { defineModel: true } - ) - assertCode(content) - expect(content).toMatch(`props: _mergeModels(['foo', 'bar'], { - "count": {}, - })`) - expect(content).toMatch(`const count = _useModel(__props, "count")`) - expect(content).not.toMatch('defineModel') - expect(bindings).toStrictEqual({ - foo: BindingTypes.PROPS, - bar: BindingTypes.PROPS, - count: BindingTypes.SETUP_REF - }) - }) - - test('w/ local flag', () => { - const { content } = compile( - ``, - { defineModel: true } - ) - assertCode(content) - expect(content).toMatch( - `_useModel(__props, "modelValue", { local: true })` - ) - expect(content).toMatch(`_useModel(__props, "bar", { [key]: true })`) - expect(content).toMatch(`_useModel(__props, "baz", { ...x })`) - expect(content).toMatch(`_useModel(__props, "qux", x)`) - expect(content).toMatch(`_useModel(__props, "foo2", { local: true })`) - expect(content).toMatch(`_useModel(__props, "hoist", { local })`) - }) - }) - - test(' - - `) - assertCode(content) - }) - describe(' - `) - assertCode(content) - expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({ - props: { foo: String }, - emits: ['a', 'b'], - setup(__props, { expose: __expose, emit }) {`) - }) - - test('defineProps w/ type', () => { - const { content, bindings } = compile(` - `) - assertCode(content) - expect(content).toMatch(`string: { type: String, required: true }`) - expect(content).toMatch(`number: { type: Number, required: true }`) - expect(content).toMatch(`boolean: { type: Boolean, required: true }`) - expect(content).toMatch(`object: { type: Object, required: true }`) - expect(content).toMatch(`objectLiteral: { type: Object, required: true }`) - expect(content).toMatch(`fn: { type: Function, required: true }`) - expect(content).toMatch(`functionRef: { type: Function, required: true }`) - expect(content).toMatch(`objectRef: { type: Object, required: true }`) - expect(content).toMatch(`dateTime: { type: Date, required: true }`) - expect(content).toMatch(`array: { type: Array, required: true }`) - expect(content).toMatch(`arrayRef: { type: Array, required: true }`) - expect(content).toMatch(`tuple: { type: Array, required: true }`) - expect(content).toMatch(`set: { type: Set, required: true }`) - expect(content).toMatch(`literal: { type: String, required: true }`) - expect(content).toMatch(`optional: { type: null, required: false }`) - expect(content).toMatch(`recordRef: { type: Object, required: true }`) - expect(content).toMatch(`interface: { type: Object, required: true }`) - expect(content).toMatch(`alias: { type: Array, required: true }`) - expect(content).toMatch(`method: { type: Function, required: true }`) - expect(content).toMatch(`symbol: { type: Symbol, required: true }`) - expect(content).toMatch( - `objectOrFn: { type: [Function, Object], required: true },` - ) - expect(content).toMatch(`extract: { type: Number, required: true }`) - expect(content).toMatch( - `exclude: { type: [Number, Boolean], required: true }` - ) - expect(content).toMatch(`uppercase: { type: String, required: true }`) - expect(content).toMatch(`params: { type: Array, required: true }`) - expect(content).toMatch(`nonNull: { type: String, required: true }`) - expect(content).toMatch( - `union: { type: [String, Number], required: true }` - ) - expect(content).toMatch(`literalUnion: { type: String, required: true }`) - expect(content).toMatch( - `literalUnionNumber: { type: Number, required: true }` - ) - expect(content).toMatch( - `literalUnionMixed: { type: [String, Number, Boolean], required: true }` - ) - expect(content).toMatch(`intersection: { type: Object, required: true }`) - expect(content).toMatch(`intersection2: { type: String, required: true }`) - expect(content).toMatch(`foo: { type: [Function, null], required: true }`) - expect(content).toMatch(`unknown: { type: null, required: true }`) - // uninon containing unknown type: skip check - expect(content).toMatch(`unknownUnion: { type: null, required: true }`) - // intersection containing unknown type: narrow to the known types - expect(content).toMatch( - `unknownIntersection: { type: Object, required: true },` - ) - expect(content).toMatch( - `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },` - ) - expect(content).toMatch( - `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }` - ) - expect(bindings).toStrictEqual({ - string: BindingTypes.PROPS, - number: BindingTypes.PROPS, - boolean: BindingTypes.PROPS, - object: BindingTypes.PROPS, - objectLiteral: BindingTypes.PROPS, - fn: BindingTypes.PROPS, - functionRef: BindingTypes.PROPS, - objectRef: BindingTypes.PROPS, - dateTime: BindingTypes.PROPS, - array: BindingTypes.PROPS, - arrayRef: BindingTypes.PROPS, - tuple: BindingTypes.PROPS, - set: BindingTypes.PROPS, - literal: BindingTypes.PROPS, - optional: BindingTypes.PROPS, - recordRef: BindingTypes.PROPS, - interface: BindingTypes.PROPS, - alias: BindingTypes.PROPS, - method: BindingTypes.PROPS, - symbol: BindingTypes.PROPS, - objectOrFn: BindingTypes.PROPS, - extract: BindingTypes.PROPS, - exclude: BindingTypes.PROPS, - union: BindingTypes.PROPS, - literalUnion: BindingTypes.PROPS, - literalUnionNumber: BindingTypes.PROPS, - literalUnionMixed: BindingTypes.PROPS, - intersection: BindingTypes.PROPS, - intersection2: BindingTypes.PROPS, - foo: BindingTypes.PROPS, - uppercase: BindingTypes.PROPS, - params: BindingTypes.PROPS, - nonNull: BindingTypes.PROPS, - unknown: BindingTypes.PROPS, - unknownUnion: BindingTypes.PROPS, - unknownIntersection: BindingTypes.PROPS, - unknownUnionWithBoolean: BindingTypes.PROPS, - unknownUnionWithFunction: BindingTypes.PROPS - }) - }) - - test('defineProps w/ interface', () => { - const { content, bindings } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`x: { type: Number, required: false }`) - expect(bindings).toStrictEqual({ - x: BindingTypes.PROPS - }) - }) - - test('defineProps w/ extends interface', () => { - const { content, bindings } = compile(` - - - `) - assertCode(content) - expect(content).toMatch(`z: { type: Number, required: true }`) - expect(content).toMatch(`y: { type: String, required: true }`) - expect(content).toMatch(`x: { type: Number, required: false }`) - expect(bindings).toStrictEqual({ - x: BindingTypes.PROPS, - y: BindingTypes.PROPS, - z: BindingTypes.PROPS - }) - }) - - test('defineProps w/ exported interface', () => { - const { content, bindings } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`x: { type: Number, required: false }`) - expect(bindings).toStrictEqual({ - x: BindingTypes.PROPS - }) - }) - - test('defineProps w/ exported interface in normal script', () => { - const { content, bindings } = compile(` - - - `) - assertCode(content) - expect(content).toMatch(`x: { type: Number, required: false }`) - expect(bindings).toStrictEqual({ - x: BindingTypes.PROPS - }) - }) - - test('defineProps w/ type alias', () => { - const { content, bindings } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`x: { type: Number, required: false }`) - expect(bindings).toStrictEqual({ - x: BindingTypes.PROPS - }) - }) - - test('defineProps w/ exported type alias', () => { - const { content, bindings } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`x: { type: Number, required: false }`) - expect(bindings).toStrictEqual({ - x: BindingTypes.PROPS - }) - }) - - test('defineProps w/ TS assertion', () => { - const { content, bindings } = compile(` - - `) - expect(content).toMatch(`props: ['foo']`) - assertCode(content) - expect(bindings).toStrictEqual({ - foo: BindingTypes.PROPS - }) - }) - - test('withDefaults (static)', () => { - const { content, bindings } = compile(` - - `) - assertCode(content) - expect(content).toMatch( - `foo: { type: String, required: false, default: 'hi' }` - ) - expect(content).toMatch(`bar: { type: Number, required: false }`) - expect(content).toMatch(`baz: { type: Boolean, required: true }`) - expect(content).toMatch( - `qux: { type: Function, required: false, default() { return 1 } }` - ) - expect(content).toMatch( - `quux: { type: Function, required: false, default() { } }` - ) - expect(content).toMatch( - `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }` - ) - expect(content).toMatch( - `fred: { type: String, required: false, get default() { return 'fred' } }` - ) - expect(content).toMatch(`const props = __props`) - expect(bindings).toStrictEqual({ - foo: BindingTypes.PROPS, - bar: BindingTypes.PROPS, - baz: BindingTypes.PROPS, - qux: BindingTypes.PROPS, - quux: BindingTypes.PROPS, - quuxx: BindingTypes.PROPS, - fred: BindingTypes.PROPS, - props: BindingTypes.SETUP_CONST - }) - }) - - test('withDefaults (static) + normal script', () => { - const { content } = compile(` - - - `) - assertCode(content) - }) - - // #7111 - test('withDefaults (static) w/ production mode', () => { - const { content } = compile( - ` - - `, - { isProd: true } - ) - assertCode(content) - expect(content).toMatch(`const props = __props`) - - // foo has no default value, the Function can be dropped - expect(content).toMatch(`foo: {}`) - expect(content).toMatch(`bar: { type: Boolean }`) - expect(content).toMatch( - `baz: { type: [Boolean, Function], default: true }` - ) - expect(content).toMatch(`qux: { default: 'hi' }`) - }) - - test('withDefaults (dynamic)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) - expect(content).toMatch( - ` - _mergeDefaults({ - foo: { type: String, required: false }, - bar: { type: Number, required: false }, - baz: { type: Boolean, required: true } - }, { ...defaults })`.trim() - ) - }) - - test('withDefaults (reference)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) - expect(content).toMatch( - ` - _mergeDefaults({ - foo: { type: String, required: false }, - bar: { type: Number, required: false }, - baz: { type: Boolean, required: true } - }, defaults)`.trim() - ) - }) - - // #7111 - test('withDefaults (dynamic) w/ production mode', () => { - const { content } = compile( - ` - - `, - { isProd: true } - ) - assertCode(content) - expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) - expect(content).toMatch( - ` - _mergeDefaults({ - foo: { type: Function }, - bar: { type: Boolean }, - baz: { type: [Boolean, Function] }, - qux: {} - }, { ...defaults })`.trim() - ) - }) - - test('withDefaults w/ dynamic object method', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) - expect(content).toMatch( - ` - _mergeDefaults({ - foo: { type: Function, required: false } - }, { - ['fo' + 'o']() { return 'foo' } - })`.trim() - ) - }) - - test('defineEmits w/ type', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar"]`) - }) - - test('defineEmits w/ type (union)', () => { - const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)` - expect(() => - compile(` - - `) - ).toThrow() - }) - - test('defineEmits w/ type (type literal w/ call signatures)', () => { - const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}` - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar", "baz"]`) - }) - - test('defineEmits w/ type (interface)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar"]`) - }) - - test('defineEmits w/ type (exported interface)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar"]`) - }) - - test('defineEmits w/ type from normal script', () => { - const { content } = compile(` - - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar"]`) - }) - - test('defineEmits w/ type (type alias)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar"]`) - }) - - test('defineEmits w/ type (exported type alias)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar"]`) - }) - - test('defineEmits w/ type (referenced function type)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar"]`) - }) - - test('defineEmits w/ type (referenced exported function type)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ["foo", "bar"]`) - }) - - // #5393 - test('defineEmits w/ type (interface ts type)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`emits: ['foo']`) - }) - - test('defineEmits w/ type (property syntax)', () => { - const { content } = compile(` - - `) - expect(content).toMatch(`emits: ["foo", "bar"]`) - assertCode(content) - }) - - // #8040 - test('defineEmits w/ type (property syntax string literal)', () => { - const { content } = compile(` - - `) - expect(content).toMatch(`emits: ["foo:bar"]`) - assertCode(content) - }) - - describe('defineSlots()', () => { - test('basic usage', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`const slots = _useSlots()`) - expect(content).not.toMatch('defineSlots') - }) - - test('w/o return value', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).not.toMatch('defineSlots') - expect(content).not.toMatch(`_useSlots`) - }) - - test('w/o generic params', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`const slots = _useSlots()`) - expect(content).not.toMatch('defineSlots') - }) - }) - - describe('defineModel()', () => { - test('basic usage', () => { - const { content, bindings } = compile( - ` - - `, - { defineModel: true } - ) - assertCode(content) - expect(content).toMatch('"modelValue": { type: [Boolean, String] }') - expect(content).toMatch('"count": { type: Number }') - expect(content).toMatch( - '"disabled": { type: Number, ...{ required: false } }' - ) - expect(content).toMatch('"any": { type: Boolean, skipCheck: true }') - expect(content).toMatch( - 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]' - ) - - expect(content).toMatch( - `const modelValue = _useModel(__props, "modelValue")` - ) - expect(content).toMatch(`const count = _useModel(__props, "count")`) - expect(content).toMatch( - `const disabled = _useModel(__props, "disabled")` - ) - expect(content).toMatch(`const any = _useModel(__props, "any")`) - - expect(bindings).toStrictEqual({ - modelValue: BindingTypes.SETUP_REF, - count: BindingTypes.SETUP_REF, - disabled: BindingTypes.SETUP_REF, - any: BindingTypes.SETUP_REF - }) - }) - - test('w/ production mode', () => { - const { content, bindings } = compile( - ` - - `, - { defineModel: true, isProd: true } - ) - assertCode(content) - expect(content).toMatch('"modelValue": { type: Boolean }') - expect(content).toMatch('"fn": {}') - expect(content).toMatch( - '"fnWithDefault": { type: Function, ...{ default: () => null } },' - ) - expect(content).toMatch('"str": {}') - expect(content).toMatch('"optional": { required: false }') - expect(content).toMatch( - 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]' - ) - expect(content).toMatch( - `const modelValue = _useModel(__props, "modelValue")` - ) - expect(content).toMatch(`const fn = _useModel(__props, "fn")`) - expect(content).toMatch(`const str = _useModel(__props, "str")`) - expect(bindings).toStrictEqual({ - modelValue: BindingTypes.SETUP_REF, - fn: BindingTypes.SETUP_REF, - fnWithDefault: BindingTypes.SETUP_REF, - str: BindingTypes.SETUP_REF, - optional: BindingTypes.SETUP_REF - }) - }) - }) - test('runtime Enum', () => { const { content, bindings } = compile( ``, - { hoistStatic: true } - ).content - ).toMatch(`foo: { type: Number`) - - expect( - compile( - ``, - { hoistStatic: true } - ).content - ).toMatch(`foo: { type: String`) - - expect( - compile( - ``, - { hoistStatic: true } - ).content - ).toMatch(`foo: { type: [String, Number]`) - - expect( - compile( - ``, - { hoistStatic: true } - ).content - ).toMatch(`foo: { type: Number`) - }) - test('import type', () => { const { content } = compile( ``) - }).toThrow(`cannot accept both type and non-type arguments`) - - expect(() => { - compile(``) - }).toThrow(`cannot accept both type and non-type arguments`) - }) - test('defineProps/Emit() referencing local var', () => { expect(() => compile(``).content ) }) - - test('mixed usage of property / call signature in defineEmits', () => { - expect(() => - compile(``) - ).toThrow( - `defineEmits() type cannot mixed call signature and property syntax.` - ) - }) }) }) diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap new file mode 100644 index 00000000000..5add78a28b3 --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap @@ -0,0 +1,232 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`defineEmits > basic usage 1`] = ` +"export default { + emits: ['foo', 'bar'], + setup(__props, { expose: __expose, emit: myEmit }) { + __expose(); + + + +return { myEmit } +} + +}" +`; + +exports[`defineEmits > w/ runtime options 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + emits: ['a', 'b'], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (exported interface) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +export interface Emits { (e: 'foo' | 'bar'): void } + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (exported type alias) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +export type Emits = { (e: 'foo' | 'bar'): void } + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (interface ts type) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +interface Emits { (e: 'foo'): void } + +export default /*#__PURE__*/_defineComponent({ + emits: ['foo'], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (interface) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +interface Emits { (e: 'foo' | 'bar'): void } + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (property syntax string literal) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo:bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (property syntax) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (referenced exported function type) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +export type Emits = (e: 'foo' | 'bar') => void + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (referenced function type) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +type Emits = (e: 'foo' | 'bar') => void + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (type alias) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +type Emits = { (e: 'foo' | 'bar'): void } + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (type literal w/ call signatures) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\", \\"baz\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type from normal script 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + + export interface Emits { (e: 'foo' | 'bar'): void } + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + +return { emit } +} + +})" +`; diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap new file mode 100644 index 00000000000..d72726460bf --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap @@ -0,0 +1,28 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` + `) + assertCode(content) + expect(bindings).toStrictEqual({ + myEmit: BindingTypes.SETUP_CONST + }) + // should remove defineEmits import and call + expect(content).not.toMatch('defineEmits') + // should generate correct setup signature + expect(content).toMatch( + `setup(__props, { expose: __expose, emit: myEmit }) {` + ) + // should include context options in default export + expect(content).toMatch(`export default { + emits: ['foo', 'bar'],`) + }) + + test('w/ runtime options', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({ + emits: ['a', 'b'], + setup(__props, { expose: __expose, emit }) {`) + }) + + test('w/ type', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar"]`) + }) + + test('w/ type (union)', () => { + const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)` + expect(() => + compile(` + + `) + ).toThrow() + }) + + test('w/ type (type literal w/ call signatures)', () => { + const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}` + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar", "baz"]`) + }) + + test('w/ type (interface)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar"]`) + }) + + test('w/ type (exported interface)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar"]`) + }) + + test('w/ type from normal script', () => { + const { content } = compile(` + + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar"]`) + }) + + test('w/ type (type alias)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar"]`) + }) + + test('w/ type (exported type alias)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar"]`) + }) + + test('w/ type (referenced function type)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar"]`) + }) + + test('w/ type (referenced exported function type)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar"]`) + }) + + // #5393 + test('w/ type (interface ts type)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ['foo']`) + }) + + test('w/ type (property syntax)', () => { + const { content } = compile(` + + `) + expect(content).toMatch(`emits: ["foo", "bar"]`) + assertCode(content) + }) + + // #8040 + test('w/ type (property syntax string literal)', () => { + const { content } = compile(` + + `) + expect(content).toMatch(`emits: ["foo:bar"]`) + assertCode(content) + }) + + describe('errors', () => { + test('w/ both type and non-type args', () => { + expect(() => { + compile(``) + }).toThrow(`cannot accept both type and non-type arguments`) + }) + + test('mixed usage of property / call signature', () => { + expect(() => + compile(``) + ).toThrow( + `defineEmits() type cannot mixed call signature and property syntax.` + ) + }) + }) +}) diff --git a/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts new file mode 100644 index 00000000000..8ddd28a89e6 --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts @@ -0,0 +1,26 @@ +import { compileSFCScript as compile, assertCode } from '../utils' + +test('defineExpose()', () => { + const { content } = compile(` + +`) + assertCode(content) + // should remove defineOptions import and call + expect(content).not.toMatch('defineExpose') + // should generate correct setup signature + expect(content).toMatch(`setup(__props, { expose: __expose }) {`) + // should replace callee + expect(content).toMatch(/\b__expose\(\{ foo: 123 \}\)/) +}) + +test(' + + `) + assertCode(content) +}) diff --git a/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts new file mode 100644 index 00000000000..61a9adcbe0d --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts @@ -0,0 +1,179 @@ +import { BindingTypes } from '@vue/compiler-core' +import { compileSFCScript as compile, assertCode } from '../utils' + +describe('defineModel()', () => { + test('basic usage', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch('props: {') + expect(content).toMatch('"modelValue": { required: true },') + expect(content).toMatch('"count": {},') + expect(content).toMatch('emits: ["update:modelValue", "update:count"],') + expect(content).toMatch( + `const modelValue = _useModel(__props, "modelValue")` + ) + expect(content).toMatch(`const c = _useModel(__props, "count")`) + expect(content).toMatch(`return { modelValue, c }`) + expect(content).not.toMatch('defineModel') + + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + count: BindingTypes.PROPS, + c: BindingTypes.SETUP_REF + }) + }) + + test('w/ defineProps and defineEmits', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch(`props: _mergeModels({ foo: String }`) + expect(content).toMatch(`"modelValue": { default: 0 }`) + expect(content).toMatch(`const count = _useModel(__props, "modelValue")`) + expect(content).not.toMatch('defineModel') + expect(bindings).toStrictEqual({ + count: BindingTypes.SETUP_REF, + foo: BindingTypes.PROPS, + modelValue: BindingTypes.PROPS + }) + }) + + test('w/ array props', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch(`props: _mergeModels(['foo', 'bar'], { + "count": {}, + })`) + expect(content).toMatch(`const count = _useModel(__props, "count")`) + expect(content).not.toMatch('defineModel') + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.PROPS, + count: BindingTypes.SETUP_REF + }) + }) + + test('w/ local flag', () => { + const { content } = compile( + ``, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch(`_useModel(__props, "modelValue", { local: true })`) + expect(content).toMatch(`_useModel(__props, "bar", { [key]: true })`) + expect(content).toMatch(`_useModel(__props, "baz", { ...x })`) + expect(content).toMatch(`_useModel(__props, "qux", x)`) + expect(content).toMatch(`_useModel(__props, "foo2", { local: true })`) + expect(content).toMatch(`_useModel(__props, "hoist", { local })`) + }) + + test('w/ types, basic usage', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true } + ) + assertCode(content) + expect(content).toMatch('"modelValue": { type: [Boolean, String] }') + expect(content).toMatch('"count": { type: Number }') + expect(content).toMatch( + '"disabled": { type: Number, ...{ required: false } }' + ) + expect(content).toMatch('"any": { type: Boolean, skipCheck: true }') + expect(content).toMatch( + 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]' + ) + + expect(content).toMatch( + `const modelValue = _useModel(__props, "modelValue")` + ) + expect(content).toMatch(`const count = _useModel(__props, "count")`) + expect(content).toMatch(`const disabled = _useModel(__props, "disabled")`) + expect(content).toMatch(`const any = _useModel(__props, "any")`) + + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + count: BindingTypes.SETUP_REF, + disabled: BindingTypes.SETUP_REF, + any: BindingTypes.SETUP_REF + }) + }) + + test('w/ types, production mode', () => { + const { content, bindings } = compile( + ` + + `, + { defineModel: true, isProd: true } + ) + assertCode(content) + expect(content).toMatch('"modelValue": { type: Boolean }') + expect(content).toMatch('"fn": {}') + expect(content).toMatch( + '"fnWithDefault": { type: Function, ...{ default: () => null } },' + ) + expect(content).toMatch('"str": {}') + expect(content).toMatch('"optional": { required: false }') + expect(content).toMatch( + 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]' + ) + expect(content).toMatch( + `const modelValue = _useModel(__props, "modelValue")` + ) + expect(content).toMatch(`const fn = _useModel(__props, "fn")`) + expect(content).toMatch(`const str = _useModel(__props, "str")`) + expect(bindings).toStrictEqual({ + modelValue: BindingTypes.SETUP_REF, + fn: BindingTypes.SETUP_REF, + fnWithDefault: BindingTypes.SETUP_REF, + str: BindingTypes.SETUP_REF, + optional: BindingTypes.SETUP_REF + }) + }) +}) diff --git a/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts new file mode 100644 index 00000000000..5337a53917c --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts @@ -0,0 +1,149 @@ +import { compileSFCScript as compile, assertCode } from '../utils' + +describe('defineOptions()', () => { + test('basic usage', () => { + const { content } = compile(` + + `) + assertCode(content) + // should remove defineOptions import and call + expect(content).not.toMatch('defineOptions') + // should include context options in default export + expect(content).toMatch( + `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, ` + ) + }) + + test('empty argument', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`export default {`) + // should remove defineOptions import and call + expect(content).not.toMatch('defineOptions') + }) + + it('should emit an error with two defineProps', () => { + expect(() => + compile(` + + `) + ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call') + }) + + it('should emit an error with props or emits property', () => { + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.' + ) + }) + + it('should emit an error with type generic', () => { + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot accept type arguments' + ) + }) + + it('should emit an error with type assertion', () => { + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.' + ) + }) + + it('should emit an error with declaring props/emits/slots/expose', () => { + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead' + ) + }) +}) diff --git a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts new file mode 100644 index 00000000000..cf61c98406c --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts @@ -0,0 +1,583 @@ +import { BindingTypes } from '@vue/compiler-core' +import { compileSFCScript as compile, assertCode } from '../utils' + +describe('defineProps', () => { + test('basic usage', () => { + const { content, bindings } = compile(` + + `) + // should generate working code + assertCode(content) + // should analyze bindings + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.LITERAL_CONST, + props: BindingTypes.SETUP_REACTIVE_CONST + }) + + // should remove defineOptions import and call + expect(content).not.toMatch('defineProps') + // should generate correct setup signature + expect(content).toMatch(`setup(__props, { expose: __expose }) {`) + // should assign user identifier to it + expect(content).toMatch(`const props = __props`) + // should include context options in default export + expect(content).toMatch(`export default { + props: { + foo: String +},`) + }) + + test('w/ external definition', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`export default { + props: propsModel,`) + }) + + // #4764 + test('w/ leading code', () => { + const { content } = compile(` + + `) + // props declaration should be inside setup, not moved along with the import + expect(content).not.toMatch(`const props = __props\nimport`) + assertCode(content) + }) + + test('defineProps w/ runtime options', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({ + props: { foo: String }, + setup(__props, { expose: __expose }) {`) + }) + + test('w/ type', () => { + const { content, bindings } = compile(` + `) + assertCode(content) + expect(content).toMatch(`string: { type: String, required: true }`) + expect(content).toMatch(`number: { type: Number, required: true }`) + expect(content).toMatch(`boolean: { type: Boolean, required: true }`) + expect(content).toMatch(`object: { type: Object, required: true }`) + expect(content).toMatch(`objectLiteral: { type: Object, required: true }`) + expect(content).toMatch(`fn: { type: Function, required: true }`) + expect(content).toMatch(`functionRef: { type: Function, required: true }`) + expect(content).toMatch(`objectRef: { type: Object, required: true }`) + expect(content).toMatch(`dateTime: { type: Date, required: true }`) + expect(content).toMatch(`array: { type: Array, required: true }`) + expect(content).toMatch(`arrayRef: { type: Array, required: true }`) + expect(content).toMatch(`tuple: { type: Array, required: true }`) + expect(content).toMatch(`set: { type: Set, required: true }`) + expect(content).toMatch(`literal: { type: String, required: true }`) + expect(content).toMatch(`optional: { type: null, required: false }`) + expect(content).toMatch(`recordRef: { type: Object, required: true }`) + expect(content).toMatch(`interface: { type: Object, required: true }`) + expect(content).toMatch(`alias: { type: Array, required: true }`) + expect(content).toMatch(`method: { type: Function, required: true }`) + expect(content).toMatch(`symbol: { type: Symbol, required: true }`) + expect(content).toMatch( + `objectOrFn: { type: [Function, Object], required: true },` + ) + expect(content).toMatch(`extract: { type: Number, required: true }`) + expect(content).toMatch( + `exclude: { type: [Number, Boolean], required: true }` + ) + expect(content).toMatch(`uppercase: { type: String, required: true }`) + expect(content).toMatch(`params: { type: Array, required: true }`) + expect(content).toMatch(`nonNull: { type: String, required: true }`) + expect(content).toMatch(`union: { type: [String, Number], required: true }`) + expect(content).toMatch(`literalUnion: { type: String, required: true }`) + expect(content).toMatch( + `literalUnionNumber: { type: Number, required: true }` + ) + expect(content).toMatch( + `literalUnionMixed: { type: [String, Number, Boolean], required: true }` + ) + expect(content).toMatch(`intersection: { type: Object, required: true }`) + expect(content).toMatch(`intersection2: { type: String, required: true }`) + expect(content).toMatch(`foo: { type: [Function, null], required: true }`) + expect(content).toMatch(`unknown: { type: null, required: true }`) + // uninon containing unknown type: skip check + expect(content).toMatch(`unknownUnion: { type: null, required: true }`) + // intersection containing unknown type: narrow to the known types + expect(content).toMatch( + `unknownIntersection: { type: Object, required: true },` + ) + expect(content).toMatch( + `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },` + ) + expect(content).toMatch( + `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }` + ) + expect(bindings).toStrictEqual({ + string: BindingTypes.PROPS, + number: BindingTypes.PROPS, + boolean: BindingTypes.PROPS, + object: BindingTypes.PROPS, + objectLiteral: BindingTypes.PROPS, + fn: BindingTypes.PROPS, + functionRef: BindingTypes.PROPS, + objectRef: BindingTypes.PROPS, + dateTime: BindingTypes.PROPS, + array: BindingTypes.PROPS, + arrayRef: BindingTypes.PROPS, + tuple: BindingTypes.PROPS, + set: BindingTypes.PROPS, + literal: BindingTypes.PROPS, + optional: BindingTypes.PROPS, + recordRef: BindingTypes.PROPS, + interface: BindingTypes.PROPS, + alias: BindingTypes.PROPS, + method: BindingTypes.PROPS, + symbol: BindingTypes.PROPS, + objectOrFn: BindingTypes.PROPS, + extract: BindingTypes.PROPS, + exclude: BindingTypes.PROPS, + union: BindingTypes.PROPS, + literalUnion: BindingTypes.PROPS, + literalUnionNumber: BindingTypes.PROPS, + literalUnionMixed: BindingTypes.PROPS, + intersection: BindingTypes.PROPS, + intersection2: BindingTypes.PROPS, + foo: BindingTypes.PROPS, + uppercase: BindingTypes.PROPS, + params: BindingTypes.PROPS, + nonNull: BindingTypes.PROPS, + unknown: BindingTypes.PROPS, + unknownUnion: BindingTypes.PROPS, + unknownIntersection: BindingTypes.PROPS, + unknownUnionWithBoolean: BindingTypes.PROPS, + unknownUnionWithFunction: BindingTypes.PROPS + }) + }) + + test('w/ interface', () => { + const { content, bindings } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`x: { type: Number, required: false }`) + expect(bindings).toStrictEqual({ + x: BindingTypes.PROPS + }) + }) + + test('w/ extends interface', () => { + const { content, bindings } = compile(` + + + `) + assertCode(content) + expect(content).toMatch(`z: { type: Number, required: true }`) + expect(content).toMatch(`y: { type: String, required: true }`) + expect(content).toMatch(`x: { type: Number, required: false }`) + expect(bindings).toStrictEqual({ + x: BindingTypes.PROPS, + y: BindingTypes.PROPS, + z: BindingTypes.PROPS + }) + }) + + test('w/ exported interface', () => { + const { content, bindings } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`x: { type: Number, required: false }`) + expect(bindings).toStrictEqual({ + x: BindingTypes.PROPS + }) + }) + + test('w/ exported interface in normal script', () => { + const { content, bindings } = compile(` + + + `) + assertCode(content) + expect(content).toMatch(`x: { type: Number, required: false }`) + expect(bindings).toStrictEqual({ + x: BindingTypes.PROPS + }) + }) + + test('w/ type alias', () => { + const { content, bindings } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`x: { type: Number, required: false }`) + expect(bindings).toStrictEqual({ + x: BindingTypes.PROPS + }) + }) + + test('w/ exported type alias', () => { + const { content, bindings } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`x: { type: Number, required: false }`) + expect(bindings).toStrictEqual({ + x: BindingTypes.PROPS + }) + }) + + test('w/ TS assertion', () => { + const { content, bindings } = compile(` + + `) + expect(content).toMatch(`props: ['foo']`) + assertCode(content) + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS + }) + }) + + test('withDefaults (static)', () => { + const { content, bindings } = compile(` + + `) + assertCode(content) + expect(content).toMatch( + `foo: { type: String, required: false, default: 'hi' }` + ) + expect(content).toMatch(`bar: { type: Number, required: false }`) + expect(content).toMatch(`baz: { type: Boolean, required: true }`) + expect(content).toMatch( + `qux: { type: Function, required: false, default() { return 1 } }` + ) + expect(content).toMatch( + `quux: { type: Function, required: false, default() { } }` + ) + expect(content).toMatch( + `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }` + ) + expect(content).toMatch( + `fred: { type: String, required: false, get default() { return 'fred' } }` + ) + expect(content).toMatch(`const props = __props`) + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.PROPS, + baz: BindingTypes.PROPS, + qux: BindingTypes.PROPS, + quux: BindingTypes.PROPS, + quuxx: BindingTypes.PROPS, + fred: BindingTypes.PROPS, + props: BindingTypes.SETUP_CONST + }) + }) + + test('withDefaults (static) + normal script', () => { + const { content } = compile(` + + + `) + assertCode(content) + }) + + // #7111 + test('withDefaults (static) w/ production mode', () => { + const { content } = compile( + ` + + `, + { isProd: true } + ) + assertCode(content) + expect(content).toMatch(`const props = __props`) + + // foo has no default value, the Function can be dropped + expect(content).toMatch(`foo: {}`) + expect(content).toMatch(`bar: { type: Boolean }`) + expect(content).toMatch(`baz: { type: [Boolean, Function], default: true }`) + expect(content).toMatch(`qux: { default: 'hi' }`) + }) + + test('withDefaults (dynamic)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) + expect(content).toMatch( + ` + _mergeDefaults({ + foo: { type: String, required: false }, + bar: { type: Number, required: false }, + baz: { type: Boolean, required: true } + }, { ...defaults })`.trim() + ) + }) + + test('withDefaults (reference)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) + expect(content).toMatch( + ` + _mergeDefaults({ + foo: { type: String, required: false }, + bar: { type: Number, required: false }, + baz: { type: Boolean, required: true } + }, defaults)`.trim() + ) + }) + + // #7111 + test('withDefaults (dynamic) w/ production mode', () => { + const { content } = compile( + ` + + `, + { isProd: true } + ) + assertCode(content) + expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) + expect(content).toMatch( + ` + _mergeDefaults({ + foo: { type: Function }, + bar: { type: Boolean }, + baz: { type: [Boolean, Function] }, + qux: {} + }, { ...defaults })`.trim() + ) + }) + + test('withDefaults w/ dynamic object method', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) + expect(content).toMatch( + ` + _mergeDefaults({ + foo: { type: Function, required: false } + }, { + ['fo' + 'o']() { return 'foo' } + })`.trim() + ) + }) + + test('runtime inference for Enum', () => { + expect( + compile( + ``, + { hoistStatic: true } + ).content + ).toMatch(`foo: { type: Number`) + + expect( + compile( + ``, + { hoistStatic: true } + ).content + ).toMatch(`foo: { type: String`) + + expect( + compile( + ``, + { hoistStatic: true } + ).content + ).toMatch(`foo: { type: [String, Number]`) + + expect( + compile( + ``, + { hoistStatic: true } + ).content + ).toMatch(`foo: { type: Number`) + }) + + describe('errors', () => { + test('w/ both type and non-type args', () => { + expect(() => { + compile(``) + }).toThrow(`cannot accept both type and non-type arguments`) + }) + }) +}) diff --git a/packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts similarity index 99% rename from packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts rename to packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts index e00d7d48b97..a459d80ff29 100644 --- a/packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts @@ -1,6 +1,6 @@ import { BindingTypes } from '@vue/compiler-core' -import { SFCScriptCompileOptions } from '../src' -import { compileSFCScript, assertCode } from './utils' +import { SFCScriptCompileOptions } from '../../src' +import { compileSFCScript, assertCode } from '../utils' describe('sfc props transform', () => { function compile(src: string, options?: Partial) { diff --git a/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts new file mode 100644 index 00000000000..c7becacc02a --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts @@ -0,0 +1,40 @@ +import { compileSFCScript as compile, assertCode } from '../utils' + +describe('defineSlots()', () => { + test('basic usage', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`const slots = _useSlots()`) + expect(content).not.toMatch('defineSlots') + }) + + test('w/o return value', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).not.toMatch('defineSlots') + expect(content).not.toMatch(`_useSlots`) + }) + + test('w/o generic params', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`const slots = _useSlots()`) + expect(content).not.toMatch('defineSlots') + }) +}) diff --git a/packages/compiler-sfc/__tests__/compileScriptHoistStatic.spec.ts b/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts similarity index 97% rename from packages/compiler-sfc/__tests__/compileScriptHoistStatic.spec.ts rename to packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts index 4879dd5f924..614a5e75bce 100644 --- a/packages/compiler-sfc/__tests__/compileScriptHoistStatic.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts @@ -1,6 +1,6 @@ import { BindingTypes } from '@vue/compiler-core' -import { SFCScriptCompileOptions } from '../src' -import { compileSFCScript, assertCode } from './utils' +import { SFCScriptCompileOptions } from '../../src' +import { compileSFCScript, assertCode } from '../utils' describe('sfc hoist static', () => { function compile(src: string, options?: Partial) { diff --git a/packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts b/packages/compiler-sfc/__tests__/compileScript/reactivityTransform.spec.ts similarity index 98% rename from packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts rename to packages/compiler-sfc/__tests__/compileScript/reactivityTransform.spec.ts index 8ae5275661e..44d51c14e75 100644 --- a/packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/reactivityTransform.spec.ts @@ -1,5 +1,6 @@ +// TODO remove in 3.4 import { BindingTypes } from '@vue/compiler-core' -import { compileSFCScript as compile, assertCode } from './utils' +import { compileSFCScript as compile, assertCode } from '../utils' // this file only tests integration with SFC - main test case for the ref // transform can be found in /packages/reactivity-transform/__tests__ From d1f973bff82581fb335d6fc05623d1ad3d84fb7c Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 12 Apr 2023 21:17:57 +0800 Subject: [PATCH 0428/1409] feat(compiler-sfc): support intersection and union types in macros close #7553 --- .../__snapshots__/defineEmits.spec.ts.snap | 16 ++ .../compileScript/defineEmits.spec.ts | 6 +- .../compileScript/resolveType.spec.ts | 179 ++++++++++++++++++ .../compiler-sfc/src/script/defineProps.ts | 21 +- .../compiler-sfc/src/script/resolveType.ts | 68 ++++++- 5 files changed, 269 insertions(+), 21 deletions(-) create mode 100644 packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap index 5add78a28b3..729c019a555 100644 --- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap @@ -191,6 +191,22 @@ export default /*#__PURE__*/_defineComponent({ +return { emit } +} + +})" +`; + +exports[`defineEmits > w/ type (union) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"foo\\", \\"bar\\", \\"baz\\"], + setup(__props, { expose: __expose, emit }) { + __expose(); + + + return { emit } } diff --git a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts index 3920f08efb8..67d9674b54c 100644 --- a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts @@ -47,13 +47,13 @@ const emit = defineEmits(['a', 'b']) test('w/ type (union)', () => { const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)` - expect(() => - compile(` + const { content } = compile(` `) - ).toThrow() + assertCode(content) + expect(content).toMatch(`emits: ["foo", "bar", "baz"]`) }) test('w/ type (type literal w/ call signatures)', () => { diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts new file mode 100644 index 00000000000..12d18e40687 --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -0,0 +1,179 @@ +import { TSTypeAliasDeclaration } from '@babel/types' +import { parse } from '../../src' +import { ScriptCompileContext } from '../../src/script/context' +import { + inferRuntimeType, + resolveTypeElements +} from '../../src/script/resolveType' + +describe('resolveType', () => { + test('type literal', () => { + const { elements, callSignatures } = resolve(`type Target = { + foo: number // property + bar(): void // method + 'baz': string // string literal key + (e: 'foo'): void // call signature + (e: 'bar'): void + }`) + expect(elements).toStrictEqual({ + foo: ['Number'], + bar: ['Function'], + baz: ['String'] + }) + expect(callSignatures?.length).toBe(2) + }) + + test('reference type', () => { + expect( + resolve(` + type Aliased = { foo: number } + type Target = Aliased + `).elements + ).toStrictEqual({ + foo: ['Number'] + }) + }) + + test('reference exported type', () => { + expect( + resolve(` + export type Aliased = { foo: number } + type Target = Aliased + `).elements + ).toStrictEqual({ + foo: ['Number'] + }) + }) + + test('reference interface', () => { + expect( + resolve(` + interface Aliased { foo: number } + type Target = Aliased + `).elements + ).toStrictEqual({ + foo: ['Number'] + }) + }) + + test('reference exported interface', () => { + expect( + resolve(` + export interface Aliased { foo: number } + type Target = Aliased + `).elements + ).toStrictEqual({ + foo: ['Number'] + }) + }) + + test('reference interface extends', () => { + expect( + resolve(` + export interface A { a(): void } + export interface B extends A { b: boolean } + interface C { c: string } + interface Aliased extends B, C { foo: number } + type Target = Aliased + `).elements + ).toStrictEqual({ + a: ['Function'], + b: ['Boolean'], + c: ['String'], + foo: ['Number'] + }) + }) + + test('function type', () => { + expect( + resolve(` + type Target = (e: 'foo') => void + `).callSignatures?.length + ).toBe(1) + }) + + test('reference function type', () => { + expect( + resolve(` + type Fn = (e: 'foo') => void + type Target = Fn + `).callSignatures?.length + ).toBe(1) + }) + + test('intersection type', () => { + expect( + resolve(` + type Foo = { foo: number } + type Bar = { bar: string } + type Baz = { bar: string | boolean } + type Target = { self: any } & Foo & Bar & Baz + `).elements + ).toStrictEqual({ + self: ['Unknown'], + foo: ['Number'], + // both Bar & Baz has 'bar', but Baz['bar] is wider so it should be + // preferred + bar: ['String', 'Boolean'] + }) + }) + + // #7553 + test('union type', () => { + expect( + resolve(` + interface CommonProps { + size?: 'xl' | 'l' | 'm' | 's' | 'xs' + } + + type ConditionalProps = + | { + color: 'normal' | 'primary' | 'secondary' + appearance: 'normal' | 'outline' | 'text' + } + | { + color: number + appearance: 'outline' + note: string + } + + type Target = CommonProps & ConditionalProps + `).elements + ).toStrictEqual({ + size: ['String'], + color: ['String', 'Number'], + appearance: ['String'], + note: ['String'] + }) + }) + + // describe('built-in utility types', () => { + + // }) + + describe('errors', () => { + test('error on computed keys', () => { + expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow( + `computed keys are not supported in types referenced by SFC macros` + ) + }) + }) +}) + +function resolve(code: string) { + const { descriptor } = parse(``) + const ctx = new ScriptCompileContext(descriptor, { id: 'test' }) + const targetDecl = ctx.scriptSetupAst!.body.find( + s => s.type === 'TSTypeAliasDeclaration' && s.id.name === 'Target' + ) as TSTypeAliasDeclaration + const raw = resolveTypeElements(ctx, targetDecl.typeAnnotation) + const elements: Record = {} + for (const key in raw) { + elements[key] = inferRuntimeType(ctx, raw[key]) + } + return { + elements, + callSignatures: raw.__callSignatures, + raw + } +} diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index bd462a2a8ea..ee8b5e55734 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -193,20 +193,15 @@ function resolveRuntimePropsFromType( const elements = resolveTypeElements(ctx, node) for (const key in elements) { const e = elements[key] - let type: string[] | undefined + let type = inferRuntimeType(ctx, e) let skipCheck = false - if (e.type === 'TSMethodSignature') { - type = ['Function'] - } else if (e.typeAnnotation) { - type = inferRuntimeType(ctx, e.typeAnnotation.typeAnnotation) - // skip check for result containing unknown types - if (type.includes(UNKNOWN_TYPE)) { - if (type.includes('Boolean') || type.includes('Function')) { - type = type.filter(t => t !== UNKNOWN_TYPE) - skipCheck = true - } else { - type = ['null'] - } + // skip check for result containing unknown types + if (type.includes(UNKNOWN_TYPE)) { + if (type.includes('Boolean') || type.includes('Function')) { + type = type.filter(t => t !== UNKNOWN_TYPE) + skipCheck = true + } else { + type = ['null'] } } props.push({ diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index ba41757069e..6711784a7af 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -16,7 +16,8 @@ import { UNKNOWN_TYPE } from './utils' import { ScriptCompileContext } from './context' import { ImportBinding } from '../compileScript' import { TSInterfaceDeclaration } from '@babel/types' -import { hasOwn } from '@vue/shared' +import { hasOwn, isArray } from '@vue/shared' +import { Expression } from '@babel/types' export interface TypeScope { filename: string @@ -63,24 +64,37 @@ function innerResolveTypeElements( addCallSignature(ret, node) return ret } - case 'TSExpressionWithTypeArguments': + case 'TSExpressionWithTypeArguments': // referenced by interface extends case 'TSTypeReference': return resolveTypeElements(ctx, resolveTypeReference(ctx, node)) + case 'TSUnionType': + case 'TSIntersectionType': + return mergeElements( + node.types.map(t => resolveTypeElements(ctx, t)), + node.type + ) } ctx.error(`Unsupported type in SFC macro: ${node.type}`, node) } function addCallSignature( elements: ResolvedElements, - node: TSCallSignatureDeclaration | TSFunctionType + node: + | TSCallSignatureDeclaration + | TSFunctionType + | (TSCallSignatureDeclaration | TSFunctionType)[] ) { if (!elements.__callSignatures) { Object.defineProperty(elements, '__callSignatures', { enumerable: false, - value: [node] + value: isArray(node) ? node : [node] }) } else { - elements.__callSignatures.push(node) + if (isArray(node)) { + elements.__callSignatures.push(...node) + } else { + elements.__callSignatures.push(node) + } } } @@ -112,6 +126,45 @@ function typeElementsToMap( return ret } +function mergeElements( + maps: ResolvedElements[], + type: 'TSUnionType' | 'TSIntersectionType' +): ResolvedElements { + const res: ResolvedElements = Object.create(null) + for (const m of maps) { + for (const key in m) { + if (!(key in res)) { + res[key] = m[key] + } else { + res[key] = createProperty(res[key].key, type, [res[key], m[key]]) + } + } + if (m.__callSignatures) { + addCallSignature(res, m.__callSignatures) + } + } + return res +} + +function createProperty( + key: Expression, + type: 'TSUnionType' | 'TSIntersectionType', + types: Node[] +): TSPropertySignature { + return { + type: 'TSPropertySignature', + key, + kind: 'get', + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type, + types: types as TSType[] + } + } + } +} + function resolveInterfaceMembers( ctx: ScriptCompileContext, node: TSInterfaceDeclaration @@ -252,6 +305,11 @@ export function inferRuntimeType( } return types.size ? Array.from(types) : ['Object'] } + case 'TSPropertySignature': + if (node.typeAnnotation) { + return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation) + } + case 'TSMethodSignature': case 'TSFunctionType': return ['Function'] case 'TSArrayType': From fb8ecc803e58bfef0971346c63fefc529812daa7 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 13 Apr 2023 09:59:54 +0800 Subject: [PATCH 0429/1409] feat(compiler-sfc): support mapped types, string types & template type in macros --- .../compileScript/resolveType.spec.ts | 43 +++++- .../compiler-sfc/src/script/resolveType.ts | 144 ++++++++++++++---- 2 files changed, 159 insertions(+), 28 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 12d18e40687..3d5e3750798 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -147,9 +147,48 @@ describe('resolveType', () => { }) }) - // describe('built-in utility types', () => { + test('template string type', () => { + expect( + resolve(` + type T = 'foo' | 'bar' + type S = 'x' | 'y' + type Target = { + [\`_\${T}_\${S}_\`]: string + } + `).elements + ).toStrictEqual({ + _foo_x_: ['String'], + _foo_y_: ['String'], + _bar_x_: ['String'], + _bar_y_: ['String'] + }) + }) - // }) + test('mapped types w/ string manipulation', () => { + expect( + resolve(` + type T = 'foo' | 'bar' + type Target = { [K in T]: string | number } & { + [K in 'optional']?: boolean + } & { + [K in Capitalize]: string + } & { + [K in Uppercase>]: string + } & { + [K in \`x\${T}\`]: string + } + `).elements + ).toStrictEqual({ + foo: ['String', 'Number'], + bar: ['String', 'Number'], + Foo: ['String'], + Bar: ['String'], + FOO: ['String'], + xfoo: ['String'], + xbar: ['String'], + optional: ['Boolean'] + }) + }) describe('errors', () => { test('error on computed keys', () => { diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 6711784a7af..101f3b4f0a8 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -5,18 +5,20 @@ import { TSEnumDeclaration, TSExpressionWithTypeArguments, TSFunctionType, + TSMappedType, TSMethodSignature, TSPropertySignature, TSType, TSTypeAnnotation, TSTypeElement, - TSTypeReference + TSTypeReference, + TemplateLiteral } from '@babel/types' import { UNKNOWN_TYPE } from './utils' import { ScriptCompileContext } from './context' import { ImportBinding } from '../compileScript' import { TSInterfaceDeclaration } from '@babel/types' -import { hasOwn, isArray } from '@vue/shared' +import { capitalize, hasOwn, isArray } from '@vue/shared' import { Expression } from '@babel/types' export interface TypeScope { @@ -65,14 +67,23 @@ function innerResolveTypeElements( return ret } case 'TSExpressionWithTypeArguments': // referenced by interface extends - case 'TSTypeReference': - return resolveTypeElements(ctx, resolveTypeReference(ctx, node)) + case 'TSTypeReference': { + const resolved = resolveTypeReference(ctx, node) + if (resolved) { + return resolveTypeElements(ctx, resolved) + } else { + // TODO Pick / Omit + ctx.error(`Failed to resolved type reference`, node) + } + } case 'TSUnionType': case 'TSIntersectionType': return mergeElements( node.types.map(t => resolveTypeElements(ctx, t)), node.type ) + case 'TSMappedType': + return resolveMappedType(ctx, node) } ctx.error(`Unsupported type in SFC macro: ${node.type}`, node) } @@ -113,6 +124,10 @@ function typeElementsToMap( : null if (name && !e.computed) { ret[name] = e + } else if (e.key.type === 'TemplateLiteral') { + for (const key of resolveTemplateKeys(ctx, e.key)) { + ret[key] = e + } } else { ctx.error( `computed keys are not supported in types referenced by SFC macros.`, @@ -136,7 +151,11 @@ function mergeElements( if (!(key in res)) { res[key] = m[key] } else { - res[key] = createProperty(res[key].key, type, [res[key], m[key]]) + res[key] = createProperty(res[key].key, { + type, + // @ts-ignore + types: [res[key], m[key]] + }) } } if (m.__callSignatures) { @@ -148,8 +167,7 @@ function mergeElements( function createProperty( key: Expression, - type: 'TSUnionType' | 'TSIntersectionType', - types: Node[] + typeAnnotation: TSType ): TSPropertySignature { return { type: 'TSPropertySignature', @@ -157,10 +175,7 @@ function createProperty( kind: 'get', typeAnnotation: { type: 'TSTypeAnnotation', - typeAnnotation: { - type, - types: types as TSType[] - } + typeAnnotation } } } @@ -183,22 +198,102 @@ function resolveInterfaceMembers( return base } -function resolveTypeReference( +function resolveMappedType( ctx: ScriptCompileContext, - node: TSTypeReference | TSExpressionWithTypeArguments, - scope?: TypeScope -): Node -function resolveTypeReference( + node: TSMappedType +): ResolvedElements { + const res: ResolvedElements = {} + if (!node.typeParameter.constraint) { + ctx.error(`mapped type used in macros must have a finite constraint.`, node) + } + const keys = resolveStringType(ctx, node.typeParameter.constraint) + for (const key of keys) { + res[key] = createProperty( + { + type: 'Identifier', + name: key + }, + node.typeAnnotation! + ) + } + return res +} + +function resolveStringType(ctx: ScriptCompileContext, node: Node): string[] { + switch (node.type) { + case 'StringLiteral': + return [node.value] + case 'TSLiteralType': + return resolveStringType(ctx, node.literal) + case 'TSUnionType': + return node.types.map(t => resolveStringType(ctx, t)).flat() + case 'TemplateLiteral': { + return resolveTemplateKeys(ctx, node) + } + case 'TSTypeReference': { + const resolved = resolveTypeReference(ctx, node) + if (resolved) { + return resolveStringType(ctx, resolved) + } + if (node.typeName.type === 'Identifier') { + const getParam = (index = 0) => + resolveStringType(ctx, node.typeParameters!.params[index]) + switch (node.typeName.name) { + case 'Extract': + return getParam(1) + case 'Exclude': { + const excluded = getParam(1) + return getParam().filter(s => !excluded.includes(s)) + } + case 'Uppercase': + return getParam().map(s => s.toUpperCase()) + case 'Lowercase': + return getParam().map(s => s.toLowerCase()) + case 'Capitalize': + return getParam().map(capitalize) + case 'Uncapitalize': + return getParam().map(s => s[0].toLowerCase() + s.slice(1)) + default: + ctx.error('Failed to resolve type reference', node) + } + } + } + } + ctx.error('Failed to resolve string type into finite keys', node) +} + +function resolveTemplateKeys( ctx: ScriptCompileContext, - node: TSTypeReference | TSExpressionWithTypeArguments, - scope: TypeScope, - bail: false -): Node | undefined + node: TemplateLiteral +): string[] { + if (!node.expressions.length) { + return [node.quasis[0].value.raw] + } + + const res: string[] = [] + const e = node.expressions[0] + const q = node.quasis[0] + const leading = q ? q.value.raw : `` + const resolved = resolveStringType(ctx, e) + const restResolved = resolveTemplateKeys(ctx, { + ...node, + expressions: node.expressions.slice(1), + quasis: q ? node.quasis.slice(1) : node.quasis + }) + + for (const r of resolved) { + for (const rr of restResolved) { + res.push(leading + r + rr) + } + } + + return res +} + function resolveTypeReference( ctx: ScriptCompileContext, node: TSTypeReference | TSExpressionWithTypeArguments, - scope = getRootScope(ctx), - bail = true + scope = getRootScope(ctx) ): Node | undefined { const ref = node.type === 'TSTypeReference' ? node.typeName : node.expression if (ref.type === 'Identifier') { @@ -211,9 +306,6 @@ function resolveTypeReference( // TODO qualified name, e.g. Foo.Bar // return resolveTypeReference() } - if (bail) { - ctx.error('Failed to resolve type reference.', node) - } } function getRootScope(ctx: ScriptCompileContext): TypeScope { @@ -332,7 +424,7 @@ export function inferRuntimeType( case 'TSTypeReference': if (node.typeName.type === 'Identifier') { - const resolved = resolveTypeReference(ctx, node, scope, false) + const resolved = resolveTypeReference(ctx, node, scope) if (resolved) { return inferRuntimeType(ctx, resolved, scope) } From 1cfab4c695b0c28f549f8c97faee5099581792a7 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 13 Apr 2023 10:28:22 +0800 Subject: [PATCH 0430/1409] feat(compiler-sfc): support limited built-in utility types in macros --- .../compileScript/resolveType.spec.ts | 25 ++++++ .../compiler-sfc/src/script/resolveType.ts | 79 +++++++++++++++++-- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 3d5e3750798..3e61e1b231a 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -190,6 +190,31 @@ describe('resolveType', () => { }) }) + test('utility type: Pick', () => { + expect( + resolve(` + type T = { foo: number, bar: string, baz: boolean } + type K = 'foo' | 'bar' + type Target = Pick + `).elements + ).toStrictEqual({ + foo: ['Number'], + bar: ['String'] + }) + }) + + test('utility type: Omit', () => { + expect( + resolve(` + type T = { foo: number, bar: string, baz: boolean } + type K = 'foo' | 'bar' + type Target = Omit + `).elements + ).toStrictEqual({ + baz: ['Boolean'] + }) + }) + describe('errors', () => { test('error on computed keys', () => { expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow( diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 101f3b4f0a8..0f6a4eb5230 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -72,8 +72,18 @@ function innerResolveTypeElements( if (resolved) { return resolveTypeElements(ctx, resolved) } else { - // TODO Pick / Omit - ctx.error(`Failed to resolved type reference`, node) + const typeName = getReferenceName(node) + if ( + typeof typeName === 'string' && + // @ts-ignore + SupportedBuiltinsSet.has(typeName) + ) { + return resolveBuiltin(ctx, node, typeName as any) + } + ctx.error( + `Failed to resolved type reference, or unsupported built-in utlility type.`, + node + ) } } case 'TSUnionType': @@ -290,17 +300,60 @@ function resolveTemplateKeys( return res } +const SupportedBuiltinsSet = new Set([ + 'Partial', + 'Required', + 'Readonly', + 'Pick', + 'Omit' +] as const) + +type GetSetType = T extends Set ? V : never + +function resolveBuiltin( + ctx: ScriptCompileContext, + node: TSTypeReference | TSExpressionWithTypeArguments, + name: GetSetType +): ResolvedElements { + const t = resolveTypeElements(ctx, node.typeParameters!.params[0]) + switch (name) { + case 'Partial': + case 'Required': + case 'Readonly': + return t + case 'Pick': { + const picked = resolveStringType(ctx, node.typeParameters!.params[1]) + const res: ResolvedElements = {} + if (t.__callSignatures) addCallSignature(res, t.__callSignatures) + for (const key of picked) { + res[key] = t[key] + } + return res + } + case 'Omit': + const omitted = resolveStringType(ctx, node.typeParameters!.params[1]) + const res: ResolvedElements = {} + if (t.__callSignatures) addCallSignature(res, t.__callSignatures) + for (const key in t) { + if (!omitted.includes(key)) { + res[key] = t[key] + } + } + return res + } +} + function resolveTypeReference( ctx: ScriptCompileContext, node: TSTypeReference | TSExpressionWithTypeArguments, scope = getRootScope(ctx) ): Node | undefined { - const ref = node.type === 'TSTypeReference' ? node.typeName : node.expression - if (ref.type === 'Identifier') { - if (scope.imports[ref.name]) { + const name = getReferenceName(node) + if (typeof name === 'string') { + if (scope.imports[name]) { // TODO external import - } else if (scope.types[ref.name]) { - return scope.types[ref.name] + } else if (scope.types[name]) { + return scope.types[name] } } else { // TODO qualified name, e.g. Foo.Bar @@ -308,6 +361,18 @@ function resolveTypeReference( } } +function getReferenceName( + node: TSTypeReference | TSExpressionWithTypeArguments +): string | string[] { + const ref = node.type === 'TSTypeReference' ? node.typeName : node.expression + if (ref.type === 'Identifier') { + return ref.name + } else { + // TODO qualified name, e.g. Foo.Bar + return [] + } +} + function getRootScope(ctx: ScriptCompileContext): TypeScope { if (ctx.scope) { return ctx.scope From 51773d5d1d7f8559ff42bc3fffa76ab8cc9ec62b Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 13 Apr 2023 10:42:15 +0800 Subject: [PATCH 0431/1409] refactor: adjust ResolvedElements shape --- .../compileScript/resolveType.spec.ts | 42 ++++----- .../compiler-sfc/src/script/defineEmits.ts | 8 +- .../compiler-sfc/src/script/defineProps.ts | 4 +- .../compiler-sfc/src/script/resolveType.ts | 88 +++++++------------ 4 files changed, 58 insertions(+), 84 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 3e61e1b231a..5d5a424f26b 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -8,19 +8,19 @@ import { describe('resolveType', () => { test('type literal', () => { - const { elements, callSignatures } = resolve(`type Target = { + const { props, calls } = resolve(`type Target = { foo: number // property bar(): void // method 'baz': string // string literal key (e: 'foo'): void // call signature (e: 'bar'): void }`) - expect(elements).toStrictEqual({ + expect(props).toStrictEqual({ foo: ['Number'], bar: ['Function'], baz: ['String'] }) - expect(callSignatures?.length).toBe(2) + expect(calls?.length).toBe(2) }) test('reference type', () => { @@ -28,7 +28,7 @@ describe('resolveType', () => { resolve(` type Aliased = { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ foo: ['Number'] }) @@ -39,7 +39,7 @@ describe('resolveType', () => { resolve(` export type Aliased = { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ foo: ['Number'] }) @@ -50,7 +50,7 @@ describe('resolveType', () => { resolve(` interface Aliased { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ foo: ['Number'] }) @@ -61,7 +61,7 @@ describe('resolveType', () => { resolve(` export interface Aliased { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ foo: ['Number'] }) @@ -75,7 +75,7 @@ describe('resolveType', () => { interface C { c: string } interface Aliased extends B, C { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ a: ['Function'], b: ['Boolean'], @@ -88,7 +88,7 @@ describe('resolveType', () => { expect( resolve(` type Target = (e: 'foo') => void - `).callSignatures?.length + `).calls?.length ).toBe(1) }) @@ -97,7 +97,7 @@ describe('resolveType', () => { resolve(` type Fn = (e: 'foo') => void type Target = Fn - `).callSignatures?.length + `).calls?.length ).toBe(1) }) @@ -108,7 +108,7 @@ describe('resolveType', () => { type Bar = { bar: string } type Baz = { bar: string | boolean } type Target = { self: any } & Foo & Bar & Baz - `).elements + `).props ).toStrictEqual({ self: ['Unknown'], foo: ['Number'], @@ -138,7 +138,7 @@ describe('resolveType', () => { } type Target = CommonProps & ConditionalProps - `).elements + `).props ).toStrictEqual({ size: ['String'], color: ['String', 'Number'], @@ -155,7 +155,7 @@ describe('resolveType', () => { type Target = { [\`_\${T}_\${S}_\`]: string } - `).elements + `).props ).toStrictEqual({ _foo_x_: ['String'], _foo_y_: ['String'], @@ -177,7 +177,7 @@ describe('resolveType', () => { } & { [K in \`x\${T}\`]: string } - `).elements + `).props ).toStrictEqual({ foo: ['String', 'Number'], bar: ['String', 'Number'], @@ -196,7 +196,7 @@ describe('resolveType', () => { type T = { foo: number, bar: string, baz: boolean } type K = 'foo' | 'bar' type Target = Pick - `).elements + `).props ).toStrictEqual({ foo: ['Number'], bar: ['String'] @@ -209,7 +209,7 @@ describe('resolveType', () => { type T = { foo: number, bar: string, baz: boolean } type K = 'foo' | 'bar' type Target = Omit - `).elements + `).props ).toStrictEqual({ baz: ['Boolean'] }) @@ -231,13 +231,13 @@ function resolve(code: string) { s => s.type === 'TSTypeAliasDeclaration' && s.id.name === 'Target' ) as TSTypeAliasDeclaration const raw = resolveTypeElements(ctx, targetDecl.typeAnnotation) - const elements: Record = {} - for (const key in raw) { - elements[key] = inferRuntimeType(ctx, raw[key]) + const props: Record = {} + for (const key in raw.props) { + props[key] = inferRuntimeType(ctx, raw.props[key]) } return { - elements, - callSignatures: raw.__callSignatures, + props, + calls: raw.calls, raw } } diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index 0e080b4fed4..e615cd71a0d 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -69,22 +69,22 @@ function extractRuntimeEmits(ctx: ScriptCompileContext): Set { return emits } - const elements = resolveTypeElements(ctx, node) + const { props, calls } = resolveTypeElements(ctx, node) let hasProperty = false - for (const key in elements) { + for (const key in props) { emits.add(key) hasProperty = true } - if (elements.__callSignatures) { + if (calls) { if (hasProperty) { ctx.error( `defineEmits() type cannot mixed call signature and property syntax.`, node ) } - for (const call of elements.__callSignatures) { + for (const call of calls) { extractEventNames(call.parameters[0], emits) } } diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index ee8b5e55734..16ea02fe3cf 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -191,8 +191,8 @@ function resolveRuntimePropsFromType( ): PropTypeData[] { const props: PropTypeData[] = [] const elements = resolveTypeElements(ctx, node) - for (const key in elements) { - const e = elements[key] + for (const key in elements.props) { + const e = elements.props[key] let type = inferRuntimeType(ctx, e) let skipCheck = false // skip check for result containing unknown types diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 0f6a4eb5230..ecd3838be7b 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -18,7 +18,7 @@ import { UNKNOWN_TYPE } from './utils' import { ScriptCompileContext } from './context' import { ImportBinding } from '../compileScript' import { TSInterfaceDeclaration } from '@babel/types' -import { capitalize, hasOwn, isArray } from '@vue/shared' +import { capitalize, hasOwn } from '@vue/shared' import { Expression } from '@babel/types' export interface TypeScope { @@ -28,11 +28,9 @@ export interface TypeScope { types: Record } -type ResolvedElements = Record< - string, - TSPropertySignature | TSMethodSignature -> & { - __callSignatures?: (TSCallSignatureDeclaration | TSFunctionType)[] +interface ResolvedElements { + props: Record + calls?: (TSCallSignatureDeclaration | TSFunctionType)[] } /** @@ -62,9 +60,7 @@ function innerResolveTypeElements( case 'TSParenthesizedType': return resolveTypeElements(ctx, node.typeAnnotation) case 'TSFunctionType': { - const ret: ResolvedElements = {} - addCallSignature(ret, node) - return ret + return { props: {}, calls: [node] } } case 'TSExpressionWithTypeArguments': // referenced by interface extends case 'TSTypeReference': { @@ -98,32 +94,11 @@ function innerResolveTypeElements( ctx.error(`Unsupported type in SFC macro: ${node.type}`, node) } -function addCallSignature( - elements: ResolvedElements, - node: - | TSCallSignatureDeclaration - | TSFunctionType - | (TSCallSignatureDeclaration | TSFunctionType)[] -) { - if (!elements.__callSignatures) { - Object.defineProperty(elements, '__callSignatures', { - enumerable: false, - value: isArray(node) ? node : [node] - }) - } else { - if (isArray(node)) { - elements.__callSignatures.push(...node) - } else { - elements.__callSignatures.push(node) - } - } -} - function typeElementsToMap( ctx: ScriptCompileContext, elements: TSTypeElement[] ): ResolvedElements { - const ret: ResolvedElements = {} + const res: ResolvedElements = { props: {} } for (const e of elements) { if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') { const name = @@ -133,10 +108,10 @@ function typeElementsToMap( ? e.key.value : null if (name && !e.computed) { - ret[name] = e + res.props[name] = e } else if (e.key.type === 'TemplateLiteral') { for (const key of resolveTemplateKeys(ctx, e.key)) { - ret[key] = e + res.props[key] = e } } else { ctx.error( @@ -145,31 +120,32 @@ function typeElementsToMap( ) } } else if (e.type === 'TSCallSignatureDeclaration') { - addCallSignature(ret, e) + ;(res.calls || (res.calls = [])).push(e) } } - return ret + return res } function mergeElements( maps: ResolvedElements[], type: 'TSUnionType' | 'TSIntersectionType' ): ResolvedElements { - const res: ResolvedElements = Object.create(null) - for (const m of maps) { - for (const key in m) { - if (!(key in res)) { - res[key] = m[key] + const res: ResolvedElements = { props: {} } + const { props: baseProps } = res + for (const { props, calls } of maps) { + for (const key in props) { + if (!hasOwn(baseProps, key)) { + baseProps[key] = props[key] } else { - res[key] = createProperty(res[key].key, { + baseProps[key] = createProperty(baseProps[key].key, { type, // @ts-ignore - types: [res[key], m[key]] + types: [baseProps[key], props[key]] }) } } - if (m.__callSignatures) { - addCallSignature(res, m.__callSignatures) + if (calls) { + ;(res.calls || (res.calls = [])).push(...calls) } } return res @@ -197,10 +173,10 @@ function resolveInterfaceMembers( const base = typeElementsToMap(ctx, node.body.body) if (node.extends) { for (const ext of node.extends) { - const resolvedExt = resolveTypeElements(ctx, ext) - for (const key in resolvedExt) { - if (!hasOwn(base, key)) { - base[key] = resolvedExt[key] + const { props } = resolveTypeElements(ctx, ext) + for (const key in props) { + if (!hasOwn(base.props, key)) { + base.props[key] = props[key] } } } @@ -212,13 +188,13 @@ function resolveMappedType( ctx: ScriptCompileContext, node: TSMappedType ): ResolvedElements { - const res: ResolvedElements = {} + const res: ResolvedElements = { props: {} } if (!node.typeParameter.constraint) { ctx.error(`mapped type used in macros must have a finite constraint.`, node) } const keys = resolveStringType(ctx, node.typeParameter.constraint) for (const key of keys) { - res[key] = createProperty( + res.props[key] = createProperty( { type: 'Identifier', name: key @@ -323,20 +299,18 @@ function resolveBuiltin( return t case 'Pick': { const picked = resolveStringType(ctx, node.typeParameters!.params[1]) - const res: ResolvedElements = {} - if (t.__callSignatures) addCallSignature(res, t.__callSignatures) + const res: ResolvedElements = { props: {}, calls: t.calls } for (const key of picked) { - res[key] = t[key] + res.props[key] = t.props[key] } return res } case 'Omit': const omitted = resolveStringType(ctx, node.typeParameters!.params[1]) - const res: ResolvedElements = {} - if (t.__callSignatures) addCallSignature(res, t.__callSignatures) - for (const key in t) { + const res: ResolvedElements = { props: {}, calls: t.calls } + for (const key in t.props) { if (!omitted.includes(key)) { - res[key] = t[key] + res.props[key] = t.props[key] } } return res From 3f779ddbf85054c8915fa4537f8a79baab392d5c Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 13 Apr 2023 11:21:09 +0800 Subject: [PATCH 0432/1409] feat(compiler-sfc): support string indexed type in macros --- .../compileScript/resolveType.spec.ts | 24 +++++++ .../compiler-sfc/src/script/resolveType.ts | 67 +++++++++++++++---- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 5d5a424f26b..1485cfe83ae 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -215,6 +215,30 @@ describe('resolveType', () => { }) }) + test('indexed access type', () => { + expect( + resolve(` + type T = { bar: number } + type S = { nested: { foo: T['bar'] }} + type Target = S['nested'] + `).props + ).toStrictEqual({ + foo: ['Number'] + }) + }) + + // test('namespace', () => { + // expect( + // resolve(` + // type T = { foo: number, bar: string, baz: boolean } + // type K = 'foo' | 'bar' + // type Target = Omit + // `).props + // ).toStrictEqual({ + // baz: ['Boolean'] + // }) + // }) + describe('errors', () => { test('error on computed keys', () => { expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow( diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index ecd3838be7b..0b6969ad8f0 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -1,4 +1,5 @@ import { + Identifier, Node, Statement, TSCallSignatureDeclaration, @@ -8,6 +9,7 @@ import { TSMappedType, TSMethodSignature, TSPropertySignature, + TSQualifiedName, TSType, TSTypeAnnotation, TSTypeElement, @@ -62,6 +64,34 @@ function innerResolveTypeElements( case 'TSFunctionType': { return { props: {}, calls: [node] } } + case 'TSUnionType': + case 'TSIntersectionType': + return mergeElements( + node.types.map(t => resolveTypeElements(ctx, t)), + node.type + ) + case 'TSMappedType': + return resolveMappedType(ctx, node) + case 'TSIndexedAccessType': { + if ( + node.indexType.type === 'TSLiteralType' && + node.indexType.literal.type === 'StringLiteral' + ) { + const resolved = resolveTypeElements(ctx, node.objectType) + const key = node.indexType.literal.value + const targetType = resolved.props[key].typeAnnotation + if (targetType) { + return resolveTypeElements(ctx, targetType.typeAnnotation) + } else { + break + } + } else { + ctx.error( + `Unsupported index type: ${node.indexType.type}`, + node.indexType + ) + } + } case 'TSExpressionWithTypeArguments': // referenced by interface extends case 'TSTypeReference': { const resolved = resolveTypeReference(ctx, node) @@ -82,16 +112,8 @@ function innerResolveTypeElements( ) } } - case 'TSUnionType': - case 'TSIntersectionType': - return mergeElements( - node.types.map(t => resolveTypeElements(ctx, t)), - node.type - ) - case 'TSMappedType': - return resolveMappedType(ctx, node) } - ctx.error(`Unsupported type in SFC macro: ${node.type}`, node) + ctx.error(`Unresolvable type in SFC macro: ${node.type}`, node) } function typeElementsToMap( @@ -342,8 +364,15 @@ function getReferenceName( if (ref.type === 'Identifier') { return ref.name } else { - // TODO qualified name, e.g. Foo.Bar - return [] + return qualifiedNameToPath(ref) + } +} + +function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] { + if (node.type === 'Identifier') { + return [node.name] + } else { + return [...qualifiedNameToPath(node.left), node.right.name] } } @@ -376,8 +405,11 @@ function recordType(node: Node, types: Record) { switch (node.type) { case 'TSInterfaceDeclaration': case 'TSEnumDeclaration': - types[node.id.name] = node + case 'TSModuleDeclaration': { + const id = node.id.type === 'Identifier' ? node.id.name : node.id.value + types[id] = node break + } case 'TSTypeAliasDeclaration': types[node.id.name] = node.typeAnnotation break @@ -542,6 +574,17 @@ export function inferRuntimeType( case 'TSSymbolKeyword': return ['Symbol'] + case 'TSIndexedAccessType': { + if ( + node.indexType.type === 'TSLiteralType' && + node.indexType.literal.type === 'StringLiteral' + ) { + const resolved = resolveTypeElements(ctx, node.objectType) + const key = node.indexType.literal.value + return inferRuntimeType(ctx, resolved.props[key]) + } + } + default: return [UNKNOWN_TYPE] // no runtime check } From 5ff40bb0dc2918b7db15fe9f49db2a135a925572 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 13 Apr 2023 12:32:52 +0800 Subject: [PATCH 0433/1409] feat(compiler-sfc): support namespace members type in macros --- .../compileScript/resolveType.spec.ts | 29 +++--- .../compiler-sfc/src/script/resolveType.ts | 94 ++++++++++++++----- 2 files changed, 91 insertions(+), 32 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 1485cfe83ae..38bcdf988ee 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -227,17 +227,24 @@ describe('resolveType', () => { }) }) - // test('namespace', () => { - // expect( - // resolve(` - // type T = { foo: number, bar: string, baz: boolean } - // type K = 'foo' | 'bar' - // type Target = Omit - // `).props - // ).toStrictEqual({ - // baz: ['Boolean'] - // }) - // }) + test('namespace', () => { + expect( + resolve(` + type X = string + namespace Foo { + type X = number + export namespace Bar { + export type A = { + foo: X + } + } + } + type Target = Foo.Bar.A + `).props + ).toStrictEqual({ + foo: ['Number'] + }) + }) describe('errors', () => { test('error on computed keys', () => { diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 0b6969ad8f0..f3b20a7ee15 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -1,6 +1,6 @@ import { Identifier, - Node, + Node as _Node, Statement, TSCallSignatureDeclaration, TSEnumDeclaration, @@ -8,6 +8,8 @@ import { TSFunctionType, TSMappedType, TSMethodSignature, + TSModuleBlock, + TSModuleDeclaration, TSPropertySignature, TSQualifiedName, TSType, @@ -25,23 +27,32 @@ import { Expression } from '@babel/types' export interface TypeScope { filename: string - body: Statement[] imports: Record types: Record + parent?: TypeScope +} + +interface WithScope { + _ownerScope?: TypeScope } interface ResolvedElements { - props: Record + props: Record calls?: (TSCallSignatureDeclaration | TSFunctionType)[] } +type Node = _Node & + WithScope & { + _resolvedElements?: ResolvedElements + } + /** * Resolve arbitrary type node to a list of type elements that can be then * mapped to runtime props or emits. */ export function resolveTypeElements( ctx: ScriptCompileContext, - node: Node & { _resolvedElements?: ResolvedElements } + node: Node ): ResolvedElements { if (node._resolvedElements) { return node._resolvedElements @@ -55,7 +66,7 @@ function innerResolveTypeElements( ): ResolvedElements { switch (node.type) { case 'TSTypeLiteral': - return typeElementsToMap(ctx, node.members) + return typeElementsToMap(ctx, node.members, node._ownerScope) case 'TSInterfaceDeclaration': return resolveInterfaceMembers(ctx, node) case 'TSTypeAliasDeclaration': @@ -118,11 +129,13 @@ function innerResolveTypeElements( function typeElementsToMap( ctx: ScriptCompileContext, - elements: TSTypeElement[] + elements: TSTypeElement[], + scope = ctxToScope(ctx) ): ResolvedElements { const res: ResolvedElements = { props: {} } for (const e of elements) { if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') { + ;(e as Node)._ownerScope = scope const name = e.key.type === 'Identifier' ? e.key.name @@ -190,9 +203,9 @@ function createProperty( function resolveInterfaceMembers( ctx: ScriptCompileContext, - node: TSInterfaceDeclaration + node: TSInterfaceDeclaration & WithScope ): ResolvedElements { - const base = typeElementsToMap(ctx, node.body.body) + const base = typeElementsToMap(ctx, node.body.body, node._ownerScope) if (node.extends) { for (const ext of node.extends) { const { props } = resolveTypeElements(ctx, ext) @@ -341,10 +354,22 @@ function resolveBuiltin( function resolveTypeReference( ctx: ScriptCompileContext, - node: TSTypeReference | TSExpressionWithTypeArguments, - scope = getRootScope(ctx) + node: (TSTypeReference | TSExpressionWithTypeArguments) & { + _resolvedReference?: Node + }, + scope = ctxToScope(ctx) ): Node | undefined { + if (node._resolvedReference) { + return node._resolvedReference + } const name = getReferenceName(node) + return (node._resolvedReference = innerResolveTypeReference(scope, name)) +} + +function innerResolveTypeReference( + scope: TypeScope, + name: string | string[] +): Node | undefined { if (typeof name === 'string') { if (scope.imports[name]) { // TODO external import @@ -352,8 +377,14 @@ function resolveTypeReference( return scope.types[name] } } else { - // TODO qualified name, e.g. Foo.Bar - // return resolveTypeReference() + const ns = innerResolveTypeReference(scope, name[0]) + if (ns && ns.type === 'TSModuleDeclaration') { + const childScope = moduleDeclToScope(ns, scope) + return innerResolveTypeReference( + childScope, + name.length > 2 ? name.slice(1) : name[name.length - 1] + ) + } } } @@ -376,7 +407,7 @@ function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] { } } -function getRootScope(ctx: ScriptCompileContext): TypeScope { +function ctxToScope(ctx: ScriptCompileContext): TypeScope { if (ctx.scope) { return ctx.scope } @@ -388,13 +419,34 @@ function getRootScope(ctx: ScriptCompileContext): TypeScope { return (ctx.scope = { filename: ctx.descriptor.filename, imports: ctx.userImports, - types: recordTypes(body), - body + types: recordTypes(body) }) } -function recordTypes(body: Statement[]) { - const types: Record = Object.create(null) +function moduleDeclToScope( + node: TSModuleDeclaration & { _resolvedChildScope?: TypeScope }, + parent: TypeScope +): TypeScope { + if (node._resolvedChildScope) { + return node._resolvedChildScope + } + const types: TypeScope['types'] = Object.create(parent.types) + const scope: TypeScope = { + filename: parent.filename, + imports: Object.create(parent.imports), + types: recordTypes((node.body as TSModuleBlock).body, types), + parent + } + for (const key of Object.keys(types)) { + types[key]._ownerScope = scope + } + return (node._resolvedChildScope = scope) +} + +function recordTypes( + body: Statement[], + types: Record = Object.create(null) +) { for (const s of body) { recordType(s, types) } @@ -414,8 +466,8 @@ function recordType(node: Node, types: Record) { types[node.id.name] = node.typeAnnotation break case 'ExportNamedDeclaration': { - if (node.exportKind === 'type') { - recordType(node.declaration!, types) + if (node.declaration) { + recordType(node.declaration, types) } break } @@ -437,7 +489,7 @@ function recordType(node: Node, types: Record) { export function inferRuntimeType( ctx: ScriptCompileContext, node: Node, - scope = getRootScope(ctx) + scope = node._ownerScope || ctxToScope(ctx) ): string[] { switch (node.type) { case 'TSStringKeyword': @@ -470,7 +522,7 @@ export function inferRuntimeType( } case 'TSPropertySignature': if (node.typeAnnotation) { - return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation) + return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation, scope) } case 'TSMethodSignature': case 'TSFunctionType': From 1c06fe1d021572fcdcf187794ce8313a7593ac21 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 13 Apr 2023 15:45:58 +0800 Subject: [PATCH 0434/1409] chore: improve sfc-playground typing + bump repl for 3.3 external type resolve support close #8051 --- packages/global.d.ts | 7 ------- packages/sfc-playground/package.json | 2 +- packages/sfc-playground/src/App.vue | 12 ++++++------ packages/sfc-playground/src/Header.vue | 9 +++++++-- packages/sfc-playground/src/download/download.ts | 3 ++- pnpm-lock.yaml | 8 ++++---- 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/global.d.ts b/packages/global.d.ts index 5992d0840d1..c814ad55eaa 100644 --- a/packages/global.d.ts +++ b/packages/global.d.ts @@ -33,13 +33,6 @@ declare module 'file-saver' { export function saveAs(blob: any, name: any): void } -declare module '@vue/repl' { - import { ComponentOptions } from '@vue/runtime-core' - const Repl: ComponentOptions - const ReplStore: any - export { Repl, ReplStore } -} - declare interface String { /** * @deprecated Please use String.prototype.slice instead of String.prototype.substring in the repository. diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index 3652e7ff6d3..848e3ee97b8 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -12,7 +12,7 @@ "vite": "^4.2.0" }, "dependencies": { - "@vue/repl": "^1.3.5", + "@vue/repl": "^1.4.0", "file-saver": "^2.0.5", "jszip": "^3.6.0", "vue": "workspace:*" diff --git a/packages/sfc-playground/src/App.vue b/packages/sfc-playground/src/App.vue index 1bba8d6f2ae..f7db1b7951b 100644 --- a/packages/sfc-playground/src/App.vue +++ b/packages/sfc-playground/src/App.vue @@ -1,6 +1,6 @@ ', + 'bar.vue': + '' + } + ).props + ).toStrictEqual({ + foo: ['Number'], + bar: ['String'] + }) + }) + + test('relative (chained)', () => { + expect( + resolve( + ` + import { P } from './foo' + type Target = P + `, + { + 'foo.ts': `import type { P as PP } from './nested/bar.vue' + export type P = { foo: number } & PP`, + 'nested/bar.vue': + '' + } + ).props + ).toStrictEqual({ + foo: ['Number'], + bar: ['String'] + }) + }) + + test('relative (chained, re-export)', () => { + expect( + resolve( + ` + import { PP as P } from './foo' + type Target = P + `, + { + 'foo.ts': `export { P as PP } from './bar'`, + 'bar.ts': 'export type P = { bar: string }' + } + ).props + ).toStrictEqual({ + bar: ['String'] + }) + }) + }) + describe('errors', () => { test('error on computed keys', () => { expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow( @@ -255,9 +335,26 @@ describe('resolveType', () => { }) }) -function resolve(code: string) { - const { descriptor } = parse(``) - const ctx = new ScriptCompileContext(descriptor, { id: 'test' }) +function resolve(code: string, files: Record = {}) { + const { descriptor } = parse(``, { + filename: 'Test.vue' + }) + const ctx = new ScriptCompileContext(descriptor, { + id: 'test', + fs: { + fileExists(file) { + return !!files[file] + }, + readFile(file) { + return files[file] + } + } + }) + + // ctx.userImports is collected when calling compileScript(), but we are + // skipping that here, so need to manually register imports + ctx.userImports = recordImports(ctx.scriptSetupAst!.body) as any + const targetDecl = ctx.scriptSetupAst!.body.find( s => s.type === 'TSTypeAliasDeclaration' && s.id.name === 'Target' ) as TSTypeAliasDeclaration diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index c828d77f6d5..593e8e072c6 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -2,8 +2,7 @@ import { BindingTypes, UNREF, isFunctionType, - walkIdentifiers, - getImportedName + walkIdentifiers } from '@vue/compiler-dom' import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse' import { parse as _parse, ParserPlugin } from '@babel/parser' @@ -45,7 +44,12 @@ import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose' import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions' import { processDefineSlots } from './script/defineSlots' import { DEFINE_MODEL, processDefineModel } from './script/defineModel' -import { isLiteralNode, unwrapTSNode, isCallOf } from './script/utils' +import { + isLiteralNode, + unwrapTSNode, + isCallOf, + getImportedName +} from './script/utils' import { analyzeScriptBindings } from './script/analyzeScriptBindings' import { isImportUsed } from './script/importUsageCheck' import { processAwait } from './script/topLevelAwait' @@ -106,6 +110,13 @@ export interface SFCScriptCompileOptions { * (**Experimental**) Enable macro `defineModel` */ defineModel?: boolean + /** + * + */ + fs?: { + fileExists(file: string): boolean + readFile(file: string): string + } } export interface ImportBinding { diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 718f23da5ca..1928ea900fc 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -1,13 +1,13 @@ import { Node, ObjectPattern, Program } from '@babel/types' import { SFCDescriptor } from '../parse' import { generateCodeFrame } from '@vue/shared' -import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser' +import { parse as babelParse, ParserPlugin } from '@babel/parser' import { ImportBinding, SFCScriptCompileOptions } from '../compileScript' import { PropsDestructureBindings } from './defineProps' import { ModelDecl } from './defineModel' import { BindingMetadata } from '../../../compiler-core/src' import MagicString from 'magic-string' -import { TypeScope } from './resolveType' +import { TypeScope, WithScope } from './resolveType' export class ScriptCompileContext { isJS: boolean @@ -83,31 +83,17 @@ export class ScriptCompileContext { scriptSetupLang === 'tsx' // resolve parser plugins - const plugins: ParserPlugin[] = [] - if (!this.isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') { - plugins.push('jsx') - } else { - // If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins - if (options.babelParserPlugins) - options.babelParserPlugins = options.babelParserPlugins.filter( - n => n !== 'jsx' - ) - } - if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins) - if (this.isTS) { - plugins.push('typescript') - if (!plugins.includes('decorators')) { - plugins.push('decorators-legacy') - } - } + const plugins: ParserPlugin[] = resolveParserPlugins( + (scriptLang || scriptSetupLang)!, + options.babelParserPlugins + ) - function parse( - input: string, - options: ParserOptions, - offset: number - ): Program { + function parse(input: string, offset: number): Program { try { - return babelParse(input, options).program + return babelParse(input, { + plugins, + sourceType: 'module' + }).program } catch (e: any) { e.message = `[@vue/compiler-sfc] ${e.message}\n\n${ descriptor.filename @@ -124,23 +110,12 @@ export class ScriptCompileContext { this.descriptor.script && parse( this.descriptor.script.content, - { - plugins, - sourceType: 'module' - }, this.descriptor.script.loc.start.offset ) this.scriptSetupAst = this.descriptor.scriptSetup && - parse( - this.descriptor.scriptSetup!.content, - { - plugins: [...plugins, 'topLevelAwait'], - sourceType: 'module' - }, - this.startOffset! - ) + parse(this.descriptor.scriptSetup!.content, this.startOffset!) } getString(node: Node, scriptSetup = true): string { @@ -150,19 +125,39 @@ export class ScriptCompileContext { return block.content.slice(node.start!, node.end!) } - error( - msg: string, - node: Node, - end: number = node.end! + this.startOffset! - ): never { + error(msg: string, node: Node & WithScope, scope?: TypeScope): never { throw new Error( `[@vue/compiler-sfc] ${msg}\n\n${ this.descriptor.filename }\n${generateCodeFrame( this.descriptor.source, node.start! + this.startOffset!, - end + node.end! + this.startOffset! )}` ) } } + +export function resolveParserPlugins( + lang: string, + userPlugins?: ParserPlugin[] +) { + const plugins: ParserPlugin[] = [] + if (lang === 'jsx' || lang === 'tsx') { + plugins.push('jsx') + } else if (userPlugins) { + // If don't match the case of adding jsx + // should remove the jsx from user options + userPlugins = userPlugins.filter(p => p !== 'jsx') + } + if (lang === 'ts' || lang === 'tsx') { + plugins.push('typescript') + if (!plugins.includes('decorators')) { + plugins.push('decorators-legacy') + } + } + if (userPlugins) { + plugins.push(...userPlugins) + } + return plugins +} diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index f3b20a7ee15..1e82d2c8326 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -1,11 +1,13 @@ import { + Expression, Identifier, - Node as _Node, + Node, Statement, TSCallSignatureDeclaration, TSEnumDeclaration, TSExpressionWithTypeArguments, TSFunctionType, + TSInterfaceDeclaration, TSMappedType, TSMethodSignature, TSModuleBlock, @@ -18,81 +20,108 @@ import { TSTypeReference, TemplateLiteral } from '@babel/types' -import { UNKNOWN_TYPE } from './utils' -import { ScriptCompileContext } from './context' -import { ImportBinding } from '../compileScript' -import { TSInterfaceDeclaration } from '@babel/types' +import { UNKNOWN_TYPE, getId, getImportedName } from './utils' +import { ScriptCompileContext, resolveParserPlugins } from './context' +import { ImportBinding, SFCScriptCompileOptions } from '../compileScript' import { capitalize, hasOwn } from '@vue/shared' -import { Expression } from '@babel/types' +import path from 'path' +import { parse as babelParse } from '@babel/parser' +import { parse } from '../parse' + +type Import = Pick export interface TypeScope { filename: string - imports: Record - types: Record - parent?: TypeScope + source: string + imports: Record + types: Record< + string, + Node & { + // scope types always has ownerScope attached + _ownerScope: TypeScope + } + > + exportedTypes: Record< + string, + Node & { + // scope types always has ownerScope attached + _ownerScope: TypeScope + } + > } -interface WithScope { +export interface WithScope { _ownerScope?: TypeScope } interface ResolvedElements { - props: Record + props: Record< + string, + (TSPropertySignature | TSMethodSignature) & { + // resolved props always has ownerScope attached + _ownerScope: TypeScope + } + > calls?: (TSCallSignatureDeclaration | TSFunctionType)[] } -type Node = _Node & - WithScope & { - _resolvedElements?: ResolvedElements - } - /** * Resolve arbitrary type node to a list of type elements that can be then * mapped to runtime props or emits. */ export function resolveTypeElements( ctx: ScriptCompileContext, - node: Node + node: Node & WithScope & { _resolvedElements?: ResolvedElements }, + scope?: TypeScope ): ResolvedElements { if (node._resolvedElements) { return node._resolvedElements } - return (node._resolvedElements = innerResolveTypeElements(ctx, node)) + return (node._resolvedElements = innerResolveTypeElements( + ctx, + node, + node._ownerScope || scope || ctxToScope(ctx) + )) } function innerResolveTypeElements( ctx: ScriptCompileContext, - node: Node + node: Node, + scope: TypeScope ): ResolvedElements { switch (node.type) { case 'TSTypeLiteral': - return typeElementsToMap(ctx, node.members, node._ownerScope) + return typeElementsToMap(ctx, node.members, scope) case 'TSInterfaceDeclaration': - return resolveInterfaceMembers(ctx, node) + return resolveInterfaceMembers(ctx, node, scope) case 'TSTypeAliasDeclaration': case 'TSParenthesizedType': - return resolveTypeElements(ctx, node.typeAnnotation) + return resolveTypeElements(ctx, node.typeAnnotation, scope) case 'TSFunctionType': { return { props: {}, calls: [node] } } case 'TSUnionType': case 'TSIntersectionType': return mergeElements( - node.types.map(t => resolveTypeElements(ctx, t)), + node.types.map(t => resolveTypeElements(ctx, t, scope)), node.type ) case 'TSMappedType': - return resolveMappedType(ctx, node) + return resolveMappedType(ctx, node, scope) case 'TSIndexedAccessType': { if ( node.indexType.type === 'TSLiteralType' && node.indexType.literal.type === 'StringLiteral' ) { - const resolved = resolveTypeElements(ctx, node.objectType) + const resolved = resolveTypeElements(ctx, node.objectType, scope) const key = node.indexType.literal.value const targetType = resolved.props[key].typeAnnotation if (targetType) { - return resolveTypeElements(ctx, targetType.typeAnnotation) + return resolveTypeElements( + ctx, + targetType.typeAnnotation, + resolved.props[key]._ownerScope + ) } else { break } @@ -105,9 +134,9 @@ function innerResolveTypeElements( } case 'TSExpressionWithTypeArguments': // referenced by interface extends case 'TSTypeReference': { - const resolved = resolveTypeReference(ctx, node) + const resolved = resolveTypeReference(ctx, node, scope) if (resolved) { - return resolveTypeElements(ctx, resolved) + return resolveTypeElements(ctx, resolved, resolved._ownerScope) } else { const typeName = getReferenceName(node) if ( @@ -118,7 +147,7 @@ function innerResolveTypeElements( return resolveBuiltin(ctx, node, typeName as any) } ctx.error( - `Failed to resolved type reference, or unsupported built-in utlility type.`, + `Failed to resolve type reference, or unsupported built-in utlility type.`, node ) } @@ -135,18 +164,13 @@ function typeElementsToMap( const res: ResolvedElements = { props: {} } for (const e of elements) { if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') { - ;(e as Node)._ownerScope = scope - const name = - e.key.type === 'Identifier' - ? e.key.name - : e.key.type === 'StringLiteral' - ? e.key.value - : null + ;(e as WithScope)._ownerScope = scope + const name = getId(e.key) if (name && !e.computed) { - res.props[name] = e + res.props[name] = e as ResolvedElements['props'][string] } else if (e.key.type === 'TemplateLiteral') { for (const key of resolveTemplateKeys(ctx, e.key)) { - res.props[key] = e + res.props[key] = e as ResolvedElements['props'][string] } } else { ctx.error( @@ -172,11 +196,15 @@ function mergeElements( if (!hasOwn(baseProps, key)) { baseProps[key] = props[key] } else { - baseProps[key] = createProperty(baseProps[key].key, { - type, - // @ts-ignore - types: [baseProps[key], props[key]] - }) + baseProps[key] = createProperty( + baseProps[key].key, + { + type, + // @ts-ignore + types: [baseProps[key], props[key]] + }, + baseProps[key]._ownerScope + ) } } if (calls) { @@ -188,8 +216,9 @@ function mergeElements( function createProperty( key: Expression, - typeAnnotation: TSType -): TSPropertySignature { + typeAnnotation: TSType, + scope: TypeScope +): TSPropertySignature & { _ownerScope: TypeScope } { return { type: 'TSPropertySignature', key, @@ -197,18 +226,20 @@ function createProperty( typeAnnotation: { type: 'TSTypeAnnotation', typeAnnotation - } + }, + _ownerScope: scope } } function resolveInterfaceMembers( ctx: ScriptCompileContext, - node: TSInterfaceDeclaration & WithScope + node: TSInterfaceDeclaration & WithScope, + scope: TypeScope ): ResolvedElements { const base = typeElementsToMap(ctx, node.body.body, node._ownerScope) if (node.extends) { for (const ext of node.extends) { - const { props } = resolveTypeElements(ctx, ext) + const { props } = resolveTypeElements(ctx, ext, scope) for (const key in props) { if (!hasOwn(base.props, key)) { base.props[key] = props[key] @@ -221,7 +252,8 @@ function resolveInterfaceMembers( function resolveMappedType( ctx: ScriptCompileContext, - node: TSMappedType + node: TSMappedType, + scope: TypeScope ): ResolvedElements { const res: ResolvedElements = { props: {} } if (!node.typeParameter.constraint) { @@ -234,7 +266,8 @@ function resolveMappedType( type: 'Identifier', name: key }, - node.typeAnnotation! + node.typeAnnotation!, + scope ) } return res @@ -357,32 +390,52 @@ function resolveTypeReference( node: (TSTypeReference | TSExpressionWithTypeArguments) & { _resolvedReference?: Node }, - scope = ctxToScope(ctx) -): Node | undefined { + scope?: TypeScope, + name?: string, + onlyExported = false +): (Node & WithScope) | undefined { if (node._resolvedReference) { return node._resolvedReference } - const name = getReferenceName(node) - return (node._resolvedReference = innerResolveTypeReference(scope, name)) + return (node._resolvedReference = innerResolveTypeReference( + ctx, + scope || ctxToScope(ctx), + name || getReferenceName(node), + node, + onlyExported + )) } function innerResolveTypeReference( + ctx: ScriptCompileContext, scope: TypeScope, - name: string | string[] + name: string | string[], + node: TSTypeReference | TSExpressionWithTypeArguments, + onlyExported: boolean ): Node | undefined { if (typeof name === 'string') { if (scope.imports[name]) { - // TODO external import - } else if (scope.types[name]) { - return scope.types[name] + return resolveTypeFromImport(ctx, scope, scope.imports[name], node) + } else { + const types = onlyExported ? scope.exportedTypes : scope.types + return types[name] } } else { - const ns = innerResolveTypeReference(scope, name[0]) + const ns = innerResolveTypeReference( + ctx, + scope, + name[0], + node, + onlyExported + ) if (ns && ns.type === 'TSModuleDeclaration') { const childScope = moduleDeclToScope(ns, scope) return innerResolveTypeReference( + ctx, childScope, - name.length > 2 ? name.slice(1) : name[name.length - 1] + name.length > 2 ? name.slice(1) : name[name.length - 1], + node, + true ) } } @@ -407,20 +460,125 @@ function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] { } } +function resolveTypeFromImport( + ctx: ScriptCompileContext, + scope: TypeScope, + { source, imported }: Import, + node: TSTypeReference | TSExpressionWithTypeArguments +): Node | undefined { + const fs = ctx.options.fs + if (!fs) { + ctx.error( + `fs options for compileScript are required for resolving imported types`, + node + ) + } + // TODO (hmr) register dependency file on ctx + const containingFile = scope.filename + if (source.startsWith('.')) { + // relative import - fast path + const filename = path.join(containingFile, '..', source) + const resolved = resolveExt(filename, fs) + if (resolved) { + return resolveTypeReference( + ctx, + node, + fileToScope(ctx, resolved, fs), + imported, + true + ) + } else { + ctx.error(`Failed to resolve import source for type`, node) + } + } else { + // TODO module or aliased import - use full TS resolution + return + } +} + +function resolveExt( + filename: string, + fs: NonNullable +) { + const tryResolve = (filename: string) => { + if (fs.fileExists(filename)) return filename + } + return ( + tryResolve(filename) || + tryResolve(filename + `.ts`) || + tryResolve(filename + `.d.ts`) || + tryResolve(filename + `/index.ts`) || + tryResolve(filename + `/index.d.ts`) + ) +} + +function fileToScope( + ctx: ScriptCompileContext, + filename: string, + fs: NonNullable +): TypeScope { + // TODO cache + const source = fs.readFile(filename) + const body = parseFile(ctx, filename, source) + const scope: TypeScope = { + filename, + source, + types: Object.create(null), + exportedTypes: Object.create(null), + imports: recordImports(body) + } + recordTypes(body, scope) + return scope +} + +function parseFile( + ctx: ScriptCompileContext, + filename: string, + content: string +): Statement[] { + const ext = path.extname(filename) + if (ext === '.ts' || ext === '.tsx') { + return babelParse(content, { + plugins: resolveParserPlugins( + ext.slice(1), + ctx.options.babelParserPlugins + ), + sourceType: 'module' + }).program.body + } else if (ext === '.vue') { + const { + descriptor: { script, scriptSetup } + } = parse(content) + const scriptContent = (script?.content || '') + (scriptSetup?.content || '') + const lang = script?.lang || scriptSetup?.lang + return babelParse(scriptContent, { + plugins: resolveParserPlugins(lang!, ctx.options.babelParserPlugins), + sourceType: 'module' + }).program.body + } + return [] +} + function ctxToScope(ctx: ScriptCompileContext): TypeScope { if (ctx.scope) { return ctx.scope } + const scope: TypeScope = { + filename: ctx.descriptor.filename, + source: ctx.descriptor.source, + imports: Object.create(ctx.userImports), + types: Object.create(null), + exportedTypes: Object.create(null) + } + const body = ctx.scriptAst ? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body] : ctx.scriptSetupAst!.body - return (ctx.scope = { - filename: ctx.descriptor.filename, - imports: ctx.userImports, - types: recordTypes(body) - }) + recordTypes(body, scope) + + return (ctx.scope = scope) } function moduleDeclToScope( @@ -430,27 +588,56 @@ function moduleDeclToScope( if (node._resolvedChildScope) { return node._resolvedChildScope } - const types: TypeScope['types'] = Object.create(parent.types) const scope: TypeScope = { - filename: parent.filename, - imports: Object.create(parent.imports), - types: recordTypes((node.body as TSModuleBlock).body, types), - parent - } - for (const key of Object.keys(types)) { - types[key]._ownerScope = scope + ...parent, + types: Object.create(parent.types), + imports: Object.create(parent.imports) } + recordTypes((node.body as TSModuleBlock).body, scope) return (node._resolvedChildScope = scope) } -function recordTypes( - body: Statement[], - types: Record = Object.create(null) -) { - for (const s of body) { - recordType(s, types) +function recordTypes(body: Statement[], scope: TypeScope) { + const { types, exportedTypes, imports } = scope + for (const stmt of body) { + recordType(stmt, types) + } + for (const stmt of body) { + if (stmt.type === 'ExportNamedDeclaration') { + if (stmt.declaration) { + recordType(stmt.declaration, types) + recordType(stmt.declaration, exportedTypes) + } else { + for (const spec of stmt.specifiers) { + if (spec.type === 'ExportSpecifier') { + const local = spec.local.name + const exported = getId(spec.exported) + if (stmt.source) { + // re-export, register an import + export as a type reference + imports[local] = { + source: stmt.source.value, + imported: local + } + exportedTypes[exported] = { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: local + }, + _ownerScope: scope + } + } else if (types[local]) { + // exporting local defined type + exportedTypes[exported] = types[local] + } + } + } + } + } + } + for (const key of Object.keys(types)) { + types[key]._ownerScope = scope } - return types } function recordType(node: Node, types: Record) { @@ -465,12 +652,6 @@ function recordType(node: Node, types: Record) { case 'TSTypeAliasDeclaration': types[node.id.name] = node.typeAnnotation break - case 'ExportNamedDeclaration': { - if (node.declaration) { - recordType(node.declaration, types) - } - break - } case 'VariableDeclaration': { if (node.declare) { for (const decl of node.declarations) { @@ -486,9 +667,29 @@ function recordType(node: Node, types: Record) { } } +export function recordImports(body: Statement[]) { + const imports: TypeScope['imports'] = Object.create(null) + for (const s of body) { + recordImport(s, imports) + } + return imports +} + +function recordImport(node: Node, imports: TypeScope['imports']) { + if (node.type !== 'ImportDeclaration') { + return + } + for (const s of node.specifiers) { + imports[s.local.name] = { + imported: getImportedName(s), + source: node.source.value + } + } +} + export function inferRuntimeType( ctx: ScriptCompileContext, - node: Node, + node: Node & WithScope, scope = node._ownerScope || ctxToScope(ctx) ): string[] { switch (node.type) { diff --git a/packages/compiler-sfc/src/script/utils.ts b/packages/compiler-sfc/src/script/utils.ts index 11bc011820e..780c780e2cc 100644 --- a/packages/compiler-sfc/src/script/utils.ts +++ b/packages/compiler-sfc/src/script/utils.ts @@ -1,4 +1,13 @@ -import { CallExpression, Node } from '@babel/types' +import { + CallExpression, + Expression, + Identifier, + ImportDefaultSpecifier, + ImportNamespaceSpecifier, + ImportSpecifier, + Node, + StringLiteral +} from '@babel/types' import { TS_NODE_TYPES } from '@vue/compiler-dom' export const UNKNOWN_TYPE = 'Unknown' @@ -48,3 +57,24 @@ export function isCallOf( export function toRuntimeTypeString(types: string[]) { return types.length > 1 ? `[${types.join(', ')}]` : types[0] } + +export function getImportedName( + specifier: ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier +) { + if (specifier.type === 'ImportSpecifier') + return specifier.imported.type === 'Identifier' + ? specifier.imported.name + : specifier.imported.value + else if (specifier.type === 'ImportNamespaceSpecifier') return '*' + return 'default' +} + +export function getId(node: Identifier | StringLiteral): string +export function getId(node: Expression): string | null +export function getId(node: Expression) { + return node.type === 'Identifier' + ? node.name + : node.type === 'StringLiteral' + ? node.value + : null +} From c93c11710e8a7572dee9f737cfba56cf0e2eb3f4 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 13 Apr 2023 21:36:23 +0800 Subject: [PATCH 0436/1409] refactor: improve type resolve error output --- .../compileScript/resolveType.spec.ts | 24 +++- packages/compiler-sfc/src/script/context.ts | 9 +- .../compiler-sfc/src/script/resolveType.ts | 116 +++++++++++------- 3 files changed, 100 insertions(+), 49 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 7f25ae4888d..c686a99ff93 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -327,16 +327,34 @@ describe('resolveType', () => { }) describe('errors', () => { - test('error on computed keys', () => { + test('failed type reference', () => { + expect(() => resolve(`type Target = X`)).toThrow( + `Unresolvable type reference` + ) + }) + + test('unsupported computed keys', () => { expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow( - `computed keys are not supported in types referenced by SFC macros` + `Unsupported computed key in type referenced by a macro` + ) + }) + + test('unsupported index type', () => { + expect(() => resolve(`type Target = X[K]`)).toThrow( + `Unsupported index type` ) }) + + test('failed improt source resolve', () => { + expect(() => + resolve(`import { X } from './foo'; type Target = X`) + ).toThrow(`Failed to resolve import source "./foo" for type X`) + }) }) }) function resolve(code: string, files: Record = {}) { - const { descriptor } = parse(``, { + const { descriptor } = parse(``, { filename: 'Test.vue' }) const ctx = new ScriptCompileContext(descriptor, { diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 1928ea900fc..efdf1368c34 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -126,13 +126,14 @@ export class ScriptCompileContext { } error(msg: string, node: Node & WithScope, scope?: TypeScope): never { + const offset = scope ? scope.offset || 0 : this.startOffset! throw new Error( `[@vue/compiler-sfc] ${msg}\n\n${ - this.descriptor.filename + (scope || this.descriptor).filename }\n${generateCodeFrame( - this.descriptor.source, - node.start! + this.startOffset!, - node.end! + this.startOffset! + (scope || this.descriptor).source, + node.start! + offset, + node.end! + offset )}` ) } diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 1e82d2c8326..f885b970212 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -33,6 +33,7 @@ type Import = Pick export interface TypeScope { filename: string source: string + offset: number imports: Record types: Record< string, @@ -128,7 +129,8 @@ function innerResolveTypeElements( } else { ctx.error( `Unsupported index type: ${node.indexType.type}`, - node.indexType + node.indexType, + scope ) } } @@ -144,16 +146,17 @@ function innerResolveTypeElements( // @ts-ignore SupportedBuiltinsSet.has(typeName) ) { - return resolveBuiltin(ctx, node, typeName as any) + return resolveBuiltin(ctx, node, typeName as any, scope) } ctx.error( - `Failed to resolve type reference, or unsupported built-in utlility type.`, - node + `Unresolvable type reference or unsupported built-in utlility type`, + node, + scope ) } } } - ctx.error(`Unresolvable type in SFC macro: ${node.type}`, node) + ctx.error(`Unresolvable type: ${node.type}`, node, scope) } function typeElementsToMap( @@ -169,13 +172,14 @@ function typeElementsToMap( if (name && !e.computed) { res.props[name] = e as ResolvedElements['props'][string] } else if (e.key.type === 'TemplateLiteral') { - for (const key of resolveTemplateKeys(ctx, e.key)) { + for (const key of resolveTemplateKeys(ctx, e.key, scope)) { res.props[key] = e as ResolvedElements['props'][string] } } else { ctx.error( - `computed keys are not supported in types referenced by SFC macros.`, - e + `Unsupported computed key in type referenced by a macro`, + e.key, + scope ) } } else if (e.type === 'TSCallSignatureDeclaration') { @@ -256,10 +260,7 @@ function resolveMappedType( scope: TypeScope ): ResolvedElements { const res: ResolvedElements = { props: {} } - if (!node.typeParameter.constraint) { - ctx.error(`mapped type used in macros must have a finite constraint.`, node) - } - const keys = resolveStringType(ctx, node.typeParameter.constraint) + const keys = resolveStringType(ctx, node.typeParameter.constraint!, scope) for (const key of keys) { res.props[key] = createProperty( { @@ -273,25 +274,29 @@ function resolveMappedType( return res } -function resolveStringType(ctx: ScriptCompileContext, node: Node): string[] { +function resolveStringType( + ctx: ScriptCompileContext, + node: Node, + scope: TypeScope +): string[] { switch (node.type) { case 'StringLiteral': return [node.value] case 'TSLiteralType': - return resolveStringType(ctx, node.literal) + return resolveStringType(ctx, node.literal, scope) case 'TSUnionType': - return node.types.map(t => resolveStringType(ctx, t)).flat() + return node.types.map(t => resolveStringType(ctx, t, scope)).flat() case 'TemplateLiteral': { - return resolveTemplateKeys(ctx, node) + return resolveTemplateKeys(ctx, node, scope) } case 'TSTypeReference': { - const resolved = resolveTypeReference(ctx, node) + const resolved = resolveTypeReference(ctx, node, scope) if (resolved) { - return resolveStringType(ctx, resolved) + return resolveStringType(ctx, resolved, scope) } if (node.typeName.type === 'Identifier') { const getParam = (index = 0) => - resolveStringType(ctx, node.typeParameters!.params[index]) + resolveStringType(ctx, node.typeParameters!.params[index], scope) switch (node.typeName.name) { case 'Extract': return getParam(1) @@ -308,17 +313,18 @@ function resolveStringType(ctx: ScriptCompileContext, node: Node): string[] { case 'Uncapitalize': return getParam().map(s => s[0].toLowerCase() + s.slice(1)) default: - ctx.error('Failed to resolve type reference', node) + ctx.error('Failed to resolve type reference', node, scope) } } } } - ctx.error('Failed to resolve string type into finite keys', node) + ctx.error('Failed to resolve string type into finite keys', node, scope) } function resolveTemplateKeys( ctx: ScriptCompileContext, - node: TemplateLiteral + node: TemplateLiteral, + scope: TypeScope ): string[] { if (!node.expressions.length) { return [node.quasis[0].value.raw] @@ -328,12 +334,16 @@ function resolveTemplateKeys( const e = node.expressions[0] const q = node.quasis[0] const leading = q ? q.value.raw : `` - const resolved = resolveStringType(ctx, e) - const restResolved = resolveTemplateKeys(ctx, { - ...node, - expressions: node.expressions.slice(1), - quasis: q ? node.quasis.slice(1) : node.quasis - }) + const resolved = resolveStringType(ctx, e, scope) + const restResolved = resolveTemplateKeys( + ctx, + { + ...node, + expressions: node.expressions.slice(1), + quasis: q ? node.quasis.slice(1) : node.quasis + }, + scope + ) for (const r of resolved) { for (const rr of restResolved) { @@ -357,7 +367,8 @@ type GetSetType = T extends Set ? V : never function resolveBuiltin( ctx: ScriptCompileContext, node: TSTypeReference | TSExpressionWithTypeArguments, - name: GetSetType + name: GetSetType, + scope: TypeScope ): ResolvedElements { const t = resolveTypeElements(ctx, node.typeParameters!.params[0]) switch (name) { @@ -366,7 +377,11 @@ function resolveBuiltin( case 'Readonly': return t case 'Pick': { - const picked = resolveStringType(ctx, node.typeParameters!.params[1]) + const picked = resolveStringType( + ctx, + node.typeParameters!.params[1], + scope + ) const res: ResolvedElements = { props: {}, calls: t.calls } for (const key of picked) { res.props[key] = t.props[key] @@ -374,7 +389,11 @@ function resolveBuiltin( return res } case 'Omit': - const omitted = resolveStringType(ctx, node.typeParameters!.params[1]) + const omitted = resolveStringType( + ctx, + node.typeParameters!.params[1], + scope + ) const res: ResolvedElements = { props: {}, calls: t.calls } for (const key in t.props) { if (!omitted.includes(key)) { @@ -415,7 +434,7 @@ function innerResolveTypeReference( ): Node | undefined { if (typeof name === 'string') { if (scope.imports[name]) { - return resolveTypeFromImport(ctx, scope, scope.imports[name], node) + return resolveTypeFromImport(ctx, node, name, scope) } else { const types = onlyExported ? scope.exportedTypes : scope.types return types[name] @@ -462,19 +481,21 @@ function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] { function resolveTypeFromImport( ctx: ScriptCompileContext, - scope: TypeScope, - { source, imported }: Import, - node: TSTypeReference | TSExpressionWithTypeArguments + node: TSTypeReference | TSExpressionWithTypeArguments, + name: string, + scope: TypeScope ): Node | undefined { const fs = ctx.options.fs if (!fs) { ctx.error( `fs options for compileScript are required for resolving imported types`, - node + node, + scope ) } // TODO (hmr) register dependency file on ctx const containingFile = scope.filename + const { source, imported } = scope.imports[name] if (source.startsWith('.')) { // relative import - fast path const filename = path.join(containingFile, '..', source) @@ -488,7 +509,13 @@ function resolveTypeFromImport( true ) } else { - ctx.error(`Failed to resolve import source for type`, node) + ctx.error( + `Failed to resolve import source ${JSON.stringify( + source + )} for type ${name}`, + node, + scope + ) } } else { // TODO module or aliased import - use full TS resolution @@ -519,10 +546,11 @@ function fileToScope( ): TypeScope { // TODO cache const source = fs.readFile(filename) - const body = parseFile(ctx, filename, source) + const [body, offset] = parseFile(ctx, filename, source) const scope: TypeScope = { filename, source, + offset, types: Object.create(null), exportedTypes: Object.create(null), imports: recordImports(body) @@ -535,10 +563,12 @@ function parseFile( ctx: ScriptCompileContext, filename: string, content: string -): Statement[] { +): [Statement[], number] { + let body: Statement[] = [] + let offset = 0 const ext = path.extname(filename) if (ext === '.ts' || ext === '.tsx') { - return babelParse(content, { + body = babelParse(content, { plugins: resolveParserPlugins( ext.slice(1), ctx.options.babelParserPlugins @@ -551,12 +581,13 @@ function parseFile( } = parse(content) const scriptContent = (script?.content || '') + (scriptSetup?.content || '') const lang = script?.lang || scriptSetup?.lang - return babelParse(scriptContent, { + body = babelParse(scriptContent, { plugins: resolveParserPlugins(lang!, ctx.options.babelParserPlugins), sourceType: 'module' }).program.body + offset = scriptSetup ? scriptSetup.loc.start.offset : 0 } - return [] + return [body, offset] } function ctxToScope(ctx: ScriptCompileContext): TypeScope { @@ -567,6 +598,7 @@ function ctxToScope(ctx: ScriptCompileContext): TypeScope { const scope: TypeScope = { filename: ctx.descriptor.filename, source: ctx.descriptor.source, + offset: ctx.startOffset!, imports: Object.create(ctx.userImports), types: Object.create(null), exportedTypes: Object.create(null) From 8451b92a7a231a8c4b8e936f0918e321d38ce690 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 13 Apr 2023 22:38:00 +0800 Subject: [PATCH 0437/1409] wip: cache fileToScope + improve vue file offset --- .../compileScript/resolveType.spec.ts | 5 ++ packages/compiler-sfc/src/cache.ts | 10 ++-- packages/compiler-sfc/src/index.ts | 1 + packages/compiler-sfc/src/script/context.ts | 2 +- .../compiler-sfc/src/script/resolveType.ts | 51 +++++++++++++++---- 5 files changed, 54 insertions(+), 15 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index c686a99ff93..3e2a5ee1776 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -3,6 +3,7 @@ import { parse } from '../../src' import { ScriptCompileContext } from '../../src/script/context' import { inferRuntimeType, + invalidateTypeCache, recordImports, resolveTypeElements } from '../../src/script/resolveType' @@ -369,6 +370,10 @@ function resolve(code: string, files: Record = {}) { } }) + for (const file in files) { + invalidateTypeCache(file) + } + // ctx.userImports is collected when calling compileScript(), but we are // skipping that here, so need to manually register imports ctx.userImports = recordImports(ctx.scriptSetupAst!.body) as any diff --git a/packages/compiler-sfc/src/cache.ts b/packages/compiler-sfc/src/cache.ts index 510dfee3547..eb6bad0f86d 100644 --- a/packages/compiler-sfc/src/cache.ts +++ b/packages/compiler-sfc/src/cache.ts @@ -1,7 +1,11 @@ import LRU from 'lru-cache' export function createCache(size = 500) { - return __GLOBAL__ || __ESM_BROWSER__ - ? new Map() - : (new LRU(size) as any as Map) + if (__GLOBAL__ || __ESM_BROWSER__) { + return new Map() + } + const cache = new LRU(size) + // @ts-expect-error + cache.delete = cache.del.bind(cache) + return cache as any as Map } diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index 6ba097b2466..0b936553a32 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -6,6 +6,7 @@ export { compileTemplate } from './compileTemplate' export { compileStyle, compileStyleAsync } from './compileStyle' export { compileScript } from './compileScript' export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault' +export { invalidateTypeCache } from './script/resolveType' export { shouldTransform as shouldTransformRef, transform as transformRef, diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index efdf1368c34..ec6bbe8d0f1 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -126,7 +126,7 @@ export class ScriptCompileContext { } error(msg: string, node: Node & WithScope, scope?: TypeScope): never { - const offset = scope ? scope.offset || 0 : this.startOffset! + const offset = scope ? scope.offset : this.startOffset! throw new Error( `[@vue/compiler-sfc] ${msg}\n\n${ (scope || this.descriptor).filename diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index f885b970212..c48e192f641 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -27,6 +27,7 @@ import { capitalize, hasOwn } from '@vue/shared' import path from 'path' import { parse as babelParse } from '@babel/parser' import { parse } from '../parse' +import { createCache } from '../cache' type Import = Pick @@ -539,23 +540,35 @@ function resolveExt( ) } +const fileToScopeCache = createCache() + +export function invalidateTypeCache(filename: string) { + fileToScopeCache.delete(filename) +} + function fileToScope( ctx: ScriptCompileContext, filename: string, fs: NonNullable ): TypeScope { - // TODO cache + const cached = fileToScopeCache.get(filename) + if (cached) { + return cached + } + const source = fs.readFile(filename) - const [body, offset] = parseFile(ctx, filename, source) + const body = parseFile(ctx, filename, source) const scope: TypeScope = { filename, source, - offset, + offset: 0, types: Object.create(null), exportedTypes: Object.create(null), imports: recordImports(body) } recordTypes(body, scope) + + fileToScopeCache.set(filename, scope) return scope } @@ -563,12 +576,10 @@ function parseFile( ctx: ScriptCompileContext, filename: string, content: string -): [Statement[], number] { - let body: Statement[] = [] - let offset = 0 +): Statement[] { const ext = path.extname(filename) if (ext === '.ts' || ext === '.tsx') { - body = babelParse(content, { + return babelParse(content, { plugins: resolveParserPlugins( ext.slice(1), ctx.options.babelParserPlugins @@ -579,15 +590,33 @@ function parseFile( const { descriptor: { script, scriptSetup } } = parse(content) - const scriptContent = (script?.content || '') + (scriptSetup?.content || '') + if (!script && !scriptSetup) { + return [] + } + + // ensure the correct offset with original source + const scriptOffset = script ? script.loc.start.offset : Infinity + const scriptSetupOffset = scriptSetup + ? scriptSetup.loc.start.offset + : Infinity + const firstBlock = scriptOffset < scriptSetupOffset ? script : scriptSetup + const secondBlock = scriptOffset < scriptSetupOffset ? scriptSetup : script + + let scriptContent = + ' '.repeat(Math.min(scriptOffset, scriptSetupOffset)) + + firstBlock!.content + if (secondBlock) { + scriptContent += + ' '.repeat(secondBlock.loc.start.offset - script!.loc.end.offset) + + secondBlock.content + } const lang = script?.lang || scriptSetup?.lang - body = babelParse(scriptContent, { + return babelParse(scriptContent, { plugins: resolveParserPlugins(lang!, ctx.options.babelParserPlugins), sourceType: 'module' }).program.body - offset = scriptSetup ? scriptSetup.loc.start.offset : 0 } - return [body, offset] + return [] } function ctxToScope(ctx: ScriptCompileContext): TypeScope { From 3982bef533b451d1b59fa243560184a13fe8c18c Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 14 Apr 2023 17:27:50 +0800 Subject: [PATCH 0438/1409] feat(compiler-sfc): support resolving type imports from modules --- .../compileScript/resolveType.spec.ts | 68 ++++++-- packages/compiler-sfc/src/compileScript.ts | 2 +- packages/compiler-sfc/src/index.ts | 4 +- .../compiler-sfc/src/script/resolveType.ts | 154 ++++++++++++++---- packages/compiler-sfc/src/script/utils.ts | 19 +++ packages/sfc-playground/src/Header.vue | 2 +- packages/sfc-playground/vite.config.ts | 13 +- packages/vue/compiler-sfc/index.js | 2 + packages/vue/compiler-sfc/index.mjs | 4 +- packages/vue/compiler-sfc/package.json | 2 +- packages/vue/compiler-sfc/register-ts.js | 5 + 11 files changed, 231 insertions(+), 44 deletions(-) create mode 100644 packages/vue/compiler-sfc/register-ts.js diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 3e2a5ee1776..6045cbd3d7a 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -5,9 +5,13 @@ import { inferRuntimeType, invalidateTypeCache, recordImports, - resolveTypeElements + resolveTypeElements, + registerTS } from '../../src/script/resolveType' +import ts from 'typescript' +registerTS(ts) + describe('resolveType', () => { test('type literal', () => { const { props, calls } = resolve(`type Target = { @@ -86,6 +90,19 @@ describe('resolveType', () => { }) }) + test('reference class', () => { + expect( + resolve(` + class Foo {} + type Target = { + foo: Foo + } + `).props + ).toStrictEqual({ + foo: ['Object'] + }) + }) + test('function type', () => { expect( resolve(` @@ -258,8 +275,8 @@ describe('resolveType', () => { type Target = P & PP `, { - 'foo.ts': 'export type P = { foo: number }', - 'bar.d.ts': 'type X = { bar: string }; export { X as Y }' + '/foo.ts': 'export type P = { foo: number }', + '/bar.d.ts': 'type X = { bar: string }; export { X as Y }' } ).props ).toStrictEqual({ @@ -277,9 +294,9 @@ describe('resolveType', () => { type Target = P & PP `, { - 'foo.vue': + '/foo.vue': '', - 'bar.vue': + '/bar.vue': '' } ).props @@ -297,9 +314,9 @@ describe('resolveType', () => { type Target = P `, { - 'foo.ts': `import type { P as PP } from './nested/bar.vue' + '/foo.ts': `import type { P as PP } from './nested/bar.vue' export type P = { foo: number } & PP`, - 'nested/bar.vue': + '/nested/bar.vue': '' } ).props @@ -317,11 +334,42 @@ describe('resolveType', () => { type Target = P `, { - 'foo.ts': `export { P as PP } from './bar'`, - 'bar.ts': 'export type P = { bar: string }' + '/foo.ts': `export { P as PP } from './bar'`, + '/bar.ts': 'export type P = { bar: string }' + } + ).props + ).toStrictEqual({ + bar: ['String'] + }) + }) + + test('ts module resolve', () => { + expect( + resolve( + ` + import { P } from 'foo' + import { PP } from 'bar' + type Target = P & PP + `, + { + '/node_modules/foo/package.json': JSON.stringify({ + name: 'foo', + version: '1.0.0', + types: 'index.d.ts' + }), + '/node_modules/foo/index.d.ts': 'export type P = { foo: number }', + '/tsconfig.json': JSON.stringify({ + compilerOptions: { + paths: { + bar: ['./other/bar.ts'] + } + } + }), + '/other/bar.ts': 'export type PP = { bar: string }' } ).props ).toStrictEqual({ + foo: ['Number'], bar: ['String'] }) }) @@ -356,7 +404,7 @@ describe('resolveType', () => { function resolve(code: string, files: Record = {}) { const { descriptor } = parse(``, { - filename: 'Test.vue' + filename: '/Test.vue' }) const ctx = new ScriptCompileContext(descriptor, { id: 'test', diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 593e8e072c6..989f61cb444 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -115,7 +115,7 @@ export interface SFCScriptCompileOptions { */ fs?: { fileExists(file: string): boolean - readFile(file: string): string + readFile(file: string): string | undefined } } diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index 0b936553a32..e171ac0885c 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -6,7 +6,6 @@ export { compileTemplate } from './compileTemplate' export { compileStyle, compileStyleAsync } from './compileStyle' export { compileScript } from './compileScript' export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault' -export { invalidateTypeCache } from './script/resolveType' export { shouldTransform as shouldTransformRef, transform as transformRef, @@ -29,6 +28,9 @@ export { isStaticProperty } from '@vue/compiler-core' +// Internals for type resolution +export { invalidateTypeCache, registerTS } from './script/resolveType' + // Types export type { SFCParseOptions, diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index c48e192f641..9d306d7bc5c 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -20,14 +20,20 @@ import { TSTypeReference, TemplateLiteral } from '@babel/types' -import { UNKNOWN_TYPE, getId, getImportedName } from './utils' +import { + UNKNOWN_TYPE, + createGetCanonicalFileName, + getId, + getImportedName +} from './utils' import { ScriptCompileContext, resolveParserPlugins } from './context' import { ImportBinding, SFCScriptCompileOptions } from '../compileScript' import { capitalize, hasOwn } from '@vue/shared' -import path from 'path' import { parse as babelParse } from '@babel/parser' import { parse } from '../parse' import { createCache } from '../cache' +import type TS from 'typescript' +import { join, extname, dirname } from 'path' type Import = Pick @@ -480,54 +486,82 @@ function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] { } } +let ts: typeof TS + +export function registerTS(_ts: any) { + ts = _ts +} + +type FS = NonNullable + function resolveTypeFromImport( ctx: ScriptCompileContext, node: TSTypeReference | TSExpressionWithTypeArguments, name: string, scope: TypeScope ): Node | undefined { - const fs = ctx.options.fs + const fs: FS = ctx.options.fs || ts?.sys if (!fs) { ctx.error( - `fs options for compileScript are required for resolving imported types`, - node, - scope + `No fs option provided to \`compileScript\` in non-Node environment. ` + + `File system access is required for resolving imported types.`, + node ) } - // TODO (hmr) register dependency file on ctx + const containingFile = scope.filename const { source, imported } = scope.imports[name] + + let resolved: string | undefined + if (source.startsWith('.')) { // relative import - fast path - const filename = path.join(containingFile, '..', source) - const resolved = resolveExt(filename, fs) - if (resolved) { - return resolveTypeReference( - ctx, + const filename = join(containingFile, '..', source) + resolved = resolveExt(filename, fs) + } else { + // module or aliased import - use full TS resolution, only supported in Node + if (!__NODE_JS__) { + ctx.error( + `Type import from non-relative sources is not supported in the browser build.`, node, - fileToScope(ctx, resolved, fs), - imported, - true + scope ) - } else { + } + if (!ts) { ctx.error( - `Failed to resolve import source ${JSON.stringify( + `Failed to resolve type ${imported} from module ${JSON.stringify( source - )} for type ${name}`, + )}. ` + + `typescript is required as a peer dep for vue in order ` + + `to support resolving types from module imports.`, node, scope ) } + resolved = resolveWithTS(containingFile, source, fs) + } + + if (resolved) { + // TODO (hmr) register dependency file on ctx + return resolveTypeReference( + ctx, + node, + fileToScope(ctx, resolved, fs), + imported, + true + ) } else { - // TODO module or aliased import - use full TS resolution - return + ctx.error( + `Failed to resolve import source ${JSON.stringify( + source + )} for type ${name}`, + node, + scope + ) } } -function resolveExt( - filename: string, - fs: NonNullable -) { +function resolveExt(filename: string, fs: FS) { const tryResolve = (filename: string) => { if (fs.fileExists(filename)) return filename } @@ -540,23 +574,83 @@ function resolveExt( ) } +const tsConfigCache = createCache<{ + options: TS.CompilerOptions + cache: TS.ModuleResolutionCache +}>() + +function resolveWithTS( + containingFile: string, + source: string, + fs: FS +): string | undefined { + if (!__NODE_JS__) return + + // 1. resolve tsconfig.json + const configPath = ts.findConfigFile(containingFile, fs.fileExists) + // 2. load tsconfig.json + let options: TS.CompilerOptions + let cache: TS.ModuleResolutionCache | undefined + if (configPath) { + const cached = tsConfigCache.get(configPath) + if (!cached) { + // The only case where `fs` is NOT `ts.sys` is during tests. + // parse config host requires an extra `readDirectory` method + // during tests, which is stubbed. + const parseConfigHost = __TEST__ + ? { + ...fs, + useCaseSensitiveFileNames: true, + readDirectory: () => [] + } + : ts.sys + const parsed = ts.parseJsonConfigFileContent( + ts.readConfigFile(configPath, fs.readFile).config, + parseConfigHost, + dirname(configPath), + undefined, + configPath + ) + options = parsed.options + cache = ts.createModuleResolutionCache( + process.cwd(), + createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames), + options + ) + tsConfigCache.set(configPath, { options, cache }) + } else { + ;({ options, cache } = cached) + } + } else { + options = {} + } + + // 3. resolve + const res = ts.resolveModuleName(source, containingFile, options, fs, cache) + + if (res.resolvedModule) { + return res.resolvedModule.resolvedFileName + } +} + const fileToScopeCache = createCache() export function invalidateTypeCache(filename: string) { fileToScopeCache.delete(filename) + tsConfigCache.delete(filename) } function fileToScope( ctx: ScriptCompileContext, filename: string, - fs: NonNullable + fs: FS ): TypeScope { const cached = fileToScopeCache.get(filename) if (cached) { return cached } - const source = fs.readFile(filename) + const source = fs.readFile(filename) || '' const body = parseFile(ctx, filename, source) const scope: TypeScope = { filename, @@ -577,7 +671,7 @@ function parseFile( filename: string, content: string ): Statement[] { - const ext = path.extname(filename) + const ext = extname(filename) if (ext === '.ts' || ext === '.tsx') { return babelParse(content, { plugins: resolveParserPlugins( @@ -705,7 +799,8 @@ function recordType(node: Node, types: Record) { switch (node.type) { case 'TSInterfaceDeclaration': case 'TSEnumDeclaration': - case 'TSModuleDeclaration': { + case 'TSModuleDeclaration': + case 'ClassDeclaration': { const id = node.id.type === 'Identifier' ? node.id.name : node.id.value types[id] = node break @@ -899,6 +994,9 @@ export function inferRuntimeType( } } + case 'ClassDeclaration': + return ['Object'] + default: return [UNKNOWN_TYPE] // no runtime check } diff --git a/packages/compiler-sfc/src/script/utils.ts b/packages/compiler-sfc/src/script/utils.ts index 780c780e2cc..6d874f8a6db 100644 --- a/packages/compiler-sfc/src/script/utils.ts +++ b/packages/compiler-sfc/src/script/utils.ts @@ -78,3 +78,22 @@ export function getId(node: Expression) { ? node.value : null } + +const identity = (str: string) => str +const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g +const toLowerCase = (str: string) => str.toLowerCase() + +function toFileNameLowerCase(x: string) { + return fileNameLowerCaseRegExp.test(x) + ? x.replace(fileNameLowerCaseRegExp, toLowerCase) + : x +} + +/** + * We need `getCanonicalFileName` when creating ts module resolution cache, + * but TS does not expose it directly. This implementation is repllicated from + * the TS source code. + */ +export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean) { + return useCaseSensitiveFileNames ? identity : toFileNameLowerCase +} diff --git a/packages/sfc-playground/src/Header.vue b/packages/sfc-playground/src/Header.vue index 91ce3efc46e..b55f0240906 100644 --- a/packages/sfc-playground/src/Header.vue +++ b/packages/sfc-playground/src/Header.vue @@ -6,7 +6,7 @@ import Moon from './icons/Moon.vue' import Share from './icons/Share.vue' import Download from './icons/Download.vue' import GitHub from './icons/GitHub.vue' -import { ReplStore } from '@vue/repl' +import type { ReplStore } from '@vue/repl' const props = defineProps<{ store: ReplStore diff --git a/packages/sfc-playground/vite.config.ts b/packages/sfc-playground/vite.config.ts index 44d5a53509f..5176b9cf061 100644 --- a/packages/sfc-playground/vite.config.ts +++ b/packages/sfc-playground/vite.config.ts @@ -7,7 +7,18 @@ import execa from 'execa' const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7) export default defineConfig({ - plugins: [vue(), copyVuePlugin()], + plugins: [ + vue({ + script: { + // @ts-ignore + fs: { + fileExists: fs.existsSync, + readFile: file => fs.readFileSync(file, 'utf-8') + } + } + }), + copyVuePlugin() + ], define: { __COMMIT__: JSON.stringify(commit), __VUE_PROD_DEVTOOLS__: JSON.stringify(true) diff --git a/packages/vue/compiler-sfc/index.js b/packages/vue/compiler-sfc/index.js index 774f9da2742..2b85ad129ef 100644 --- a/packages/vue/compiler-sfc/index.js +++ b/packages/vue/compiler-sfc/index.js @@ -1 +1,3 @@ module.exports = require('@vue/compiler-sfc') + +require('./register-ts.js') diff --git a/packages/vue/compiler-sfc/index.mjs b/packages/vue/compiler-sfc/index.mjs index 8df9a989d18..ae5d6e8e5ca 100644 --- a/packages/vue/compiler-sfc/index.mjs +++ b/packages/vue/compiler-sfc/index.mjs @@ -1 +1,3 @@ -export * from '@vue/compiler-sfc' \ No newline at end of file +export * from '@vue/compiler-sfc' + +import './register-ts.js' diff --git a/packages/vue/compiler-sfc/package.json b/packages/vue/compiler-sfc/package.json index 1b15fb844ac..778c7ebf51c 100644 --- a/packages/vue/compiler-sfc/package.json +++ b/packages/vue/compiler-sfc/package.json @@ -2,4 +2,4 @@ "main": "index.js", "module": "index.mjs", "types": "index.d.ts" -} \ No newline at end of file +} diff --git a/packages/vue/compiler-sfc/register-ts.js b/packages/vue/compiler-sfc/register-ts.js new file mode 100644 index 00000000000..87f61b64863 --- /dev/null +++ b/packages/vue/compiler-sfc/register-ts.js @@ -0,0 +1,5 @@ +if (typeof require !== 'undefined') { + try { + require('@vue/compiler-sfc').registerTS(require('typescript')) + } catch (e) {} +} From 34a007d00dd7fe6a16dd0ec1c999725cb0753704 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 14 Apr 2023 17:43:14 +0800 Subject: [PATCH 0439/1409] test: refactor resolveType test --- .../compileScript/resolveType.spec.ts | 119 +++++++++--------- 1 file changed, 62 insertions(+), 57 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 6045cbd3d7a..2f5b233091d 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -1,4 +1,4 @@ -import { TSTypeAliasDeclaration } from '@babel/types' +import { Identifier } from '@babel/types' import { parse } from '../../src' import { ScriptCompileContext } from '../../src/script/context' import { @@ -14,13 +14,13 @@ registerTS(ts) describe('resolveType', () => { test('type literal', () => { - const { props, calls } = resolve(`type Target = { + const { props, calls } = resolve(`defineProps<{ foo: number // property bar(): void // method 'baz': string // string literal key (e: 'foo'): void // call signature (e: 'bar'): void - }`) + }>()`) expect(props).toStrictEqual({ foo: ['Number'], bar: ['Function'], @@ -33,7 +33,7 @@ describe('resolveType', () => { expect( resolve(` type Aliased = { foo: number } - type Target = Aliased + defineProps() `).props ).toStrictEqual({ foo: ['Number'] @@ -44,7 +44,7 @@ describe('resolveType', () => { expect( resolve(` export type Aliased = { foo: number } - type Target = Aliased + defineProps() `).props ).toStrictEqual({ foo: ['Number'] @@ -55,7 +55,7 @@ describe('resolveType', () => { expect( resolve(` interface Aliased { foo: number } - type Target = Aliased + defineProps() `).props ).toStrictEqual({ foo: ['Number'] @@ -66,7 +66,7 @@ describe('resolveType', () => { expect( resolve(` export interface Aliased { foo: number } - type Target = Aliased + defineProps() `).props ).toStrictEqual({ foo: ['Number'] @@ -80,7 +80,7 @@ describe('resolveType', () => { export interface B extends A { b: boolean } interface C { c: string } interface Aliased extends B, C { foo: number } - type Target = Aliased + defineProps() `).props ).toStrictEqual({ a: ['Function'], @@ -94,9 +94,7 @@ describe('resolveType', () => { expect( resolve(` class Foo {} - type Target = { - foo: Foo - } + defineProps<{ foo: Foo }>() `).props ).toStrictEqual({ foo: ['Object'] @@ -106,7 +104,7 @@ describe('resolveType', () => { test('function type', () => { expect( resolve(` - type Target = (e: 'foo') => void + defineProps<(e: 'foo') => void>() `).calls?.length ).toBe(1) }) @@ -115,7 +113,7 @@ describe('resolveType', () => { expect( resolve(` type Fn = (e: 'foo') => void - type Target = Fn + defineProps() `).calls?.length ).toBe(1) }) @@ -126,7 +124,7 @@ describe('resolveType', () => { type Foo = { foo: number } type Bar = { bar: string } type Baz = { bar: string | boolean } - type Target = { self: any } & Foo & Bar & Baz + defineProps<{ self: any } & Foo & Bar & Baz>() `).props ).toStrictEqual({ self: ['Unknown'], @@ -156,7 +154,7 @@ describe('resolveType', () => { note: string } - type Target = CommonProps & ConditionalProps + defineProps() `).props ).toStrictEqual({ size: ['String'], @@ -171,9 +169,9 @@ describe('resolveType', () => { resolve(` type T = 'foo' | 'bar' type S = 'x' | 'y' - type Target = { + defineProps<{ [\`_\${T}_\${S}_\`]: string - } + }>() `).props ).toStrictEqual({ _foo_x_: ['String'], @@ -187,7 +185,7 @@ describe('resolveType', () => { expect( resolve(` type T = 'foo' | 'bar' - type Target = { [K in T]: string | number } & { + defineProps<{ [K in T]: string | number } & { [K in 'optional']?: boolean } & { [K in Capitalize]: string @@ -195,7 +193,7 @@ describe('resolveType', () => { [K in Uppercase>]: string } & { [K in \`x\${T}\`]: string - } + }>() `).props ).toStrictEqual({ foo: ['String', 'Number'], @@ -214,7 +212,7 @@ describe('resolveType', () => { resolve(` type T = { foo: number, bar: string, baz: boolean } type K = 'foo' | 'bar' - type Target = Pick + defineProps>() `).props ).toStrictEqual({ foo: ['Number'], @@ -227,7 +225,7 @@ describe('resolveType', () => { resolve(` type T = { foo: number, bar: string, baz: boolean } type K = 'foo' | 'bar' - type Target = Omit + defineProps>() `).props ).toStrictEqual({ baz: ['Boolean'] @@ -239,7 +237,7 @@ describe('resolveType', () => { resolve(` type T = { bar: number } type S = { nested: { foo: T['bar'] }} - type Target = S['nested'] + defineProps() `).props ).toStrictEqual({ foo: ['Number'] @@ -258,7 +256,7 @@ describe('resolveType', () => { } } } - type Target = Foo.Bar.A + defineProps() `).props ).toStrictEqual({ foo: ['Number'] @@ -272,7 +270,7 @@ describe('resolveType', () => { ` import { P } from './foo' import { Y as PP } from './bar' - type Target = P & PP + defineProps

() `, { '/foo.ts': 'export type P = { foo: number }', @@ -291,7 +289,7 @@ describe('resolveType', () => { ` import { P } from './foo.vue' import { P as PP } from './bar.vue' - type Target = P & PP + defineProps

() `, { '/foo.vue': @@ -311,7 +309,7 @@ describe('resolveType', () => { resolve( ` import { P } from './foo' - type Target = P + defineProps

() `, { '/foo.ts': `import type { P as PP } from './nested/bar.vue' @@ -331,7 +329,7 @@ describe('resolveType', () => { resolve( ` import { PP as P } from './foo' - type Target = P + defineProps

() `, { '/foo.ts': `export { P as PP } from './bar'`, @@ -344,31 +342,31 @@ describe('resolveType', () => { }) test('ts module resolve', () => { - expect( - resolve( - ` + const files = { + '/node_modules/foo/package.json': JSON.stringify({ + types: 'index.d.ts' + }), + '/node_modules/foo/index.d.ts': 'export type P = { foo: number }', + '/tsconfig.json': JSON.stringify({ + compilerOptions: { + paths: { + bar: ['./pp.ts'] + } + } + }), + '/pp.ts': 'export type PP = { bar: string }' + } + + const { props } = resolve( + ` import { P } from 'foo' import { PP } from 'bar' - type Target = P & PP + defineProps

() `, - { - '/node_modules/foo/package.json': JSON.stringify({ - name: 'foo', - version: '1.0.0', - types: 'index.d.ts' - }), - '/node_modules/foo/index.d.ts': 'export type P = { foo: number }', - '/tsconfig.json': JSON.stringify({ - compilerOptions: { - paths: { - bar: ['./other/bar.ts'] - } - } - }), - '/other/bar.ts': 'export type PP = { bar: string }' - } - ).props - ).toStrictEqual({ + files + ) + + expect(props).toStrictEqual({ foo: ['Number'], bar: ['String'] }) @@ -377,26 +375,26 @@ describe('resolveType', () => { describe('errors', () => { test('failed type reference', () => { - expect(() => resolve(`type Target = X`)).toThrow( + expect(() => resolve(`defineProps()`)).toThrow( `Unresolvable type reference` ) }) test('unsupported computed keys', () => { - expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow( + expect(() => resolve(`defineProps<{ [Foo]: string }>()`)).toThrow( `Unsupported computed key in type referenced by a macro` ) }) test('unsupported index type', () => { - expect(() => resolve(`type Target = X[K]`)).toThrow( + expect(() => resolve(`defineProps()`)).toThrow( `Unsupported index type` ) }) test('failed improt source resolve', () => { expect(() => - resolve(`import { X } from './foo'; type Target = X`) + resolve(`import { X } from './foo'; defineProps()`) ).toThrow(`Failed to resolve import source "./foo" for type X`) }) }) @@ -426,10 +424,17 @@ function resolve(code: string, files: Record = {}) { // skipping that here, so need to manually register imports ctx.userImports = recordImports(ctx.scriptSetupAst!.body) as any - const targetDecl = ctx.scriptSetupAst!.body.find( - s => s.type === 'TSTypeAliasDeclaration' && s.id.name === 'Target' - ) as TSTypeAliasDeclaration - const raw = resolveTypeElements(ctx, targetDecl.typeAnnotation) + let target: any + for (const s of ctx.scriptSetupAst!.body) { + if ( + s.type === 'ExpressionStatement' && + s.expression.type === 'CallExpression' && + (s.expression.callee as Identifier).name === 'defineProps' + ) { + target = s.expression.typeParameters!.params[0] + } + } + const raw = resolveTypeElements(ctx, target) const props: Record = {} for (const key in raw.props) { props[key] = inferRuntimeType(ctx, raw.props[key]) From a9f5e14c7c7eb279bea538f69548fc6badd7df92 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 14 Apr 2023 20:53:21 +0800 Subject: [PATCH 0440/1409] chore: comments [ci skip] --- packages/compiler-sfc/src/compileScript.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 989f61cb444..c561a77a49d 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -111,7 +111,9 @@ export interface SFCScriptCompileOptions { */ defineModel?: boolean /** - * + * File system access methods to be used when resolving types + * imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten + * to use a virtual file system for use in browsers (e.g. in REPLs) */ fs?: { fileExists(file: string): boolean From 075498c959c547d0532e8b216bd6ac4c2d19230d Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 15 Apr 2023 17:59:50 +0800 Subject: [PATCH 0441/1409] refactor: avoid hard error when inferring runtime type --- .../compileScript/resolveType.spec.ts | 5 +++++ packages/compiler-sfc/src/parse.ts | 7 +++++++ packages/compiler-sfc/src/script/context.ts | 10 ++++++--- .../compiler-sfc/src/script/resolveType.ts | 21 ++++++++++++++----- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 2f5b233091d..6f07cfcfab4 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -397,6 +397,11 @@ describe('resolveType', () => { resolve(`import { X } from './foo'; defineProps()`) ).toThrow(`Failed to resolve import source "./foo" for type X`) }) + + test('should not error on unresolved type when inferring runtime type', () => { + expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow() + expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow() + }) }) }) diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index b46c5ea1332..29c91cc1977 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -47,6 +47,13 @@ export interface SFCScriptBlock extends SFCBlock { imports?: Record scriptAst?: import('@babel/types').Statement[] scriptSetupAst?: import('@babel/types').Statement[] + warnings?: string[] + /** + * Fully resolved dependency file paths (unix slashes) with imported types + * used in macros, used for HMR cache busting in @vitejs/plugin-vue and + * vue-loader. + */ + deps?: string[] } export interface SFCStyleBlock extends SFCBlock { diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index ec6bbe8d0f1..1f96584507b 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -7,7 +7,7 @@ import { PropsDestructureBindings } from './defineProps' import { ModelDecl } from './defineModel' import { BindingMetadata } from '../../../compiler-core/src' import MagicString from 'magic-string' -import { TypeScope, WithScope } from './resolveType' +import { TypeScope } from './resolveType' export class ScriptCompileContext { isJS: boolean @@ -56,13 +56,17 @@ export class ScriptCompileContext { // codegen bindingMetadata: BindingMetadata = {} - helperImports: Set = new Set() helper(key: string): string { this.helperImports.add(key) return `_${key}` } + /** + * to be exposed on compiled script block for HMR cache busting + */ + deps?: string[] + constructor( public descriptor: SFCDescriptor, public options: SFCScriptCompileOptions @@ -125,7 +129,7 @@ export class ScriptCompileContext { return block.content.slice(node.start!, node.end!) } - error(msg: string, node: Node & WithScope, scope?: TypeScope): never { + error(msg: string, node: Node, scope?: TypeScope): never { const offset = scope ? scope.offset : this.startOffset! throw new Error( `[@vue/compiler-sfc] ${msg}\n\n${ diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 9d306d7bc5c..bbbbd4c5bc0 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -134,6 +134,7 @@ function innerResolveTypeElements( break } } else { + // TODO support `number` and `string` index type when possible ctx.error( `Unsupported index type: ${node.indexType.type}`, node.indexType, @@ -320,7 +321,11 @@ function resolveStringType( case 'Uncapitalize': return getParam().map(s => s[0].toLowerCase() + s.slice(1)) default: - ctx.error('Failed to resolve type reference', node, scope) + ctx.error( + 'Unsupported type when resolving string type', + node.typeName, + scope + ) } } } @@ -906,7 +911,7 @@ export function inferRuntimeType( if (node.typeName.type === 'Identifier') { const resolved = resolveTypeReference(ctx, node, scope) if (resolved) { - return inferRuntimeType(ctx, resolved, scope) + return inferRuntimeType(ctx, resolved, resolved._ownerScope) } switch (node.typeName.name) { case 'Array': @@ -988,9 +993,15 @@ export function inferRuntimeType( node.indexType.type === 'TSLiteralType' && node.indexType.literal.type === 'StringLiteral' ) { - const resolved = resolveTypeElements(ctx, node.objectType) - const key = node.indexType.literal.value - return inferRuntimeType(ctx, resolved.props[key]) + try { + const resolved = resolveTypeElements(ctx, node.objectType, scope) + const key = node.indexType.literal.value + const prop = resolved.props[key] + return inferRuntimeType(ctx, prop, prop._ownerScope) + } catch (e) { + // avoid hard error, fallback to unknown + return [UNKNOWN_TYPE] + } } } From 8d8ddd686c832b2ea29b87ef47666b13c4ad5d4c Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 15 Apr 2023 18:06:48 +0800 Subject: [PATCH 0442/1409] feat(compiler-sfc): expose type import deps on compiled script block --- .../compileScript/resolveType.spec.ts | 100 ++++++++++-------- packages/compiler-sfc/src/compileScript.ts | 3 +- packages/compiler-sfc/src/script/context.ts | 2 +- .../compiler-sfc/src/script/resolveType.ts | 4 +- 4 files changed, 60 insertions(+), 49 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 6f07cfcfab4..a6aad1ea197 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -264,81 +264,85 @@ describe('resolveType', () => { }) describe('external type imports', () => { + const files = { + '/foo.ts': 'export type P = { foo: number }', + '/bar.d.ts': 'type X = { bar: string }; export { X as Y }' + } test('relative ts', () => { - expect( - resolve( - ` + const { props, deps } = resolve( + ` import { P } from './foo' import { Y as PP } from './bar' defineProps

() - `, - { - '/foo.ts': 'export type P = { foo: number }', - '/bar.d.ts': 'type X = { bar: string }; export { X as Y }' - } - ).props - ).toStrictEqual({ + `, + files + ) + expect(props).toStrictEqual({ foo: ['Number'], bar: ['String'] }) + expect(deps && [...deps]).toStrictEqual(Object.keys(files)) }) test('relative vue', () => { - expect( - resolve( - ` + const files = { + '/foo.vue': + '', + '/bar.vue': + '' + } + const { props, deps } = resolve( + ` import { P } from './foo.vue' import { P as PP } from './bar.vue' defineProps

() - `, - { - '/foo.vue': - '', - '/bar.vue': - '' - } - ).props - ).toStrictEqual({ + `, + files + ) + expect(props).toStrictEqual({ foo: ['Number'], bar: ['String'] }) + expect(deps && [...deps]).toStrictEqual(Object.keys(files)) }) test('relative (chained)', () => { - expect( - resolve( - ` + const files = { + '/foo.ts': `import type { P as PP } from './nested/bar.vue' + export type P = { foo: number } & PP`, + '/nested/bar.vue': + '' + } + const { props, deps } = resolve( + ` import { P } from './foo' defineProps

() - `, - { - '/foo.ts': `import type { P as PP } from './nested/bar.vue' - export type P = { foo: number } & PP`, - '/nested/bar.vue': - '' - } - ).props - ).toStrictEqual({ + `, + files + ) + expect(props).toStrictEqual({ foo: ['Number'], bar: ['String'] }) + expect(deps && [...deps]).toStrictEqual(Object.keys(files)) }) test('relative (chained, re-export)', () => { - expect( - resolve( - ` + const files = { + '/foo.ts': `export { P as PP } from './bar'`, + '/bar.ts': 'export type P = { bar: string }' + } + const { props, deps } = resolve( + ` import { PP as P } from './foo' defineProps

() - `, - { - '/foo.ts': `export { P as PP } from './bar'`, - '/bar.ts': 'export type P = { bar: string }' - } - ).props - ).toStrictEqual({ + `, + files + ) + expect(props).toStrictEqual({ bar: ['String'] }) + expect(deps && [...deps]).toStrictEqual(Object.keys(files)) }) test('ts module resolve', () => { @@ -357,7 +361,7 @@ describe('resolveType', () => { '/pp.ts': 'export type PP = { bar: string }' } - const { props } = resolve( + const { props, deps } = resolve( ` import { P } from 'foo' import { PP } from 'bar' @@ -370,6 +374,10 @@ describe('resolveType', () => { foo: ['Number'], bar: ['String'] }) + expect(deps && [...deps]).toStrictEqual([ + '/node_modules/foo/index.d.ts', + '/pp.ts' + ]) }) }) @@ -447,6 +455,6 @@ function resolve(code: string, files: Record = {}) { return { props, calls: raw.calls, - raw + deps: ctx.deps } } diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index c561a77a49d..e5e1bea4fd1 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1035,7 +1035,8 @@ export function compileScript( }) as unknown as RawSourceMap) : undefined, scriptAst: scriptAst?.body, - scriptSetupAst: scriptSetupAst?.body + scriptSetupAst: scriptSetupAst?.body, + deps: ctx.deps ? [...ctx.deps] : undefined } } diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 1f96584507b..9141b95c572 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -65,7 +65,7 @@ export class ScriptCompileContext { /** * to be exposed on compiled script block for HMR cache busting */ - deps?: string[] + deps?: Set constructor( public descriptor: SFCDescriptor, diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index bbbbd4c5bc0..d5661c871aa 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -547,7 +547,9 @@ function resolveTypeFromImport( } if (resolved) { - // TODO (hmr) register dependency file on ctx + // (hmr) register dependency file on ctx + ;(ctx.deps || (ctx.deps = new Set())).add(resolved) + return resolveTypeReference( ctx, node, From 760755f4f83680bee13ad546cdab2e48ade38dff Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 15 Apr 2023 21:46:44 +0800 Subject: [PATCH 0443/1409] feat(compiler-sfc): support string/number indexed types in macros --- .../compileScript/resolveType.spec.ts | 35 +++++- .../compiler-sfc/src/script/resolveType.ts | 117 ++++++++++++------ 2 files changed, 111 insertions(+), 41 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index a6aad1ea197..7aa08d595f5 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -232,7 +232,7 @@ describe('resolveType', () => { }) }) - test('indexed access type', () => { + test('indexed access type (literal)', () => { expect( resolve(` type T = { bar: number } @@ -244,6 +244,37 @@ describe('resolveType', () => { }) }) + test('indexed access type (advanced)', () => { + expect( + resolve(` + type K = 'foo' | 'bar' + type T = { foo: string, bar: number } + type S = { foo: { foo: T[string] }, bar: { bar: string } } + defineProps() + `).props + ).toStrictEqual({ + foo: ['String', 'Number'], + bar: ['String'] + }) + }) + + test('indexed access type (number)', () => { + expect( + resolve(` + type A = (string | number)[] + type AA = Array + type T = [1, 'foo'] + type TT = [foo: 1, bar: 'foo'] + defineProps<{ foo: A[number], bar: AA[number], tuple: T[number], namedTuple: TT[number] }>() + `).props + ).toStrictEqual({ + foo: ['String', 'Number'], + bar: ['String'], + tuple: ['Number', 'String'], + namedTuple: ['Number', 'String'] + }) + }) + test('namespace', () => { expect( resolve(` @@ -396,7 +427,7 @@ describe('resolveType', () => { test('unsupported index type', () => { expect(() => resolve(`defineProps()`)).toThrow( - `Unsupported index type` + `Unsupported type when resolving index type` ) }) diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index d5661c871aa..f1fce65a287 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -7,6 +7,7 @@ import { TSEnumDeclaration, TSExpressionWithTypeArguments, TSFunctionType, + TSIndexedAccessType, TSInterfaceDeclaration, TSMappedType, TSMethodSignature, @@ -117,30 +118,11 @@ function innerResolveTypeElements( case 'TSMappedType': return resolveMappedType(ctx, node, scope) case 'TSIndexedAccessType': { - if ( - node.indexType.type === 'TSLiteralType' && - node.indexType.literal.type === 'StringLiteral' - ) { - const resolved = resolveTypeElements(ctx, node.objectType, scope) - const key = node.indexType.literal.value - const targetType = resolved.props[key].typeAnnotation - if (targetType) { - return resolveTypeElements( - ctx, - targetType.typeAnnotation, - resolved.props[key]._ownerScope - ) - } else { - break - } - } else { - // TODO support `number` and `string` index type when possible - ctx.error( - `Unsupported index type: ${node.indexType.type}`, - node.indexType, - scope - ) - } + const types = resolveIndexType(ctx, node, scope) + return mergeElements( + types.map(t => resolveTypeElements(ctx, t, t._ownerScope)), + 'TSUnionType' + ) } case 'TSExpressionWithTypeArguments': // referenced by interface extends case 'TSTypeReference': { @@ -201,6 +183,7 @@ function mergeElements( maps: ResolvedElements[], type: 'TSUnionType' | 'TSIntersectionType' ): ResolvedElements { + if (maps.length === 1) return maps[0] const res: ResolvedElements = { props: {} } const { props: baseProps } = res for (const { props, calls } of maps) { @@ -282,6 +265,66 @@ function resolveMappedType( return res } +function resolveIndexType( + ctx: ScriptCompileContext, + node: TSIndexedAccessType, + scope: TypeScope +): (TSType & WithScope)[] { + if (node.indexType.type === 'TSNumberKeyword') { + return resolveArrayElementType(ctx, node.objectType, scope) + } + + const { indexType, objectType } = node + const types: TSType[] = [] + let keys: string[] + let resolved: ResolvedElements + if (indexType.type === 'TSStringKeyword') { + resolved = resolveTypeElements(ctx, objectType, scope) + keys = Object.keys(resolved.props) + } else { + keys = resolveStringType(ctx, indexType, scope) + resolved = resolveTypeElements(ctx, objectType, scope) + } + for (const key of keys) { + const targetType = resolved.props[key]?.typeAnnotation?.typeAnnotation + if (targetType) { + ;(targetType as TSType & WithScope)._ownerScope = + resolved.props[key]._ownerScope + types.push(targetType) + } + } + return types +} + +function resolveArrayElementType( + ctx: ScriptCompileContext, + node: Node, + scope: TypeScope +): TSType[] { + // type[] + if (node.type === 'TSArrayType') { + return [node.elementType] + } + // tuple + if (node.type === 'TSTupleType') { + return node.elementTypes.map(t => + t.type === 'TSNamedTupleMember' ? t.elementType : t + ) + } + if (node.type === 'TSTypeReference') { + // Array + if (getReferenceName(node) === 'Array' && node.typeParameters) { + return node.typeParameters.params + } else { + const resolved = resolveTypeReference(ctx, node, scope) + if (resolved) { + return resolveArrayElementType(ctx, resolved, scope) + } + } + } + ctx.error('Failed to resolve element type from target type', node) +} + function resolveStringType( ctx: ScriptCompileContext, node: Node, @@ -322,7 +365,7 @@ function resolveStringType( return getParam().map(s => s[0].toLowerCase() + s.slice(1)) default: ctx.error( - 'Unsupported type when resolving string type', + 'Unsupported type when resolving index type', node.typeName, scope ) @@ -330,7 +373,7 @@ function resolveStringType( } } } - ctx.error('Failed to resolve string type into finite keys', node, scope) + ctx.error('Failed to resolve index type into finite keys', node, scope) } function resolveTemplateKeys( @@ -991,19 +1034,12 @@ export function inferRuntimeType( return ['Symbol'] case 'TSIndexedAccessType': { - if ( - node.indexType.type === 'TSLiteralType' && - node.indexType.literal.type === 'StringLiteral' - ) { - try { - const resolved = resolveTypeElements(ctx, node.objectType, scope) - const key = node.indexType.literal.value - const prop = resolved.props[key] - return inferRuntimeType(ctx, prop, prop._ownerScope) - } catch (e) { - // avoid hard error, fallback to unknown - return [UNKNOWN_TYPE] - } + try { + const types = resolveIndexType(ctx, node, scope) + return flattenTypes(ctx, types, scope) + } catch (e) { + // avoid hard error, fallback to unknown + return [UNKNOWN_TYPE] } } @@ -1020,6 +1056,9 @@ function flattenTypes( types: TSType[], scope: TypeScope ): string[] { + if (types.length === 1) { + return inferRuntimeType(ctx, types[0], scope) + } return [ ...new Set( ([] as string[]).concat( From 6b13e04b4c83fcdbb180dc1d59f536a1309c2960 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 15 Apr 2023 17:15:50 +0800 Subject: [PATCH 0444/1409] feat(compiler-sfc): mark props destructure as experimental and require explicit opt-in --- .../definePropsDestructure.spec.ts.snap | 22 +++++++++---------- .../definePropsDestructure.spec.ts | 3 ++- packages/compiler-sfc/src/compileScript.ts | 22 ++++++++++++------- .../src/script/definePropsDestructure.ts | 8 +++++++ 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap index 37c334e838d..35926709cec 100644 --- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`sfc props transform > aliasing 1`] = ` +exports[`sfc reactive props destructure > aliasing 1`] = ` "import { toDisplayString as _toDisplayString } from \\"vue\\" @@ -20,7 +20,7 @@ return (_ctx, _cache) => { }" `; -exports[`sfc props transform > basic usage 1`] = ` +exports[`sfc reactive props destructure > basic usage 1`] = ` "import { toDisplayString as _toDisplayString } from \\"vue\\" @@ -39,7 +39,7 @@ return (_ctx, _cache) => { }" `; -exports[`sfc props transform > computed static key 1`] = ` +exports[`sfc reactive props destructure > computed static key 1`] = ` "import { toDisplayString as _toDisplayString } from \\"vue\\" @@ -58,7 +58,7 @@ return (_ctx, _cache) => { }" `; -exports[`sfc props transform > default values w/ array runtime declaration 1`] = ` +exports[`sfc reactive props destructure > default values w/ array runtime declaration 1`] = ` "import { mergeDefaults as _mergeDefaults } from 'vue' export default { @@ -77,7 +77,7 @@ return () => {} }" `; -exports[`sfc props transform > default values w/ object runtime declaration 1`] = ` +exports[`sfc reactive props destructure > default values w/ object runtime declaration 1`] = ` "import { mergeDefaults as _mergeDefaults } from 'vue' export default { @@ -97,7 +97,7 @@ return () => {} }" `; -exports[`sfc props transform > default values w/ type declaration 1`] = ` +exports[`sfc reactive props destructure > default values w/ type declaration 1`] = ` "import { defineComponent as _defineComponent } from 'vue' export default /*#__PURE__*/_defineComponent({ @@ -116,7 +116,7 @@ return () => {} })" `; -exports[`sfc props transform > default values w/ type declaration, prod mode 1`] = ` +exports[`sfc reactive props destructure > default values w/ type declaration, prod mode 1`] = ` "import { defineComponent as _defineComponent } from 'vue' export default /*#__PURE__*/_defineComponent({ @@ -138,7 +138,7 @@ return () => {} })" `; -exports[`sfc props transform > multiple variable declarations 1`] = ` +exports[`sfc reactive props destructure > multiple variable declarations 1`] = ` "import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\" @@ -156,7 +156,7 @@ return (_ctx, _cache) => { }" `; -exports[`sfc props transform > nested scope 1`] = ` +exports[`sfc reactive props destructure > nested scope 1`] = ` "export default { props: ['foo', 'bar'], setup(__props) { @@ -173,7 +173,7 @@ return () => {} }" `; -exports[`sfc props transform > non-identifier prop names 1`] = ` +exports[`sfc reactive props destructure > non-identifier prop names 1`] = ` "import { toDisplayString as _toDisplayString } from \\"vue\\" @@ -192,7 +192,7 @@ return (_ctx, _cache) => { }" `; -exports[`sfc props transform > rest spread 1`] = ` +exports[`sfc reactive props destructure > rest spread 1`] = ` "import { createPropsRestProxy as _createPropsRestProxy } from 'vue' export default { diff --git a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts index a459d80ff29..b8912092afd 100644 --- a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts @@ -2,10 +2,11 @@ import { BindingTypes } from '@vue/compiler-core' import { SFCScriptCompileOptions } from '../../src' import { compileSFCScript, assertCode } from '../utils' -describe('sfc props transform', () => { +describe('sfc reactive props destructure', () => { function compile(src: string, options?: Partial) { return compileSFCScript(src, { inlineTemplate: true, + propsDestructure: true, ...options }) } diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index e5e1bea4fd1..0d09f02538a 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -72,14 +72,6 @@ export interface SFCScriptCompileOptions { * https://babeljs.io/docs/en/babel-parser#plugins */ babelParserPlugins?: ParserPlugin[] - /** - * (Experimental) Enable syntax transform for using refs without `.value` and - * using destructured props with reactivity - * @deprecated the Reactivity Transform proposal has been dropped. This - * feature will be removed from Vue core in 3.4. If you intend to continue - * using it, disable this and switch to the [Vue Macros implementation](https://vue-macros.sxzz.moe/features/reactivity-transform.html). - */ - reactivityTransform?: boolean /** * Compile the template and inline the resulting render function * directly inside setup(). @@ -108,8 +100,14 @@ export interface SFCScriptCompileOptions { hoistStatic?: boolean /** * (**Experimental**) Enable macro `defineModel` + * @default false */ defineModel?: boolean + /** + * (**Experimental**) Enable reactive destructure for `defineProps` + * @default false + */ + propsDestructure?: boolean /** * File system access methods to be used when resolving types * imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten @@ -119,6 +117,14 @@ export interface SFCScriptCompileOptions { fileExists(file: string): boolean readFile(file: string): string | undefined } + /** + * (Experimental) Enable syntax transform for using refs without `.value` and + * using destructured props with reactivity + * @deprecated the Reactivity Transform proposal has been dropped. This + * feature will be removed from Vue core in 3.4. If you intend to continue + * using it, disable this and switch to the [Vue Macros implementation](https://vue-macros.sxzz.moe/features/reactivity-transform.html). + */ + reactivityTransform?: boolean } export interface ImportBinding { diff --git a/packages/compiler-sfc/src/script/definePropsDestructure.ts b/packages/compiler-sfc/src/script/definePropsDestructure.ts index 220c7ce41db..9f693f8519d 100644 --- a/packages/compiler-sfc/src/script/definePropsDestructure.ts +++ b/packages/compiler-sfc/src/script/definePropsDestructure.ts @@ -26,6 +26,10 @@ export function processPropsDestructure( ctx: ScriptCompileContext, declId: ObjectPattern ) { + if (!ctx.options.propsDestructure) { + return + } + ctx.propsDestructureDecl = declId const registerBinding = ( @@ -91,6 +95,10 @@ export function transformDestructuredProps( ctx: ScriptCompileContext, vueImportAliases: Record ) { + if (!ctx.options.propsDestructure) { + return + } + const rootScope: Scope = {} const scopeStack: Scope[] = [rootScope] let currentScope: Scope = rootScope From f22e32e365bf6292cb606cb7289609e82da8b790 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 16 Apr 2023 11:11:26 +0800 Subject: [PATCH 0445/1409] feat(compiler-sfc): expose type resolve APIs --- packages/compiler-sfc/src/index.ts | 6 ++ packages/compiler-sfc/src/script/context.ts | 8 +- .../compiler-sfc/src/script/resolveType.ts | 102 ++++++++++++------ 3 files changed, 83 insertions(+), 33 deletions(-) diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index e171ac0885c..78a89c7b498 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -6,6 +6,7 @@ export { compileTemplate } from './compileTemplate' export { compileStyle, compileStyleAsync } from './compileStyle' export { compileScript } from './compileScript' export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault' +export { resolveTypeElements, inferRuntimeType } from './script/resolveType' export { shouldTransform as shouldTransformRef, transform as transformRef, @@ -52,6 +53,11 @@ export type { SFCStyleCompileResults } from './compileStyle' export type { SFCScriptCompileOptions } from './compileScript' +export type { ScriptCompileContext } from './script/context' +export type { + TypeResolveContext, + SimpleTypeResolveContext +} from './script/resolveType' export type { AssetURLOptions, AssetURLTagConfig diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 9141b95c572..641e463741f 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -16,12 +16,14 @@ export class ScriptCompileContext { scriptAst: Program | null scriptSetupAst: Program | null - s = new MagicString(this.descriptor.source) + source = this.descriptor.source + filename = this.descriptor.filename + s = new MagicString(this.source) startOffset = this.descriptor.scriptSetup?.loc.start.offset endOffset = this.descriptor.scriptSetup?.loc.end.offset // import / type analysis - scope: TypeScope | undefined + scope?: TypeScope userImports: Record = Object.create(null) // macros presence check @@ -69,7 +71,7 @@ export class ScriptCompileContext { constructor( public descriptor: SFCDescriptor, - public options: SFCScriptCompileOptions + public options: Partial ) { const { script, scriptSetup } = descriptor const scriptLang = script && script.lang diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index f1fce65a287..1e89c5712dc 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -36,6 +36,32 @@ import { createCache } from '../cache' import type TS from 'typescript' import { join, extname, dirname } from 'path' +/** + * TypeResolveContext is compatible with ScriptCompileContext + * but also allows a simpler version of it with minimal required properties + * when resolveType needs to be used in a non-SFC context, e.g. in a babel + * plugin. The simplest context can be just: + * ```ts + * const ctx: SimpleTypeResolveContext = { + * filename: '...', + * source: '...', + * options: {}, + * error() {}, + * ast: [] + * } + * ``` + */ +export type SimpleTypeResolveContext = Pick< + ScriptCompileContext, + // required + 'source' | 'filename' | 'error' | 'options' +> & + Partial> & { + ast: Statement[] + } + +export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext + type Import = Pick export interface TypeScope { @@ -79,7 +105,7 @@ interface ResolvedElements { * mapped to runtime props or emits. */ export function resolveTypeElements( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: Node & WithScope & { _resolvedElements?: ResolvedElements }, scope?: TypeScope ): ResolvedElements { @@ -94,7 +120,7 @@ export function resolveTypeElements( } function innerResolveTypeElements( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: Node, scope: TypeScope ): ResolvedElements { @@ -138,7 +164,7 @@ function innerResolveTypeElements( ) { return resolveBuiltin(ctx, node, typeName as any, scope) } - ctx.error( + return ctx.error( `Unresolvable type reference or unsupported built-in utlility type`, node, scope @@ -146,11 +172,11 @@ function innerResolveTypeElements( } } } - ctx.error(`Unresolvable type: ${node.type}`, node, scope) + return ctx.error(`Unresolvable type: ${node.type}`, node, scope) } function typeElementsToMap( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, elements: TSTypeElement[], scope = ctxToScope(ctx) ): ResolvedElements { @@ -227,7 +253,7 @@ function createProperty( } function resolveInterfaceMembers( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: TSInterfaceDeclaration & WithScope, scope: TypeScope ): ResolvedElements { @@ -246,7 +272,7 @@ function resolveInterfaceMembers( } function resolveMappedType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: TSMappedType, scope: TypeScope ): ResolvedElements { @@ -266,7 +292,7 @@ function resolveMappedType( } function resolveIndexType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: TSIndexedAccessType, scope: TypeScope ): (TSType & WithScope)[] { @@ -297,7 +323,7 @@ function resolveIndexType( } function resolveArrayElementType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: Node, scope: TypeScope ): TSType[] { @@ -322,11 +348,15 @@ function resolveArrayElementType( } } } - ctx.error('Failed to resolve element type from target type', node) + return ctx.error( + 'Failed to resolve element type from target type', + node, + scope + ) } function resolveStringType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: Node, scope: TypeScope ): string[] { @@ -373,11 +403,11 @@ function resolveStringType( } } } - ctx.error('Failed to resolve index type into finite keys', node, scope) + return ctx.error('Failed to resolve index type into finite keys', node, scope) } function resolveTemplateKeys( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: TemplateLiteral, scope: TypeScope ): string[] { @@ -420,7 +450,7 @@ const SupportedBuiltinsSet = new Set([ type GetSetType = T extends Set ? V : never function resolveBuiltin( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: TSTypeReference | TSExpressionWithTypeArguments, name: GetSetType, scope: TypeScope @@ -460,7 +490,7 @@ function resolveBuiltin( } function resolveTypeReference( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: (TSTypeReference | TSExpressionWithTypeArguments) & { _resolvedReference?: Node }, @@ -481,7 +511,7 @@ function resolveTypeReference( } function innerResolveTypeReference( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, scope: TypeScope, name: string | string[], node: TSTypeReference | TSExpressionWithTypeArguments, @@ -536,6 +566,9 @@ function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] { let ts: typeof TS +/** + * @private + */ export function registerTS(_ts: any) { ts = _ts } @@ -543,7 +576,7 @@ export function registerTS(_ts: any) { type FS = NonNullable function resolveTypeFromImport( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: TSTypeReference | TSExpressionWithTypeArguments, name: string, scope: TypeScope @@ -685,13 +718,16 @@ function resolveWithTS( const fileToScopeCache = createCache() +/** + * @private + */ export function invalidateTypeCache(filename: string) { fileToScopeCache.delete(filename) tsConfigCache.delete(filename) } function fileToScope( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, filename: string, fs: FS ): TypeScope { @@ -717,7 +753,7 @@ function fileToScope( } function parseFile( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, filename: string, content: string ): Statement[] { @@ -763,24 +799,30 @@ function parseFile( return [] } -function ctxToScope(ctx: ScriptCompileContext): TypeScope { +function ctxToScope(ctx: TypeResolveContext): TypeScope { if (ctx.scope) { return ctx.scope } + const body = + 'ast' in ctx + ? ctx.ast + : ctx.scriptAst + ? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body] + : ctx.scriptSetupAst!.body + const scope: TypeScope = { - filename: ctx.descriptor.filename, - source: ctx.descriptor.source, - offset: ctx.startOffset!, - imports: Object.create(ctx.userImports), + filename: ctx.filename, + source: ctx.source, + offset: 'startOffset' in ctx ? ctx.startOffset! : 0, + imports: + 'userImports' in ctx + ? Object.create(ctx.userImports) + : recordImports(body), types: Object.create(null), exportedTypes: Object.create(null) } - const body = ctx.scriptAst - ? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body] - : ctx.scriptSetupAst!.body - recordTypes(body, scope) return (ctx.scope = scope) @@ -894,7 +936,7 @@ function recordImport(node: Node, imports: TypeScope['imports']) { } export function inferRuntimeType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: Node & WithScope, scope = node._ownerScope || ctxToScope(ctx) ): string[] { @@ -1052,7 +1094,7 @@ export function inferRuntimeType( } function flattenTypes( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, types: TSType[], scope: TypeScope ): string[] { From 4e028b966991937c83fb2529973fd3d41080bb61 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 16 Apr 2023 15:49:41 +0800 Subject: [PATCH 0446/1409] feat(compiler-sfc): support specifying global types for sfc macros ref: https://github.com/vuejs/core/pull/8083#issuecomment-1508468713 --- .../compileScript/resolveType.spec.ts | 37 +++- packages/compiler-sfc/src/compileScript.ts | 5 + packages/compiler-sfc/src/script/context.ts | 14 +- .../compiler-sfc/src/script/resolveType.ts | 172 +++++++++++------- 4 files changed, 149 insertions(+), 79 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 7aa08d595f5..0f2a47d6b99 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -1,5 +1,5 @@ import { Identifier } from '@babel/types' -import { parse } from '../../src' +import { SFCScriptCompileOptions, parse } from '../../src' import { ScriptCompileContext } from '../../src/script/context' import { inferRuntimeType, @@ -410,6 +410,32 @@ describe('resolveType', () => { '/pp.ts' ]) }) + + test('global types', () => { + const files = { + // ambient + '/app.d.ts': + 'declare namespace App { interface User { name: string } }', + // module - should only respect the declare global block + '/global.d.ts': ` + declare type PP = { bar: number } + declare global { + type PP = { bar: string } + } + export {} + ` + } + + const { props, deps } = resolve(`defineProps()`, files, { + globalTypeFiles: Object.keys(files) + }) + + expect(props).toStrictEqual({ + name: ['String'], + bar: ['String'] + }) + expect(deps && [...deps]).toStrictEqual(Object.keys(files)) + }) }) describe('errors', () => { @@ -444,7 +470,11 @@ describe('resolveType', () => { }) }) -function resolve(code: string, files: Record = {}) { +function resolve( + code: string, + files: Record = {}, + options?: Partial +) { const { descriptor } = parse(``, { filename: '/Test.vue' }) @@ -457,7 +487,8 @@ function resolve(code: string, files: Record = {}) { readFile(file) { return files[file] } - } + }, + ...options }) for (const file in files) { diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 0d09f02538a..1f525005c4d 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -72,6 +72,11 @@ export interface SFCScriptCompileOptions { * https://babeljs.io/docs/en/babel-parser#plugins */ babelParserPlugins?: ParserPlugin[] + /** + * A list of files to parse for global types to be made available for type + * resolving in SFC macros. The list must be fully resolved file system paths. + */ + globalTypeFiles?: string[] /** * Compile the template and inline the resulting render function * directly inside setup(). diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 641e463741f..d2c5dabd194 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -24,6 +24,7 @@ export class ScriptCompileContext { // import / type analysis scope?: TypeScope + globalScopes?: TypeScope[] userImports: Record = Object.create(null) // macros presence check @@ -101,7 +102,7 @@ export class ScriptCompileContext { sourceType: 'module' }).program } catch (e: any) { - e.message = `[@vue/compiler-sfc] ${e.message}\n\n${ + e.message = `[vue/compiler-sfc] ${e.message}\n\n${ descriptor.filename }\n${generateCodeFrame( descriptor.source, @@ -113,15 +114,12 @@ export class ScriptCompileContext { } this.scriptAst = - this.descriptor.script && - parse( - this.descriptor.script.content, - this.descriptor.script.loc.start.offset - ) + descriptor.script && + parse(descriptor.script.content, descriptor.script.loc.start.offset) this.scriptSetupAst = - this.descriptor.scriptSetup && - parse(this.descriptor.scriptSetup!.content, this.startOffset!) + descriptor.scriptSetup && + parse(descriptor.scriptSetup!.content, this.startOffset!) } getString(node: Node, scriptSetup = true): string { diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 1e89c5712dc..8efa04579f8 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -56,7 +56,7 @@ export type SimpleTypeResolveContext = Pick< // required 'source' | 'filename' | 'error' | 'options' > & - Partial> & { + Partial> & { ast: Statement[] } @@ -64,25 +64,18 @@ export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext type Import = Pick +type ScopeTypeNode = Node & { + // scope types always has ownerScope attached + _ownerScope: TypeScope +} + export interface TypeScope { filename: string source: string offset: number imports: Record - types: Record< - string, - Node & { - // scope types always has ownerScope attached - _ownerScope: TypeScope - } - > - exportedTypes: Record< - string, - Node & { - // scope types always has ownerScope attached - _ownerScope: TypeScope - } - > + types: Record + exportedTypes: Record } export interface WithScope { @@ -492,12 +485,12 @@ function resolveBuiltin( function resolveTypeReference( ctx: TypeResolveContext, node: (TSTypeReference | TSExpressionWithTypeArguments) & { - _resolvedReference?: Node + _resolvedReference?: ScopeTypeNode }, scope?: TypeScope, name?: string, onlyExported = false -): (Node & WithScope) | undefined { +): ScopeTypeNode | undefined { if (node._resolvedReference) { return node._resolvedReference } @@ -516,13 +509,26 @@ function innerResolveTypeReference( name: string | string[], node: TSTypeReference | TSExpressionWithTypeArguments, onlyExported: boolean -): Node | undefined { +): ScopeTypeNode | undefined { if (typeof name === 'string') { if (scope.imports[name]) { return resolveTypeFromImport(ctx, node, name, scope) } else { const types = onlyExported ? scope.exportedTypes : scope.types - return types[name] + if (types[name]) { + return types[name] + } else { + // fallback to global + const globalScopes = resolveGlobalScope(ctx) + if (globalScopes) { + for (const s of globalScopes) { + if (s.types[name]) { + ;(ctx.deps || (ctx.deps = new Set())).add(s.filename) + return s.types[name] + } + } + } + } } } else { const ns = innerResolveTypeReference( @@ -539,7 +545,7 @@ function innerResolveTypeReference( childScope, name.length > 2 ? name.slice(1) : name[name.length - 1], node, - true + !ns.declare ) } } @@ -564,6 +570,19 @@ function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] { } } +function resolveGlobalScope(ctx: TypeResolveContext): TypeScope[] | undefined { + if (ctx.options.globalTypeFiles) { + const fs: FS = ctx.options.fs || ts?.sys + if (!fs) { + throw new Error('[vue/compiler-sfc] globalTypeFiles requires fs access.') + } + return ctx.options.globalTypeFiles.map(file => + // TODO: differentiate ambient vs non-ambient module + fileToScope(file, fs, ctx.options.babelParserPlugins, true) + ) + } +} + let ts: typeof TS /** @@ -580,7 +599,7 @@ function resolveTypeFromImport( node: TSTypeReference | TSExpressionWithTypeArguments, name: string, scope: TypeScope -): Node | undefined { +): ScopeTypeNode | undefined { const fs: FS = ctx.options.fs || ts?.sys if (!fs) { ctx.error( @@ -629,7 +648,7 @@ function resolveTypeFromImport( return resolveTypeReference( ctx, node, - fileToScope(ctx, resolved, fs), + fileToScope(resolved, fs, ctx.options.babelParserPlugins), imported, true ) @@ -726,10 +745,11 @@ export function invalidateTypeCache(filename: string) { tsConfigCache.delete(filename) } -function fileToScope( - ctx: TypeResolveContext, +export function fileToScope( filename: string, - fs: FS + fs: FS, + parserPlugins: SFCScriptCompileOptions['babelParserPlugins'], + asGlobal = false ): TypeScope { const cached = fileToScopeCache.get(filename) if (cached) { @@ -737,33 +757,30 @@ function fileToScope( } const source = fs.readFile(filename) || '' - const body = parseFile(ctx, filename, source) + const body = parseFile(filename, source, parserPlugins) const scope: TypeScope = { filename, source, offset: 0, + imports: recordImports(body), types: Object.create(null), - exportedTypes: Object.create(null), - imports: recordImports(body) + exportedTypes: Object.create(null) } - recordTypes(body, scope) + recordTypes(body, scope, asGlobal) fileToScopeCache.set(filename, scope) return scope } function parseFile( - ctx: TypeResolveContext, filename: string, - content: string + content: string, + parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'] ): Statement[] { const ext = extname(filename) if (ext === '.ts' || ext === '.tsx') { return babelParse(content, { - plugins: resolveParserPlugins( - ext.slice(1), - ctx.options.babelParserPlugins - ), + plugins: resolveParserPlugins(ext.slice(1), parserPlugins), sourceType: 'module' }).program.body } else if (ext === '.vue') { @@ -792,7 +809,7 @@ function parseFile( } const lang = script?.lang || scriptSetup?.lang return babelParse(scriptContent, { - plugins: resolveParserPlugins(lang!, ctx.options.babelParserPlugins), + plugins: resolveParserPlugins(lang!, parserPlugins), sourceType: 'module' }).program.body } @@ -830,52 +847,71 @@ function ctxToScope(ctx: TypeResolveContext): TypeScope { function moduleDeclToScope( node: TSModuleDeclaration & { _resolvedChildScope?: TypeScope }, - parent: TypeScope + parentScope: TypeScope ): TypeScope { if (node._resolvedChildScope) { return node._resolvedChildScope } const scope: TypeScope = { - ...parent, - types: Object.create(parent.types), - imports: Object.create(parent.imports) + ...parentScope, + types: Object.create(parentScope.types), + imports: Object.create(parentScope.imports) } recordTypes((node.body as TSModuleBlock).body, scope) return (node._resolvedChildScope = scope) } -function recordTypes(body: Statement[], scope: TypeScope) { +const importExportRE = /^Import|^Export/ + +function recordTypes(body: Statement[], scope: TypeScope, asGlobal = false) { const { types, exportedTypes, imports } = scope + const isAmbient = asGlobal + ? !body.some(s => importExportRE.test(s.type)) + : false for (const stmt of body) { - recordType(stmt, types) + if (asGlobal) { + if (isAmbient) { + if ((stmt as any).declare) { + recordType(stmt, types) + } + } else if (stmt.type === 'TSModuleDeclaration' && stmt.global) { + for (const s of (stmt.body as TSModuleBlock).body) { + recordType(s, types) + } + } + } else { + recordType(stmt, types) + } } - for (const stmt of body) { - if (stmt.type === 'ExportNamedDeclaration') { - if (stmt.declaration) { - recordType(stmt.declaration, types) - recordType(stmt.declaration, exportedTypes) - } else { - for (const spec of stmt.specifiers) { - if (spec.type === 'ExportSpecifier') { - const local = spec.local.name - const exported = getId(spec.exported) - if (stmt.source) { - // re-export, register an import + export as a type reference - imports[local] = { - source: stmt.source.value, - imported: local - } - exportedTypes[exported] = { - type: 'TSTypeReference', - typeName: { - type: 'Identifier', - name: local - }, - _ownerScope: scope + if (!asGlobal) { + for (const stmt of body) { + if (stmt.type === 'ExportNamedDeclaration') { + if (stmt.declaration) { + recordType(stmt.declaration, types) + recordType(stmt.declaration, exportedTypes) + } else { + for (const spec of stmt.specifiers) { + if (spec.type === 'ExportSpecifier') { + const local = spec.local.name + const exported = getId(spec.exported) + if (stmt.source) { + // re-export, register an import + export as a type reference + imports[local] = { + source: stmt.source.value, + imported: local + } + exportedTypes[exported] = { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: local + }, + _ownerScope: scope + } + } else if (types[local]) { + // exporting local defined type + exportedTypes[exported] = types[local] } - } else if (types[local]) { - // exporting local defined type - exportedTypes[exported] = types[local] } } } From 4b5b384485cf8f6124f6738b89e3d047358f3a11 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 17 Apr 2023 18:05:17 +0800 Subject: [PATCH 0447/1409] fix(hmr): invalidate cached props/emits options on hmr --- packages/runtime-core/src/hmr.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index c5039f62b6f..fe8ca132bc8 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -123,6 +123,8 @@ function reload(id: string, newComp: HMRComponent) { } // 3. invalidate options resolution cache + instance.appContext.propsCache.delete(instance.type as any) + instance.appContext.emitsCache.delete(instance.type as any) instance.appContext.optionsCache.delete(instance.type as any) // 4. actually update From 33adc2a17ae450069470385eb1c62cabfc10a780 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 17 Apr 2023 18:15:17 +0800 Subject: [PATCH 0448/1409] release: v3.3.0-alpha.10 --- CHANGELOG.md | 31 +++++++++++++ package.json | 2 +- packages/compiler-core/package.json | 4 +- packages/compiler-dom/package.json | 6 +-- packages/compiler-sfc/package.json | 12 ++--- packages/compiler-ssr/package.json | 6 +-- packages/dts-test/package.json | 2 +- packages/reactivity-transform/package.json | 6 +-- packages/reactivity/package.json | 4 +- packages/runtime-core/package.json | 6 +-- packages/runtime-dom/package.json | 6 +-- packages/runtime-test/package.json | 6 +-- packages/server-renderer/package.json | 8 ++-- packages/sfc-playground/package.json | 2 +- packages/shared/package.json | 2 +- packages/size-check/package.json | 2 +- packages/template-explorer/package.json | 2 +- packages/vue-compat/package.json | 4 +- packages/vue/package.json | 12 ++--- pnpm-lock.yaml | 52 +++++++++++----------- 20 files changed, 103 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59681946234..fe7596228cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,34 @@ +# [3.3.0-alpha.10](https://github.com/vuejs/core/compare/v3.3.0-alpha.9...v3.3.0-alpha.10) (2023-04-17) + + +### Bug Fixes + +* **hmr:** invalidate cached props/emits options on hmr ([4b5b384](https://github.com/vuejs/core/commit/4b5b384485cf8f6124f6738b89e3d047358f3a11)) +* **runtime-core:** properly merge props and emits options from mixins ([#8052](https://github.com/vuejs/core/issues/8052)) ([c94ef02](https://github.com/vuejs/core/commit/c94ef02421d7422bc59d10cf2eee9f4e7dcea6c8)), closes [#7989](https://github.com/vuejs/core/issues/7989) + + +### Features + +* **compiler-sfc:** expose type import deps on compiled script block ([8d8ddd6](https://github.com/vuejs/core/commit/8d8ddd686c832b2ea29b87ef47666b13c4ad5d4c)) +* **compiler-sfc:** expose type resolve APIs ([f22e32e](https://github.com/vuejs/core/commit/f22e32e365bf6292cb606cb7289609e82da8b790)) +* **compiler-sfc:** mark props destructure as experimental and require explicit opt-in ([6b13e04](https://github.com/vuejs/core/commit/6b13e04b4c83fcdbb180dc1d59f536a1309c2960)) +* **compiler-sfc:** support intersection and union types in macros ([d1f973b](https://github.com/vuejs/core/commit/d1f973bff82581fb335d6fc05623d1ad3d84fb7c)), closes [#7553](https://github.com/vuejs/core/issues/7553) +* **compiler-sfc:** support limited built-in utility types in macros ([1cfab4c](https://github.com/vuejs/core/commit/1cfab4c695b0c28f549f8c97faee5099581792a7)) +* **compiler-sfc:** support mapped types, string types & template type in macros ([fb8ecc8](https://github.com/vuejs/core/commit/fb8ecc803e58bfef0971346c63fefc529812daa7)) +* **compiler-sfc:** support namespace members type in macros ([5ff40bb](https://github.com/vuejs/core/commit/5ff40bb0dc2918b7db15fe9f49db2a135a925572)) +* **compiler-sfc:** support relative imported types in macros ([8aa4ea8](https://github.com/vuejs/core/commit/8aa4ea81d6e4d3110aa1619cca594543da4c9b63)) +* **compiler-sfc:** support resolving type imports from modules ([3982bef](https://github.com/vuejs/core/commit/3982bef533b451d1b59fa243560184a13fe8c18c)) +* **compiler-sfc:** support specifying global types for sfc macros ([4e028b9](https://github.com/vuejs/core/commit/4e028b966991937c83fb2529973fd3d41080bb61)), closes [/github.com/vuejs/core/pull/8083#issuecomment-1508468713](https://github.com//github.com/vuejs/core/pull/8083/issues/issuecomment-1508468713) +* **compiler-sfc:** support string indexed type in macros ([3f779dd](https://github.com/vuejs/core/commit/3f779ddbf85054c8915fa4537f8a79baab392d5c)) +* **compiler-sfc:** support string/number indexed types in macros ([760755f](https://github.com/vuejs/core/commit/760755f4f83680bee13ad546cdab2e48ade38dff)) + + +### Performance Improvements + +* **compiler:** use source-map-js ([19e17a9](https://github.com/vuejs/core/commit/19e17a951c3387cbd6a1597e6cd9048a4aad4528)) + + + # [3.3.0-alpha.9](https://github.com/vuejs/core/compare/v3.3.0-alpha.8...v3.3.0-alpha.9) (2023-04-08) diff --git a/package.json b/package.json index 35d89e177a4..4e28fef6f77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "packageManager": "pnpm@7.26.0", "type": "module", "scripts": { diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 5b4b0da895c..4bda8ec8828 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/compiler-core", "main": "index.js", "module": "dist/compiler-core.esm-bundler.js", @@ -33,7 +33,7 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme", "dependencies": { "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.0-alpha.9", + "@vue/shared": "3.3.0-alpha.10", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" }, diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index a73978cec7e..b07b66e5a9f 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/compiler-dom", "main": "index.js", "module": "dist/compiler-dom.esm-bundler.js", @@ -37,7 +37,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/compiler-core": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.10", + "@vue/compiler-core": "3.3.0-alpha.10" } } diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index d43f0c6355f..70d0a020451 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", "module": "dist/compiler-sfc.esm-browser.js", @@ -33,11 +33,11 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.9", - "@vue/compiler-dom": "3.3.0-alpha.9", - "@vue/compiler-ssr": "3.3.0-alpha.9", - "@vue/reactivity-transform": "3.3.0-alpha.9", - "@vue/shared": "3.3.0-alpha.9", + "@vue/compiler-core": "3.3.0-alpha.10", + "@vue/compiler-dom": "3.3.0-alpha.10", + "@vue/compiler-ssr": "3.3.0-alpha.10", + "@vue/reactivity-transform": "3.3.0-alpha.10", + "@vue/shared": "3.3.0-alpha.10", "estree-walker": "^2.0.2", "magic-string": "^0.30.0", "postcss": "^8.1.10", diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index b1809422b1d..928ac6969eb 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-ssr", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/compiler-ssr", "main": "dist/compiler-ssr.cjs.js", "types": "dist/compiler-ssr.d.ts", @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/compiler-dom": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.10", + "@vue/compiler-dom": "3.3.0-alpha.10" } } diff --git a/packages/dts-test/package.json b/packages/dts-test/package.json index 833b92e5008..6717f6d3806 100644 --- a/packages/dts-test/package.json +++ b/packages/dts-test/package.json @@ -4,5 +4,5 @@ "dependencies": { "vue": "workspace:*" }, - "version": "3.3.0-alpha.9" + "version": "3.3.0-alpha.10" } diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json index 5045a40a818..000a9b2ddab 100644 --- a/packages/reactivity-transform/package.json +++ b/packages/reactivity-transform/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity-transform", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/reactivity-transform", "main": "dist/reactivity-transform.cjs.js", "files": [ @@ -29,8 +29,8 @@ "homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.9", - "@vue/shared": "3.3.0-alpha.9", + "@vue/compiler-core": "3.3.0-alpha.10", + "@vue/shared": "3.3.0-alpha.10", "estree-walker": "^2.0.2", "magic-string": "^0.30.0" }, diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index 4c7988e82a6..71577a27848 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/reactivity", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", @@ -36,6 +36,6 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.10" } } diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index 606fef5414a..c9facdc653a 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-core", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/runtime-core", "main": "index.js", "module": "dist/runtime-core.esm-bundler.js", @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/reactivity": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.10", + "@vue/reactivity": "3.3.0-alpha.10" } } diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json index 25da3f9657a..223ccb4bae2 100644 --- a/packages/runtime-dom/package.json +++ b/packages/runtime-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-dom", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/runtime-dom", "main": "index.js", "module": "dist/runtime-dom.esm-bundler.js", @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/runtime-core": "3.3.0-alpha.9", + "@vue/shared": "3.3.0-alpha.10", + "@vue/runtime-core": "3.3.0-alpha.10", "csstype": "^3.1.1" } } diff --git a/packages/runtime-test/package.json b/packages/runtime-test/package.json index 202161c9474..67718fe4c4c 100644 --- a/packages/runtime-test/package.json +++ b/packages/runtime-test/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-test", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/runtime-test", "private": true, "main": "index.js", @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-test#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/runtime-core": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.10", + "@vue/runtime-core": "3.3.0-alpha.10" } } diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json index 9f8a2f8eafd..676c0eb46ed 100644 --- a/packages/server-renderer/package.json +++ b/packages/server-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/server-renderer", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "@vue/server-renderer", "main": "index.js", "module": "dist/server-renderer.esm-bundler.js", @@ -32,10 +32,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme", "peerDependencies": { - "vue": "3.3.0-alpha.9" + "vue": "3.3.0-alpha.10" }, "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/compiler-ssr": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.10", + "@vue/compiler-ssr": "3.3.0-alpha.10" } } diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index 848e3ee97b8..e067028e040 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -1,6 +1,6 @@ { "name": "@vue/sfc-playground", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "private": true, "scripts": { "dev": "vite", diff --git a/packages/shared/package.json b/packages/shared/package.json index a7ceb93b4ae..d3c5665d3cf 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vue/shared", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "internal utils shared across @vue packages", "main": "index.js", "module": "dist/shared.esm-bundler.js", diff --git a/packages/size-check/package.json b/packages/size-check/package.json index 34c7df41c92..96c8b1f2969 100644 --- a/packages/size-check/package.json +++ b/packages/size-check/package.json @@ -1,6 +1,6 @@ { "name": "@vue/size-check", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "private": true, "scripts": { "build": "vite build" diff --git a/packages/template-explorer/package.json b/packages/template-explorer/package.json index b75666ae9dd..c8a4fd03ebe 100644 --- a/packages/template-explorer/package.json +++ b/packages/template-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/template-explorer", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "private": true, "buildOptions": { "formats": [ diff --git a/packages/vue-compat/package.json b/packages/vue-compat/package.json index ce29f6a5752..047b5d0f1c9 100644 --- a/packages/vue-compat/package.json +++ b/packages/vue-compat/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compat", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "Vue 3 compatibility build for Vue 2", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -43,6 +43,6 @@ "source-map-js": "^1.0.2" }, "peerDependencies": { - "vue": "3.3.0-alpha.9" + "vue": "3.3.0-alpha.10" } } diff --git a/packages/vue/package.json b/packages/vue/package.json index 5a2e32f0e5a..fe1da15c0cc 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "vue", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.10", "description": "The progressive JavaScript framework for building modern web UI.", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -81,10 +81,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/compiler-dom": "3.3.0-alpha.9", - "@vue/runtime-dom": "3.3.0-alpha.9", - "@vue/compiler-sfc": "3.3.0-alpha.9", - "@vue/server-renderer": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.10", + "@vue/compiler-dom": "3.3.0-alpha.10", + "@vue/runtime-dom": "3.3.0-alpha.10", + "@vue/compiler-sfc": "3.3.0-alpha.10", + "@vue/server-renderer": "3.3.0-alpha.10" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b32007ae6ce..2c63903452d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,7 +100,7 @@ importers: specifiers: '@babel/parser': ^7.21.3 '@babel/types': ^7.21.3 - '@vue/shared': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.10 estree-walker: ^2.0.2 source-map-js: ^1.0.2 dependencies: @@ -113,8 +113,8 @@ importers: packages/compiler-dom: specifiers: - '@vue/compiler-core': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-core': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 dependencies: '@vue/compiler-core': link:../compiler-core '@vue/shared': link:../shared @@ -125,12 +125,12 @@ importers: '@babel/types': ^7.21.3 '@types/estree': ^0.0.48 '@types/lru-cache': ^5.1.0 - '@vue/compiler-core': 3.3.0-alpha.9 - '@vue/compiler-dom': 3.3.0-alpha.9 - '@vue/compiler-ssr': 3.3.0-alpha.9 + '@vue/compiler-core': 3.3.0-alpha.10 + '@vue/compiler-dom': 3.3.0-alpha.10 + '@vue/compiler-ssr': 3.3.0-alpha.10 '@vue/consolidate': ^0.17.3 - '@vue/reactivity-transform': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/reactivity-transform': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 estree-walker: ^2.0.2 hash-sum: ^2.0.0 lru-cache: ^5.1.1 @@ -168,8 +168,8 @@ importers: packages/compiler-ssr: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-dom': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/shared': link:../shared @@ -182,7 +182,7 @@ importers: packages/reactivity: specifiers: - '@vue/shared': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.10 dependencies: '@vue/shared': link:../shared @@ -191,8 +191,8 @@ importers: '@babel/core': ^7.21.3 '@babel/parser': ^7.20.15 '@babel/types': ^7.21.3 - '@vue/compiler-core': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-core': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 estree-walker: ^2.0.2 magic-string: ^0.30.0 dependencies: @@ -207,16 +207,16 @@ importers: packages/runtime-core: specifiers: - '@vue/reactivity': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/reactivity': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 dependencies: '@vue/reactivity': link:../reactivity '@vue/shared': link:../shared packages/runtime-dom: specifiers: - '@vue/runtime-core': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/runtime-core': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 csstype: ^3.1.1 dependencies: '@vue/runtime-core': link:../runtime-core @@ -225,16 +225,16 @@ importers: packages/runtime-test: specifiers: - '@vue/runtime-core': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/runtime-core': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 dependencies: '@vue/runtime-core': link:../runtime-core '@vue/shared': link:../shared packages/server-renderer: specifiers: - '@vue/compiler-ssr': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-ssr': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 dependencies: '@vue/compiler-ssr': link:../compiler-ssr '@vue/shared': link:../shared @@ -275,11 +275,11 @@ importers: packages/vue: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.9 - '@vue/compiler-sfc': 3.3.0-alpha.9 - '@vue/runtime-dom': 3.3.0-alpha.9 - '@vue/server-renderer': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-dom': 3.3.0-alpha.10 + '@vue/compiler-sfc': 3.3.0-alpha.10 + '@vue/runtime-dom': 3.3.0-alpha.10 + '@vue/server-renderer': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.10 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/compiler-sfc': link:../compiler-sfc From 271df09470c61d073185ba6cf3cf50358713c500 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 17 Apr 2023 20:59:03 +0800 Subject: [PATCH 0449/1409] fix(compiler-sfc): normalize windows paths when resolving types --- .../compiler-sfc/src/script/resolveType.ts | 21 +++++++++++-------- packages/compiler-sfc/src/script/utils.ts | 8 +++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 8efa04579f8..247bac3a3be 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -25,7 +25,8 @@ import { UNKNOWN_TYPE, createGetCanonicalFileName, getId, - getImportedName + getImportedName, + normalizePath } from './utils' import { ScriptCompileContext, resolveParserPlugins } from './context' import { ImportBinding, SFCScriptCompileOptions } from '../compileScript' @@ -34,7 +35,7 @@ import { parse as babelParse } from '@babel/parser' import { parse } from '../parse' import { createCache } from '../cache' import type TS from 'typescript' -import { join, extname, dirname } from 'path' +import path from 'path' /** * TypeResolveContext is compatible with ScriptCompileContext @@ -577,8 +578,7 @@ function resolveGlobalScope(ctx: TypeResolveContext): TypeScope[] | undefined { throw new Error('[vue/compiler-sfc] globalTypeFiles requires fs access.') } return ctx.options.globalTypeFiles.map(file => - // TODO: differentiate ambient vs non-ambient module - fileToScope(file, fs, ctx.options.babelParserPlugins, true) + fileToScope(normalizePath(file), fs, ctx.options.babelParserPlugins, true) ) } } @@ -616,7 +616,7 @@ function resolveTypeFromImport( if (source.startsWith('.')) { // relative import - fast path - const filename = join(containingFile, '..', source) + const filename = path.join(containingFile, '..', source) resolved = resolveExt(filename, fs) } else { // module or aliased import - use full TS resolution, only supported in Node @@ -642,6 +642,8 @@ function resolveTypeFromImport( } if (resolved) { + resolved = normalizePath(resolved) + // (hmr) register dependency file on ctx ;(ctx.deps || (ctx.deps = new Set())).add(resolved) @@ -694,7 +696,8 @@ function resolveWithTS( let options: TS.CompilerOptions let cache: TS.ModuleResolutionCache | undefined if (configPath) { - const cached = tsConfigCache.get(configPath) + const normalizedConfigPath = normalizePath(configPath) + const cached = tsConfigCache.get(normalizedConfigPath) if (!cached) { // The only case where `fs` is NOT `ts.sys` is during tests. // parse config host requires an extra `readDirectory` method @@ -709,7 +712,7 @@ function resolveWithTS( const parsed = ts.parseJsonConfigFileContent( ts.readConfigFile(configPath, fs.readFile).config, parseConfigHost, - dirname(configPath), + path.dirname(configPath), undefined, configPath ) @@ -719,7 +722,7 @@ function resolveWithTS( createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames), options ) - tsConfigCache.set(configPath, { options, cache }) + tsConfigCache.set(normalizedConfigPath, { options, cache }) } else { ;({ options, cache } = cached) } @@ -777,7 +780,7 @@ function parseFile( content: string, parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'] ): Statement[] { - const ext = extname(filename) + const ext = path.extname(filename) if (ext === '.ts' || ext === '.tsx') { return babelParse(content, { plugins: resolveParserPlugins(ext.slice(1), parserPlugins), diff --git a/packages/compiler-sfc/src/script/utils.ts b/packages/compiler-sfc/src/script/utils.ts index 6d874f8a6db..04df22a2b64 100644 --- a/packages/compiler-sfc/src/script/utils.ts +++ b/packages/compiler-sfc/src/script/utils.ts @@ -8,6 +8,7 @@ import { Node, StringLiteral } from '@babel/types' +import path from 'path' import { TS_NODE_TYPES } from '@vue/compiler-dom' export const UNKNOWN_TYPE = 'Unknown' @@ -97,3 +98,10 @@ function toFileNameLowerCase(x: string) { export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean) { return useCaseSensitiveFileNames ? identity : toFileNameLowerCase } + +const windowsSlashRE = /\\/g +export function normalizePath(p: string) { + // in the browser build, the polyfill doesn't expose posix, but defualts to + // posix behavior. + return (path.posix || path).normalize(p.replace(windowsSlashRE, '/')) +} From 57f0fbe76ae6454e10817771800e80d98f683e55 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 17 Apr 2023 21:17:17 +0800 Subject: [PATCH 0450/1409] release: v3.3.0-alpha.11 --- CHANGELOG.md | 9 ++++ package.json | 2 +- packages/compiler-core/package.json | 4 +- packages/compiler-dom/package.json | 6 +-- packages/compiler-sfc/package.json | 12 ++--- packages/compiler-ssr/package.json | 6 +-- packages/dts-test/package.json | 2 +- packages/reactivity-transform/package.json | 6 +-- packages/reactivity/package.json | 4 +- packages/runtime-core/package.json | 6 +-- packages/runtime-dom/package.json | 6 +-- packages/runtime-test/package.json | 6 +-- packages/server-renderer/package.json | 8 ++-- packages/sfc-playground/package.json | 2 +- packages/shared/package.json | 2 +- packages/size-check/package.json | 2 +- packages/template-explorer/package.json | 2 +- packages/vue-compat/package.json | 4 +- packages/vue/package.json | 12 ++--- pnpm-lock.yaml | 52 +++++++++++----------- 20 files changed, 81 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe7596228cb..69af50d2351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [3.3.0-alpha.11](https://github.com/vuejs/core/compare/v3.3.0-alpha.10...v3.3.0-alpha.11) (2023-04-17) + + +### Bug Fixes + +* **compiler-sfc:** normalize windows paths when resolving types ([271df09](https://github.com/vuejs/core/commit/271df09470c61d073185ba6cf3cf50358713c500)) + + + # [3.3.0-alpha.10](https://github.com/vuejs/core/compare/v3.3.0-alpha.9...v3.3.0-alpha.10) (2023-04-17) diff --git a/package.json b/package.json index 4e28fef6f77..3817cbeaea5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "packageManager": "pnpm@7.26.0", "type": "module", "scripts": { diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 4bda8ec8828..d0f00cf98cb 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/compiler-core", "main": "index.js", "module": "dist/compiler-core.esm-bundler.js", @@ -33,7 +33,7 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme", "dependencies": { "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.0-alpha.10", + "@vue/shared": "3.3.0-alpha.11", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" }, diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index b07b66e5a9f..12f902a9dbd 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/compiler-dom", "main": "index.js", "module": "dist/compiler-dom.esm-bundler.js", @@ -37,7 +37,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.10", - "@vue/compiler-core": "3.3.0-alpha.10" + "@vue/shared": "3.3.0-alpha.11", + "@vue/compiler-core": "3.3.0-alpha.11" } } diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index 70d0a020451..fb1c1bb34a4 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", "module": "dist/compiler-sfc.esm-browser.js", @@ -33,11 +33,11 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.10", - "@vue/compiler-dom": "3.3.0-alpha.10", - "@vue/compiler-ssr": "3.3.0-alpha.10", - "@vue/reactivity-transform": "3.3.0-alpha.10", - "@vue/shared": "3.3.0-alpha.10", + "@vue/compiler-core": "3.3.0-alpha.11", + "@vue/compiler-dom": "3.3.0-alpha.11", + "@vue/compiler-ssr": "3.3.0-alpha.11", + "@vue/reactivity-transform": "3.3.0-alpha.11", + "@vue/shared": "3.3.0-alpha.11", "estree-walker": "^2.0.2", "magic-string": "^0.30.0", "postcss": "^8.1.10", diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index 928ac6969eb..c7da262ea2d 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-ssr", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/compiler-ssr", "main": "dist/compiler-ssr.cjs.js", "types": "dist/compiler-ssr.d.ts", @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.10", - "@vue/compiler-dom": "3.3.0-alpha.10" + "@vue/shared": "3.3.0-alpha.11", + "@vue/compiler-dom": "3.3.0-alpha.11" } } diff --git a/packages/dts-test/package.json b/packages/dts-test/package.json index 6717f6d3806..9fe3201bfd0 100644 --- a/packages/dts-test/package.json +++ b/packages/dts-test/package.json @@ -4,5 +4,5 @@ "dependencies": { "vue": "workspace:*" }, - "version": "3.3.0-alpha.10" + "version": "3.3.0-alpha.11" } diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json index 000a9b2ddab..fae33dc3c2c 100644 --- a/packages/reactivity-transform/package.json +++ b/packages/reactivity-transform/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity-transform", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/reactivity-transform", "main": "dist/reactivity-transform.cjs.js", "files": [ @@ -29,8 +29,8 @@ "homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.10", - "@vue/shared": "3.3.0-alpha.10", + "@vue/compiler-core": "3.3.0-alpha.11", + "@vue/shared": "3.3.0-alpha.11", "estree-walker": "^2.0.2", "magic-string": "^0.30.0" }, diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index 71577a27848..6aab8e70881 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/reactivity", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", @@ -36,6 +36,6 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.10" + "@vue/shared": "3.3.0-alpha.11" } } diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index c9facdc653a..a6024f0941b 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-core", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/runtime-core", "main": "index.js", "module": "dist/runtime-core.esm-bundler.js", @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.10", - "@vue/reactivity": "3.3.0-alpha.10" + "@vue/shared": "3.3.0-alpha.11", + "@vue/reactivity": "3.3.0-alpha.11" } } diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json index 223ccb4bae2..16070ec700b 100644 --- a/packages/runtime-dom/package.json +++ b/packages/runtime-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-dom", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/runtime-dom", "main": "index.js", "module": "dist/runtime-dom.esm-bundler.js", @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.10", - "@vue/runtime-core": "3.3.0-alpha.10", + "@vue/shared": "3.3.0-alpha.11", + "@vue/runtime-core": "3.3.0-alpha.11", "csstype": "^3.1.1" } } diff --git a/packages/runtime-test/package.json b/packages/runtime-test/package.json index 67718fe4c4c..9417bbeb2d8 100644 --- a/packages/runtime-test/package.json +++ b/packages/runtime-test/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-test", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/runtime-test", "private": true, "main": "index.js", @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-test#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.10", - "@vue/runtime-core": "3.3.0-alpha.10" + "@vue/shared": "3.3.0-alpha.11", + "@vue/runtime-core": "3.3.0-alpha.11" } } diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json index 676c0eb46ed..e2fa4fc1a44 100644 --- a/packages/server-renderer/package.json +++ b/packages/server-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/server-renderer", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "@vue/server-renderer", "main": "index.js", "module": "dist/server-renderer.esm-bundler.js", @@ -32,10 +32,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme", "peerDependencies": { - "vue": "3.3.0-alpha.10" + "vue": "3.3.0-alpha.11" }, "dependencies": { - "@vue/shared": "3.3.0-alpha.10", - "@vue/compiler-ssr": "3.3.0-alpha.10" + "@vue/shared": "3.3.0-alpha.11", + "@vue/compiler-ssr": "3.3.0-alpha.11" } } diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index e067028e040..56380d969d5 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -1,6 +1,6 @@ { "name": "@vue/sfc-playground", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "private": true, "scripts": { "dev": "vite", diff --git a/packages/shared/package.json b/packages/shared/package.json index d3c5665d3cf..219e29b3a28 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vue/shared", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "internal utils shared across @vue packages", "main": "index.js", "module": "dist/shared.esm-bundler.js", diff --git a/packages/size-check/package.json b/packages/size-check/package.json index 96c8b1f2969..e9550f808d6 100644 --- a/packages/size-check/package.json +++ b/packages/size-check/package.json @@ -1,6 +1,6 @@ { "name": "@vue/size-check", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "private": true, "scripts": { "build": "vite build" diff --git a/packages/template-explorer/package.json b/packages/template-explorer/package.json index c8a4fd03ebe..0d1c25a68d3 100644 --- a/packages/template-explorer/package.json +++ b/packages/template-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/template-explorer", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "private": true, "buildOptions": { "formats": [ diff --git a/packages/vue-compat/package.json b/packages/vue-compat/package.json index 047b5d0f1c9..0c349b48883 100644 --- a/packages/vue-compat/package.json +++ b/packages/vue-compat/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compat", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "Vue 3 compatibility build for Vue 2", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -43,6 +43,6 @@ "source-map-js": "^1.0.2" }, "peerDependencies": { - "vue": "3.3.0-alpha.10" + "vue": "3.3.0-alpha.11" } } diff --git a/packages/vue/package.json b/packages/vue/package.json index fe1da15c0cc..cd4a392fb21 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "vue", - "version": "3.3.0-alpha.10", + "version": "3.3.0-alpha.11", "description": "The progressive JavaScript framework for building modern web UI.", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -81,10 +81,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.10", - "@vue/compiler-dom": "3.3.0-alpha.10", - "@vue/runtime-dom": "3.3.0-alpha.10", - "@vue/compiler-sfc": "3.3.0-alpha.10", - "@vue/server-renderer": "3.3.0-alpha.10" + "@vue/shared": "3.3.0-alpha.11", + "@vue/compiler-dom": "3.3.0-alpha.11", + "@vue/runtime-dom": "3.3.0-alpha.11", + "@vue/compiler-sfc": "3.3.0-alpha.11", + "@vue/server-renderer": "3.3.0-alpha.11" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c63903452d..08edce44f29 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,7 +100,7 @@ importers: specifiers: '@babel/parser': ^7.21.3 '@babel/types': ^7.21.3 - '@vue/shared': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.11 estree-walker: ^2.0.2 source-map-js: ^1.0.2 dependencies: @@ -113,8 +113,8 @@ importers: packages/compiler-dom: specifiers: - '@vue/compiler-core': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/compiler-core': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 dependencies: '@vue/compiler-core': link:../compiler-core '@vue/shared': link:../shared @@ -125,12 +125,12 @@ importers: '@babel/types': ^7.21.3 '@types/estree': ^0.0.48 '@types/lru-cache': ^5.1.0 - '@vue/compiler-core': 3.3.0-alpha.10 - '@vue/compiler-dom': 3.3.0-alpha.10 - '@vue/compiler-ssr': 3.3.0-alpha.10 + '@vue/compiler-core': 3.3.0-alpha.11 + '@vue/compiler-dom': 3.3.0-alpha.11 + '@vue/compiler-ssr': 3.3.0-alpha.11 '@vue/consolidate': ^0.17.3 - '@vue/reactivity-transform': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/reactivity-transform': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 estree-walker: ^2.0.2 hash-sum: ^2.0.0 lru-cache: ^5.1.1 @@ -168,8 +168,8 @@ importers: packages/compiler-ssr: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/compiler-dom': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/shared': link:../shared @@ -182,7 +182,7 @@ importers: packages/reactivity: specifiers: - '@vue/shared': 3.3.0-alpha.10 + '@vue/shared': 3.3.0-alpha.11 dependencies: '@vue/shared': link:../shared @@ -191,8 +191,8 @@ importers: '@babel/core': ^7.21.3 '@babel/parser': ^7.20.15 '@babel/types': ^7.21.3 - '@vue/compiler-core': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/compiler-core': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 estree-walker: ^2.0.2 magic-string: ^0.30.0 dependencies: @@ -207,16 +207,16 @@ importers: packages/runtime-core: specifiers: - '@vue/reactivity': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/reactivity': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 dependencies: '@vue/reactivity': link:../reactivity '@vue/shared': link:../shared packages/runtime-dom: specifiers: - '@vue/runtime-core': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/runtime-core': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 csstype: ^3.1.1 dependencies: '@vue/runtime-core': link:../runtime-core @@ -225,16 +225,16 @@ importers: packages/runtime-test: specifiers: - '@vue/runtime-core': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/runtime-core': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 dependencies: '@vue/runtime-core': link:../runtime-core '@vue/shared': link:../shared packages/server-renderer: specifiers: - '@vue/compiler-ssr': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/compiler-ssr': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 dependencies: '@vue/compiler-ssr': link:../compiler-ssr '@vue/shared': link:../shared @@ -275,11 +275,11 @@ importers: packages/vue: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.10 - '@vue/compiler-sfc': 3.3.0-alpha.10 - '@vue/runtime-dom': 3.3.0-alpha.10 - '@vue/server-renderer': 3.3.0-alpha.10 - '@vue/shared': 3.3.0-alpha.10 + '@vue/compiler-dom': 3.3.0-alpha.11 + '@vue/compiler-sfc': 3.3.0-alpha.11 + '@vue/runtime-dom': 3.3.0-alpha.11 + '@vue/server-renderer': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.11 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/compiler-sfc': link:../compiler-sfc From 7f1b546a99bae9c70502470a13301faabaed184b Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 18 Apr 2023 11:39:21 +0800 Subject: [PATCH 0451/1409] workflow: support building types in build script --- rollup.dts.config.js | 8 +++++++- scripts/build.js | 26 ++++++++++++++++++++------ scripts/release.js | 6 +++--- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/rollup.dts.config.js b/rollup.dts.config.js index 1b4df21adac..bb146c46279 100644 --- a/rollup.dts.config.js +++ b/rollup.dts.config.js @@ -12,7 +12,13 @@ if (!existsSync('temp/packages')) { process.exit(1) } -export default readdirSync('temp/packages').map(pkg => { +const packages = readdirSync('temp/packages') +const targets = process.env.TARGETS ? process.env.TARGETS.split(',') : null +const targetPackages = targets + ? packages.filter(pkg => targets.includes(pkg)) + : packages + +export default targetPackages.map(pkg => { return { input: `./temp/packages/${pkg}/src/index.d.ts`, output: { diff --git a/scripts/build.js b/scripts/build.js index 05ed32ebc5c..689d3dc9351 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -34,6 +34,7 @@ const targets = args._ const formats = args.formats || args.f const devOnly = args.devOnly || args.d const prodOnly = !devOnly && (args.prodOnly || args.p) +const buildTypes = args.withTypes || args.t const sourceMap = args.sourcemap || args.s const isRelease = args.release const buildAllMatching = args.all || args.a @@ -44,12 +45,25 @@ run() async function run() { const removeCache = scanEnums() try { - if (!targets.length) { - await buildAll(allTargets) - checkAllSizes(allTargets) - } else { - await buildAll(fuzzyMatchTarget(targets, buildAllMatching)) - checkAllSizes(fuzzyMatchTarget(targets, buildAllMatching)) + const resolvedTargets = targets.length + ? fuzzyMatchTarget(targets, buildAllMatching) + : allTargets + await buildAll(resolvedTargets) + checkAllSizes(resolvedTargets) + if (buildTypes) { + await execa( + 'pnpm', + [ + 'run', + 'build-dts', + ...(targets.length + ? ['--environment', `TARGETS:${resolvedTargets.join(',')}`] + : []) + ], + { + stdio: 'inherit' + } + ) } } finally { removeCache() diff --git a/scripts/release.js b/scripts/release.js index 15130174149..4eabe911bee 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -199,9 +199,9 @@ async function main() { // build all packages with types step('\nBuilding all packages...') if (!skipBuild && !isDryRun) { - await run('pnpm', ['run', 'build']) - step('\nBuilding and testing types...') - await run('pnpm', ['test-dts']) + await run('pnpm', ['run', 'build', '--withTypes']) + step('\nTesting built types...') + await run('pnpm', ['test-dts-only']) } else { console.log(`(skipped)`) } From 0f77a2b1d1047d66ccdfda70382d1a223886130c Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 18 Apr 2023 11:39:53 +0800 Subject: [PATCH 0452/1409] fix(compiler): fix expression codegen for literal const bindings in non-inline mode --- .../transforms/transformExpressions.spec.ts | 2 +- .../src/transforms/transformExpression.ts | 7 ++++--- .../__tests__/compileScript/hoistStatic.spec.ts | 12 ++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 66f988d3474..5a77b2eddbc 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -549,7 +549,7 @@ describe('compiler: expression transform', () => { test('literal const handling, non-inline mode', () => { const { code } = compileWithBindingMetadata(`

{{ literal }}
`) - expect(code).toMatch(`toDisplayString(literal)`) + expect(code).toMatch(`toDisplayString($setup.literal)`) // #7973 should skip patch for literal const expect(code).not.toMatch( `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */` diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 466027682b1..35fc278ac86 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -197,13 +197,14 @@ export function processExpression( return genPropsAccessExp(bindingMetadata.__propsAliases![raw]) } } else { - if (type && type.startsWith('setup')) { + if ( + (type && type.startsWith('setup')) || + type === BindingTypes.LITERAL_CONST + ) { // setup bindings in non-inline mode return `$setup.${raw}` } else if (type === BindingTypes.PROPS_ALIASED) { return `$props['${bindingMetadata.__propsAliases![raw]}']` - } else if (type === BindingTypes.LITERAL_CONST) { - return raw } else if (type) { return `$${type}.${raw}` } diff --git a/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts b/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts index 614a5e75bce..7b3a8a813c5 100644 --- a/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts @@ -202,4 +202,16 @@ describe('sfc hoist static', () => { }) assertCode(content) }) + + test('template binding access in inline mode', () => { + const { content } = compile( + ` + + + ` + ) + expect(content).toMatch('_toDisplayString(foo)') + }) }) From 72be89423da29841682d294bf70acb9e20594330 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 18 Apr 2023 22:18:56 +0800 Subject: [PATCH 0453/1409] release: v3.3.0-alpha.12 --- CHANGELOG.md | 9 ++++ package.json | 2 +- packages/compiler-core/package.json | 4 +- packages/compiler-dom/package.json | 6 +-- packages/compiler-sfc/package.json | 12 ++--- packages/compiler-ssr/package.json | 6 +-- packages/dts-test/package.json | 2 +- packages/reactivity-transform/package.json | 6 +-- packages/reactivity/package.json | 4 +- packages/runtime-core/package.json | 6 +-- packages/runtime-dom/package.json | 6 +-- packages/runtime-test/package.json | 6 +-- packages/server-renderer/package.json | 8 ++-- packages/sfc-playground/package.json | 2 +- packages/shared/package.json | 2 +- packages/size-check/package.json | 2 +- packages/template-explorer/package.json | 2 +- packages/vue-compat/package.json | 4 +- packages/vue/package.json | 12 ++--- pnpm-lock.yaml | 52 +++++++++++----------- 20 files changed, 81 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69af50d2351..dba7c533dc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [3.3.0-alpha.12](https://github.com/vuejs/core/compare/v3.3.0-alpha.11...v3.3.0-alpha.12) (2023-04-18) + + +### Bug Fixes + +* **compiler:** fix expression codegen for literal const bindings in non-inline mode ([0f77a2b](https://github.com/vuejs/core/commit/0f77a2b1d1047d66ccdfda70382d1a223886130c)) + + + # [3.3.0-alpha.11](https://github.com/vuejs/core/compare/v3.3.0-alpha.10...v3.3.0-alpha.11) (2023-04-17) diff --git a/package.json b/package.json index 3817cbeaea5..9aadea2b94c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "packageManager": "pnpm@7.26.0", "type": "module", "scripts": { diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index d0f00cf98cb..3e04a35b48c 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/compiler-core", "main": "index.js", "module": "dist/compiler-core.esm-bundler.js", @@ -33,7 +33,7 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme", "dependencies": { "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.0-alpha.11", + "@vue/shared": "3.3.0-alpha.12", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" }, diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index 12f902a9dbd..77315a776aa 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/compiler-dom", "main": "index.js", "module": "dist/compiler-dom.esm-bundler.js", @@ -37,7 +37,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.11", - "@vue/compiler-core": "3.3.0-alpha.11" + "@vue/shared": "3.3.0-alpha.12", + "@vue/compiler-core": "3.3.0-alpha.12" } } diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index fb1c1bb34a4..7bdce2ae52f 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", "module": "dist/compiler-sfc.esm-browser.js", @@ -33,11 +33,11 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.11", - "@vue/compiler-dom": "3.3.0-alpha.11", - "@vue/compiler-ssr": "3.3.0-alpha.11", - "@vue/reactivity-transform": "3.3.0-alpha.11", - "@vue/shared": "3.3.0-alpha.11", + "@vue/compiler-core": "3.3.0-alpha.12", + "@vue/compiler-dom": "3.3.0-alpha.12", + "@vue/compiler-ssr": "3.3.0-alpha.12", + "@vue/reactivity-transform": "3.3.0-alpha.12", + "@vue/shared": "3.3.0-alpha.12", "estree-walker": "^2.0.2", "magic-string": "^0.30.0", "postcss": "^8.1.10", diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index c7da262ea2d..00a19fd30fb 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-ssr", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/compiler-ssr", "main": "dist/compiler-ssr.cjs.js", "types": "dist/compiler-ssr.d.ts", @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.11", - "@vue/compiler-dom": "3.3.0-alpha.11" + "@vue/shared": "3.3.0-alpha.12", + "@vue/compiler-dom": "3.3.0-alpha.12" } } diff --git a/packages/dts-test/package.json b/packages/dts-test/package.json index 9fe3201bfd0..35f773f4c8f 100644 --- a/packages/dts-test/package.json +++ b/packages/dts-test/package.json @@ -4,5 +4,5 @@ "dependencies": { "vue": "workspace:*" }, - "version": "3.3.0-alpha.11" + "version": "3.3.0-alpha.12" } diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json index fae33dc3c2c..8f9f27714a2 100644 --- a/packages/reactivity-transform/package.json +++ b/packages/reactivity-transform/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity-transform", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/reactivity-transform", "main": "dist/reactivity-transform.cjs.js", "files": [ @@ -29,8 +29,8 @@ "homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.11", - "@vue/shared": "3.3.0-alpha.11", + "@vue/compiler-core": "3.3.0-alpha.12", + "@vue/shared": "3.3.0-alpha.12", "estree-walker": "^2.0.2", "magic-string": "^0.30.0" }, diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index 6aab8e70881..976c806f7d3 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/reactivity", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", @@ -36,6 +36,6 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.11" + "@vue/shared": "3.3.0-alpha.12" } } diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index a6024f0941b..262062392b3 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-core", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/runtime-core", "main": "index.js", "module": "dist/runtime-core.esm-bundler.js", @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.11", - "@vue/reactivity": "3.3.0-alpha.11" + "@vue/shared": "3.3.0-alpha.12", + "@vue/reactivity": "3.3.0-alpha.12" } } diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json index 16070ec700b..75f5abfaed3 100644 --- a/packages/runtime-dom/package.json +++ b/packages/runtime-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-dom", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/runtime-dom", "main": "index.js", "module": "dist/runtime-dom.esm-bundler.js", @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.11", - "@vue/runtime-core": "3.3.0-alpha.11", + "@vue/shared": "3.3.0-alpha.12", + "@vue/runtime-core": "3.3.0-alpha.12", "csstype": "^3.1.1" } } diff --git a/packages/runtime-test/package.json b/packages/runtime-test/package.json index 9417bbeb2d8..410b18e315a 100644 --- a/packages/runtime-test/package.json +++ b/packages/runtime-test/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-test", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/runtime-test", "private": true, "main": "index.js", @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-test#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.11", - "@vue/runtime-core": "3.3.0-alpha.11" + "@vue/shared": "3.3.0-alpha.12", + "@vue/runtime-core": "3.3.0-alpha.12" } } diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json index e2fa4fc1a44..a33085b4b4c 100644 --- a/packages/server-renderer/package.json +++ b/packages/server-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/server-renderer", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "@vue/server-renderer", "main": "index.js", "module": "dist/server-renderer.esm-bundler.js", @@ -32,10 +32,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme", "peerDependencies": { - "vue": "3.3.0-alpha.11" + "vue": "3.3.0-alpha.12" }, "dependencies": { - "@vue/shared": "3.3.0-alpha.11", - "@vue/compiler-ssr": "3.3.0-alpha.11" + "@vue/shared": "3.3.0-alpha.12", + "@vue/compiler-ssr": "3.3.0-alpha.12" } } diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index 56380d969d5..fa21efb2cd2 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -1,6 +1,6 @@ { "name": "@vue/sfc-playground", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "private": true, "scripts": { "dev": "vite", diff --git a/packages/shared/package.json b/packages/shared/package.json index 219e29b3a28..f45c496a974 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vue/shared", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "internal utils shared across @vue packages", "main": "index.js", "module": "dist/shared.esm-bundler.js", diff --git a/packages/size-check/package.json b/packages/size-check/package.json index e9550f808d6..ec6535ed06d 100644 --- a/packages/size-check/package.json +++ b/packages/size-check/package.json @@ -1,6 +1,6 @@ { "name": "@vue/size-check", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "private": true, "scripts": { "build": "vite build" diff --git a/packages/template-explorer/package.json b/packages/template-explorer/package.json index 0d1c25a68d3..a3864701851 100644 --- a/packages/template-explorer/package.json +++ b/packages/template-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/template-explorer", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "private": true, "buildOptions": { "formats": [ diff --git a/packages/vue-compat/package.json b/packages/vue-compat/package.json index 0c349b48883..0a1f8117f26 100644 --- a/packages/vue-compat/package.json +++ b/packages/vue-compat/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compat", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "Vue 3 compatibility build for Vue 2", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -43,6 +43,6 @@ "source-map-js": "^1.0.2" }, "peerDependencies": { - "vue": "3.3.0-alpha.11" + "vue": "3.3.0-alpha.12" } } diff --git a/packages/vue/package.json b/packages/vue/package.json index cd4a392fb21..7769f075d1b 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "vue", - "version": "3.3.0-alpha.11", + "version": "3.3.0-alpha.12", "description": "The progressive JavaScript framework for building modern web UI.", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -81,10 +81,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.11", - "@vue/compiler-dom": "3.3.0-alpha.11", - "@vue/runtime-dom": "3.3.0-alpha.11", - "@vue/compiler-sfc": "3.3.0-alpha.11", - "@vue/server-renderer": "3.3.0-alpha.11" + "@vue/shared": "3.3.0-alpha.12", + "@vue/compiler-dom": "3.3.0-alpha.12", + "@vue/runtime-dom": "3.3.0-alpha.12", + "@vue/compiler-sfc": "3.3.0-alpha.12", + "@vue/server-renderer": "3.3.0-alpha.12" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08edce44f29..1d880dbbd69 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,7 +100,7 @@ importers: specifiers: '@babel/parser': ^7.21.3 '@babel/types': ^7.21.3 - '@vue/shared': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.12 estree-walker: ^2.0.2 source-map-js: ^1.0.2 dependencies: @@ -113,8 +113,8 @@ importers: packages/compiler-dom: specifiers: - '@vue/compiler-core': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/compiler-core': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 dependencies: '@vue/compiler-core': link:../compiler-core '@vue/shared': link:../shared @@ -125,12 +125,12 @@ importers: '@babel/types': ^7.21.3 '@types/estree': ^0.0.48 '@types/lru-cache': ^5.1.0 - '@vue/compiler-core': 3.3.0-alpha.11 - '@vue/compiler-dom': 3.3.0-alpha.11 - '@vue/compiler-ssr': 3.3.0-alpha.11 + '@vue/compiler-core': 3.3.0-alpha.12 + '@vue/compiler-dom': 3.3.0-alpha.12 + '@vue/compiler-ssr': 3.3.0-alpha.12 '@vue/consolidate': ^0.17.3 - '@vue/reactivity-transform': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/reactivity-transform': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 estree-walker: ^2.0.2 hash-sum: ^2.0.0 lru-cache: ^5.1.1 @@ -168,8 +168,8 @@ importers: packages/compiler-ssr: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/compiler-dom': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/shared': link:../shared @@ -182,7 +182,7 @@ importers: packages/reactivity: specifiers: - '@vue/shared': 3.3.0-alpha.11 + '@vue/shared': 3.3.0-alpha.12 dependencies: '@vue/shared': link:../shared @@ -191,8 +191,8 @@ importers: '@babel/core': ^7.21.3 '@babel/parser': ^7.20.15 '@babel/types': ^7.21.3 - '@vue/compiler-core': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/compiler-core': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 estree-walker: ^2.0.2 magic-string: ^0.30.0 dependencies: @@ -207,16 +207,16 @@ importers: packages/runtime-core: specifiers: - '@vue/reactivity': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/reactivity': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 dependencies: '@vue/reactivity': link:../reactivity '@vue/shared': link:../shared packages/runtime-dom: specifiers: - '@vue/runtime-core': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/runtime-core': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 csstype: ^3.1.1 dependencies: '@vue/runtime-core': link:../runtime-core @@ -225,16 +225,16 @@ importers: packages/runtime-test: specifiers: - '@vue/runtime-core': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/runtime-core': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 dependencies: '@vue/runtime-core': link:../runtime-core '@vue/shared': link:../shared packages/server-renderer: specifiers: - '@vue/compiler-ssr': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/compiler-ssr': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 dependencies: '@vue/compiler-ssr': link:../compiler-ssr '@vue/shared': link:../shared @@ -275,11 +275,11 @@ importers: packages/vue: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.11 - '@vue/compiler-sfc': 3.3.0-alpha.11 - '@vue/runtime-dom': 3.3.0-alpha.11 - '@vue/server-renderer': 3.3.0-alpha.11 - '@vue/shared': 3.3.0-alpha.11 + '@vue/compiler-dom': 3.3.0-alpha.12 + '@vue/compiler-sfc': 3.3.0-alpha.12 + '@vue/runtime-dom': 3.3.0-alpha.12 + '@vue/server-renderer': 3.3.0-alpha.12 + '@vue/shared': 3.3.0-alpha.12 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/compiler-sfc': link:../compiler-sfc From 9b5a34bf8c0d1b4c6ec3cf1434076b7e25065f84 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 18 Apr 2023 22:21:29 +0800 Subject: [PATCH 0454/1409] fix(compiler-sfc): normalize filename when invalidating cache --- packages/compiler-sfc/src/script/resolveType.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 247bac3a3be..6557b589bf1 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -744,6 +744,7 @@ const fileToScopeCache = createCache() * @private */ export function invalidateTypeCache(filename: string) { + filename = normalizePath(filename) fileToScopeCache.delete(filename) tsConfigCache.delete(filename) } From 94fa67a4f73b3646c8c1e29512a71b17bd56efc3 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 20 Apr 2023 10:06:06 +0800 Subject: [PATCH 0455/1409] fix(hmr): force update cached slots during HMR close #7155 close #7158 --- packages/runtime-core/__tests__/hmr.spec.ts | 31 +++++++ packages/runtime-core/src/component.ts | 86 +++++++++++++------ .../src/componentPublicInstance.ts | 2 + packages/runtime-core/src/componentSlots.ts | 3 + 4 files changed, 95 insertions(+), 27 deletions(-) diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts index d1392b78465..b81a8b3af63 100644 --- a/packages/runtime-core/__tests__/hmr.spec.ts +++ b/packages/runtime-core/__tests__/hmr.spec.ts @@ -537,4 +537,35 @@ describe('hot module replacement', () => { render(h(Foo), root) expect(serializeInner(root)).toBe('bar') }) + + // #7155 - force HMR on slots content update + test('force update slot content change', () => { + const root = nodeOps.createElement('div') + const parentId = 'test-force-computed-parent' + const childId = 'test-force-computed-child' + + const Child: ComponentOptions = { + __hmrId: childId, + computed: { + slotContent() { + return this.$slots.default?.() + } + }, + render: compileToFunction(``) + } + createRecord(childId, Child) + + const Parent: ComponentOptions = { + __hmrId: parentId, + components: { Child }, + render: compileToFunction(`1`) + } + createRecord(parentId, Parent) + + render(h(Parent), root) + expect(serializeInner(root)).toBe(`1`) + + rerender(parentId, compileToFunction(`2`)) + expect(serializeInner(root)).toBe(`2`) + }) }) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 941231b393d..087e901354b 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -349,6 +349,10 @@ export interface ComponentInternalInstance { slots: InternalSlots refs: Data emit: EmitFn + + attrsProxy: Data | null + slotsProxy: Slots | null + /** * used for keeping track of .once event handlers on components * @internal @@ -536,6 +540,9 @@ export function createComponentInstance( setupState: EMPTY_OBJ, setupContext: null, + attrsProxy: null, + slotsProxy: null, + // suspense related suspense, suspenseId: suspense ? suspense.pendingId : 0, @@ -923,31 +930,57 @@ export function finishComponentSetup( } } -function createAttrsProxy(instance: ComponentInternalInstance): Data { - return new Proxy( - instance.attrs, - __DEV__ - ? { - get(target, key: string) { - markAttrsAccessed() - track(instance, TrackOpTypes.GET, '$attrs') - return target[key] - }, - set() { - warn(`setupContext.attrs is readonly.`) - return false - }, - deleteProperty() { - warn(`setupContext.attrs is readonly.`) - return false +function getAttrsProxy(instance: ComponentInternalInstance): Data { + return ( + instance.attrsProxy || + (instance.attrsProxy = new Proxy( + instance.attrs, + __DEV__ + ? { + get(target, key: string) { + markAttrsAccessed() + track(instance, TrackOpTypes.GET, '$attrs') + return target[key] + }, + set() { + warn(`setupContext.attrs is readonly.`) + return false + }, + deleteProperty() { + warn(`setupContext.attrs is readonly.`) + return false + } } - } - : { - get(target, key: string) { - track(instance, TrackOpTypes.GET, '$attrs') - return target[key] + : { + get(target, key: string) { + track(instance, TrackOpTypes.GET, '$attrs') + return target[key] + } } - } + )) + ) +} + +/** + * Dev-only + */ +function getSlotsProxy(instance: ComponentInternalInstance): Slots { + return ( + instance.slotsProxy || + (instance.slotsProxy = new Proxy(instance.slots, { + get(target, key: string) { + track(instance, TrackOpTypes.GET, '$slots') + return target[key] + }, + set() { + warn(`setupContext.slots is readonly.`) + return false + }, + deleteProperty() { + warn(`setupContext.slots is readonly.`) + return false + } + })) ) } @@ -978,16 +1011,15 @@ export function createSetupContext( instance.exposed = exposed || {} } - let attrs: Data if (__DEV__) { // We use getters in dev in case libs like test-utils overwrite instance // properties (overwrites should not be done in prod) return Object.freeze({ get attrs() { - return attrs || (attrs = createAttrsProxy(instance)) + return getAttrsProxy(instance) }, get slots() { - return shallowReadonly(instance.slots) + return getSlotsProxy(instance) }, get emit() { return (event: string, ...args: any[]) => instance.emit(event, ...args) @@ -997,7 +1029,7 @@ export function createSetupContext( } else { return { get attrs() { - return attrs || (attrs = createAttrsProxy(instance)) + return getAttrsProxy(instance) }, slots: instance.slots, emit: instance.emit, diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index 7b0ccf77ac9..dd2d29670e6 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -356,6 +356,8 @@ export const PublicInstanceProxyHandlers: ProxyHandler = { if (key === '$attrs') { track(instance, TrackOpTypes.GET, key) __DEV__ && markAttrsAccessed() + } else if (__DEV__ && key === '$slots') { + track(instance, TrackOpTypes.GET, key) } return publicGetter(instance) } else if ( diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index 81988599981..8f59099d833 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -23,6 +23,8 @@ import { ContextualRenderFn, withCtx } from './componentRenderContext' import { isHmrUpdating } from './hmr' import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig' import { toRaw } from '@vue/reactivity' +import { trigger } from '@vue/reactivity' +import { TriggerOpTypes } from '@vue/reactivity' export type Slot = ( ...args: IfAny @@ -196,6 +198,7 @@ export const updateSlots = ( // Parent was HMR updated so slot content may have changed. // force update slots and mark instance for hmr as well extend(slots, children as Slots) + trigger(instance, TriggerOpTypes.SET, '$slots') } else if (optimized && type === SlotFlags.STABLE) { // compiled AND stable. // no need to update, and skip stale slots removal. From a58785945d112827185faac801f15828df642da8 Mon Sep 17 00:00:00 2001 From: agoni1212 <22545824+agoni1212@users.noreply.github.com> Date: Thu, 20 Apr 2023 10:07:31 +0800 Subject: [PATCH 0456/1409] chore: typo (#8108) [ci skip] --- packages/compiler-sfc/src/script/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler-sfc/src/script/utils.ts b/packages/compiler-sfc/src/script/utils.ts index 04df22a2b64..e8a0518b570 100644 --- a/packages/compiler-sfc/src/script/utils.ts +++ b/packages/compiler-sfc/src/script/utils.ts @@ -101,7 +101,7 @@ export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean) { const windowsSlashRE = /\\/g export function normalizePath(p: string) { - // in the browser build, the polyfill doesn't expose posix, but defualts to + // in the browser build, the polyfill doesn't expose posix, but defaults to // posix behavior. return (path.posix || path).normalize(p.replace(windowsSlashRE, '/')) } From f630555caa2d57b078454c9a57851a3f7fab327e Mon Sep 17 00:00:00 2001 From: n028 Date: Thu, 20 Apr 2023 04:08:00 +0200 Subject: [PATCH 0457/1409] chore: fix typo (#8113) [ci skip] --- packages/compiler-sfc/src/script/resolveType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 6557b589bf1..022c259f79e 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -159,7 +159,7 @@ function innerResolveTypeElements( return resolveBuiltin(ctx, node, typeName as any, scope) } return ctx.error( - `Unresolvable type reference or unsupported built-in utlility type`, + `Unresolvable type reference or unsupported built-in utility type`, node, scope ) From 2f9f6eceb9aeffa5ef20a416dd4d1d17eb998111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E9=9B=BE=E4=B8=89=E8=AF=AD?= <32354856+baiwusanyu-c@users.noreply.github.com> Date: Thu, 20 Apr 2023 10:11:22 +0800 Subject: [PATCH 0458/1409] chore: delete outdated content in readme (#8093) [ci skip] close #8084 --- packages/reactivity/README.md | 2 +- packages/runtime-core/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/README.md b/packages/reactivity/README.md index 93685ea5a1b..e4780740dab 100644 --- a/packages/reactivity/README.md +++ b/packages/reactivity/README.md @@ -4,7 +4,7 @@ This package is inlined into Global & Browser ESM builds of user-facing renderers (e.g. `@vue/runtime-dom`), but also published as a package that can be used standalone. The standalone build should not be used alongside a pre-bundled build of a user-facing renderer, as they will have different internal storage for reactivity connections. A user-facing renderer should re-export all APIs from this package. -For full exposed APIs, see `src/index.ts`. You can also run `pnpm build reactivity --types` from repo root, which will generate an API report at `temp/reactivity.api.md`. +For full exposed APIs, see `src/index.ts`. ## Credits diff --git a/packages/runtime-core/README.md b/packages/runtime-core/README.md index 753378bb749..3a5b2981203 100644 --- a/packages/runtime-core/README.md +++ b/packages/runtime-core/README.md @@ -2,7 +2,7 @@ > This package is published only for typing and building custom renderers. It is NOT meant to be used in applications. -For full exposed APIs, see `src/index.ts`. You can also run `pnpm build runtime-core --types` from repo root, which will generate an API report at `temp/runtime-core.api.md`. +For full exposed APIs, see `src/index.ts`. ## Building a Custom Renderer From 5510ce385abfa151c07a5253cccf4abccabdd01d Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Thu, 20 Apr 2023 04:12:18 +0200 Subject: [PATCH 0459/1409] feat: hasInjectionContext() for libraries (#8111) --- .../runtime-core/__tests__/apiInject.spec.ts | 31 +++++++++++++++++-- packages/runtime-core/src/apiInject.ts | 9 ++++++ packages/runtime-core/src/index.ts | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/__tests__/apiInject.spec.ts b/packages/runtime-core/__tests__/apiInject.spec.ts index 87a415aa972..a7aae7ebfa9 100644 --- a/packages/runtime-core/__tests__/apiInject.spec.ts +++ b/packages/runtime-core/__tests__/apiInject.spec.ts @@ -8,9 +8,10 @@ import { Ref, readonly, reactive, - defineComponent + defineComponent, + hasInjectionContext } from '../src/index' -import { render, nodeOps, serialize } from '@vue/runtime-test' +import { render, nodeOps, serialize, createApp } from '@vue/runtime-test' // reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject describe('api: provide/inject', () => { @@ -347,4 +348,30 @@ describe('api: provide/inject', () => { render(h(Comp), root) expect(serialize(root)).toBe(`
`) }) + + describe('hasInjectionContext', () => { + it('should be false outside of setup', () => { + expect(hasInjectionContext()).toBe(false) + }) + + it('should be true within setup', () => { + expect.assertions(1) + const Comp = { + setup() { + expect(hasInjectionContext()).toBe(true) + return () => null + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + }) + + it('should be true within app.runWithContext()', () => { + expect.assertions(1) + createApp({}).runWithContext(() => { + expect(hasInjectionContext()).toBe(true) + }) + }) + }) }) diff --git a/packages/runtime-core/src/apiInject.ts b/packages/runtime-core/src/apiInject.ts index 6eedee88c09..4559c1b702f 100644 --- a/packages/runtime-core/src/apiInject.ts +++ b/packages/runtime-core/src/apiInject.ts @@ -73,3 +73,12 @@ export function inject( warn(`inject() can only be used inside setup() or functional components.`) } } + +/** + * Returns true if `inject()` can be used without warning about being called in the wrong place (e.g. outside of + * setup()). This is used by libraries that want to use `inject()` internally without triggering a warning to the end + * user. One example is `useRoute()` in `vue-router`. + */ +export function hasInjectionContext(): boolean { + return !!(currentInstance || currentRenderingInstance || currentApp) +} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index e427773a70e..a115b0179c1 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -56,7 +56,7 @@ export { onErrorCaptured, onServerPrefetch } from './apiLifecycle' -export { provide, inject } from './apiInject' +export { provide, inject, hasInjectionContext } from './apiInject' export { nextTick } from './scheduler' export { defineComponent } from './apiDefineComponent' export { defineAsyncComponent } from './apiAsyncComponent' From d53e157805678db7a3b9ca2fccc74530e1dfbc48 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 20 Apr 2023 14:13:08 +0800 Subject: [PATCH 0460/1409] fix(compiler-sfc): handle type merging + fix namespace access when inferring type close #8102 --- .../compileScript/resolveType.spec.ts | 106 ++++++++++++ .../compiler-sfc/src/script/resolveType.ts | 160 ++++++++++++++---- 2 files changed, 229 insertions(+), 37 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 0f2a47d6b99..960ef592ca7 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -294,6 +294,84 @@ describe('resolveType', () => { }) }) + test('interface merging', () => { + expect( + resolve(` + interface Foo { + a: string + } + interface Foo { + b: number + } + defineProps<{ + foo: Foo['a'], + bar: Foo['b'] + }>() + `).props + ).toStrictEqual({ + foo: ['String'], + bar: ['Number'] + }) + }) + + test('namespace merging', () => { + expect( + resolve(` + namespace Foo { + export type A = string + } + namespace Foo { + export type B = number + } + defineProps<{ + foo: Foo.A, + bar: Foo.B + }>() + `).props + ).toStrictEqual({ + foo: ['String'], + bar: ['Number'] + }) + }) + + test('namespace merging with other types', () => { + expect( + resolve(` + namespace Foo { + export type A = string + } + interface Foo { + b: number + } + defineProps<{ + foo: Foo.A, + bar: Foo['b'] + }>() + `).props + ).toStrictEqual({ + foo: ['String'], + bar: ['Number'] + }) + }) + + test('enum merging', () => { + expect( + resolve(` + enum Foo { + A = 1 + } + enum Foo { + B = 'hi' + } + defineProps<{ + foo: Foo + }>() + `).props + ).toStrictEqual({ + foo: ['Number', 'String'] + }) + }) + describe('external type imports', () => { const files = { '/foo.ts': 'export type P = { foo: number }', @@ -436,6 +514,34 @@ describe('resolveType', () => { }) expect(deps && [...deps]).toStrictEqual(Object.keys(files)) }) + + test('global types with ambient references', () => { + const files = { + // with references + '/backend.d.ts': ` + declare namespace App.Data { + export type AircraftData = { + id: string + manufacturer: App.Data.Listings.ManufacturerData + } + } + declare namespace App.Data.Listings { + export type ManufacturerData = { + id: string + } + } + ` + } + + const { props } = resolve(`defineProps()`, files, { + globalTypeFiles: Object.keys(files) + }) + + expect(props).toStrictEqual({ + id: ['String'], + manufacturer: ['Object'] + }) + }) }) describe('errors', () => { diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 022c259f79e..d34c8046970 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -65,11 +65,14 @@ export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext type Import = Pick -type ScopeTypeNode = Node & { - // scope types always has ownerScope attached +interface WithScope { _ownerScope: TypeScope } +// scope types always has ownerScope attached +type ScopeTypeNode = Node & + WithScope & { _ns?: TSModuleDeclaration & WithScope } + export interface TypeScope { filename: string source: string @@ -79,7 +82,7 @@ export interface TypeScope { exportedTypes: Record } -export interface WithScope { +export interface MaybeWithScope { _ownerScope?: TypeScope } @@ -100,7 +103,7 @@ interface ResolvedElements { */ export function resolveTypeElements( ctx: TypeResolveContext, - node: Node & WithScope & { _resolvedElements?: ResolvedElements }, + node: Node & MaybeWithScope & { _resolvedElements?: ResolvedElements }, scope?: TypeScope ): ResolvedElements { if (node._resolvedElements) { @@ -177,7 +180,7 @@ function typeElementsToMap( const res: ResolvedElements = { props: {} } for (const e of elements) { if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') { - ;(e as WithScope)._ownerScope = scope + ;(e as MaybeWithScope)._ownerScope = scope const name = getId(e.key) if (name && !e.computed) { res.props[name] = e as ResolvedElements['props'][string] @@ -248,7 +251,7 @@ function createProperty( function resolveInterfaceMembers( ctx: TypeResolveContext, - node: TSInterfaceDeclaration & WithScope, + node: TSInterfaceDeclaration & MaybeWithScope, scope: TypeScope ): ResolvedElements { const base = typeElementsToMap(ctx, node.body.body, node._ownerScope) @@ -289,7 +292,7 @@ function resolveIndexType( ctx: TypeResolveContext, node: TSIndexedAccessType, scope: TypeScope -): (TSType & WithScope)[] { +): (TSType & MaybeWithScope)[] { if (node.indexType.type === 'TSNumberKeyword') { return resolveArrayElementType(ctx, node.objectType, scope) } @@ -308,7 +311,7 @@ function resolveIndexType( for (const key of keys) { const targetType = resolved.props[key]?.typeAnnotation?.typeAnnotation if (targetType) { - ;(targetType as TSType & WithScope)._ownerScope = + ;(targetType as TSType & MaybeWithScope)._ownerScope = resolved.props[key]._ownerScope types.push(targetType) } @@ -532,22 +535,22 @@ function innerResolveTypeReference( } } } else { - const ns = innerResolveTypeReference( - ctx, - scope, - name[0], - node, - onlyExported - ) - if (ns && ns.type === 'TSModuleDeclaration') { - const childScope = moduleDeclToScope(ns, scope) - return innerResolveTypeReference( - ctx, - childScope, - name.length > 2 ? name.slice(1) : name[name.length - 1], - node, - !ns.declare - ) + let ns = innerResolveTypeReference(ctx, scope, name[0], node, onlyExported) + if (ns) { + if (ns.type !== 'TSModuleDeclaration') { + // namespace merged with other types, attached as _ns + ns = ns._ns + } + if (ns) { + const childScope = moduleDeclToScope(ns, ns._ownerScope || scope) + return innerResolveTypeReference( + ctx, + childScope, + name.length > 2 ? name.slice(1) : name[name.length - 1], + node, + !ns.declare + ) + } } } } @@ -771,7 +774,6 @@ export function fileToScope( exportedTypes: Object.create(null) } recordTypes(body, scope, asGlobal) - fileToScopeCache.set(filename, scope) return scope } @@ -858,10 +860,21 @@ function moduleDeclToScope( } const scope: TypeScope = { ...parentScope, + imports: Object.create(parentScope.imports), + // TODO this seems wrong types: Object.create(parentScope.types), - imports: Object.create(parentScope.imports) + exportedTypes: Object.create(null) + } + + if (node.body.type === 'TSModuleDeclaration') { + const decl = node.body as TSModuleDeclaration & WithScope + decl._ownerScope = scope + const id = getId(decl.id) + scope.types[id] = scope.exportedTypes[id] = decl + } else { + recordTypes(node.body.body, scope) } - recordTypes((node.body as TSModuleBlock).body, scope) + return (node._resolvedChildScope = scope) } @@ -923,7 +936,9 @@ function recordTypes(body: Statement[], scope: TypeScope, asGlobal = false) { } } for (const key of Object.keys(types)) { - types[key]._ownerScope = scope + const node = types[key] + node._ownerScope = scope + if (node._ns) node._ns._ownerScope = scope } } @@ -931,12 +946,42 @@ function recordType(node: Node, types: Record) { switch (node.type) { case 'TSInterfaceDeclaration': case 'TSEnumDeclaration': - case 'TSModuleDeclaration': - case 'ClassDeclaration': { - const id = node.id.type === 'Identifier' ? node.id.name : node.id.value - types[id] = node + case 'TSModuleDeclaration': { + const id = getId(node.id) + let existing = types[id] + if (existing) { + if (node.type === 'TSModuleDeclaration') { + if (existing.type === 'TSModuleDeclaration') { + mergeNamespaces(existing as typeof node, node) + } else { + attachNamespace(existing, node) + } + break + } + if (existing.type === 'TSModuleDeclaration') { + // replace and attach namespace + types[id] = node + attachNamespace(node, existing) + break + } + + if (existing.type !== node.type) { + // type-level error + break + } + if (node.type === 'TSInterfaceDeclaration') { + ;(existing as typeof node).body.body.push(...node.body.body) + } else { + ;(existing as typeof node).members.push(...node.members) + } + } else { + types[id] = node + } break } + case 'ClassDeclaration': + types[getId(node.id)] = node + break case 'TSTypeAliasDeclaration': types[node.id.name] = node.typeAnnotation break @@ -955,6 +1000,47 @@ function recordType(node: Node, types: Record) { } } +function mergeNamespaces(to: TSModuleDeclaration, from: TSModuleDeclaration) { + const toBody = to.body + const fromBody = from.body + if (toBody.type === 'TSModuleDeclaration') { + if (fromBody.type === 'TSModuleDeclaration') { + // both decl + mergeNamespaces(toBody, fromBody) + } else { + // to: decl -> from: block + fromBody.body.push({ + type: 'ExportNamedDeclaration', + declaration: toBody, + exportKind: 'type', + specifiers: [] + }) + } + } else if (fromBody.type === 'TSModuleDeclaration') { + // to: block <- from: decl + toBody.body.push({ + type: 'ExportNamedDeclaration', + declaration: fromBody, + exportKind: 'type', + specifiers: [] + }) + } else { + // both block + toBody.body.push(...fromBody.body) + } +} + +function attachNamespace( + to: Node & { _ns?: TSModuleDeclaration }, + ns: TSModuleDeclaration +) { + if (!to._ns) { + to._ns = ns + } else { + mergeNamespaces(to._ns, ns) + } +} + export function recordImports(body: Statement[]) { const imports: TypeScope['imports'] = Object.create(null) for (const s of body) { @@ -977,7 +1063,7 @@ function recordImport(node: Node, imports: TypeScope['imports']) { export function inferRuntimeType( ctx: TypeResolveContext, - node: Node & WithScope, + node: Node & MaybeWithScope, scope = node._ownerScope || ctxToScope(ctx) ): string[] { switch (node.type) { @@ -1035,11 +1121,11 @@ export function inferRuntimeType( } case 'TSTypeReference': + const resolved = resolveTypeReference(ctx, node, scope) + if (resolved) { + return inferRuntimeType(ctx, resolved, resolved._ownerScope) + } if (node.typeName.type === 'Identifier') { - const resolved = resolveTypeReference(ctx, node, scope) - if (resolved) { - return inferRuntimeType(ctx, resolved, resolved._ownerScope) - } switch (node.typeName.name) { case 'Array': case 'Function': From f17a82c769cfb60ee6785ef5d34d91191d153542 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 20 Apr 2023 14:31:54 +0800 Subject: [PATCH 0461/1409] fix(hmr): always traverse static children in dev fix #7921 close #8100 --- packages/runtime-core/src/renderer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index b1e048e588b..413355508b9 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -835,7 +835,8 @@ function baseCreateRenderer( areChildrenSVG, slotScopeIds ) - if (__DEV__ && parentComponent && parentComponent.type.__hmrId) { + if (__DEV__) { + // necessary for HMR traverseStaticChildren(n1, n2) } } else if (!optimized) { @@ -1110,7 +1111,8 @@ function baseCreateRenderer( isSVG, slotScopeIds ) - if (__DEV__ && parentComponent && parentComponent.type.__hmrId) { + if (__DEV__) { + // necessary for HMR traverseStaticChildren(n1, n2) } else if ( // #2080 if the stable fragment has a key, it's a