feat(sfc): wip scopeId compiler support

This commit is contained in:
Evan You 2019-12-16 12:11:51 -05:00
parent b2c2d0590e
commit 51980afca2
8 changed files with 107 additions and 22 deletions

View File

@ -272,6 +272,7 @@ export interface FunctionExpression extends Node {
params: ExpressionNode | ExpressionNode[] | undefined params: ExpressionNode | ExpressionNode[] | undefined
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
newline: boolean newline: boolean
isSlot: boolean
} }
export interface SequenceExpression extends Node { export interface SequenceExpression extends Node {
@ -581,6 +582,7 @@ export function createFunctionExpression(
params: FunctionExpression['params'], params: FunctionExpression['params'],
returns: FunctionExpression['returns'], returns: FunctionExpression['returns'],
newline: boolean = false, newline: boolean = false,
isSlot: boolean = false,
loc: SourceLocation = locStub loc: SourceLocation = locStub
): FunctionExpression { ): FunctionExpression {
return { return {
@ -588,6 +590,7 @@ export function createFunctionExpression(
params, params,
returns, returns,
newline, newline,
isSlot,
loc loc
} }
} }

View File

@ -37,7 +37,10 @@ import {
RESOLVE_DIRECTIVE, RESOLVE_DIRECTIVE,
SET_BLOCK_TRACKING, SET_BLOCK_TRACKING,
CREATE_COMMENT, CREATE_COMMENT,
CREATE_TEXT CREATE_TEXT,
PUSH_SCOPE_ID,
POP_SCOPE_ID,
WITH_SCOPE_ID
} from './runtimeHelpers' } from './runtimeHelpers'
import { ImportItem } from './transform' import { ImportItem } from './transform'
@ -70,7 +73,8 @@ function createCodegenContext(
mode = 'function', mode = 'function',
prefixIdentifiers = mode === 'module', prefixIdentifiers = mode === 'module',
sourceMap = false, sourceMap = false,
filename = `template.vue.html` filename = `template.vue.html`,
scopeId = null
}: CodegenOptions }: CodegenOptions
): CodegenContext { ): CodegenContext {
const context: CodegenContext = { const context: CodegenContext = {
@ -78,6 +82,7 @@ function createCodegenContext(
prefixIdentifiers, prefixIdentifiers,
sourceMap, sourceMap,
filename, filename,
scopeId,
source: ast.loc.source, source: ast.loc.source,
code: ``, code: ``,
column: 1, column: 1,
@ -163,10 +168,12 @@ export function generate(
prefixIdentifiers, prefixIdentifiers,
indent, indent,
deindent, deindent,
newline newline,
scopeId
} = context } = context
const hasHelpers = ast.helpers.length > 0 const hasHelpers = ast.helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module' const useWithBlock = !prefixIdentifiers && mode !== 'module'
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
// preambles // preambles
if (mode === 'function') { if (mode === 'function') {
@ -198,6 +205,12 @@ export function generate(
push(`return `) push(`return `)
} else { } else {
// generate import statements for helpers // generate import statements for helpers
if (genScopeId) {
ast.helpers.push(WITH_SCOPE_ID)
if (ast.hoists.length) {
ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
}
}
if (hasHelpers) { if (hasHelpers) {
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`) push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
} }
@ -205,12 +218,19 @@ export function generate(
genImports(ast.imports, context) genImports(ast.imports, context)
newline() newline()
} }
if (genScopeId) {
push(`const withId = ${helper(WITH_SCOPE_ID)}("${scopeId}")`)
newline()
}
genHoists(ast.hoists, context) genHoists(ast.hoists, context)
newline() newline()
push(`export default `) push(`export default `)
} }
// enter render function // enter render function
if (genScopeId) {
push(`withId(`)
}
push(`function render() {`) push(`function render() {`)
indent() indent()
@ -267,6 +287,11 @@ export function generate(
deindent() deindent()
push(`}`) push(`}`)
if (genScopeId) {
push(`)`)
}
return { return {
ast, ast,
code: context.code, code: context.code,
@ -296,12 +321,27 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
if (!hoists.length) { if (!hoists.length) {
return return
} }
context.newline() const { push, newline, helper, scopeId, mode } = context
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
newline()
// push scope Id before initilaizing hoisted vnodes so that these vnodes
// get the proper scopeId as well.
if (genScopeId) {
push(`${helper(PUSH_SCOPE_ID)}("${scopeId}")`)
newline()
}
hoists.forEach((exp, i) => { hoists.forEach((exp, i) => {
context.push(`const _hoisted_${i + 1} = `) push(`const _hoisted_${i + 1} = `)
genNode(exp, context) genNode(exp, context)
context.newline() newline()
}) })
if (genScopeId) {
push(`${helper(POP_SCOPE_ID)}()`)
newline()
}
} }
function genImports(importsOptions: ImportItem[], context: CodegenContext) { function genImports(importsOptions: ImportItem[], context: CodegenContext) {
@ -545,8 +585,15 @@ function genFunctionExpression(
node: FunctionExpression, node: FunctionExpression,
context: CodegenContext context: CodegenContext
) { ) {
const { push, indent, deindent } = context const { push, indent, deindent, scopeId, mode } = context
const { params, returns, newline } = node const { params, returns, newline, isSlot } = node
// slot functions also need to push scopeId before rendering its content
const genScopeId =
!__BROWSER__ && isSlot && scopeId != null && mode === 'module'
if (genScopeId) {
push(`withId(`)
}
push(`(`, node) push(`(`, node)
if (isArray(params)) { if (isArray(params)) {
genNodeList(params, context) genNodeList(params, context)
@ -568,6 +615,9 @@ function genFunctionExpression(
deindent() deindent()
push(`}`) push(`}`)
} }
if (genScopeId) {
push(`)`)
}
} }
function genConditionalExpression( function genConditionalExpression(

View File

@ -62,6 +62,8 @@ export interface CodegenOptions {
// Filename for source map generation. // Filename for source map generation.
// Default: `template.vue.html` // Default: `template.vue.html`
filename?: string filename?: string
// SFC scoped styles ID
scopeId?: string | null
} }
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions

View File

@ -25,7 +25,6 @@ import {
} from './ast' } from './ast'
import { extend } from '@vue/shared' import { extend } from '@vue/shared'
// `isNativeTag` is optional, others are required
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent' type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> & type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
Pick<ParserOptions, OptionalOptions> Pick<ParserOptions, OptionalOptions>

View File

@ -22,6 +22,9 @@ export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``) export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``) export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``) export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
// Name mapping for runtime helpers that need to be imported from 'vue' in // Name mapping for runtime helpers that need to be imported from 'vue' in
// generated code. Make sure these are correctly exported in the runtime! // generated code. Make sure these are correctly exported in the runtime!
@ -48,7 +51,10 @@ export const helperNameMap: any = {
[MERGE_PROPS]: `mergeProps`, [MERGE_PROPS]: `mergeProps`,
[TO_HANDLERS]: `toHandlers`, [TO_HANDLERS]: `toHandlers`,
[CAMELIZE]: `camelize`, [CAMELIZE]: `camelize`,
[SET_BLOCK_TRACKING]: `setBlockTracking` [SET_BLOCK_TRACKING]: `setBlockTracking`,
[PUSH_SCOPE_ID]: `pushScopeId`,
[POP_SCOPE_ID]: `popScopeId`,
[WITH_SCOPE_ID]: `withScopeId`
} }
export function registerRuntimeHelpers(helpers: any) { export function registerRuntimeHelpers(helpers: any) {

View File

@ -119,6 +119,16 @@ function createTransformContext(
}: TransformOptions }: TransformOptions
): TransformContext { ): TransformContext {
const context: TransformContext = { const context: TransformContext = {
// options
prefixIdentifiers,
hoistStatic,
cacheHandlers,
nodeTransforms,
directiveTransforms,
isBuiltInComponent,
onError,
// state
root, root,
helpers: new Set(), helpers: new Set(),
components: new Set(), components: new Set(),
@ -133,16 +143,11 @@ function createTransformContext(
vPre: 0, vPre: 0,
vOnce: 0 vOnce: 0
}, },
prefixIdentifiers,
hoistStatic,
cacheHandlers,
nodeTransforms,
directiveTransforms,
isBuiltInComponent,
onError,
parent: null, parent: null,
currentNode: root, currentNode: root,
childIndex: 0, childIndex: 0,
// methods
helper(name) { helper(name) {
context.helpers.add(name) context.helpers.add(name)
return name return name

View File

@ -175,7 +175,8 @@ export function buildSlots(
const slotFunction = createFunctionExpression( const slotFunction = createFunctionExpression(
slotProps, slotProps,
slotChildren, slotChildren,
false, false /* newline */,
true /* isSlot */,
slotChildren.length ? slotChildren[0].loc : slotLoc slotChildren.length ? slotChildren[0].loc : slotLoc
) )
@ -244,7 +245,7 @@ export function buildSlots(
createFunctionExpression( createFunctionExpression(
createForLoopParams(parseResult), createForLoopParams(parseResult),
buildDynamicSlot(slotName, slotFunction), buildDynamicSlot(slotName, slotFunction),
true true /* force newline */
) )
]) ])
) )
@ -314,7 +315,8 @@ function buildDefaultSlot(
createFunctionExpression( createFunctionExpression(
slotProps, slotProps,
children, children,
false, false /* newline */,
true /* isSlot */,
children.length ? children[0].loc : loc children.length ? children[0].loc : loc
) )
) )

View File

@ -5,7 +5,8 @@ export const compilerOptions: CompilerOptions = reactive({
mode: 'module', mode: 'module',
prefixIdentifiers: false, prefixIdentifiers: false,
hoistStatic: false, hoistStatic: false,
cacheHandlers: false cacheHandlers: false,
scopeId: null
}) })
const App = { const App = {
@ -86,7 +87,24 @@ const App = {
)).checked )).checked
} }
}), }),
h('label', { for: 'cache' }, 'cacheHandlers') h('label', { for: 'cache' }, 'cacheHandlers'),
// toggle scopeId
h('input', {
type: 'checkbox',
id: 'scope-id',
disabled: compilerOptions.mode !== 'module',
checked:
compilerOptions.mode === 'module' && compilerOptions.scopeId,
onChange(e: Event) {
compilerOptions.scopeId =
compilerOptions.mode === 'module' &&
(<HTMLInputElement>e.target).checked
? 'scope-id'
: null
}
}),
h('label', { for: 'scope-id' }, 'scopeId')
]) ])
] ]
} }