]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(sfc): wip scopeId compiler support
authorEvan You <yyx990803@gmail.com>
Mon, 16 Dec 2019 17:11:51 +0000 (12:11 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 17 Dec 2019 17:31:38 +0000 (12:31 -0500)
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/parse.ts
packages/compiler-core/src/runtimeHelpers.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/vSlot.ts
packages/template-explorer/src/options.ts

index 6f7abff5ec43c6f11e8dadb6b7afdc9acfb173fc..4261e7383452696e08170a506619ec5186a8a9e8 100644 (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
   }
 }
index dee28eebfadb280290159d710a2965f412ad4ecb..f46b332254c634d39809908b77f23d9c4d18b91a 100644 (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(
index 6f7a168b89179a6b93a5ba2028025f0188615a9e..b2d7e878a66d42629754a740a71e53ddcfc5cda8 100644 (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
index e7de25bcfc511ff66c02432f4bf274544e7cee3c..6c125bb0fe641dc869f2203457b4a54338eefeb0 100644 (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>
index 59e9e25c8759b84c5d950d91d17c9a820002fbae..00b84f2f0ab5e081a34862f6d616d7da92da0f26 100644 (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) {
index 0e32d29884213849c5024034e0ba1bfee9040ad0..4bfd749fc678766e3bfebec56699a57164216020 100644 (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
index 33506c84e14970d6904d25139be42e48f9a24e60..f2ce7497c0a201b88538cf66121d342573369707 100644 (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
     )
   )
index 9f7c4b16f9446ee3a859a6fac93b00c530a25d20..0e637dffc0dac14b367037373c55c70cdadf589b 100644 (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')
         ])
       ]
     }