Skip to content

New <script setup> and ref sugar implementation #2532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions packages/compiler-core/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode

export interface CodegenResult {
code: string
preamble: string
ast: RootNode
map?: RawSourceMap
}

export interface CodegenContext
extends Omit<Required<CodegenOptions>, 'bindingMetadata'> {
extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
source: string
code: string
line: number
Expand Down Expand Up @@ -199,12 +200,18 @@ export function generate(
const hasHelpers = ast.helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module'
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
const isSetupInlined = !!options.inline

// preambles
// in setup() inline mode, the preamble is generated in a sub context
// and returned separately.
const preambleContext = isSetupInlined
? createCodegenContext(ast, options)
: context
if (!__BROWSER__ && mode === 'module') {
genModulePreamble(ast, context, genScopeId)
genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
} else {
genFunctionPreamble(ast, context)
genFunctionPreamble(ast, preambleContext)
}

// binding optimizations
Expand All @@ -213,10 +220,17 @@ export function generate(
: ``
// enter render function
if (!ssr) {
if (genScopeId) {
push(`const render = ${PURE_ANNOTATION}_withId(`)
if (isSetupInlined) {
if (genScopeId) {
push(`${PURE_ANNOTATION}_withId(`)
}
push(`(_ctx, _cache${optimizeSources}) => {`)
} else {
if (genScopeId) {
push(`const render = ${PURE_ANNOTATION}_withId(`)
}
push(`function render(_ctx, _cache${optimizeSources}) {`)
}
push(`function render(_ctx, _cache${optimizeSources}) {`)
} else {
if (genScopeId) {
push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
Expand Down Expand Up @@ -290,6 +304,7 @@ export function generate(
return {
ast,
code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``,
// SourceMapGenerator does have toJSON() method but it's not in the types
map: context.map ? (context.map as any).toJSON() : undefined
}
Expand Down Expand Up @@ -356,7 +371,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
function genModulePreamble(
ast: RootNode,
context: CodegenContext,
genScopeId: boolean
genScopeId: boolean,
inline?: boolean
) {
const {
push,
Expand Down Expand Up @@ -423,7 +439,10 @@ function genModulePreamble(

genHoists(ast.hoists, context)
newline()
push(`export `)

if (!inline) {
push(`export `)
}
}

function genAssets(
Expand Down
50 changes: 31 additions & 19 deletions packages/compiler-core/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,38 @@ export type HoistTransform = (
) => void

export interface BindingMetadata {
[key: string]: 'data' | 'props' | 'setup' | 'options'
[key: string]: 'data' | 'props' | 'setup' | 'options' | 'component-import'
}

export interface TransformOptions {
interface SharedTransformCodegenOptions {
/**
* Transform expressions like {{ foo }} to `_ctx.foo`.
* If this option is false, the generated code will be wrapped in a
* `with (this) { ... }` block.
* - This is force-enabled in module mode, since modules are by default strict
* and cannot use `with`
* @default mode === 'module'
*/
prefixIdentifiers?: boolean
/**
* Generate SSR-optimized render functions instead.
* The resulting function must be attached to the component via the
* `ssrRender` option instead of `render`.
*/
ssr?: boolean
/**
* Optional binding metadata analyzed from script - used to optimize
* binding access when `prefixIdentifiers` is enabled.
*/
bindingMetadata?: BindingMetadata
/**
* Compile the function for inlining inside setup().
* This allows the function to directly access setup() local bindings.
*/
inline?: boolean
}

export interface TransformOptions extends SharedTransformCodegenOptions {
/**
* An array of node transforms to be applied to every AST node.
*/
Expand Down Expand Up @@ -128,26 +156,15 @@ export interface TransformOptions {
* SFC scoped styles ID
*/
scopeId?: string | null
/**
* Generate SSR-optimized render functions instead.
* The resulting function must be attached to the component via the
* `ssrRender` option instead of `render`.
*/
ssr?: boolean
/**
* SFC `<style vars>` injection string
* needed to render inline CSS variables on component root
*/
ssrCssVars?: string
/**
* Optional binding metadata analyzed from script - used to optimize
* binding access when `prefixIdentifiers` is enabled.
*/
bindingMetadata?: BindingMetadata
onError?: (error: CompilerError) => void
}

export interface CodegenOptions {
export interface CodegenOptions extends SharedTransformCodegenOptions {
/**
* - `module` mode will generate ES module import statements for helpers
* and export the render function as the default export.
Expand Down Expand Up @@ -189,11 +206,6 @@ export interface CodegenOptions {
* @default 'Vue'
*/
runtimeGlobalName?: string
// we need to know this during codegen to generate proper preambles
prefixIdentifiers?: boolean
bindingMetadata?: BindingMetadata
// generate ssr-specific code?
ssr?: boolean
}

export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
4 changes: 3 additions & 1 deletion packages/compiler-core/src/runtimeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
export const UNREF = Symbol(__DEV__ ? `unref` : ``)

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

export function registerRuntimeHelpers(helpers: any) {
Expand Down
7 changes: 5 additions & 2 deletions packages/compiler-core/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
isArray,
NOOP,
PatchFlags,
PatchFlagNames
PatchFlagNames,
EMPTY_OBJ
} from '@vue/shared'
import { defaultOnError } from './errors'
import {
Expand Down Expand Up @@ -122,7 +123,8 @@ export function createTransformContext(
scopeId = null,
ssr = false,
ssrCssVars = ``,
bindingMetadata = {},
bindingMetadata = EMPTY_OBJ,
inline = false,
onError = defaultOnError
}: TransformOptions
): TransformContext {
Expand All @@ -141,6 +143,7 @@ export function createTransformContext(
ssr,
ssrCssVars,
bindingMetadata,
inline,
onError,

// state
Expand Down
35 changes: 31 additions & 4 deletions packages/compiler-core/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import {
isSymbol,
isOn,
isObject,
isReservedProp
isReservedProp,
capitalize,
camelize,
EMPTY_OBJ
} from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
Expand All @@ -37,7 +40,8 @@ import {
TO_HANDLERS,
TELEPORT,
KEEP_ALIVE,
SUSPENSE
SUSPENSE,
UNREF
} from '../runtimeHelpers'
import {
getInnerRange,
Expand All @@ -50,6 +54,7 @@ import {
} from '../utils'
import { buildSlots } from './vSlot'
import { getStaticType } from './hoistStatic'
import { BindingMetadata } from '../options'

// some directive transforms (e.g. v-model) may return a symbol for runtime
// import, which should be used instead of a resolveDirective call.
Expand Down Expand Up @@ -246,8 +251,30 @@ export function resolveComponentType(
}

// 3. user component (from setup bindings)
if (context.bindingMetadata[tag] === 'setup') {
return `$setup[${JSON.stringify(tag)}]`
const bindings = context.bindingMetadata
if (bindings !== EMPTY_OBJ) {
const checkType = (type: BindingMetadata[string]) => {
let resolvedTag = tag
if (
bindings[resolvedTag] === type ||
bindings[(resolvedTag = camelize(tag))] === type ||
bindings[(resolvedTag = capitalize(camelize(tag)))] === type
) {
return resolvedTag
}
}
const tagFromSetup = checkType('setup')
if (tagFromSetup) {
return context.inline
? // setup scope bindings may be refs so they need to be unrefed
`${context.helperString(UNREF)}(${tagFromSetup})`
: `$setup[${JSON.stringify(tagFromSetup)}]`
}
const tagFromImport = checkType('component-import')
if (tagFromImport) {
// imports can be used as-is
return tagFromImport
}
}

// 4. user component (resolve)
Expand Down
Loading