feat(sfc): wip scopeId compiler support
This commit is contained in:
parent
b2c2d0590e
commit
51980afca2
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -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')
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user