test: test scopeId support
This commit is contained in:
		
							parent
							
								
									3d16c0ea5a
								
							
						
					
					
						commit
						b689ca6e85
					
				@ -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\\"))
 | 
			
		||||
})"
 | 
			
		||||
`;
 | 
			
		||||
							
								
								
									
										91
									
								
								packages/compiler-core/__tests__/scopeId.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								packages/compiler-core/__tests__/scopeId.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -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(`<div/>`, {
 | 
			
		||||
      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(`<Child><div/></Child>`, {
 | 
			
		||||
      mode: 'module',
 | 
			
		||||
      scopeId: 'test'
 | 
			
		||||
    })
 | 
			
		||||
    expect(code).toMatch(`default: withId(() => [`)
 | 
			
		||||
    expect(code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('should wrap named slots', () => {
 | 
			
		||||
    const { code } = baseCompile(
 | 
			
		||||
      `<Child>
 | 
			
		||||
        <template #foo="{ msg }">{{ msg }}</template>
 | 
			
		||||
        <template #bar><div/></template>
 | 
			
		||||
      </Child>
 | 
			
		||||
      `,
 | 
			
		||||
      {
 | 
			
		||||
        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(
 | 
			
		||||
      `<Child>
 | 
			
		||||
        <template #foo v-if="ok"><div/></template>
 | 
			
		||||
        <template v-for="i in list" #[i]><div/></template>
 | 
			
		||||
      </Child>
 | 
			
		||||
      `,
 | 
			
		||||
      {
 | 
			
		||||
        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(
 | 
			
		||||
      `<div><div>hello</div><div>world</div></div>`,
 | 
			
		||||
      {
 | 
			
		||||
        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()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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.`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										47
									
								
								packages/runtime-core/__tests__/helpers/scopeId.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								packages/runtime-core/__tests__/helpers/scopeId.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -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(`<div parent><div parent></div></div>`)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  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(
 | 
			
		||||
      `<div child><div parent child-s></div></div>`
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@ -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}`
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user