From 2dde5e2ca0ce268e1a4b8e0bd255790e592351d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90?= Date: Sun, 17 Apr 2022 18:49:48 +0800 Subject: [PATCH 01/11] feat(compiler-sfc): add defineOptions marco related RFC: https://github.com/vuejs/rfcs/discussions/430 --- packages/compiler-sfc/src/compileScript.ts | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index b7e4c0ea778..dd08f2c1322 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -62,6 +62,7 @@ const DEFINE_PROPS = 'defineProps' const DEFINE_EMITS = 'defineEmits' const DEFINE_EXPOSE = 'defineExpose' const WITH_DEFAULTS = 'withDefaults' +const DEFINE_OPTIONS = 'defineOptions' // constants const DEFAULT_VAR = `__default__` @@ -289,6 +290,7 @@ export function compileScript( let hasDefineExposeCall = false let hasDefaultExportName = false let hasDefaultExportRender = false + let hasDefineOptionsCall = false let propsRuntimeDecl: Node | undefined let propsRuntimeDefaults: ObjectExpression | undefined let propsDestructureDecl: Node | undefined @@ -300,6 +302,7 @@ export function compileScript( let emitsTypeDecl: EmitsDeclType | undefined let emitsTypeDeclRaw: Node | undefined let emitIdentifier: string | undefined + let optionsRuntimeDecl: Node | undefined let hasAwait = false let hasInlinedSsrRenderFn = false // props/emits declared via types @@ -666,6 +669,39 @@ export function compileScript( }) } + function processDefineOptions(node: Node): boolean { + if (!isCallOf(node, DEFINE_OPTIONS)) { + return false + } + if (hasDefineOptionsCall) { + error(`duplicate ${DEFINE_OPTIONS}() call`, node) + } + if (script) { + error(`cannot be used, with both script and script-setup`, node) + } + if (node.typeParameters) { + error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node) + } + + hasDefineOptionsCall = true + optionsRuntimeDecl = node.arguments[0] + if (optionsRuntimeDecl.type !== 'ObjectExpression') { + error(`${DEFINE_OPTIONS}() argument must be an object`, node) + } + + const hasPropOrEmits = optionsRuntimeDecl.properties.some( + prop => + (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') && + prop.key.type === 'Identifier' && + (prop.key.name === 'props' || prop.key.name === 'emits') + ) + if (hasPropOrEmits) { + error(`${DEFINE_OPTIONS}() use defineProps or defineEmits instead.`, node) + } + + return true + } + function resolveQualifiedType( node: Node, qualifier: (node: Node) => boolean @@ -1194,6 +1230,7 @@ export function compileScript( if ( processDefineProps(node.expression) || processDefineEmits(node.expression) || + processDefineOptions(node.expression) || processWithDefaults(node.expression) ) { s.remove(node.start! + startOffset, node.end! + startOffset) @@ -1214,6 +1251,14 @@ export function compileScript( for (let i = 0; i < total; i++) { const decl = node.declarations[i] if (decl.init) { + if (processDefineOptions(decl.init)) { + error( + `${DEFINE_OPTIONS}() A has no return value, ` + + `it cannot be assigned a value.`, + node + ) + } + // defineProps / defineEmits const isDefineProps = processDefineProps(decl.init, decl.id, node.kind) || @@ -1358,6 +1403,7 @@ export function compileScript( checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS) checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS) checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS) + checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS) // 6. remove non-script content if (script) { @@ -1644,6 +1690,11 @@ export function compileScript( } else if (emitsTypeDecl) { runtimeOptions += genRuntimeEmits(typeDeclaredEmits) } + if (optionsRuntimeDecl) { + runtimeOptions = `\n ...${scriptSetup.content + .slice(optionsRuntimeDecl.start!, optionsRuntimeDecl.end!) + .trim()},${runtimeOptions}` + } // + `) + 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 { + ...{ name: 'FooApp' },`) + }) + + it('should report an error with two defineProps', () => { + expect(() => + compile(` + + `) + ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call') + }) + + it('should report an error with props or emits property', () => { + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() use defineProps or defineEmits instead.' + ) + + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() use defineProps or defineEmits instead.' + ) + }) + + it('should report an error with type generic', () => { + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot accept type arguments' + ) + }) + + it('should report an error with both normal script and script setup', () => { + expect(() => + compile(` + + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() cannot be used, with both script and script-setup' + ) + }) + + it('should report an error with a wrong argument', () => { + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() argument must be an object' + ) + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() argument must be an object' + ) + expect(() => + compile(` + + `) + ).toThrowError( + '[@vue/compiler-sfc] defineOptions() argument must be an object' + ) + }) + }) + test('defineExpose()', () => { const { content } = compile(` `) ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used, with both script and script-setup' + '[@vue/compiler-sfc] defineOptions() cannot be used, with both - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() cannot be used, with both - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() argument must be an object' - ) - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() argument must be an object' - ) - expect(() => - compile(` - - `) - ).toThrowError( - '[@vue/compiler-sfc] defineOptions() argument must be an object' - ) - }) }) test('defineExpose()', () => { diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 3ef72a4e631..7a04925e1fa 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1686,10 +1686,12 @@ export function compileScript( } else if (emitsTypeDecl) { runtimeOptions += genRuntimeEmits(typeDeclaredEmits) } + + let definedOptions = '' if (optionsRuntimeDecl) { - runtimeOptions = `\n ...${scriptSetup.content + definedOptions = scriptSetup.content .slice(optionsRuntimeDecl.start!, optionsRuntimeDecl.end!) - .trim()},${runtimeOptions}` + .trim() } // `) ).toThrowError( - '[@vue/compiler-sfc] defineOptions() use defineProps or defineEmits instead.' + '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.' ) expect(() => @@ -217,7 +217,7 @@ defineOptions({ name: 'FooApp' }) `) ).toThrowError( - '[@vue/compiler-sfc] defineOptions() use defineProps or defineEmits instead.' + '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.' ) }) @@ -1198,7 +1198,7 @@ const emit = defineEmits(['a', 'b']) `) assertCode(content) }) - + // #7111 test('withDefaults (static) w/ production mode', () => { const { content } = compile( @@ -1339,7 +1339,6 @@ const emit = defineEmits(['a', 'b']) expect(content).toMatch(`emits: ["foo", "bar"]`) }) - test('defineEmits w/ type from normal script', () => { const { content } = compile(`