Bläddra i källkod

feat(sfc): wip scopeId compiler support

Evan You 6 år sedan
förälder
incheckning
51980afca2

+ 3 - 0
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
   }
 }

+ 58 - 8
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(

+ 2 - 0
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

+ 0 - 1
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<Required<ParserOptions>, OptionalOptions> &
   Pick<ParserOptions, OptionalOptions>

+ 7 - 1
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) {

+ 12 - 7
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

+ 5 - 3
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
     )
   )

+ 20 - 2
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' &&
+                (<HTMLInputElement>e.target).checked
+                  ? 'scope-id'
+                  : null
+            }
+          }),
+          h('label', { for: 'scope-id' }, 'scopeId')
         ])
       ]
     }