]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler): prefix all imported helpers to avoid scope collision
authorEvan You <yyx990803@gmail.com>
Fri, 7 Feb 2020 23:53:39 +0000 (18:53 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 7 Feb 2020 23:53:39 +0000 (18:53 -0500)
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/options.ts
packages/compiler-ssr/src/runtimeHelpers.ts
packages/server-renderer/src/index.ts
packages/template-explorer/src/options.ts
packages/template-explorer/style.css

index 2541c77c64fea988767c9901ec8ee75f598ca209..01e8bfbb873ce7b02abc8bd14eeea47884a48370 100644 (file)
@@ -80,6 +80,7 @@ function createCodegenContext(
     sourceMap = false,
     filename = `template.vue.html`,
     scopeId = null,
+    optimizeBindings = false,
     runtimeGlobalName = `Vue`,
     runtimeModuleName = `vue`,
     ssr = false
@@ -91,6 +92,7 @@ function createCodegenContext(
     sourceMap,
     filename,
     scopeId,
+    optimizeBindings,
     runtimeGlobalName,
     runtimeModuleName,
     ssr,
@@ -102,8 +104,7 @@ function createCodegenContext(
     indentLevel: 0,
     map: undefined,
     helper(key) {
-      const name = helperNameMap[key]
-      return prefixIdentifiers ? name : `_${name}`
+      return `_${helperNameMap[key]}`
     },
     push(code, node) {
       context.code += code
@@ -282,7 +283,6 @@ export function generate(
 function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
   const {
     ssr,
-    helper,
     prefixIdentifiers,
     push,
     newline,
@@ -293,13 +293,16 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
     !__BROWSER__ && ssr
       ? `require(${JSON.stringify(runtimeModuleName)})`
       : runtimeGlobalName
+  const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
   // Generate const declaration for helpers
   // In prefix mode, we place the const declaration at top so it's done
   // only once; But if we not prefixing, we place the declaration inside the
   // with block so it doesn't incur the `in` check cost for every helper access.
   if (ast.helpers.length > 0) {
     if (!__BROWSER__ && prefixIdentifiers) {
-      push(`const { ${ast.helpers.map(helper).join(', ')} } = ${VueBinding}\n`)
+      push(
+        `const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`
+      )
     } else {
       // "with" mode.
       // save Vue in a separate variable to avoid collision
@@ -310,7 +313,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
       if (ast.hoists.length) {
         const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT]
           .filter(helper => ast.helpers.includes(helper))
-          .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
+          .map(aliasHelper)
           .join(', ')
         push(`const { ${staticHelpers} } = _Vue\n`)
       }
@@ -321,7 +324,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
     // ssr guaruntees prefixIdentifier: true
     push(
       `const { ${ast.ssrHelpers
-        .map(helper)
+        .map(aliasHelper)
         .join(', ')} } = require("@vue/server-renderer")\n`
     )
   }
@@ -335,7 +338,14 @@ function genModulePreamble(
   context: CodegenContext,
   genScopeId: boolean
 ) {
-  const { push, helper, newline, scopeId, runtimeModuleName } = context
+  const {
+    push,
+    helper,
+    newline,
+    scopeId,
+    optimizeBindings,
+    runtimeModuleName
+  } = context
 
   if (genScopeId) {
     ast.helpers.push(WITH_SCOPE_ID)
@@ -346,17 +356,35 @@ function genModulePreamble(
 
   // generate import statements for helpers
   if (ast.helpers.length) {
-    push(
-      `import { ${ast.helpers.map(helper).join(', ')} } from ${JSON.stringify(
-        runtimeModuleName
-      )}\n`
-    )
+    if (optimizeBindings) {
+      // when bundled with webpack with code-split, calling an import binding
+      // as a function leads to it being wrapped with `Object(a.b)` or `(0,a.b)`,
+      // incurring both payload size increase and potential perf overhead.
+      // therefore we assign the imports to vairables (which is a constant ~50b
+      // cost per-component instead of scaling with template size)
+      push(
+        `import { ${ast.helpers
+          .map(s => helperNameMap[s])
+          .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
+      )
+      push(
+        `\n// Binding optimization for webpack code-split\nconst ${ast.helpers
+          .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
+          .join(', ')}\n`
+      )
+    } else {
+      push(
+        `import { ${ast.helpers
+          .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
+          .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
+      )
+    }
   }
 
   if (ast.ssrHelpers && ast.ssrHelpers.length) {
     push(
       `import { ${ast.ssrHelpers
-        .map(helper)
+        .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
         .join(', ')} } from "@vue/server-renderer"\n`
     )
   }
index f88b007b56352c4e59c9ab52d0f08497c3027b70..b729ac3d07d5059d2d5a2433c294c7acdd0a6856 100644 (file)
@@ -73,6 +73,9 @@ export interface CodegenOptions {
   scopeId?: string | null
   // we need to know about this to generate proper preambles
   prefixIdentifiers?: boolean
+  // option to optimize helper import bindings via variable assignment
+  // (only used for webpack code-split)
+  optimizeBindings?: boolean
   // for specifying where to import helpers
   runtimeModuleName?: string
   runtimeGlobalName?: string
index 00865ca65687dc272006234ac39bce468e61c2af..2f394514de6232568176f9e85c1362b41d1a0301 100644 (file)
@@ -15,19 +15,19 @@ export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
 export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
 
 export const ssrHelpers = {
-  [SSR_INTERPOLATE]: `_ssrInterpolate`,
-  [SSR_RENDER_COMPONENT]: `_ssrRenderComponent`,
-  [SSR_RENDER_SLOT]: `_ssrRenderSlot`,
-  [SSR_RENDER_CLASS]: `_ssrRenderClass`,
-  [SSR_RENDER_STYLE]: `_ssrRenderStyle`,
-  [SSR_RENDER_ATTRS]: `_ssrRenderAttrs`,
-  [SSR_RENDER_ATTR]: `_ssrRenderAttr`,
-  [SSR_RENDER_DYNAMIC_ATTR]: `_ssrRenderDynamicAttr`,
-  [SSR_RENDER_LIST]: `_ssrRenderList`,
-  [SSR_LOOSE_EQUAL]: `_ssrLooseEqual`,
-  [SSR_LOOSE_CONTAIN]: `_ssrLooseContain`,
-  [SSR_RENDER_DYNAMIC_MODEL]: `_ssrRenderDynamicModel`,
-  [SSR_GET_DYNAMIC_MODEL_PROPS]: `_ssrGetDynamicModelProps`
+  [SSR_INTERPOLATE]: `ssrInterpolate`,
+  [SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
+  [SSR_RENDER_SLOT]: `ssrRenderSlot`,
+  [SSR_RENDER_CLASS]: `ssrRenderClass`,
+  [SSR_RENDER_STYLE]: `ssrRenderStyle`,
+  [SSR_RENDER_ATTRS]: `ssrRenderAttrs`,
+  [SSR_RENDER_ATTR]: `ssrRenderAttr`,
+  [SSR_RENDER_DYNAMIC_ATTR]: `ssrRenderDynamicAttr`,
+  [SSR_RENDER_LIST]: `ssrRenderList`,
+  [SSR_LOOSE_EQUAL]: `ssrLooseEqual`,
+  [SSR_LOOSE_CONTAIN]: `ssrLooseContain`,
+  [SSR_RENDER_DYNAMIC_MODEL]: `ssrRenderDynamicModel`,
+  [SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`
 }
 
 // Note: these are helpers imported from @vue/server-renderer
index 2c038acfcd18e8deb806378aabaaec07ec34f73b..06a184563284a6b424ac21b024c575fb514584d1 100644 (file)
@@ -2,22 +2,22 @@
 export { renderToString } from './renderToString'
 
 // internal runtime helpers
-export { renderComponent as _ssrRenderComponent } from './renderToString'
-export { ssrRenderSlot as _ssrRenderSlot } from './helpers/ssrRenderSlot'
+export { renderComponent } from './renderToString'
+export { ssrRenderSlot } from './helpers/ssrRenderSlot'
 export {
-  ssrRenderClass as _ssrRenderClass,
-  ssrRenderStyle as _ssrRenderStyle,
-  ssrRenderAttrs as _ssrRenderAttrs,
-  ssrRenderAttr as _ssrRenderAttr,
-  ssrRenderDynamicAttr as _ssrRenderDynamicAttr
+  ssrRenderClass,
+  ssrRenderStyle,
+  ssrRenderAttrs,
+  ssrRenderAttr,
+  ssrRenderDynamicAttr
 } from './helpers/ssrRenderAttrs'
-export { ssrInterpolate as _ssrInterpolate } from './helpers/ssrInterpolate'
-export { ssrRenderList as _ssrRenderList } from './helpers/ssrRenderList'
+export { ssrInterpolate } from './helpers/ssrInterpolate'
+export { ssrRenderList } from './helpers/ssrRenderList'
 
 // v-model helpers
 export {
-  ssrLooseEqual as _ssrLooseEqual,
-  ssrLooseContain as _ssrLooseContain,
-  ssrRenderDynamicModel as _ssrRenderDynamicModel,
-  ssrGetDynamicModelProps as _ssrGetDynamicModelProps
+  ssrLooseEqual,
+  ssrLooseContain,
+  ssrRenderDynamicModel,
+  ssrGetDynamicModelProps
 } from './helpers/ssrVModelHelpers'
index 9dd21c8014ea9393a302c0947051afe39f4ba469..7a2a09c127ece6436be6f4ab0bddbaa557af7e43 100644 (file)
@@ -6,6 +6,7 @@ export const ssrMode = ref(false)
 export const compilerOptions: CompilerOptions = reactive({
   mode: 'module',
   prefixIdentifiers: false,
+  optimizeBindings: false,
   hoistStatic: false,
   cacheHandlers: false,
   scopeId: null
@@ -29,96 +30,134 @@ const App = {
           },
           `@${__COMMIT__}`
         ),
+        ' | ',
+        h(
+          'a',
+          {
+            href:
+              'https://app.netlify.com/sites/vue-next-template-explorer/deploys',
+            target: `_blank`
+          },
+          'History'
+        ),
+
+        h('div', { id: 'options-wrapper' }, [
+          h('div', { id: 'options-label' }, 'Options ↘'),
+          h('ul', { id: 'options' }, [
+            // mode selection
+            h('li', { id: 'mode' }, [
+              h('span', { class: 'label' }, 'Mode: '),
+              h('input', {
+                type: 'radio',
+                id: 'mode-module',
+                name: 'mode',
+                checked: isModule,
+                onChange() {
+                  compilerOptions.mode = 'module'
+                }
+              }),
+              h('label', { for: 'mode-module' }, 'module'),
+              ' ',
+              h('input', {
+                type: 'radio',
+                id: 'mode-function',
+                name: 'mode',
+                checked: !isModule,
+                onChange() {
+                  compilerOptions.mode = 'function'
+                }
+              }),
+              h('label', { for: 'mode-function' }, 'function')
+            ]),
 
-        h('div', { id: 'options' }, [
-          // mode selection
-          h('span', { class: 'options-group' }, [
-            h('span', { class: 'label' }, 'Mode:'),
-            h('input', {
-              type: 'radio',
-              id: 'mode-module',
-              name: 'mode',
-              checked: isModule,
-              onChange() {
-                compilerOptions.mode = 'module'
-              }
-            }),
-            h('label', { for: 'mode-module' }, 'module'),
-            h('input', {
-              type: 'radio',
-              id: 'mode-function',
-              name: 'mode',
-              checked: !isModule,
-              onChange() {
-                compilerOptions.mode = 'function'
-              }
-            }),
-            h('label', { for: 'mode-function' }, 'function')
-          ]),
+            // SSR
+            h('li', [
+              h('input', {
+                type: 'checkbox',
+                id: 'ssr',
+                name: 'ssr',
+                checked: ssrMode.value,
+                onChange(e: Event) {
+                  ssrMode.value = (e.target as HTMLInputElement).checked
+                }
+              }),
+              h('label', { for: 'ssr' }, 'SSR')
+            ]),
 
-          // SSR
-          h('input', {
-            type: 'checkbox',
-            id: 'ssr',
-            name: 'ssr',
-            checked: ssrMode.value,
-            onChange(e: Event) {
-              ssrMode.value = (e.target as HTMLInputElement).checked
-            }
-          }),
-          h('label', { for: 'ssr' }, 'SSR'),
+            // toggle prefixIdentifiers
+            h('li', [
+              h('input', {
+                type: 'checkbox',
+                id: 'prefix',
+                disabled: isModule || isSSR,
+                checked: usePrefix || isSSR,
+                onChange(e: Event) {
+                  compilerOptions.prefixIdentifiers =
+                    (e.target as HTMLInputElement).checked || isModule
+                }
+              }),
+              h('label', { for: 'prefix' }, 'prefixIdentifiers')
+            ]),
 
-          // toggle prefixIdentifiers
-          h('input', {
-            type: 'checkbox',
-            id: 'prefix',
-            disabled: isModule || isSSR,
-            checked: usePrefix || isSSR,
-            onChange(e: Event) {
-              compilerOptions.prefixIdentifiers =
-                (e.target as HTMLInputElement).checked || isModule
-            }
-          }),
-          h('label', { for: 'prefix' }, 'prefixIdentifiers'),
+            // toggle hoistStatic
+            h('li', [
+              h('input', {
+                type: 'checkbox',
+                id: 'hoist',
+                checked: compilerOptions.hoistStatic && !isSSR,
+                disabled: isSSR,
+                onChange(e: Event) {
+                  compilerOptions.hoistStatic = (e.target as HTMLInputElement).checked
+                }
+              }),
+              h('label', { for: 'hoist' }, 'hoistStatic')
+            ]),
 
-          // toggle hoistStatic
-          h('input', {
-            type: 'checkbox',
-            id: 'hoist',
-            checked: compilerOptions.hoistStatic && !isSSR,
-            disabled: isSSR,
-            onChange(e: Event) {
-              compilerOptions.hoistStatic = (e.target as HTMLInputElement).checked
-            }
-          }),
-          h('label', { for: 'hoist' }, 'hoistStatic'),
+            // toggle cacheHandlers
+            h('li', [
+              h('input', {
+                type: 'checkbox',
+                id: 'cache',
+                checked: usePrefix && compilerOptions.cacheHandlers && !isSSR,
+                disabled: !usePrefix || isSSR,
+                onChange(e: Event) {
+                  compilerOptions.cacheHandlers = (e.target as HTMLInputElement).checked
+                }
+              }),
+              h('label', { for: 'cache' }, 'cacheHandlers')
+            ]),
 
-          // toggle cacheHandlers
-          h('input', {
-            type: 'checkbox',
-            id: 'cache',
-            checked: usePrefix && compilerOptions.cacheHandlers && !isSSR,
-            disabled: !usePrefix || isSSR,
-            onChange(e: Event) {
-              compilerOptions.cacheHandlers = (e.target as HTMLInputElement).checked
-            }
-          }),
-          h('label', { for: 'cache' }, 'cacheHandlers'),
+            // toggle scopeId
+            h('li', [
+              h('input', {
+                type: 'checkbox',
+                id: 'scope-id',
+                disabled: !isModule,
+                checked: isModule && compilerOptions.scopeId,
+                onChange(e: Event) {
+                  compilerOptions.scopeId =
+                    isModule && (e.target as HTMLInputElement).checked
+                      ? 'scope-id'
+                      : null
+                }
+              }),
+              h('label', { for: 'scope-id' }, 'scopeId')
+            ]),
 
-          // toggle scopeId
-          h('input', {
-            type: 'checkbox',
-            id: 'scope-id',
-            disabled: !isModule,
-            checked: isModule && compilerOptions.scopeId,
-            onChange(e: Event) {
-              compilerOptions.scopeId =
-                isModule && (e.target as HTMLInputElement).checked
-                  ? 'scope-id'
-                  : null
-            }
-          }),
-          h('label', { for: 'scope-id' }, 'scopeId')
+            // toggle optimizeBindings
+            h('li', [
+              h('input', {
+                type: 'checkbox',
+                id: 'optimize-bindings',
+                disabled: !isModule || isSSR,
+                checked: isModule && !isSSR && compilerOptions.optimizeBindings,
+                onChange(e: Event) {
+                  compilerOptions.optimizeBindings = (e.target as HTMLInputElement).checked
+                }
+              }),
+              h('label', { for: 'optimize-bindings' }, 'optimizeBindings')
+            ])
+          ])
         ])
       ]
     }
index fca19d04a450a387faa8f55b6d5457ed4559fe19..b51eca4b75661fd6a6de3b62df388481969a028d 100644 (file)
@@ -14,6 +14,7 @@ body {
   border-bottom: 1px solid #333;
   padding: 0.3em 1.6em;
   color: #fff;
+  z-index: 1;
 }
 
 h1 {
@@ -22,17 +23,34 @@ h1 {
   margin-right: 15px;
 }
 
-#options {
-  float: right;
-  margin-top: 1em;
+#options-wrapper {
+  position: absolute;
+  top: 20px;
+  right: 10px;
 }
 
-.options-group {
-  margin-right: 30px;
+#options-wrapper:hover #options {
+  display: block;
 }
 
-#header span, #header label, #header input, #header a {
-  display: inline-block;
+#options-label {
+  cursor: pointer;
+  text-align: right;
+  padding-right: 10px;
+  font-weight: bold;
+}
+
+#options {
+  display: none;
+  margin-top: 15px;
+  list-style-type: none;
+  background-color: #1e1e1e;
+  border: 1px solid #333;
+  padding: 15px 30px;
+}
+
+#options li {
+  margin: 8px 0;
 }
 
 #header a {
@@ -45,7 +63,6 @@ h1 {
 }
 
 #header input {
-  margin-left: 12px;
   margin-right: 6px;
 }