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,
|
||||
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`
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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' }, [
|
||||
// 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')
|
||||
]),
|
||||
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')
|
||||
]),
|
||||
|
||||
// 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'),
|
||||
// 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')
|
||||
]),
|
||||
|
||||
// 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 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 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 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 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 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 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 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 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;
|
||||
padding: 0.3em 1.6em;
|
||||
color: #fff;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -22,17 +23,34 @@ h1 {
|
||||
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 {
|
||||
float: right;
|
||||
margin-top: 1em;
|
||||
display: none;
|
||||
margin-top: 15px;
|
||||
list-style-type: none;
|
||||
background-color: #1e1e1e;
|
||||
border: 1px solid #333;
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.options-group {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
#header span, #header label, #header input, #header a {
|
||||
display: inline-block;
|
||||
#options li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
#header a {
|
||||
@ -45,7 +63,6 @@ h1 {
|
||||
}
|
||||
|
||||
#header input {
|
||||
margin-left: 12px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user