From 51980afca2e90784c51d13729cf5e18995bde43e Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 16 Dec 2019 12:11:51 -0500 Subject: [PATCH] feat(sfc): wip scopeId compiler support --- packages/compiler-core/src/ast.ts | 3 + packages/compiler-core/src/codegen.ts | 66 ++++++++++++++++--- packages/compiler-core/src/options.ts | 2 + packages/compiler-core/src/parse.ts | 1 - packages/compiler-core/src/runtimeHelpers.ts | 8 ++- packages/compiler-core/src/transform.ts | 19 ++++-- .../compiler-core/src/transforms/vSlot.ts | 8 ++- packages/template-explorer/src/options.ts | 22 ++++++- 8 files changed, 107 insertions(+), 22 deletions(-) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 6f7abff5ec..4261e73834 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -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 } } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index dee28eebfa..f46b332254 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -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( diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index 6f7a168b89..b2d7e878a6 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -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 diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index e7de25bcfc..6c125bb0fe 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -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, OptionalOptions> & Pick diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts index 59e9e25c87..00b84f2f0a 100644 --- a/packages/compiler-core/src/runtimeHelpers.ts +++ b/packages/compiler-core/src/runtimeHelpers.ts @@ -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) { diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 0e32d29884..4bfd749fc6 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -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 diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 33506c84e1..f2ce7497c0 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -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 ) ) diff --git a/packages/template-explorer/src/options.ts b/packages/template-explorer/src/options.ts index 9f7c4b16f9..0e637dffc0 100644 --- a/packages/template-explorer/src/options.ts +++ b/packages/template-explorer/src/options.ts @@ -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' && + (e.target).checked + ? 'scope-id' + : null + } + }), + h('label', { for: 'scope-id' }, 'scopeId') ]) ] } -- 2.47.3