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

View File

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

View File

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

View File

@ -25,7 +25,6 @@ import {
} from './ast'
import { extend } from '@vue/shared'
// `isNativeTag` is optional, others are required
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
type MergedParserOptions = Omit<Required<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 CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
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
// generated code. Make sure these are correctly exported in the runtime!
@ -48,7 +51,10 @@ export const helperNameMap: any = {
[MERGE_PROPS]: `mergeProps`,
[TO_HANDLERS]: `toHandlers`,
[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) {

View File

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

View File

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

View File

@ -5,7 +5,8 @@ export const compilerOptions: CompilerOptions = reactive({
mode: 'module',
prefixIdentifiers: false,
hoistStatic: false,
cacheHandlers: false
cacheHandlers: false,
scopeId: null
})
const App = {
@ -86,7 +87,24 @@ const App = {
)).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')
])
]
}