From 620327d527593c6263a21500baddbae1ebc30db8 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: Sat, 26 Nov 2022 03:52:39 +0800 Subject: [PATCH 0001/2084] fix(runtime-core): ensure prop type validation warning shows custom class names (#7198) * fix(runtime-core): * fix(runtime-core): update * fix(runtime-core): update reg * test(runtime-core): add test case for warnings about prop type mismatches Co-authored-by: Thorsten Luenborg --- .../__tests__/componentProps.spec.ts | 36 +++++++++++++++++++ packages/runtime-core/src/componentProps.ts | 4 +-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts index cdb77838e31..195f352154c 100644 --- a/packages/runtime-core/__tests__/componentProps.spec.ts +++ b/packages/runtime-core/__tests__/componentProps.spec.ts @@ -321,6 +321,42 @@ describe('component props', () => { expect(`Missing required prop: "num"`).toHaveBeenWarned() }) + test('warn on type mismatch', () => { + class MyClass { + + } + const Comp = { + props: { + bool: { type: Boolean }, + str: { type: String }, + num: { type: Number }, + arr: { type: Array }, + obj: { type: Object }, + cls: { type: MyClass }, + fn: { type: Function }, + }, + setup() { + return () => null + } + } + render(h(Comp, { + bool: 'true', + str: 100, + num: '100', + arr: {}, + obj: 'false', + cls: {}, + fn: true, + }), nodeOps.createElement('div')) + expect(`Invalid prop: type check failed for prop "bool". Expected Boolean, got String`).toHaveBeenWarned() + expect(`Invalid prop: type check failed for prop "str". Expected String with value "100", got Number with value 100.`).toHaveBeenWarned() + expect(`Invalid prop: type check failed for prop "num". Expected Number with value 100, got String with value "100".`).toHaveBeenWarned() + expect(`Invalid prop: type check failed for prop "arr". Expected Array, got Object`).toHaveBeenWarned() + expect(`Invalid prop: type check failed for prop "obj". Expected Object, got String with value "false"`).toHaveBeenWarned() + expect(`Invalid prop: type check failed for prop "fn". Expected Function, got Boolean with value true.`).toHaveBeenWarned() + expect(`Invalid prop: type check failed for prop "cls". Expected MyClass, got Object`).toHaveBeenWarned() + }) + // #3495 test('should not warn required props using kebab-case', async () => { const Comp = { diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index d59a4e94699..fa756fa32f2 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -557,8 +557,8 @@ function validatePropName(key: string) { // use function string name to check type constructors // so that it works across vms / iframes. function getType(ctor: Prop): string { - const match = ctor && ctor.toString().match(/^\s*function (\w+)/) - return match ? match[1] : ctor === null ? 'null' : '' + const match = ctor && ctor.toString().match(/^\s*(function|class) (\w+)/) + return match ? match[2] : ctor === null ? 'null' : '' } function isSameType(a: Prop, b: Prop): boolean { From 92051b74d74c8d20e330daa47f8f4ef68de39236 Mon Sep 17 00:00:00 2001 From: Brian Nduhiu <43300544+Brian-Nduhiu@users.noreply.github.com> Date: Thu, 1 Dec 2022 03:05:51 +0300 Subject: [PATCH 0002/2084] chore: update README.md (#7139) [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d2342c1ea0..17d9abc6bae 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Please follow the documentation at [vuejs.org](https://vuejs.org/)! ## Sponsors -Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome [backers](https://github.com/vuejs/core/blob/main/BACKERS.md). If you'd like to join them, please consider [ sponsor Vue's development](https://vuejs.org/sponsor/). +Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome [backers](https://github.com/vuejs/core/blob/main/BACKERS.md). If you'd like to join them, please consider [ sponsoring Vue's development](https://vuejs.org/sponsor/).

Special Sponsor

From fe77e2bddaa5930ad37a43fe8e6254ddb0f9c2d7 Mon Sep 17 00:00:00 2001 From: Brian Nduhiu <43300544+Brian-Nduhiu@users.noreply.github.com> Date: Thu, 1 Dec 2022 03:06:19 +0300 Subject: [PATCH 0003/2084] chore: update BACKERS.md (#7140) [ci skip] --- BACKERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BACKERS.md b/BACKERS.md index fa66d206698..631bcb91120 100644 --- a/BACKERS.md +++ b/BACKERS.md @@ -1,6 +1,6 @@

Sponsors & Backers

-Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of the awesome sponsors and backers listed in this file. If you'd like to join them, please consider [ sponsor Vue's development](https://vuejs.org/sponsor/). +Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of the awesome sponsors and backers listed in this file. If you'd like to join them, please consider [ sponsoring Vue's development](https://vuejs.org/sponsor/).

From c6e5bda27d13554675d68dbe33b07f3474467aa6 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: Sat, 24 Dec 2022 05:32:21 +0800 Subject: [PATCH 0004/2084] fix(customElement): customElement can emit event (#7296) close https://github.com/vuejs/core/issues/7293 --- packages/runtime-core/src/vnode.ts | 3 ++- .../__tests__/customElement.spec.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 7d873f5a125..83eedc4ccef 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -672,7 +672,8 @@ export function cloneVNode( ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback), el: vnode.el, anchor: vnode.anchor, - ctx: vnode.ctx + ctx: vnode.ctx, + ce: vnode.ce } if (__COMPAT__) { defineLegacyVNodeProperties(cloned as VNode) diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 7826f438300..f4199192278 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -384,6 +384,25 @@ describe('defineCustomElement', () => { detail: [1] }) }) + // #7293 + test('emit in an async component wrapper with properties bound', async () => { + const E = defineCustomElement( + defineAsyncComponent( + () => new Promise(res => res(CompDef as any)) + ) + ) + customElements.define('my-async-el-props-emits', E) + container.innerHTML = `` + const e = container.childNodes[0] as VueElement + const spy = jest.fn() + e.addEventListener('my-click', spy) + await customElements.whenDefined('my-async-el-props-emits') + e.shadowRoot!.childNodes[0].dispatchEvent(new CustomEvent('click')) + expect(spy).toHaveBeenCalled() + expect(spy.mock.calls[0][0]).toMatchObject({ + detail: [1] + }) + }) }) describe('slots', () => { From 1fa3d9573051f549e6d381a5e88ec8d5d855e4c9 Mon Sep 17 00:00:00 2001 From: Yue Yang Date: Sun, 1 Jan 2023 18:44:20 +0800 Subject: [PATCH 0005/2084] fix(types): add or update referrerpolicy (#7199) --- packages/runtime-dom/types/jsx.d.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/runtime-dom/types/jsx.d.ts b/packages/runtime-dom/types/jsx.d.ts index 5898b5e0d5f..9471140310c 100644 --- a/packages/runtime-dom/types/jsx.d.ts +++ b/packages/runtime-dom/types/jsx.d.ts @@ -309,6 +309,17 @@ export interface HTMLAttributes extends AriaAttributes, EventHandlers { is?: string } +type HTMLAttributeReferrerPolicy = + | '' + | 'no-referrer' + | 'no-referrer-when-downgrade' + | 'origin' + | 'origin-when-cross-origin' + | 'same-origin' + | 'strict-origin' + | 'strict-origin-when-cross-origin' + | 'unsafe-url' + export interface AnchorHTMLAttributes extends HTMLAttributes { download?: any href?: string @@ -318,7 +329,7 @@ export interface AnchorHTMLAttributes extends HTMLAttributes { rel?: string target?: string type?: string - referrerpolicy?: string + referrerpolicy?: HTMLAttributeReferrerPolicy } export interface AreaHTMLAttributes extends HTMLAttributes { @@ -328,6 +339,7 @@ export interface AreaHTMLAttributes extends HTMLAttributes { href?: string hreflang?: string media?: string + referrerpolicy?: HTMLAttributeReferrerPolicy rel?: string shape?: string target?: string @@ -426,7 +438,7 @@ export interface IframeHTMLAttributes extends HTMLAttributes { marginheight?: Numberish marginwidth?: Numberish name?: string - referrerpolicy?: string + referrerpolicy?: HTMLAttributeReferrerPolicy sandbox?: string scrolling?: string seamless?: Booleanish @@ -440,6 +452,7 @@ export interface ImgHTMLAttributes extends HTMLAttributes { crossorigin?: 'anonymous' | 'use-credentials' | '' decoding?: 'async' | 'auto' | 'sync' height?: Numberish + referrerpolicy?: HTMLAttributeReferrerPolicy sizes?: string src?: string srcset?: string @@ -514,6 +527,7 @@ export interface LinkHTMLAttributes extends HTMLAttributes { hreflang?: string integrity?: string media?: string + referrerpolicy?: HTMLAttributeReferrerPolicy rel?: string sizes?: string type?: string @@ -614,6 +628,7 @@ export interface ScriptHTMLAttributes extends HTMLAttributes { defer?: Booleanish integrity?: string nomodule?: Booleanish + referrerpolicy?: HTMLAttributeReferrerPolicy nonce?: string src?: string type?: string From 686c829fec9137cdfe6e51af34f2af01a575f7c6 Mon Sep 17 00:00:00 2001 From: Julien Huang <63512348+huang-julien@users.noreply.github.com> Date: Mon, 9 Jan 2023 15:13:58 +0100 Subject: [PATCH 0006/2084] fix(compiler-sfc): allow declaring variables after defineProps (#7461) * fix(compiler-sfc): allow declaring variables after defineProps * test(compiler-sfc): test defineProps in multiple variable declaration --- .../compileScriptPropsTransform.spec.ts.snap | 18 +++++++++++++++++ .../compileScriptPropsTransform.spec.ts | 20 +++++++++++++++++++ packages/compiler-sfc/src/compileScript.ts | 3 +-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap index 7641a151ef5..77048203996 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap @@ -136,6 +136,24 @@ return () => {} })" `; +exports[`sfc props transform multiple variable declarations 1`] = ` +"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" + + +export default { + props: ['foo'], + setup(__props) { + + const bar = 'fish', hello = 'world' + +return (_ctx, _cache) => { + return (_openBlock(), _createElementBlock("div", null, _toDisplayString(__props.foo) + " " + _toDisplayString(hello) + " " + _toDisplayString(bar), 1 /* TEXT */)) +} +} + +}" +`; + exports[`sfc props transform nested scope 1`] = ` "export default { props: ['foo', 'bar'], diff --git a/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts b/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts index 6fcc0e59068..05c7989b8f1 100644 --- a/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts @@ -28,6 +28,26 @@ describe('sfc props transform', () => { }) }) + test('multiple variable declarations', () => { + const { content, bindings } = compile(` + + + `) + expect(content).not.toMatch(`const { foo } =`) + expect(content).toMatch(`const bar = 'fish', hello = 'world'`) + expect(content).toMatch(`_toDisplayString(hello)`) + expect(content).toMatch(`_toDisplayString(bar)`) + expect(content).toMatch(`_toDisplayString(__props.foo)`) + assertCode(content) + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.SETUP_CONST, + hello: BindingTypes.SETUP_CONST + }) + }) + test('nested scope', () => { const { content, bindings } = compile(` +

` + ) + 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 0150/2084] 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 0151/2084] 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 0152/2084] 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 0153/2084] 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 0154/2084] 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 0155/2084] 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 0156/2084] 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 0157/2084] 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 0169/2084] 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 0173/2084] 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 0178/2084] 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 0181/2084] 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 0182/2084] 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 0183/2084] 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 0184/2084] 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 0185/2084] 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 0186/2084] 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 0187/2084] 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 0188/2084] 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 0189/2084] 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 0190/2084] 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 0191/2084] 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 0192/2084] 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 0193/2084] 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 0194/2084] 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 0195/2084] 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 0196/2084] 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 0197/2084] 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 0198/2084] 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 0199/2084] 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 0200/2084] 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 0226/2084] 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 0227/2084] 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 0228/2084] 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 0229/2084] 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 0230/2084] 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 0231/2084] 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 0232/2084] 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 0234/2084] 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 0235/2084] 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 0236/2084] 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 0237/2084] 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 0238/2084] 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 0239/2084] 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 0240/2084] 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 0241/2084] 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 0242/2084] 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 0243/2084] 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 0244/2084] 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 0245/2084] 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 0246/2084] 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 0247/2084] 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 0248/2084] 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 0249/2084] 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 0250/2084] 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 0251/2084] 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 0252/2084] 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 0253/2084] 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 0254/2084] 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 0255/2084] 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 0256/2084] 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 0257/2084] 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 0258/2084] 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 0259/2084] 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