params: ExpressionNode | ExpressionNode[] | undefined
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
newline: boolean
+ isSlot: boolean
}
export interface SequenceExpression extends Node {
params: FunctionExpression['params'],
returns: FunctionExpression['returns'],
newline: boolean = false,
+ isSlot: boolean = false,
loc: SourceLocation = locStub
): FunctionExpression {
return {
params,
returns,
newline,
+ isSlot,
loc
}
}
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'
mode = 'function',
prefixIdentifiers = mode === 'module',
sourceMap = false,
- filename = `template.vue.html`
+ filename = `template.vue.html`,
+ scopeId = null
}: CodegenOptions
): CodegenContext {
const context: CodegenContext = {
prefixIdentifiers,
sourceMap,
filename,
+ scopeId,
source: ast.loc.source,
code: ``,
column: 1,
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') {
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`)
}
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()
deindent()
push(`}`)
+
+ if (genScopeId) {
+ push(`)`)
+ }
+
return {
ast,
code: context.code,
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) {
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)
deindent()
push(`}`)
}
+ if (genScopeId) {
+ push(`)`)
+ }
}
function genConditionalExpression(
// Filename for source map generation.
// Default: `template.vue.html`
filename?: string
+ // SFC scoped styles ID
+ scopeId?: string | null
}
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
} 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>
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!
[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) {
}: TransformOptions
): TransformContext {
const context: TransformContext = {
+ // options
+ prefixIdentifiers,
+ hoistStatic,
+ cacheHandlers,
+ nodeTransforms,
+ directiveTransforms,
+ isBuiltInComponent,
+ onError,
+
+ // state
root,
helpers: new Set(),
components: new Set(),
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
const slotFunction = createFunctionExpression(
slotProps,
slotChildren,
- false,
+ false /* newline */,
+ true /* isSlot */,
slotChildren.length ? slotChildren[0].loc : slotLoc
)
createFunctionExpression(
createForLoopParams(parseResult),
buildDynamicSlot(slotName, slotFunction),
- true
+ true /* force newline */
)
])
)
createFunctionExpression(
slotProps,
children,
- false,
+ false /* newline */,
+ true /* isSlot */,
children.length ? children[0].loc : loc
)
)
mode: 'module',
prefixIdentifiers: false,
hoistStatic: false,
- cacheHandlers: false
+ cacheHandlers: false,
+ scopeId: null
})
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')
])
]
}