refactor(compiler): prefix all imported helpers to avoid scope collision
This commit is contained in:
parent
c44d9fbe3d
commit
51317af6e8
@ -80,6 +80,7 @@ function createCodegenContext(
|
|||||||
sourceMap = false,
|
sourceMap = false,
|
||||||
filename = `template.vue.html`,
|
filename = `template.vue.html`,
|
||||||
scopeId = null,
|
scopeId = null,
|
||||||
|
optimizeBindings = false,
|
||||||
runtimeGlobalName = `Vue`,
|
runtimeGlobalName = `Vue`,
|
||||||
runtimeModuleName = `vue`,
|
runtimeModuleName = `vue`,
|
||||||
ssr = false
|
ssr = false
|
||||||
@ -91,6 +92,7 @@ function createCodegenContext(
|
|||||||
sourceMap,
|
sourceMap,
|
||||||
filename,
|
filename,
|
||||||
scopeId,
|
scopeId,
|
||||||
|
optimizeBindings,
|
||||||
runtimeGlobalName,
|
runtimeGlobalName,
|
||||||
runtimeModuleName,
|
runtimeModuleName,
|
||||||
ssr,
|
ssr,
|
||||||
@ -102,8 +104,7 @@ function createCodegenContext(
|
|||||||
indentLevel: 0,
|
indentLevel: 0,
|
||||||
map: undefined,
|
map: undefined,
|
||||||
helper(key) {
|
helper(key) {
|
||||||
const name = helperNameMap[key]
|
return `_${helperNameMap[key]}`
|
||||||
return prefixIdentifiers ? name : `_${name}`
|
|
||||||
},
|
},
|
||||||
push(code, node) {
|
push(code, node) {
|
||||||
context.code += code
|
context.code += code
|
||||||
@ -282,7 +283,6 @@ export function generate(
|
|||||||
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
||||||
const {
|
const {
|
||||||
ssr,
|
ssr,
|
||||||
helper,
|
|
||||||
prefixIdentifiers,
|
prefixIdentifiers,
|
||||||
push,
|
push,
|
||||||
newline,
|
newline,
|
||||||
@ -293,13 +293,16 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
|||||||
!__BROWSER__ && ssr
|
!__BROWSER__ && ssr
|
||||||
? `require(${JSON.stringify(runtimeModuleName)})`
|
? `require(${JSON.stringify(runtimeModuleName)})`
|
||||||
: runtimeGlobalName
|
: runtimeGlobalName
|
||||||
|
const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
|
||||||
// Generate const declaration for helpers
|
// Generate const declaration for helpers
|
||||||
// In prefix mode, we place the const declaration at top so it's done
|
// 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
|
// 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.
|
// with block so it doesn't incur the `in` check cost for every helper access.
|
||||||
if (ast.helpers.length > 0) {
|
if (ast.helpers.length > 0) {
|
||||||
if (!__BROWSER__ && prefixIdentifiers) {
|
if (!__BROWSER__ && prefixIdentifiers) {
|
||||||
push(`const { ${ast.helpers.map(helper).join(', ')} } = ${VueBinding}\n`)
|
push(
|
||||||
|
`const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// "with" mode.
|
// "with" mode.
|
||||||
// save Vue in a separate variable to avoid collision
|
// save Vue in a separate variable to avoid collision
|
||||||
@ -310,7 +313,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
|||||||
if (ast.hoists.length) {
|
if (ast.hoists.length) {
|
||||||
const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT]
|
const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT]
|
||||||
.filter(helper => ast.helpers.includes(helper))
|
.filter(helper => ast.helpers.includes(helper))
|
||||||
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
|
.map(aliasHelper)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
push(`const { ${staticHelpers} } = _Vue\n`)
|
push(`const { ${staticHelpers} } = _Vue\n`)
|
||||||
}
|
}
|
||||||
@ -321,7 +324,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
|||||||
// ssr guaruntees prefixIdentifier: true
|
// ssr guaruntees prefixIdentifier: true
|
||||||
push(
|
push(
|
||||||
`const { ${ast.ssrHelpers
|
`const { ${ast.ssrHelpers
|
||||||
.map(helper)
|
.map(aliasHelper)
|
||||||
.join(', ')} } = require("@vue/server-renderer")\n`
|
.join(', ')} } = require("@vue/server-renderer")\n`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -335,7 +338,14 @@ function genModulePreamble(
|
|||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
genScopeId: boolean
|
genScopeId: boolean
|
||||||
) {
|
) {
|
||||||
const { push, helper, newline, scopeId, runtimeModuleName } = context
|
const {
|
||||||
|
push,
|
||||||
|
helper,
|
||||||
|
newline,
|
||||||
|
scopeId,
|
||||||
|
optimizeBindings,
|
||||||
|
runtimeModuleName
|
||||||
|
} = context
|
||||||
|
|
||||||
if (genScopeId) {
|
if (genScopeId) {
|
||||||
ast.helpers.push(WITH_SCOPE_ID)
|
ast.helpers.push(WITH_SCOPE_ID)
|
||||||
@ -346,17 +356,35 @@ function genModulePreamble(
|
|||||||
|
|
||||||
// generate import statements for helpers
|
// generate import statements for helpers
|
||||||
if (ast.helpers.length) {
|
if (ast.helpers.length) {
|
||||||
|
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(
|
push(
|
||||||
`import { ${ast.helpers.map(helper).join(', ')} } from ${JSON.stringify(
|
`import { ${ast.helpers
|
||||||
runtimeModuleName
|
.map(s => helperNameMap[s])
|
||||||
)}\n`
|
.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) {
|
if (ast.ssrHelpers && ast.ssrHelpers.length) {
|
||||||
push(
|
push(
|
||||||
`import { ${ast.ssrHelpers
|
`import { ${ast.ssrHelpers
|
||||||
.map(helper)
|
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
|
||||||
.join(', ')} } from "@vue/server-renderer"\n`
|
.join(', ')} } from "@vue/server-renderer"\n`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,9 @@ export interface CodegenOptions {
|
|||||||
scopeId?: string | null
|
scopeId?: string | null
|
||||||
// we need to know about this to generate proper preambles
|
// we need to know about this to generate proper preambles
|
||||||
prefixIdentifiers?: boolean
|
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
|
// for specifying where to import helpers
|
||||||
runtimeModuleName?: string
|
runtimeModuleName?: string
|
||||||
runtimeGlobalName?: string
|
runtimeGlobalName?: string
|
||||||
|
@ -15,19 +15,19 @@ export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
|
|||||||
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
|
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
|
||||||
|
|
||||||
export const ssrHelpers = {
|
export const ssrHelpers = {
|
||||||
[SSR_INTERPOLATE]: `_ssrInterpolate`,
|
[SSR_INTERPOLATE]: `ssrInterpolate`,
|
||||||
[SSR_RENDER_COMPONENT]: `_ssrRenderComponent`,
|
[SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
|
||||||
[SSR_RENDER_SLOT]: `_ssrRenderSlot`,
|
[SSR_RENDER_SLOT]: `ssrRenderSlot`,
|
||||||
[SSR_RENDER_CLASS]: `_ssrRenderClass`,
|
[SSR_RENDER_CLASS]: `ssrRenderClass`,
|
||||||
[SSR_RENDER_STYLE]: `_ssrRenderStyle`,
|
[SSR_RENDER_STYLE]: `ssrRenderStyle`,
|
||||||
[SSR_RENDER_ATTRS]: `_ssrRenderAttrs`,
|
[SSR_RENDER_ATTRS]: `ssrRenderAttrs`,
|
||||||
[SSR_RENDER_ATTR]: `_ssrRenderAttr`,
|
[SSR_RENDER_ATTR]: `ssrRenderAttr`,
|
||||||
[SSR_RENDER_DYNAMIC_ATTR]: `_ssrRenderDynamicAttr`,
|
[SSR_RENDER_DYNAMIC_ATTR]: `ssrRenderDynamicAttr`,
|
||||||
[SSR_RENDER_LIST]: `_ssrRenderList`,
|
[SSR_RENDER_LIST]: `ssrRenderList`,
|
||||||
[SSR_LOOSE_EQUAL]: `_ssrLooseEqual`,
|
[SSR_LOOSE_EQUAL]: `ssrLooseEqual`,
|
||||||
[SSR_LOOSE_CONTAIN]: `_ssrLooseContain`,
|
[SSR_LOOSE_CONTAIN]: `ssrLooseContain`,
|
||||||
[SSR_RENDER_DYNAMIC_MODEL]: `_ssrRenderDynamicModel`,
|
[SSR_RENDER_DYNAMIC_MODEL]: `ssrRenderDynamicModel`,
|
||||||
[SSR_GET_DYNAMIC_MODEL_PROPS]: `_ssrGetDynamicModelProps`
|
[SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: these are helpers imported from @vue/server-renderer
|
// Note: these are helpers imported from @vue/server-renderer
|
||||||
|
@ -2,22 +2,22 @@
|
|||||||
export { renderToString } from './renderToString'
|
export { renderToString } from './renderToString'
|
||||||
|
|
||||||
// internal runtime helpers
|
// internal runtime helpers
|
||||||
export { renderComponent as _ssrRenderComponent } from './renderToString'
|
export { renderComponent } from './renderToString'
|
||||||
export { ssrRenderSlot as _ssrRenderSlot } from './helpers/ssrRenderSlot'
|
export { ssrRenderSlot } from './helpers/ssrRenderSlot'
|
||||||
export {
|
export {
|
||||||
ssrRenderClass as _ssrRenderClass,
|
ssrRenderClass,
|
||||||
ssrRenderStyle as _ssrRenderStyle,
|
ssrRenderStyle,
|
||||||
ssrRenderAttrs as _ssrRenderAttrs,
|
ssrRenderAttrs,
|
||||||
ssrRenderAttr as _ssrRenderAttr,
|
ssrRenderAttr,
|
||||||
ssrRenderDynamicAttr as _ssrRenderDynamicAttr
|
ssrRenderDynamicAttr
|
||||||
} from './helpers/ssrRenderAttrs'
|
} from './helpers/ssrRenderAttrs'
|
||||||
export { ssrInterpolate as _ssrInterpolate } from './helpers/ssrInterpolate'
|
export { ssrInterpolate } from './helpers/ssrInterpolate'
|
||||||
export { ssrRenderList as _ssrRenderList } from './helpers/ssrRenderList'
|
export { ssrRenderList } from './helpers/ssrRenderList'
|
||||||
|
|
||||||
// v-model helpers
|
// v-model helpers
|
||||||
export {
|
export {
|
||||||
ssrLooseEqual as _ssrLooseEqual,
|
ssrLooseEqual,
|
||||||
ssrLooseContain as _ssrLooseContain,
|
ssrLooseContain,
|
||||||
ssrRenderDynamicModel as _ssrRenderDynamicModel,
|
ssrRenderDynamicModel,
|
||||||
ssrGetDynamicModelProps as _ssrGetDynamicModelProps
|
ssrGetDynamicModelProps
|
||||||
} from './helpers/ssrVModelHelpers'
|
} from './helpers/ssrVModelHelpers'
|
||||||
|
@ -6,6 +6,7 @@ export const ssrMode = ref(false)
|
|||||||
export const compilerOptions: CompilerOptions = reactive({
|
export const compilerOptions: CompilerOptions = reactive({
|
||||||
mode: 'module',
|
mode: 'module',
|
||||||
prefixIdentifiers: false,
|
prefixIdentifiers: false,
|
||||||
|
optimizeBindings: false,
|
||||||
hoistStatic: false,
|
hoistStatic: false,
|
||||||
cacheHandlers: false,
|
cacheHandlers: false,
|
||||||
scopeId: null
|
scopeId: null
|
||||||
@ -29,11 +30,23 @@ const App = {
|
|||||||
},
|
},
|
||||||
`@${__COMMIT__}`
|
`@${__COMMIT__}`
|
||||||
),
|
),
|
||||||
|
' | ',
|
||||||
|
h(
|
||||||
|
'a',
|
||||||
|
{
|
||||||
|
href:
|
||||||
|
'https://app.netlify.com/sites/vue-next-template-explorer/deploys',
|
||||||
|
target: `_blank`
|
||||||
|
},
|
||||||
|
'History'
|
||||||
|
),
|
||||||
|
|
||||||
h('div', { id: 'options' }, [
|
h('div', { id: 'options-wrapper' }, [
|
||||||
|
h('div', { id: 'options-label' }, 'Options ↘'),
|
||||||
|
h('ul', { id: 'options' }, [
|
||||||
// mode selection
|
// mode selection
|
||||||
h('span', { class: 'options-group' }, [
|
h('li', { id: 'mode' }, [
|
||||||
h('span', { class: 'label' }, 'Mode:'),
|
h('span', { class: 'label' }, 'Mode: '),
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
id: 'mode-module',
|
id: 'mode-module',
|
||||||
@ -44,6 +57,7 @@ const App = {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
h('label', { for: 'mode-module' }, 'module'),
|
h('label', { for: 'mode-module' }, 'module'),
|
||||||
|
' ',
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
id: 'mode-function',
|
id: 'mode-function',
|
||||||
@ -57,6 +71,7 @@ const App = {
|
|||||||
]),
|
]),
|
||||||
|
|
||||||
// SSR
|
// SSR
|
||||||
|
h('li', [
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
id: 'ssr',
|
id: 'ssr',
|
||||||
@ -66,9 +81,11 @@ const App = {
|
|||||||
ssrMode.value = (e.target as HTMLInputElement).checked
|
ssrMode.value = (e.target as HTMLInputElement).checked
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
h('label', { for: 'ssr' }, 'SSR'),
|
h('label', { for: 'ssr' }, 'SSR')
|
||||||
|
]),
|
||||||
|
|
||||||
// toggle prefixIdentifiers
|
// toggle prefixIdentifiers
|
||||||
|
h('li', [
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
id: 'prefix',
|
id: 'prefix',
|
||||||
@ -79,9 +96,11 @@ const App = {
|
|||||||
(e.target as HTMLInputElement).checked || isModule
|
(e.target as HTMLInputElement).checked || isModule
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
h('label', { for: 'prefix' }, 'prefixIdentifiers'),
|
h('label', { for: 'prefix' }, 'prefixIdentifiers')
|
||||||
|
]),
|
||||||
|
|
||||||
// toggle hoistStatic
|
// toggle hoistStatic
|
||||||
|
h('li', [
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
id: 'hoist',
|
id: 'hoist',
|
||||||
@ -91,9 +110,11 @@ const App = {
|
|||||||
compilerOptions.hoistStatic = (e.target as HTMLInputElement).checked
|
compilerOptions.hoistStatic = (e.target as HTMLInputElement).checked
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
h('label', { for: 'hoist' }, 'hoistStatic'),
|
h('label', { for: 'hoist' }, 'hoistStatic')
|
||||||
|
]),
|
||||||
|
|
||||||
// toggle cacheHandlers
|
// toggle cacheHandlers
|
||||||
|
h('li', [
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
id: 'cache',
|
id: 'cache',
|
||||||
@ -103,9 +124,11 @@ const App = {
|
|||||||
compilerOptions.cacheHandlers = (e.target as HTMLInputElement).checked
|
compilerOptions.cacheHandlers = (e.target as HTMLInputElement).checked
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
h('label', { for: 'cache' }, 'cacheHandlers'),
|
h('label', { for: 'cache' }, 'cacheHandlers')
|
||||||
|
]),
|
||||||
|
|
||||||
// toggle scopeId
|
// toggle scopeId
|
||||||
|
h('li', [
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
id: 'scope-id',
|
id: 'scope-id',
|
||||||
@ -119,6 +142,22 @@ const App = {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
h('label', { for: 'scope-id' }, 'scopeId')
|
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')
|
||||||
|
])
|
||||||
|
])
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ body {
|
|||||||
border-bottom: 1px solid #333;
|
border-bottom: 1px solid #333;
|
||||||
padding: 0.3em 1.6em;
|
padding: 0.3em 1.6em;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@ -22,17 +23,34 @@ h1 {
|
|||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#options-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-wrapper:hover #options {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-label {
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
#options {
|
#options {
|
||||||
float: right;
|
display: none;
|
||||||
margin-top: 1em;
|
margin-top: 15px;
|
||||||
|
list-style-type: none;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
border: 1px solid #333;
|
||||||
|
padding: 15px 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-group {
|
#options li {
|
||||||
margin-right: 30px;
|
margin: 8px 0;
|
||||||
}
|
|
||||||
|
|
||||||
#header span, #header label, #header input, #header a {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#header a {
|
#header a {
|
||||||
@ -45,7 +63,6 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#header input {
|
#header input {
|
||||||
margin-left: 12px;
|
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user