diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineRender.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineRender.spec.ts.snap new file mode 100644 index 00000000000..e2914e7add0 --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineRender.spec.ts.snap @@ -0,0 +1,62 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`defineRender() > JSX Element 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + + +return () =>
+} + +})" +`; + +exports[`defineRender() > empty argument 1`] = ` +"const foo = 'bar' + +export default { + setup(__props, { expose: __expose }) { + __expose(); + + + +return { foo } +} + +}" +`; + +exports[`defineRender() > function 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + + +return () => +} + +})" +`; + +exports[`defineRender() > identifier 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { renderFn } from './ctx' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + + +return renderFn +} + +})" +`; diff --git a/packages/compiler-sfc/__tests__/compileScript/defineRender.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineRender.spec.ts new file mode 100644 index 00000000000..d33eb551ca2 --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/defineRender.spec.ts @@ -0,0 +1,79 @@ +import { assertCode, compileSFCScript as compile } from '../utils' + +describe('defineRender()', () => { + test('JSX Element', () => { + const { content } = compile( + ` + + `, + { defineRender: true }, + ) + assertCode(content) + expect(content).toMatch(`return () => `) + expect(content).not.toMatch('defineRender') + }) + + test('function', () => { + const { content } = compile( + ` + + `, + { defineRender: true }, + ) + assertCode(content) + expect(content).toMatch(`return () => `) + expect(content).not.toMatch('defineRender') + }) + + test('identifier', () => { + const { content } = compile( + ` + + `, + { defineRender: true }, + ) + assertCode(content) + expect(content).toMatch(`return renderFn`) + expect(content).not.toMatch('defineRender') + }) + + test('empty argument', () => { + const { content } = compile( + ` + + `, + { defineRender: true }, + ) + assertCode(content) + expect(content).toMatch(`return { foo }`) + expect(content).not.toMatch('defineRender') + }) + + describe('errors', () => { + test('w/ ', () => { + expect(() => + compile( + ` + + + hello + + `, + { defineRender: true }, + ), + ).toThrow(`defineRender() cannot be used with .`) + }) + }) +}) diff --git a/packages/compiler-sfc/__tests__/utils.ts b/packages/compiler-sfc/__tests__/utils.ts index 5498ede2717..30db906a4ec 100644 --- a/packages/compiler-sfc/__tests__/utils.ts +++ b/packages/compiler-sfc/__tests__/utils.ts @@ -31,6 +31,7 @@ export function assertCode(code: string) { plugins: [ 'typescript', ['importAttributes', { deprecatedAssertSyntax: true }], + 'jsx', ], }) } catch (e: any) { diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 46fc65c0069..ccc33432654 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -55,6 +55,7 @@ import { getImportedName, isCallOf, isLiteralNode } from './script/utils' import { analyzeScriptBindings } from './script/analyzeScriptBindings' import { isImportUsed } from './script/importUsageCheck' import { processAwait } from './script/topLevelAwait' +import { DEFINE_RENDER, processDefineRender } from './script/defineRender' export interface SFCScriptCompileOptions { /** @@ -105,6 +106,11 @@ export interface SFCScriptCompileOptions { * @default true */ hoistStatic?: boolean + /** + * (**Experimental**) Enable macro `defineRender` + * @default false + */ + defineRender?: boolean /** * (**Experimental**) Enable reactive destructure for `defineProps` * @default false @@ -491,7 +497,8 @@ export function compileScript( processDefineProps(ctx, expr) || processDefineEmits(ctx, expr) || processDefineOptions(ctx, expr) || - processDefineSlots(ctx, expr) + processDefineSlots(ctx, expr) || + processDefineRender(ctx, expr) ) { ctx.s.remove(node.start! + startOffset, node.end! + startOffset) } else if (processDefineExpose(ctx, expr)) { @@ -529,7 +536,8 @@ export function compileScript( !isDefineProps && processDefineEmits(ctx, init, decl.id) !isDefineEmits && (processDefineSlots(ctx, init, decl.id) || - processDefineModel(ctx, init, decl.id)) + processDefineModel(ctx, init, decl.id) || + processDefineRender(ctx, init)) if ( isDefineProps && @@ -797,8 +805,22 @@ export function compileScript( } // 9. generate return statement - let returned - if ( + let returned = '' + if (ctx.renderFunction) { + if (sfc.template) { + ctx.error( + `${DEFINE_RENDER}() cannot be used with .`, + ctx.renderFunction, + ) + } + if (ctx.renderFunction.type === 'JSXElement') { + returned = '() => ' + } + returned += scriptSetup.content.slice( + ctx.renderFunction.start!, + ctx.renderFunction.end!, + ) + } else if ( !options.inlineTemplate || (!sfc.template && ctx.hasDefaultExportRender) ) { @@ -837,66 +859,59 @@ export function compileScript( } } returned = returned.replace(/, $/, '') + ` }` - } else { + } else if (sfc.template && !sfc.template.src) { // inline mode - if (sfc.template && !sfc.template.src) { - if (options.templateOptions && options.templateOptions.ssr) { - hasInlinedSsrRenderFn = true - } - // inline render function mode - we are going to compile the template and - // inline it right here - const { code, ast, preamble, tips, errors } = compileTemplate({ - filename, - ast: sfc.template.ast, - source: sfc.template.content, - inMap: sfc.template.map, - ...options.templateOptions, - id: scopeId, - scoped: sfc.styles.some(s => s.scoped), - isProd: options.isProd, - ssrCssVars: sfc.cssVars, - compilerOptions: { - ...(options.templateOptions && - options.templateOptions.compilerOptions), - inline: true, - isTS: ctx.isTS, - bindingMetadata: ctx.bindingMetadata, - }, - }) - if (tips.length) { - tips.forEach(warnOnce) - } - const err = errors[0] - if (typeof err === 'string') { - throw new Error(err) - } else if (err) { - if (err.loc) { - err.message += - `\n\n` + - sfc.filename + - '\n' + - generateCodeFrame( - source, - err.loc.start.offset, - err.loc.end.offset, - ) + - `\n` - } - throw err - } - if (preamble) { - ctx.s.prepend(preamble) - } - // avoid duplicated unref import - // as this may get injected by the render function preamble OR the - // css vars codegen - if (ast && ast.helpers.has(UNREF)) { - ctx.helperImports.delete('unref') + if (options.templateOptions && options.templateOptions.ssr) { + hasInlinedSsrRenderFn = true + } + // inline render function mode - we are going to compile the template and + // inline it right here + const { code, ast, preamble, tips, errors } = compileTemplate({ + filename, + ast: sfc.template.ast, + source: sfc.template.content, + inMap: sfc.template.map, + ...options.templateOptions, + id: scopeId, + scoped: sfc.styles.some(s => s.scoped), + isProd: options.isProd, + ssrCssVars: sfc.cssVars, + compilerOptions: { + ...(options.templateOptions && options.templateOptions.compilerOptions), + inline: true, + isTS: ctx.isTS, + bindingMetadata: ctx.bindingMetadata, + }, + }) + if (tips.length) { + tips.forEach(warnOnce) + } + const err = errors[0] + if (typeof err === 'string') { + throw new Error(err) + } else if (err) { + if (err.loc) { + err.message += + `\n\n` + + sfc.filename + + '\n' + + generateCodeFrame(source, err.loc.start.offset, err.loc.end.offset) + + `\n` } - returned = code - } else { - returned = `() => {}` + throw err } + if (preamble) { + ctx.s.prepend(preamble) + } + // avoid duplicated unref import + // as this may get injected by the render function preamble OR the + // css vars codegen + if (ast && ast.helpers.has(UNREF)) { + ctx.helperImports.delete('unref') + } + returned = code + } else { + returned = `() => {}` } if (!options.inlineTemplate && !__TEST__) { diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index f74adee603b..f573645ca9c 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -36,6 +36,7 @@ export class ScriptCompileContext { hasDefaultExportRender = false hasDefineOptionsCall = false hasDefineSlotsCall = false + hasDefineRenderCall = false hasDefineModelCall = false // defineProps @@ -59,6 +60,9 @@ export class ScriptCompileContext { // defineOptions optionsRuntimeDecl: Node | undefined + // defineRender + renderFunction?: Node + // codegen bindingMetadata: BindingMetadata = {} helperImports: Set