Skip to content

Commit 886ed76

Browse files
committed
feat(compiler-sfc): compileScript inline render function mode
1 parent 3f99e23 commit 886ed76

File tree

8 files changed

+192
-77
lines changed

8 files changed

+192
-77
lines changed

packages/compiler-core/src/codegen.ts

+30-8
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,16 @@ type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
6060

6161
export interface CodegenResult {
6262
code: string
63+
preamble: string
6364
ast: RootNode
6465
map?: RawSourceMap
6566
}
6667

6768
export interface CodegenContext
68-
extends Omit<Required<CodegenOptions>, 'bindingMetadata'> {
69+
extends Omit<
70+
Required<CodegenOptions>,
71+
'bindingMetadata' | 'inline' | 'inlinePropsIdentifier'
72+
> {
6973
source: string
7074
code: string
7175
line: number
@@ -199,12 +203,18 @@ export function generate(
199203
const hasHelpers = ast.helpers.length > 0
200204
const useWithBlock = !prefixIdentifiers && mode !== 'module'
201205
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
206+
const isSetupInlined = !!options.inline
202207

203208
// preambles
209+
// in setup() inline mode, the preamble is generated in a sub context
210+
// and returned separately.
211+
const preambleContext = isSetupInlined
212+
? createCodegenContext(ast, options)
213+
: context
204214
if (!__BROWSER__ && mode === 'module') {
205-
genModulePreamble(ast, context, genScopeId)
215+
genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
206216
} else {
207-
genFunctionPreamble(ast, context)
217+
genFunctionPreamble(ast, preambleContext)
208218
}
209219

210220
// binding optimizations
@@ -213,10 +223,17 @@ export function generate(
213223
: ``
214224
// enter render function
215225
if (!ssr) {
216-
if (genScopeId) {
217-
push(`const render = ${PURE_ANNOTATION}_withId(`)
226+
if (isSetupInlined) {
227+
if (genScopeId) {
228+
push(`${PURE_ANNOTATION}_withId(`)
229+
}
230+
push(`() => {`)
231+
} else {
232+
if (genScopeId) {
233+
push(`const render = ${PURE_ANNOTATION}_withId(`)
234+
}
235+
push(`function render(_ctx, _cache${optimizeSources}) {`)
218236
}
219-
push(`function render(_ctx, _cache${optimizeSources}) {`)
220237
} else {
221238
if (genScopeId) {
222239
push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
@@ -290,6 +307,7 @@ export function generate(
290307
return {
291308
ast,
292309
code: context.code,
310+
preamble: isSetupInlined ? preambleContext.code : ``,
293311
// SourceMapGenerator does have toJSON() method but it's not in the types
294312
map: context.map ? (context.map as any).toJSON() : undefined
295313
}
@@ -356,7 +374,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
356374
function genModulePreamble(
357375
ast: RootNode,
358376
context: CodegenContext,
359-
genScopeId: boolean
377+
genScopeId: boolean,
378+
inline?: boolean
360379
) {
361380
const {
362381
push,
@@ -423,7 +442,10 @@ function genModulePreamble(
423442

424443
genHoists(ast.hoists, context)
425444
newline()
426-
push(`export `)
445+
446+
if (!inline) {
447+
push(`export `)
448+
}
427449
}
428450

429451
function genAssets(

packages/compiler-core/src/options.ts

+34-18
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,39 @@ export interface BindingMetadata {
6565
[key: string]: 'data' | 'props' | 'setup' | 'options'
6666
}
6767

68-
export interface TransformOptions {
68+
interface SharedTransformCodegenOptions {
69+
/**
70+
* Transform expressions like {{ foo }} to `_ctx.foo`.
71+
* If this option is false, the generated code will be wrapped in a
72+
* `with (this) { ... }` block.
73+
* - This is force-enabled in module mode, since modules are by default strict
74+
* and cannot use `with`
75+
* @default mode === 'module'
76+
*/
77+
prefixIdentifiers?: boolean
78+
/**
79+
* Generate SSR-optimized render functions instead.
80+
* The resulting function must be attached to the component via the
81+
* `ssrRender` option instead of `render`.
82+
*/
83+
ssr?: boolean
84+
/**
85+
* Optional binding metadata analyzed from script - used to optimize
86+
* binding access when `prefixIdentifiers` is enabled.
87+
*/
88+
bindingMetadata?: BindingMetadata
89+
/**
90+
* Compile the function for inlining inside setup().
91+
* This allows the function to directly access setup() local bindings.
92+
*/
93+
inline?: boolean
94+
/**
95+
* Identifier for props in setup() inline mode.
96+
*/
97+
inlinePropsIdentifier?: string
98+
}
99+
100+
export interface TransformOptions extends SharedTransformCodegenOptions {
69101
/**
70102
* An array of node transforms to be applied to every AST node.
71103
*/
@@ -128,26 +160,15 @@ export interface TransformOptions {
128160
* SFC scoped styles ID
129161
*/
130162
scopeId?: string | null
131-
/**
132-
* Generate SSR-optimized render functions instead.
133-
* The resulting function must be attached to the component via the
134-
* `ssrRender` option instead of `render`.
135-
*/
136-
ssr?: boolean
137163
/**
138164
* SFC `<style vars>` injection string
139165
* needed to render inline CSS variables on component root
140166
*/
141167
ssrCssVars?: string
142-
/**
143-
* Optional binding metadata analyzed from script - used to optimize
144-
* binding access when `prefixIdentifiers` is enabled.
145-
*/
146-
bindingMetadata?: BindingMetadata
147168
onError?: (error: CompilerError) => void
148169
}
149170

150-
export interface CodegenOptions {
171+
export interface CodegenOptions extends SharedTransformCodegenOptions {
151172
/**
152173
* - `module` mode will generate ES module import statements for helpers
153174
* and export the render function as the default export.
@@ -189,11 +210,6 @@ export interface CodegenOptions {
189210
* @default 'Vue'
190211
*/
191212
runtimeGlobalName?: string
192-
// we need to know this during codegen to generate proper preambles
193-
prefixIdentifiers?: boolean
194-
bindingMetadata?: BindingMetadata
195-
// generate ssr-specific code?
196-
ssr?: boolean
197213
}
198214

199215
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions

packages/compiler-core/src/runtimeHelpers.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
2929
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
3030
export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
3131
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
32+
export const UNREF = Symbol(__DEV__ ? `unref` : ``)
3233

3334
// Name mapping for runtime helpers that need to be imported from 'vue' in
3435
// generated code. Make sure these are correctly exported in the runtime!
@@ -62,7 +63,8 @@ export const helperNameMap: any = {
6263
[PUSH_SCOPE_ID]: `pushScopeId`,
6364
[POP_SCOPE_ID]: `popScopeId`,
6465
[WITH_SCOPE_ID]: `withScopeId`,
65-
[WITH_CTX]: `withCtx`
66+
[WITH_CTX]: `withCtx`,
67+
[UNREF]: `unref`
6668
}
6769

6870
export function registerRuntimeHelpers(helpers: any) {

packages/compiler-core/src/transform.ts

+4
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export function createTransformContext(
124124
ssr = false,
125125
ssrCssVars = ``,
126126
bindingMetadata = EMPTY_OBJ,
127+
inline = false,
128+
inlinePropsIdentifier = `$props`,
127129
onError = defaultOnError
128130
}: TransformOptions
129131
): TransformContext {
@@ -142,6 +144,8 @@ export function createTransformContext(
142144
ssr,
143145
ssrCssVars,
144146
bindingMetadata,
147+
inline,
148+
inlinePropsIdentifier,
145149
onError,
146150

147151
// state

packages/compiler-core/src/transforms/transformExpression.ts

+15-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
2828
import { validateBrowserExpression } from '../validateExpression'
2929
import { parse } from '@babel/parser'
3030
import { walk } from 'estree-walker'
31+
import { UNREF } from '../runtimeHelpers'
3132

3233
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
3334

@@ -97,12 +98,21 @@ export function processExpression(
9798
return node
9899
}
99100

100-
const { bindingMetadata } = context
101+
const { inline, inlinePropsIdentifier, bindingMetadata } = context
101102
const prefix = (raw: string) => {
102-
const source = hasOwn(bindingMetadata, raw)
103-
? `$` + bindingMetadata[raw]
104-
: `_ctx`
105-
return `${source}.${raw}`
103+
if (inline) {
104+
// setup inline mode, it's either props or setup
105+
if (bindingMetadata[raw] !== 'setup') {
106+
return `${inlinePropsIdentifier}.${raw}`
107+
} else {
108+
return `${context.helperString(UNREF)}(${raw})`
109+
}
110+
} else {
111+
const source = hasOwn(bindingMetadata, raw)
112+
? `$` + bindingMetadata[raw]
113+
: `_ctx`
114+
return `${source}.${raw}`
115+
}
106116
}
107117

108118
// fast path if expression is a simple identifier.

packages/compiler-sfc/__tests__/compileScript.spec.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -464,14 +464,20 @@ describe('SFC compile <script setup>', () => {
464464
compile(`<script setup>
465465
export const a = 1
466466
</script>`)
467-
).toThrow(`cannot contain non-type named exports`)
467+
).toThrow(`cannot contain non-type named or * exports`)
468+
469+
expect(() =>
470+
compile(`<script setup>
471+
export * from './foo'
472+
</script>`)
473+
).toThrow(`cannot contain non-type named or * exports`)
468474

469475
expect(() =>
470476
compile(`<script setup>
471477
const bar = 1
472478
export { bar as default }
473479
</script>`)
474-
).toThrow(`cannot contain non-type named exports`)
480+
).toThrow(`cannot contain non-type named or * exports`)
475481
})
476482

477483
test('ref: non-assignment expressions', () => {

0 commit comments

Comments
 (0)