diff --git a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
new file mode 100644
index 00000000..ea08f272
--- /dev/null
+++ b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
@@ -0,0 +1,95 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = `
+"import { createVNode, createBlock, openBlock, withScopeId, pushScopeId, popScopeId } from \\"vue\\"
+const withId = withScopeId(\\"test\\")
+
+pushScopeId(\\"test\\")
+const _hoisted_1 = createVNode(\\"div\\", null, \\"hello\\")
+const _hoisted_2 = createVNode(\\"div\\", null, \\"world\\")
+popScopeId()
+
+export default withId(function render() {
+ const _ctx = this
+ return (openBlock(), createBlock(\\"div\\", null, [
+ _hoisted_1,
+ _hoisted_2
+ ]))
+})"
+`;
+
+exports[`scopeId compiler support should wrap default slot 1`] = `
+"import { createVNode, resolveComponent, createBlock, openBlock, withScopeId } from \\"vue\\"
+const withId = withScopeId(\\"test\\")
+
+export default withId(function render() {
+ const _ctx = this
+ const _component_Child = resolveComponent(\\"Child\\")
+
+ return (openBlock(), createBlock(_component_Child, null, {
+ default: withId(() => [
+ createVNode(\\"div\\")
+ ]),
+ _compiled: true
+ }))
+})"
+`;
+
+exports[`scopeId compiler support should wrap dynamic slots 1`] = `
+"import { createVNode, resolveComponent, renderList, createSlots, createBlock, openBlock, withScopeId } from \\"vue\\"
+const withId = withScopeId(\\"test\\")
+
+export default withId(function render() {
+ const _ctx = this
+ const _component_Child = resolveComponent(\\"Child\\")
+
+ return (openBlock(), createBlock(_component_Child, null, createSlots({ _compiled: true }, [
+ (_ctx.ok)
+ ? {
+ name: \\"foo\\",
+ fn: withId(() => [
+ createVNode(\\"div\\")
+ ])
+ }
+ : undefined,
+ renderList(_ctx.list, (i) => {
+ return {
+ name: i,
+ fn: withId(() => [
+ createVNode(\\"div\\")
+ ])
+ }
+ })
+ ]), 512 /* DYNAMIC_SLOTS */))
+})"
+`;
+
+exports[`scopeId compiler support should wrap named slots 1`] = `
+"import { toString, createTextVNode, createVNode, resolveComponent, createBlock, openBlock, withScopeId } from \\"vue\\"
+const withId = withScopeId(\\"test\\")
+
+export default withId(function render() {
+ const _ctx = this
+ const _component_Child = resolveComponent(\\"Child\\")
+
+ return (openBlock(), createBlock(_component_Child, null, {
+ foo: withId(({ msg }) => [
+ createTextVNode(toString(msg), 1 /* TEXT */)
+ ]),
+ bar: withId(() => [
+ createVNode(\\"div\\")
+ ]),
+ _compiled: true
+ }))
+})"
+`;
+
+exports[`scopeId compiler support should wrap render function 1`] = `
+"import { createVNode, createBlock, openBlock, withScopeId } from \\"vue\\"
+const withId = withScopeId(\\"test\\")
+
+export default withId(function render() {
+ const _ctx = this
+ return (openBlock(), createBlock(\\"div\\"))
+})"
+`;
diff --git a/packages/compiler-core/__tests__/scopeId.spec.ts b/packages/compiler-core/__tests__/scopeId.spec.ts
new file mode 100644
index 00000000..37cde9c2
--- /dev/null
+++ b/packages/compiler-core/__tests__/scopeId.spec.ts
@@ -0,0 +1,91 @@
+import { baseCompile } from '../src/compile'
+import {
+ WITH_SCOPE_ID,
+ PUSH_SCOPE_ID,
+ POP_SCOPE_ID
+} from '../src/runtimeHelpers'
+
+describe('scopeId compiler support', () => {
+ test('should only work in module mode', () => {
+ expect(() => {
+ baseCompile(``, { scopeId: 'test' })
+ }).toThrow(`"scopeId" option is only supported in module mode`)
+ })
+
+ test('should wrap render function', () => {
+ const { ast, code } = baseCompile(`
`, {
+ mode: 'module',
+ scopeId: 'test'
+ })
+ expect(ast.helpers).toContain(WITH_SCOPE_ID)
+ expect(code).toMatch(`const withId = withScopeId("test")`)
+ expect(code).toMatch(`export default withId(function render() {`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should wrap default slot', () => {
+ const { code } = baseCompile(``, {
+ mode: 'module',
+ scopeId: 'test'
+ })
+ expect(code).toMatch(`default: withId(() => [`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should wrap named slots', () => {
+ const { code } = baseCompile(
+ `
+ {{ msg }}
+
+
+ `,
+ {
+ mode: 'module',
+ scopeId: 'test'
+ }
+ )
+ expect(code).toMatch(`foo: withId(({ msg }) => [`)
+ expect(code).toMatch(`bar: withId(() => [`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should wrap dynamic slots', () => {
+ const { code } = baseCompile(
+ `
+
+
+
+ `,
+ {
+ mode: 'module',
+ scopeId: 'test'
+ }
+ )
+ expect(code).toMatch(/name: "foo",\s+fn: withId\(/)
+ expect(code).toMatch(/name: i,\s+fn: withId\(/)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should push scopeId for hoisted nodes', () => {
+ const { ast, code } = baseCompile(
+ ``,
+ {
+ mode: 'module',
+ scopeId: 'test',
+ hoistStatic: true
+ }
+ )
+ expect(ast.helpers).toContain(PUSH_SCOPE_ID)
+ expect(ast.helpers).toContain(POP_SCOPE_ID)
+ expect(ast.hoists.length).toBe(2)
+ expect(code).toMatch(
+ [
+ `pushScopeId("test")`,
+ `const _hoisted_1 = createVNode("div", null, "hello")`,
+ `const _hoisted_2 = createVNode("div", null, "world")`,
+ `popScopeId()`
+ ].join('\n')
+ )
+ expect(code).toMatchSnapshot()
+ })
+})
diff --git a/packages/compiler-core/src/compile.ts b/packages/compiler-core/src/compile.ts
index 0ff53166..d6214137 100644
--- a/packages/compiler-core/src/compile.ts
+++ b/packages/compiler-core/src/compile.ts
@@ -17,28 +17,33 @@ import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
-// we name it `baseCompile` so that higher order compilers like @vue/compiler-dom
-// can export `compile` while re-exporting everything else.
+// we name it `baseCompile` so that higher order compilers like
+// @vue/compiler-dom can export `compile` while re-exporting everything else.
export function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
+ const onError = options.onError || defaultOnError
+ const isModuleMode = options.mode === 'module'
/* istanbul ignore if */
if (__BROWSER__) {
- const onError = options.onError || defaultOnError
if (options.prefixIdentifiers === true) {
onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
- } else if (options.mode === 'module') {
+ } else if (isModuleMode) {
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
}
}
- const ast = isString(template) ? parse(template, options) : template
-
const prefixIdentifiers =
- !__BROWSER__ &&
- (options.prefixIdentifiers === true || options.mode === 'module')
+ !__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
+ if (!prefixIdentifiers && options.cacheHandlers) {
+ onError(createCompilerError(ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED))
+ }
+ if (options.scopeId && !isModuleMode) {
+ onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
+ }
+ const ast = isString(template) ? parse(template, options) : template
transform(ast, {
...options,
prefixIdentifiers,
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index b507a416..71c19e26 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -86,6 +86,8 @@ export const enum ErrorCodes {
// generic errors
X_PREFIX_ID_NOT_SUPPORTED,
X_MODULE_MODE_NOT_SUPPORTED,
+ X_CACHE_HANDLER_NOT_SUPPORTED,
+ X_SCOPE_ID_NOT_SUPPORTED,
// Special value for higher-order compilers to pick up the last code
// to avoid collision of error codes. This should always be kept as the last
@@ -177,5 +179,7 @@ export const errorMessages: { [code: number]: string } = {
// generic errors
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
- [ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`
+ [ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`,
+ [ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED]: `"cacheHandlers" option is only supported when the "prefixIdentifiers" option is enabled.`,
+ [ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`
}
diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts
index b2d7e878..2e08cdae 100644
--- a/packages/compiler-core/src/options.ts
+++ b/packages/compiler-core/src/options.ts
@@ -28,17 +28,21 @@ export interface TransformOptions {
directiveTransforms?: { [name: string]: DirectiveTransform }
isBuiltInComponent?: (tag: string) => symbol | void
// Transform expressions like {{ foo }} to `_ctx.foo`.
- // Default: mode === 'module'
+ // - This is force-enabled in module mode, since modules are by default strict
+ // and cannot use `with`
+ // - Default: mode === 'module'
prefixIdentifiers?: boolean
// Hoist static VNodes and props objects to `_hoisted_x` constants
- // Default: false
+ // - Default: false
hoistStatic?: boolean
// Cache v-on handlers to avoid creating new inline functions on each render,
// also avoids the need for dynamically patching the handlers by wrapping it.
// e.g `@click="foo"` by default is compiled to `{ onClick: foo }`. With this
// option it's compiled to:
// `{ onClick: _cache[0] || (_cache[0] = e => _ctx.foo(e)) }`
- // Default: false
+ // - Requires "prefixIdentifiers" to be enabled because it relies on scope
+ // analysis to determine if a handler is safe to cache.
+ // - Default: false
cacheHandlers?: boolean
onError?: (error: CompilerError) => void
}
@@ -49,18 +53,20 @@ export interface CodegenOptions {
// - Function mode will generate a single `const { helpers... } = Vue`
// statement and return the render function. It is meant to be used with
// `new Function(code)()` to generate a render function at runtime.
- // Default: 'function'
+ // - Default: 'function'
mode?: 'module' | 'function'
// Prefix suitable identifiers with _ctx.
// If this option is false, the generated code will be wrapped in a
// `with (this) { ... }` block.
- // Default: false
+ // - This is force-enabled in module mode, since modules are by default strict
+ // and cannot use `with`
+ // - Default: mode === 'module'
prefixIdentifiers?: boolean
// Generate source map?
- // Default: false
+ // - Default: false
sourceMap?: boolean
// Filename for source map generation.
- // Default: `template.vue.html`
+ // - Default: `template.vue.html`
filename?: string
// SFC scoped styles ID
scopeId?: string | null
diff --git a/packages/runtime-core/__tests__/helpers/scopeId.spec.ts b/packages/runtime-core/__tests__/helpers/scopeId.spec.ts
new file mode 100644
index 00000000..08f7b1e7
--- /dev/null
+++ b/packages/runtime-core/__tests__/helpers/scopeId.spec.ts
@@ -0,0 +1,47 @@
+import { withScopeId } from '../../src/helpers/scopeId'
+import { h, render, nodeOps, serializeInner } from '@vue/runtime-test'
+
+describe('scopeId runtime support', () => {
+ const withParentId = withScopeId('parent')
+ const withChildId = withScopeId('child')
+
+ test('should attach scopeId', () => {
+ const App = {
+ __scopeId: 'parent',
+ render: withParentId(() => {
+ return h('div', [h('div')])
+ })
+ }
+ const root = nodeOps.createElement('div')
+ render(h(App), root)
+ expect(serializeInner(root)).toBe(``)
+ })
+
+ test('should work on slots', () => {
+ const Child = {
+ __scopeId: 'child',
+ render: withChildId(function(this: any) {
+ return h('div', this.$slots.default())
+ })
+ }
+ const App = {
+ __scopeId: 'parent',
+ render: withParentId(() => {
+ return h(
+ Child,
+ withParentId(() => {
+ return h('div')
+ })
+ )
+ })
+ }
+ const root = nodeOps.createElement('div')
+ render(h(App), root)
+ // slot content should have:
+ // - scopeId from parent
+ // - slotted scopeId (with `-s` postfix) from child (the tree owner)
+ expect(serializeInner(root)).toBe(
+ ``
+ )
+ })
+})
diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts
index aea2b61e..e40ec89e 100644
--- a/packages/vue/src/index.ts
+++ b/packages/vue/src/index.ts
@@ -36,7 +36,6 @@ function compileToFunction(
const { code } = compile(template, {
hoistStatic: true,
- cacheHandlers: true,
onError(err: CompilerError) {
if (__DEV__) {
const message = `Template compilation error: ${err.message}`