-/* eslint-disable no-restricted-globals */
-
+const { builtinModules } = require('node:module')
const DOMGlobals = ['window', 'document']
const NodeGlobals = ['module', 'require']
const banConstEnum = {
selector: 'TSEnumDeclaration[const=true]',
message:
- 'Please use non-const enums. This project automatically inlines enums.'
+ 'Please use non-const enums. This project automatically inlines enums.',
}
/**
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
- sourceType: 'module'
+ sourceType: 'module',
},
- plugins: ['jest'],
+ plugins: ['jest', 'import', '@typescript-eslint'],
rules: {
'no-debugger': 'error',
// most of the codebase are expected to be env agnostic
// tsc compiles assignment spread into Object.assign() calls, but esbuild
// still generates verbose helpers, so spread assignment is also prohiboted
'ObjectExpression > SpreadElement',
- 'AwaitExpression'
- ]
+ 'AwaitExpression',
+ ],
+ 'sort-imports': ['error', { ignoreDeclarationSort: true }],
+
+ 'import/no-nodejs-modules': [
+ 'error',
+ { allow: builtinModules.map(mod => `node:${mod}`) },
+ ],
+ // This rule enforces the preference for using '@ts-expect-error' comments in TypeScript
+ // code to indicate intentional type errors, improving code clarity and maintainability.
+ '@typescript-eslint/prefer-ts-expect-error': 'error',
+ // Enforce the use of 'import type' for importing types
+ '@typescript-eslint/consistent-type-imports': [
+ 'error',
+ {
+ fixStyle: 'inline-type-imports',
+ disallowTypeAnnotations: false,
+ },
+ ],
+ // Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers
+ '@typescript-eslint/no-import-type-side-effects': 'error',
},
overrides: [
// tests, no restrictions (runs in Node / jest with jsdom)
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
'jest/no-disabled-tests': 'error',
- 'jest/no-focused-tests': 'error'
- }
+ 'jest/no-focused-tests': 'error',
+ },
},
// shared, may be used in any env
{
- files: ['packages/shared/**'],
+ files: ['packages/shared/**', '.eslintrc.cjs'],
rules: {
- 'no-restricted-globals': 'off'
- }
+ 'no-restricted-globals': 'off',
+ },
},
// Packages targeting DOM
{
files: ['packages/{vue,vue-compat,runtime-dom}/**'],
rules: {
- 'no-restricted-globals': ['error', ...NodeGlobals]
- }
+ 'no-restricted-globals': ['error', ...NodeGlobals],
+ },
},
// Packages targeting Node
{
files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'],
rules: {
'no-restricted-globals': ['error', ...DOMGlobals],
- 'no-restricted-syntax': ['error', banConstEnum]
- }
+ 'no-restricted-syntax': ['error', banConstEnum],
+ },
},
// Private package, browser only + no syntax restrictions
{
files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
rules: {
'no-restricted-globals': ['error', ...NodeGlobals],
- 'no-restricted-syntax': ['error', banConstEnum]
- }
+ 'no-restricted-syntax': ['error', banConstEnum],
+ },
},
// JavaScript files
{
files: ['*.js', '*.cjs'],
rules: {
// We only do `no-unused-vars` checks for js files, TS files are checked by TypeScript itself.
- 'no-unused-vars': ['error', { vars: 'all', args: 'none' }]
- }
+ 'no-unused-vars': ['error', { vars: 'all', args: 'none' }],
+ },
},
// Node scripts
{
- files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'],
+ files: [
+ 'scripts/**',
+ './*.{js,ts}',
+ 'packages/*/*.js',
+ 'packages/vue/*/*.js',
+ ],
rules: {
'no-restricted-globals': 'off',
- 'no-restricted-syntax': ['error', banConstEnum]
- }
- }
- ]
+ 'no-restricted-syntax': ['error', banConstEnum],
+ },
+ },
+ // Import nodejs modules in compiler-sfc
+ {
+ files: ['packages/compiler-sfc/src/**'],
+ rules: {
+ 'import/no-nodejs-modules': ['error', { allow: builtinModules }],
+ },
+ },
+ ],
}
packageRules: [
{
depTypeList: ['peerDependencies'],
- enabled: false
+ enabled: false,
},
{
groupName: 'test',
matchPackageNames: ['vitest', 'jsdom', 'puppeteer'],
- matchPackagePrefixes: ['@vitest']
+ matchPackagePrefixes: ['@vitest'],
},
{
groupName: 'playground',
matchFileNames: [
'packages/sfc-playground/package.json',
- 'packages/template-explorer/package.json'
- ]
+ 'packages/template-explorer/package.json',
+ ],
},
{
groupName: 'compiler',
matchPackageNames: ['magic-string'],
- matchPackagePrefixes: ['@babel', 'postcss']
+ matchPackagePrefixes: ['@babel', 'postcss'],
},
{
groupName: 'build',
matchPackageNames: ['vite', 'terser'],
- matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs']
+ matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
},
{
groupName: 'lint',
matchPackageNames: ['simple-git-hooks', 'lint-staged'],
- matchPackagePrefixes: ['@typescript-eslint', 'eslint', 'prettier']
- }
+ matchPackagePrefixes: ['@typescript-eslint', 'eslint', 'prettier'],
+ },
],
ignoreDeps: [
'vue',
'typescript',
// ESM only
- 'estree-walker'
- ]
+ 'estree-walker',
+ ],
}
dist
+*.md
+*.html
+pnpm-lock.yaml
-semi: false
-singleQuote: true
-printWidth: 80
-trailingComma: 'none'
-arrowParens: 'avoid'
+{
+ "semi": false,
+ "singleQuote": true,
+ "arrowParens": "avoid"
+}
"console": "integratedTerminal",
"sourceMaps": true,
"windows": {
- "program": "${workspaceFolder}/node_modules/jest/bin/jest",
+ "program": "${workspaceFolder}/node_modules/jest/bin/jest"
}
}
]
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
"size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler",
"check": "tsc --incremental --noEmit",
- "lint": "eslint --cache --ext .ts packages/*/{src,__tests__}/**.ts",
- "format": "prettier --write --cache \"**/*.[tj]s?(x)\"",
- "format-check": "prettier --check --cache \"**/*.[tj]s?(x)\"",
+ "lint": "eslint --cache --ext .js,.ts,.tsx .",
+ "format": "prettier --write --cache .",
+ "format-check": "prettier --check --cache .",
"test": "vitest",
"test-unit": "vitest -c vitest.unit.config.ts",
"test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts",
"@types/minimist": "^1.2.5",
"@types/node": "^20.10.5",
"@types/semver": "^7.5.5",
+ "@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.15.0",
"@vitest/coverage-istanbul": "^1.1.0",
"@vue/consolidate": "0.17.3",
"esbuild-plugin-polyfill-node": "^0.3.0",
"eslint": "^8.56.0",
"eslint-define-config": "^1.24.1",
+ "eslint-plugin-import": "npm:eslint-plugin-i@^2.29.1",
"eslint-plugin-jest": "^27.6.0",
"estree-walker": "^2.0.2",
"execa": "^8.0.1",
import {
- locStub,
- generate,
+ ConstantTypes,
+ type DirectiveArguments,
+ type ForCodegenNode,
+ type IfConditionalExpression,
NodeTypes,
- RootNode,
- createSimpleExpression,
- createObjectExpression,
- createObjectProperty,
+ type RootNode,
+ type VNodeCall,
createArrayExpression,
- createCompoundExpression,
- createInterpolation,
+ createAssignmentExpression,
+ createBlockStatement,
+ createCacheExpression,
createCallExpression,
+ createCompoundExpression,
createConditionalExpression,
- ForCodegenNode,
- createCacheExpression,
- createTemplateLiteral,
- createBlockStatement,
createIfStatement,
- createAssignmentExpression,
- IfConditionalExpression,
+ createInterpolation,
+ createObjectExpression,
+ createObjectProperty,
+ createSimpleExpression,
+ createTemplateLiteral,
createVNodeCall,
- VNodeCall,
- DirectiveArguments,
- ConstantTypes
+ generate,
+ locStub,
} from '../src'
import {
- CREATE_VNODE,
- TO_DISPLAY_STRING,
- RESOLVE_DIRECTIVE,
- helperNameMap,
- RESOLVE_COMPONENT,
CREATE_COMMENT,
+ CREATE_ELEMENT_VNODE,
+ CREATE_VNODE,
FRAGMENT,
RENDER_LIST,
- CREATE_ELEMENT_VNODE
+ RESOLVE_COMPONENT,
+ RESOLVE_DIRECTIVE,
+ TO_DISPLAY_STRING,
+ helperNameMap,
} from '../src/runtimeHelpers'
import { createElementWithCodegen, genFlagText } from './testUtils'
import { PatchFlags } from '@vue/shared'
temps: 0,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
- ...options
+ ...options,
}
}
describe('compiler: codegen', () => {
test('module mode preamble', () => {
const root = createRoot({
- helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE])
+ helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
})
const { code } = generate(root, { mode: 'module' })
expect(code).toMatch(
- `import { ${helperNameMap[CREATE_VNODE]} as _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`
+ `import { ${helperNameMap[CREATE_VNODE]} as _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`,
)
expect(code).toMatchSnapshot()
})
test('module mode preamble w/ optimizeImports: true', () => {
const root = createRoot({
- helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE])
+ helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
})
const { code } = generate(root, { mode: 'module', optimizeImports: true })
expect(code).toMatch(
- `import { ${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`
+ `import { ${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`,
)
expect(code).toMatch(
- `const _${helperNameMap[CREATE_VNODE]} = ${helperNameMap[CREATE_VNODE]}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${helperNameMap[RESOLVE_DIRECTIVE]}`
+ `const _${helperNameMap[CREATE_VNODE]} = ${helperNameMap[CREATE_VNODE]}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${helperNameMap[RESOLVE_DIRECTIVE]}`,
)
expect(code).toMatchSnapshot()
})
test('function mode preamble', () => {
const root = createRoot({
- helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE])
+ helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
})
const { code } = generate(root, { mode: 'function' })
expect(code).toMatch(`const _Vue = Vue`)
expect(code).toMatch(
- `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = _Vue`
+ `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = _Vue`,
)
expect(code).toMatchSnapshot()
})
test('function mode preamble w/ prefixIdentifiers: true', () => {
const root = createRoot({
- helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE])
+ helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
})
const { code } = generate(root, {
mode: 'function',
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(code).not.toMatch(`const _Vue = Vue`)
expect(code).toMatch(
- `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = Vue`
+ `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = Vue`,
)
expect(code).toMatchSnapshot()
})
const root = createRoot({
components: [`Foo`, `bar-baz`, `barbaz`, `Qux__self`],
directives: [`my_dir_0`, `my_dir_1`],
- temps: 3
+ temps: 3,
})
const { code } = generate(root, { mode: 'function' })
expect(code).toMatch(
- `const _component_Foo = _${helperNameMap[RESOLVE_COMPONENT]}("Foo")\n`
+ `const _component_Foo = _${helperNameMap[RESOLVE_COMPONENT]}("Foo")\n`,
)
expect(code).toMatch(
- `const _component_bar_baz = _${helperNameMap[RESOLVE_COMPONENT]}("bar-baz")\n`
+ `const _component_bar_baz = _${helperNameMap[RESOLVE_COMPONENT]}("bar-baz")\n`,
)
expect(code).toMatch(
- `const _component_barbaz = _${helperNameMap[RESOLVE_COMPONENT]}("barbaz")\n`
+ `const _component_barbaz = _${helperNameMap[RESOLVE_COMPONENT]}("barbaz")\n`,
)
// implicit self reference from SFC filename
expect(code).toMatch(
- `const _component_Qux = _${helperNameMap[RESOLVE_COMPONENT]}("Qux", true)\n`
+ `const _component_Qux = _${helperNameMap[RESOLVE_COMPONENT]}("Qux", true)\n`,
)
expect(code).toMatch(
- `const _directive_my_dir_0 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_0")\n`
+ `const _directive_my_dir_0 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_0")\n`,
)
expect(code).toMatch(
- `const _directive_my_dir_1 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_1")\n`
+ `const _directive_my_dir_1 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_1")\n`,
)
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
expect(code).toMatchSnapshot()
[
createObjectProperty(
createSimpleExpression(`id`, true, locStub),
- createSimpleExpression(`foo`, true, locStub)
- )
+ createSimpleExpression(`foo`, true, locStub),
+ ),
],
- locStub
- )
- ]
+ locStub,
+ ),
+ ],
})
const { code } = generate(root)
expect(code).toMatch(`const _hoisted_1 = hello`)
test('temps', () => {
const root = createRoot({
- temps: 3
+ temps: 3,
})
const { code } = generate(root)
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
codegenNode: {
type: NodeTypes.TEXT,
content: 'hello',
- loc: locStub
- }
- })
+ loc: locStub,
+ },
+ }),
)
expect(code).toMatch(`return "hello"`)
expect(code).toMatchSnapshot()
test('interpolation', () => {
const { code } = generate(
createRoot({
- codegenNode: createInterpolation(`hello`, locStub)
- })
+ codegenNode: createInterpolation(`hello`, locStub),
+ }),
)
expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`)
expect(code).toMatchSnapshot()
codegenNode: {
type: NodeTypes.COMMENT,
content: 'foo',
- loc: locStub
- }
- })
+ loc: locStub,
+ },
+ }),
)
expect(code).toMatch(`return _${helperNameMap[CREATE_COMMENT]}("foo")`)
expect(code).toMatchSnapshot()
{
type: NodeTypes.INTERPOLATION,
loc: locStub,
- content: createSimpleExpression(`bar`, false, locStub)
+ content: createSimpleExpression(`bar`, false, locStub),
},
// nested compound
- createCompoundExpression([` + `, `nested`])
- ])
- })
+ createCompoundExpression([` + `, `nested`]),
+ ]),
+ }),
)
expect(code).toMatch(
- `return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar) + nested`
+ `return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar) + nested`,
)
expect(code).toMatchSnapshot()
})
codegenNode: createConditionalExpression(
createSimpleExpression('foo', false),
createSimpleExpression('bar', false),
- createSimpleExpression('baz', false)
- ) as IfConditionalExpression
- }
- })
+ createSimpleExpression('baz', false),
+ ) as IfConditionalExpression,
+ },
+ }),
)
expect(code).toMatch(/return foo\s+\? bar\s+: baz/)
expect(code).toMatchSnapshot()
patchFlag: '1',
dynamicProps: undefined,
directives: undefined,
- loc: locStub
- } as ForCodegenNode
- }
- })
+ loc: locStub,
+ } as ForCodegenNode,
+ },
+ }),
)
expect(code).toMatch(`openBlock(true)`)
expect(code).toMatchSnapshot()
'1 + 2',
false,
locStub,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
),
valueAlias: undefined,
keyAlias: undefined,
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
dynamicProps: undefined,
directives: undefined,
- loc: locStub
- } as ForCodegenNode
- }
- })
+ loc: locStub,
+ } as ForCodegenNode,
+ },
+ }),
)
expect(code).toMatch(`openBlock()`)
expect(code).toMatchSnapshot()
[
createObjectProperty(
createSimpleExpression(`id`, true, locStub),
- createSimpleExpression(`foo`, true, locStub)
+ createSimpleExpression(`foo`, true, locStub),
),
createObjectProperty(
createSimpleExpression(`prop`, false, locStub),
- createSimpleExpression(`bar`, false, locStub)
+ createSimpleExpression(`bar`, false, locStub),
),
// compound expression as computed key
createObjectProperty(
loc: locStub,
children: [
`foo + `,
- createSimpleExpression(`bar`, false, locStub)
- ]
+ createSimpleExpression(`bar`, false, locStub),
+ ],
},
- createSimpleExpression(`bar`, false, locStub)
- )
+ createSimpleExpression(`bar`, false, locStub),
+ ),
],
- locStub
+ locStub,
),
// ChildNode[]
[
createObjectProperty(
// should quote the key!
createSimpleExpression(`some-key`, true, locStub),
- createSimpleExpression(`foo`, true, locStub)
- )
+ createSimpleExpression(`foo`, true, locStub),
+ ),
],
- locStub
- )
- )
+ locStub,
+ ),
+ ),
],
// flag
- PatchFlags.FULL_PROPS + ''
- )
- })
+ PatchFlags.FULL_PROPS + '',
+ ),
+ }),
)
expect(code).toMatch(`
return _${helperNameMap[CREATE_ELEMENT_VNODE]}("div", {
createRoot({
codegenNode: createArrayExpression([
createSimpleExpression(`foo`, false),
- createCallExpression(`bar`, [`baz`])
- ])
- })
+ createCallExpression(`bar`, [`baz`]),
+ ]),
+ }),
)
expect(code).toMatch(`return [
foo,
createConditionalExpression(
createSimpleExpression(`orNot`, false),
createCallExpression(`bar`),
- createCallExpression(`baz`)
- )
- )
- })
+ createCallExpression(`baz`),
+ ),
+ ),
+ }),
)
expect(code).toMatch(
`return ok
? foo()
: orNot
? bar()
- : baz()`
+ : baz()`,
)
expect(code).toMatchSnapshot()
})
cached: 1,
codegenNode: createCacheExpression(
1,
- createSimpleExpression(`foo`, false)
- )
+ createSimpleExpression(`foo`, false),
+ ),
}),
{
mode: 'module',
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
expect(code).toMatchSnapshot()
codegenNode: createCacheExpression(
1,
createSimpleExpression(`foo`, false),
- true
- )
+ true,
+ ),
}),
{
mode: 'module',
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(code).toMatch(
`
_setBlockTracking(1),
_cache[1]
)
- `.trim()
+ `.trim(),
)
expect(code).toMatchSnapshot()
})
createTemplateLiteral([
`foo`,
createCallExpression(`_renderAttr`, ['id', 'foo']),
- `bar`
- ])
- ])
+ `bar`,
+ ]),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
codegenNode: createBlockStatement([
createIfStatement(
createSimpleExpression('foo', false),
- createBlockStatement([createCallExpression(`ok`)])
- )
- ])
+ createBlockStatement([createCallExpression(`ok`)]),
+ ),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
createIfStatement(
createSimpleExpression('foo', false),
createBlockStatement([createCallExpression(`foo`)]),
- createBlockStatement([createCallExpression('bar')])
- )
- ])
+ createBlockStatement([createCallExpression('bar')]),
+ ),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
createBlockStatement([createCallExpression(`foo`)]),
createIfStatement(
createSimpleExpression('bar', false),
- createBlockStatement([createCallExpression(`bar`)])
- )
- )
- ])
+ createBlockStatement([createCallExpression(`bar`)]),
+ ),
+ ),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
createIfStatement(
createSimpleExpression('bar', false),
createBlockStatement([createCallExpression(`bar`)]),
- createBlockStatement([createCallExpression('baz')])
- )
- )
- ])
+ createBlockStatement([createCallExpression('baz')]),
+ ),
+ ),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
createRoot({
codegenNode: createAssignmentExpression(
createSimpleExpression(`foo`, false),
- createSimpleExpression(`bar`, false)
- )
- })
+ createSimpleExpression(`bar`, false),
+ ),
+ }),
)
expect(code).toMatchInlineSnapshot(`
"
function genCode(node: VNodeCall) {
return generate(
createRoot({
- codegenNode: node
- })
+ codegenNode: node,
+ }),
).code.match(/with \(_ctx\) \{\s+([^]+)\s+\}\s+\}$/)![1]
}
const mockProps = createObjectExpression([
- createObjectProperty(`foo`, createSimpleExpression(`bar`, true))
+ createObjectProperty(`foo`, createSimpleExpression(`bar`, true)),
])
const mockChildren = createCompoundExpression(['children'])
const mockDirs = createArrayExpression([
- createArrayExpression([`foo`, createSimpleExpression(`bar`, false)])
+ createArrayExpression([`foo`, createSimpleExpression(`bar`, false)]),
]) as DirectiveArguments
test('tag only', () => {
undefined,
undefined,
undefined,
- true
- )
- )
+ true,
+ ),
+ ),
).toMatchInlineSnapshot(`
"return (_openBlock(), _createElementBlock("div", { foo: "bar" }, children))
"
undefined,
undefined,
true,
- true
- )
- )
+ true,
+ ),
+ ),
).toMatchInlineSnapshot(`
"return (_openBlock(true), _createElementBlock("div", { foo: "bar" }, children))
"
mockChildren,
undefined,
undefined,
- mockDirs
- )
- )
+ mockDirs,
+ ),
+ ),
).toMatchInlineSnapshot(`
"return _withDirectives(_createElementVNode("div", { foo: "bar" }, children), [
[foo, bar]
undefined,
undefined,
mockDirs,
- true
- )
- )
+ true,
+ ),
+ ),
).toMatchInlineSnapshot(`
"return _withDirectives((_openBlock(), _createElementBlock("div", { foo: "bar" }, children)), [
[foo, bar]
import { baseCompile as compile } from '../src'
-import { SourceMapConsumer, RawSourceMap } from 'source-map-js'
+import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
describe('compiler: integration tests', () => {
const source = `
function getPositionInCode(
code: string,
token: string,
- expectName: string | boolean = false
+ expectName: string | boolean = false,
): Pos {
const generatedOffset = code.indexOf(token)
let line = 1
column:
lastNewLinePos === -1
? generatedOffset
- : generatedOffset - lastNewLinePos - 1
+ : generatedOffset - lastNewLinePos - 1,
}
if (expectName) {
res.name = typeof expectName === 'string' ? expectName : token
test('function mode', () => {
const { code, map } = compile(source, {
sourceMap: true,
- filename: `foo.vue`
+ filename: `foo.vue`,
})
expect(code).toMatchSnapshot()
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, `id`))
+ consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
+ consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `class:`))
+ consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`))
// without prefixIdentifiers: true, identifiers inside compound expressions
// are mapped to closest parent expression.
expect(
- consumer.originalPositionFor(getPositionInCode(code, `baz`))
+ consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `bar`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `world`))
+ consumer.originalPositionFor(getPositionInCode(code, `world`)),
).toMatchObject(getPositionInCode(source, `world`))
// without prefixIdentifiers: true, identifiers inside compound expressions
// are mapped to closest parent expression.
expect(
- consumer.originalPositionFor(getPositionInCode(code, `burn()`))
+ consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `world`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `list`))
+ consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value`))
+ consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `index`))
+ consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value + index`))
+ consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`))
})
const { code, map } = compile(source, {
sourceMap: true,
filename: `foo.vue`,
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(code).toMatchSnapshot()
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, `id`))
+ consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
+ consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `class:`))
+ consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `baz`))
+ consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `baz`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `world`, true))
+ consumer.originalPositionFor(getPositionInCode(code, `world`, true)),
).toMatchObject(getPositionInCode(source, `world`, `world`))
expect(
consumer.originalPositionFor(
- getPositionInCode(code, `_ctx.world`, `world`)
- )
+ getPositionInCode(code, `_ctx.world`, `world`),
+ ),
).toMatchObject(getPositionInCode(source, `world`, `world`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `burn()`))
+ consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `burn()`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `list`))
+ consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`, `list`))
+ consumer.originalPositionFor(
+ getPositionInCode(code, `_ctx.list`, `list`),
+ ),
).toMatchObject(getPositionInCode(source, `list`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value`))
+ consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `index`))
+ consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value + index`))
+ consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`))
})
const { code, map } = compile(source, {
mode: 'module',
sourceMap: true,
- filename: `foo.vue`
+ filename: `foo.vue`,
})
expect(code).toMatchSnapshot()
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, `id`))
+ consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
+ consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `class:`))
+ consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `baz`))
+ consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `baz`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `world`, true))
+ consumer.originalPositionFor(getPositionInCode(code, `world`, true)),
).toMatchObject(getPositionInCode(source, `world`, `world`))
expect(
consumer.originalPositionFor(
- getPositionInCode(code, `_ctx.world`, `world`)
- )
+ getPositionInCode(code, `_ctx.world`, `world`),
+ ),
).toMatchObject(getPositionInCode(source, `world`, `world`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `burn()`))
+ consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `burn()`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `list`))
+ consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`, `list`))
+ consumer.originalPositionFor(
+ getPositionInCode(code, `_ctx.list`, `list`),
+ ),
).toMatchObject(getPositionInCode(source, `list`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value`))
+ consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `index`))
+ consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value + index`))
+ consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`))
})
})
-import { ParserOptions } from '../src/options'
+import type { ParserOptions } from '../src/options'
import { ErrorCodes } from '../src/errors'
import {
- CommentNode,
- ElementNode,
+ type CommentNode,
+ ConstantTypes,
+ type DirectiveNode,
+ type ElementNode,
ElementTypes,
+ type InterpolationNode,
Namespaces,
NodeTypes,
- Position,
- TextNode,
- InterpolationNode,
- ConstantTypes,
- DirectiveNode
+ type Position,
+ type TextNode,
} from '../src/ast'
import { baseParse } from '../src/parser'
-import { Program } from '@babel/types'
+import type { Program } from '@babel/types'
/* eslint jest/no-disabled-tests: "off" */
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
- source: 'some text'
- }
+ source: 'some text',
+ },
})
})
code: ErrorCodes.X_INVALID_END_TAG,
loc: {
start: { column: 10, line: 1, offset: 9 },
- end: { column: 10, line: 1, offset: 9 }
- }
- }
- ]
+ end: { column: 10, line: 1, offset: 9 },
+ },
+ },
+ ],
])
expect(text).toStrictEqual({
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
- source: 'some text'
- }
+ source: 'some text',
+ },
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: 'some '
- }
+ source: 'some ',
+ },
})
expect(text2).toStrictEqual({
type: NodeTypes.TEXT,
loc: {
start: { offset: 20, line: 1, column: 21 },
end: { offset: 25, line: 1, column: 26 },
- source: ' text'
- }
+ source: ' text',
+ },
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: 'some '
- }
+ source: 'some ',
+ },
})
expect(text2).toStrictEqual({
type: NodeTypes.TEXT,
loc: {
start: { offset: 21, line: 1, column: 22 },
end: { offset: 26, line: 1, column: 27 },
- source: ' text'
- }
+ source: ' text',
+ },
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: 'some '
- }
+ source: 'some ',
+ },
})
expect(text2).toStrictEqual({
type: NodeTypes.TEXT,
loc: {
start: { offset: 32, line: 1, column: 33 },
end: { offset: 37, line: 1, column: 38 },
- source: ' text'
- }
+ source: ' text',
+ },
})
})
if (err.code !== ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME) {
throw err
}
- }
+ },
})
const text = ast.children[0] as TextNode
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: 'a < b'
- }
+ source: 'a < b',
+ },
})
})
if (error.code !== ErrorCodes.X_MISSING_INTERPOLATION_END) {
throw error
}
- }
+ },
})
const text = ast.children[0] as TextNode
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 6, line: 1, column: 7 },
- source: 'a {{ b'
- }
+ source: 'a {{ b',
+ },
})
})
})
loc: {
start: { offset: 2, line: 1, column: 3 },
end: { offset: 9, line: 1, column: 10 },
- source: 'message'
- }
+ source: 'message',
+ },
},
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 11, line: 1, column: 12 },
- source: '{{message}}'
- }
+ source: '{{message}}',
+ },
})
})
loc: {
start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 },
- source: 'a<b'
- }
+ source: 'a<b',
+ },
},
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
- source: '{{ a<b }}'
- }
+ source: '{{ a<b }}',
+ },
})
})
loc: {
start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 },
- source: 'a<b'
- }
+ source: 'a<b',
+ },
},
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
- source: '{{ a<b }}'
- }
+ source: '{{ a<b }}',
+ },
})
expect(interpolation2).toStrictEqual({
loc: {
start: { offset: 12, line: 1, column: 13 },
end: { offset: 15, line: 1, column: 16 },
- source: 'c>d'
- }
+ source: 'c>d',
+ },
},
loc: {
start: { offset: 9, line: 1, column: 10 },
end: { offset: 18, line: 1, column: 19 },
- source: '{{ c>d }}'
- }
+ source: '{{ c>d }}',
+ },
})
})
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 16, line: 1, column: 17 },
- source: '"</div>"'
- }
+ source: '"</div>"',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 19, line: 1, column: 20 },
- source: '{{ "</div>" }}'
- }
+ source: '{{ "</div>" }}',
+ },
})
})
test('custom delimiters', () => {
const ast = baseParse('<p>{msg}</p>', {
- delimiters: ['{', '}']
+ delimiters: ['{', '}'],
})
const element = ast.children[0] as ElementNode
const interpolation = element.children[0] as InterpolationNode
loc: {
start: { offset: 4, line: 1, column: 5 },
end: { offset: 7, line: 1, column: 8 },
- source: 'msg'
- }
+ source: 'msg',
+ },
},
loc: {
start: { offset: 3, line: 1, column: 4 },
end: { offset: 8, line: 1, column: 9 },
- source: '{msg}'
- }
+ source: '{msg}',
+ },
})
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 7, line: 1, column: 8 },
- source: '<!---->'
- }
+ source: '<!---->',
+ },
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 10, line: 1, column: 11 },
- source: '<!--abc-->'
- }
+ source: '<!--abc-->',
+ },
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 10, line: 1, column: 11 },
- source: '<!--abc-->'
- }
+ source: '<!--abc-->',
+ },
})
expect(comment2).toStrictEqual({
type: NodeTypes.COMMENT,
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 20, line: 1, column: 21 },
- source: '<!--def-->'
- }
+ source: '<!--def-->',
+ },
})
})
const rawText = `<p/><!-- foo --><p/>`
const astWithComments = baseParse(`<pre>${rawText}</pre>`, {
- comments: true
+ comments: true,
})
expect(
- (astWithComments.children[0] as ElementNode).children
+ (astWithComments.children[0] as ElementNode).children,
).toMatchObject([
{
type: NodeTypes.ELEMENT,
- tag: 'p'
+ tag: 'p',
},
{
- type: NodeTypes.COMMENT
+ type: NodeTypes.COMMENT,
},
{
type: NodeTypes.ELEMENT,
- tag: 'p'
- }
+ tag: 'p',
+ },
])
const astWithoutComments = baseParse(`<pre>${rawText}</pre>`, {
- comments: false
+ comments: false,
})
expect(
- (astWithoutComments.children[0] as ElementNode).children
+ (astWithoutComments.children[0] as ElementNode).children,
).toMatchObject([
{
type: NodeTypes.ELEMENT,
- tag: 'p'
+ tag: 'p',
},
{
type: NodeTypes.ELEMENT,
- tag: 'p'
- }
+ tag: 'p',
+ },
])
})
})
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: 'hello'
- }
- }
+ source: 'hello',
+ },
+ },
],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 16, line: 1, column: 17 },
- source: '<div>hello</div>'
- }
+ source: '<div>hello</div>',
+ },
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 11, line: 1, column: 12 },
- source: '<div></div>'
- }
+ source: '<div></div>',
+ },
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 6, line: 1, column: 7 },
- source: '<div/>'
- }
+ source: '<div/>',
+ },
})
})
test('void element', () => {
const ast = baseParse('<img>after', {
- isVoidTag: tag => tag === 'img'
+ isVoidTag: tag => tag === 'img',
})
const element = ast.children[0] as ElementNode
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: '<img>'
- }
+ source: '<img>',
+ },
})
})
test('self-closing void element', () => {
const ast = baseParse('<img/>after', {
- isVoidTag: tag => tag === 'img'
+ isVoidTag: tag => tag === 'img',
})
const element = ast.children[0] as ElementNode
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 6, line: 1, column: 7 },
- source: '<img/>'
- }
+ source: '<img/>',
+ },
})
})
const element = ast.children[0]
expect(element).toMatchObject({
type: NodeTypes.ELEMENT,
- tagType: ElementTypes.TEMPLATE
+ tagType: ElementTypes.TEMPLATE,
})
})
const element = ast.children[0]
expect(element).toMatchObject({
type: NodeTypes.ELEMENT,
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
})
test('native element with `isNativeTag`', () => {
const ast = baseParse('<div></div><comp></comp><Comp></Comp>', {
- isNativeTag: tag => tag === 'div'
+ isNativeTag: tag => tag === 'div',
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
const ast = baseParse(
`<div></div><div is="vue:foo"></div><Comp></Comp>`,
{
- isNativeTag: tag => tag === 'div'
- }
+ isNativeTag: tag => tag === 'div',
+ },
)
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
test('custom element', () => {
const ast = baseParse('<div></div><comp></comp>', {
isNativeTag: tag => tag === 'div',
- isCustomElement: tag => tag === 'comp'
+ isCustomElement: tag => tag === 'comp',
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
})
test('built-in component', () => {
const ast = baseParse('<div></div><comp></comp>', {
- isBuiltInComponent: tag => (tag === 'comp' ? Symbol() : void 0)
+ isBuiltInComponent: tag => (tag === 'comp' ? Symbol() : void 0),
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'slot',
- tagType: ElementTypes.SLOT
+ tagType: ElementTypes.SLOT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
nameLoc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
+ source: 'id',
},
value: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
- }
- }
+ source: 'id',
+ },
+ },
],
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 14, line: 1, column: 15 },
- source: '<div id></div>'
- }
+ source: '<div id></div>',
+ },
})
})
nameLoc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
+ source: 'id',
},
value: {
type: NodeTypes.TEXT,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 10, line: 1, column: 11 },
- source: '""'
- }
+ source: '""',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: 'id=""'
- }
- }
+ source: 'id=""',
+ },
+ },
],
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 17, line: 1, column: 18 },
- source: '<div id=""></div>'
- }
+ source: '<div id=""></div>',
+ },
})
})
nameLoc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
+ source: 'id',
},
value: {
type: NodeTypes.TEXT,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 10, line: 1, column: 11 },
- source: "''"
- }
+ source: "''",
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: "id=''"
- }
- }
+ source: "id=''",
+ },
+ },
],
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 17, line: 1, column: 18 },
- source: "<div id=''></div>"
- }
+ source: "<div id=''></div>",
+ },
})
})
nameLoc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
+ source: 'id',
},
value: {
type: NodeTypes.TEXT,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 12, line: 1, column: 13 },
- source: '">\'"'
- }
+ source: '">\'"',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 12, line: 1, column: 13 },
- source: 'id=">\'"'
- }
- }
+ source: 'id=">\'"',
+ },
+ },
],
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 19, line: 1, column: 20 },
- source: '<div id=">\'"></div>'
- }
+ source: '<div id=">\'"></div>',
+ },
})
})
nameLoc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
+ source: 'id',
},
value: {
type: NodeTypes.TEXT,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 12, line: 1, column: 13 },
- source: "'>\"'"
- }
+ source: "'>\"'",
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 12, line: 1, column: 13 },
- source: "id='>\"'"
- }
- }
+ source: "id='>\"'",
+ },
+ },
],
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 19, line: 1, column: 20 },
- source: "<div id='>\"'></div>"
- }
+ source: "<div id='>\"'></div>",
+ },
})
})
nameLoc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
+ source: 'id',
},
value: {
type: NodeTypes.TEXT,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 10, line: 1, column: 11 },
- source: 'a/'
- }
+ source: 'a/',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: 'id=a/'
- }
- }
+ source: 'id=a/',
+ },
+ },
],
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 17, line: 1, column: 18 },
- source: '<div id=a/></div>'
- }
+ source: '<div id=a/></div>',
+ },
})
})
test('attribute value with >', () => {
const ast = baseParse(
'<script setup lang="ts" generic="T extends Record<string,string>"></script>',
- { parseMode: 'sfc' }
+ { parseMode: 'sfc' },
)
const element = ast.children[0] as ElementNode
expect(element).toMatchObject({
children: [],
innerLoc: {
start: { column: 67, line: 1, offset: 66 },
- end: { column: 67, line: 1, offset: 66 }
+ end: { column: 67, line: 1, offset: 66 },
},
props: [
{
loc: {
source: 'setup',
end: { column: 14, line: 1, offset: 13 },
- start: { column: 9, line: 1, offset: 8 }
+ start: { column: 9, line: 1, offset: 8 },
},
name: 'setup',
nameLoc: {
source: 'setup',
end: { column: 14, line: 1, offset: 13 },
- start: { column: 9, line: 1, offset: 8 }
+ start: { column: 9, line: 1, offset: 8 },
},
type: NodeTypes.ATTRIBUTE,
- value: undefined
+ value: undefined,
},
{
loc: {
source: 'lang="ts"',
end: { column: 24, line: 1, offset: 23 },
- start: { column: 15, line: 1, offset: 14 }
+ start: { column: 15, line: 1, offset: 14 },
},
name: 'lang',
nameLoc: {
source: 'lang',
end: { column: 19, line: 1, offset: 18 },
- start: { column: 15, line: 1, offset: 14 }
+ start: { column: 15, line: 1, offset: 14 },
},
type: NodeTypes.ATTRIBUTE,
value: {
loc: {
source: '"ts"',
end: { column: 24, line: 1, offset: 23 },
- start: { column: 20, line: 1, offset: 19 }
+ start: { column: 20, line: 1, offset: 19 },
},
- type: NodeTypes.TEXT
- }
+ type: NodeTypes.TEXT,
+ },
},
{
loc: {
source: 'generic="T extends Record<string,string>"',
end: { column: 66, line: 1, offset: 65 },
- start: { column: 25, line: 1, offset: 24 }
+ start: { column: 25, line: 1, offset: 24 },
},
name: 'generic',
nameLoc: {
source: 'generic',
end: { column: 32, line: 1, offset: 31 },
- start: { column: 25, line: 1, offset: 24 }
+ start: { column: 25, line: 1, offset: 24 },
},
type: NodeTypes.ATTRIBUTE,
value: {
loc: {
source: '"T extends Record<string,string>"',
end: { column: 66, line: 1, offset: 65 },
- start: { column: 33, line: 1, offset: 32 }
+ start: { column: 33, line: 1, offset: 32 },
},
- type: NodeTypes.TEXT
- }
- }
- ]
+ type: NodeTypes.TEXT,
+ },
+ },
+ ],
})
})
nameLoc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
+ source: 'id',
},
value: {
type: NodeTypes.TEXT,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
- source: 'a'
- }
+ source: 'a',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: 'id=a'
- }
+ source: 'id=a',
+ },
},
{
type: NodeTypes.ATTRIBUTE,
nameLoc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 15, line: 1, column: 16 },
- source: 'class'
+ source: 'class',
},
value: {
type: NodeTypes.TEXT,
loc: {
start: { offset: 16, line: 1, column: 17 },
end: { offset: 19, line: 1, column: 20 },
- source: '"c"'
- }
+ source: '"c"',
+ },
},
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 19, line: 1, column: 20 },
- source: 'class="c"'
- }
+ source: 'class="c"',
+ },
},
{
type: NodeTypes.ATTRIBUTE,
nameLoc: {
start: { offset: 20, line: 1, column: 21 },
end: { offset: 25, line: 1, column: 26 },
- source: 'inert'
+ source: 'inert',
},
value: undefined,
loc: {
start: { offset: 20, line: 1, column: 21 },
end: { offset: 25, line: 1, column: 26 },
- source: 'inert'
- }
+ source: 'inert',
+ },
},
{
type: NodeTypes.ATTRIBUTE,
nameLoc: {
start: { offset: 26, line: 1, column: 27 },
end: { offset: 31, line: 1, column: 32 },
- source: 'style'
+ source: 'style',
},
value: {
type: NodeTypes.TEXT,
loc: {
start: { offset: 32, line: 1, column: 33 },
end: { offset: 34, line: 1, column: 35 },
- source: "''"
- }
+ source: "''",
+ },
},
loc: {
start: { offset: 26, line: 1, column: 27 },
end: { offset: 34, line: 1, column: 35 },
- source: "style=''"
- }
- }
+ source: "style=''",
+ },
+ },
],
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 41, line: 1, column: 42 },
- source: '<div id=a class="c" inert style=\'\'></div>'
- }
+ source: '<div id=a class="c" inert style=\'\'></div>',
+ },
})
})
loc: {
start: { column: 1, line: 1, offset: 0 },
end: { column: 10, line: 3, offset: 29 },
- source: '<div class=" \n\t c \t\n "></div>'
+ source: '<div class=" \n\t c \t\n "></div>',
},
ns: Namespaces.HTML,
props: [
nameLoc: {
start: { column: 6, line: 1, offset: 5 },
end: { column: 11, line: 1, offset: 10 },
- source: 'class'
+ source: 'class',
},
type: NodeTypes.ATTRIBUTE,
value: {
loc: {
start: { column: 12, line: 1, offset: 11 },
end: { column: 3, line: 3, offset: 22 },
- source: '" \n\t c \t\n "'
+ source: '" \n\t c \t\n "',
},
- type: NodeTypes.TEXT
+ type: NodeTypes.TEXT,
},
loc: {
start: { column: 6, line: 1, offset: 5 },
end: { column: 3, line: 3, offset: 22 },
- source: 'class=" \n\t c \t\n "'
- }
- }
+ source: 'class=" \n\t c \t\n "',
+ },
+ },
],
tag: 'div',
tagType: ElementTypes.ELEMENT,
- type: NodeTypes.ELEMENT
+ type: NodeTypes.ELEMENT,
})
})
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: 'v-if'
- }
+ source: 'v-if',
+ },
})
})
loc: {
start: { offset: 11, line: 1, column: 12 },
end: { offset: 12, line: 1, column: 13 },
- source: 'a'
- }
+ source: 'a',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 13, line: 1, column: 14 },
- source: 'v-if="a"'
- }
+ source: 'v-if="a"',
+ },
})
})
loc: {
start: { column: 11, line: 1, offset: 10 },
end: { column: 16, line: 1, offset: 15 },
- source: 'click'
- }
+ source: 'click',
+ },
},
modifiers: [],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 15, line: 1, column: 16 },
- source: 'v-on:click'
- }
+ source: 'v-on:click',
+ },
})
})
expect(directive.arg).toMatchObject({
loc: {
start: { offset: 12, line: 1, column: 13 },
- end: { offset: 16, line: 1, column: 17 }
- }
+ end: { offset: 16, line: 1, column: 17 },
+ },
})
})
content: 'item.item',
loc: {
start: { offset: 6, line: 1, column: 7 },
- end: { offset: 15, line: 1, column: 16 }
- }
+ end: { offset: 15, line: 1, column: 16 },
+ },
})
})
loc: {
start: { column: 11, line: 1, offset: 10 },
end: { column: 18, line: 1, offset: 17 },
- source: '[event]'
- }
+ source: '[event]',
+ },
},
modifiers: [],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 17, line: 1, column: 18 },
- source: 'v-on:[event]'
- }
+ source: 'v-on:[event]',
+ },
})
})
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 15, line: 1, column: 16 },
- source: 'v-on.enter'
- }
+ source: 'v-on.enter',
+ },
})
})
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 21, line: 1, column: 22 },
- source: 'v-on.enter.exact'
- }
+ source: 'v-on.enter.exact',
+ },
})
})
loc: {
start: { column: 11, line: 1, offset: 10 },
end: { column: 16, line: 1, offset: 15 },
- source: 'click'
- }
+ source: 'click',
+ },
},
modifiers: ['enter', 'exact'],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 27, line: 1, column: 28 },
- source: 'v-on:click.enter.exact'
- }
+ source: 'v-on:click.enter.exact',
+ },
})
})
loc: {
start: { column: 11, line: 1, offset: 10 },
end: { column: 16, line: 1, offset: 15 },
- source: '[a.b]'
- }
+ source: '[a.b]',
+ },
},
modifiers: ['camel'],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 21, line: 1, column: 22 },
- source: 'v-on:[a.b].camel'
- }
+ source: 'v-on:[a.b].camel',
+ },
})
})
const ast = baseParse('<div v-/>', {
onError: err => {
errorCode = err.code as number
- }
+ },
})
const directive = (ast.children[0] as ElementNode).props[0]
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'v-'
+ source: 'v-',
},
nameLoc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'v-'
- }
+ source: 'v-',
+ },
})
})
loc: {
start: { column: 7, line: 1, offset: 6 },
end: { column: 8, line: 1, offset: 7 },
- source: 'a'
- }
+ source: 'a',
+ },
},
modifiers: [],
exp: {
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: ':a=b'
- }
+ source: ':a=b',
+ },
})
})
loc: {
start: { column: 7, line: 1, offset: 6 },
end: { column: 8, line: 1, offset: 7 },
- source: 'a'
- }
+ source: 'a',
+ },
},
modifiers: ['prop'],
exp: {
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: '.a=b'
- }
+ source: '.a=b',
+ },
})
})
loc: {
start: { column: 7, line: 1, offset: 6 },
end: { column: 8, line: 1, offset: 7 },
- source: 'a'
- }
+ source: 'a',
+ },
},
modifiers: ['sync'],
exp: {
loc: {
start: { offset: 13, line: 1, column: 14 },
end: { offset: 14, line: 1, column: 15 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 14, line: 1, column: 15 },
- source: ':a.sync=b'
- }
+ source: ':a.sync=b',
+ },
})
})
loc: {
start: { column: 7, line: 1, offset: 6 },
end: { column: 8, line: 1, offset: 7 },
- source: 'a'
- }
+ source: 'a',
+ },
},
modifiers: [],
exp: {
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: '@a=b'
- }
+ source: '@a=b',
+ },
})
})
loc: {
start: { column: 7, line: 1, offset: 6 },
end: { column: 8, line: 1, offset: 7 },
- source: 'a'
- }
+ source: 'a',
+ },
},
modifiers: ['enter'],
exp: {
loc: {
start: { offset: 14, line: 1, column: 15 },
end: { offset: 15, line: 1, column: 16 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 15, line: 1, column: 16 },
- source: '@a.enter=b'
- }
+ source: '@a.enter=b',
+ },
})
})
loc: {
start: { column: 8, line: 1, offset: 7 },
end: { column: 9, line: 1, offset: 8 },
- source: 'a'
- }
+ source: 'a',
+ },
},
modifiers: [],
exp: {
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 15, line: 1, column: 16 },
- source: '{ b }'
- }
+ source: '{ b }',
+ },
},
loc: {
start: { offset: 6, line: 1, column: 7 },
end: { offset: 16, line: 1, column: 17 },
- source: '#a="{ b }"'
- }
+ source: '#a="{ b }"',
+ },
})
})
start: {
column: 14,
line: 1,
- offset: 13
+ offset: 13,
},
end: {
column: 21,
line: 1,
- offset: 20
- }
- }
- }
+ offset: 20,
+ },
+ },
+ },
})
})
test('v-pre', () => {
const ast = baseParse(
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
- `<div :id="foo"><Comp/>{{ bar }}</div>`
+ `<div :id="foo"><Comp/>{{ bar }}</div>`,
)
const divWithPre = ast.children[0] as ElementNode
name: `:id`,
value: {
type: NodeTypes.TEXT,
- content: `foo`
+ content: `foo`,
},
loc: {
start: { line: 1, column: 12 },
- end: { line: 1, column: 21 }
- }
- }
+ end: { line: 1, column: 21 },
+ },
+ },
])
expect(divWithPre.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.ELEMENT,
- tag: `Comp`
+ tag: `Comp`,
})
expect(divWithPre.children[1]).toMatchObject({
type: NodeTypes.TEXT,
- content: `{{ bar }}`
+ content: `{{ bar }}`,
})
// should not affect siblings after it
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: true,
- content: `id`
+ content: `id`,
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
- content: `foo`
+ content: `foo`,
},
loc: {
start: {
line: 2,
- column: 6
+ column: 6,
},
end: {
line: 2,
- column: 15
- }
- }
- }
+ column: 15,
+ },
+ },
+ },
])
expect(divWithoutPre.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.COMPONENT,
- tag: `Comp`
+ tag: `Comp`,
})
expect(divWithoutPre.children[1]).toMatchObject({
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
<span>{{ number </span>
<span>}}</span>
</div>
- `
+ `,
)
expect((ast.children[0] as ElementNode).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
- children: [{ type: NodeTypes.TEXT, content: `{{ number ` }]
+ children: [{ type: NodeTypes.TEXT, content: `{{ number ` }],
},
{
type: NodeTypes.ELEMENT,
- children: [{ type: NodeTypes.TEXT, content: `}}` }]
- }
+ children: [{ type: NodeTypes.TEXT, content: `}}` }],
+ },
])
const ast2 = baseParse(`<div v-pre><span>{{ number </span></div>`)
expect((ast2.children[0] as ElementNode).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
- children: [{ type: NodeTypes.TEXT, content: `{{ number ` }]
- }
+ children: [{ type: NodeTypes.TEXT, content: `{{ number ` }],
+ },
])
})
test('self-closing v-pre', () => {
const ast = baseParse(
- `<div v-pre/>\n<div :id="foo"><Comp/>{{ bar }}</div>`
+ `<div v-pre/>\n<div :id="foo"><Comp/>{{ bar }}</div>`,
)
// should not affect siblings after it
const divWithoutPre = ast.children[1] as ElementNode
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: true,
- content: `id`
+ content: `id`,
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
- content: `foo`
+ content: `foo`,
},
loc: {
start: {
line: 2,
- column: 6
+ column: 6,
},
end: {
line: 2,
- column: 15
- }
- }
- }
+ column: 15,
+ },
+ },
+ },
])
expect(divWithoutPre.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.COMPONENT,
- tag: `Comp`
+ tag: `Comp`,
})
expect(divWithoutPre.children[1]).toMatchObject({
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: 'hello'
- }
+ source: 'hello',
+ },
})
})
})
test('self closing multiple tag', () => {
const ast = baseParse(
`<div :class="{ some: condition }" />\n` +
- `<p v-bind:style="{ color: 'red' }"/>`
+ `<p v-bind:style="{ color: 'red' }"/>`,
)
expect(ast).toMatchSnapshot()
`<div :class="{ some: condition }">\n` +
` <p v-bind:style="{ color: 'red' }"/>\n` +
` <!-- a comment with <html> inside it -->\n` +
- `</div>`
+ `</div>`,
)
expect(ast).toMatchSnapshot()
expect(ast.children).toHaveLength(1)
const el = ast.children[0] as any
expect(el).toMatchObject({
- tag: 'div'
+ tag: 'div',
})
expect(el.children).toHaveLength(2)
expect(el.children[0]).toMatchObject({
- tag: 'p'
+ tag: 'p',
})
expect(el.children[1]).toMatchObject({
- type: NodeTypes.COMMENT
+ type: NodeTypes.COMMENT,
})
})
const spy = vi.fn()
const ast = baseParse(`<div>\n<span>\n</div>\n</span>`, {
- onError: spy
+ onError: spy,
})
expect(spy.mock.calls).toMatchObject([
start: {
offset: 6,
line: 2,
- column: 1
- }
- }
- }
+ column: 1,
+ },
+ },
+ },
],
[
{
start: {
offset: 20,
line: 4,
- column: 1
- }
- }
- }
- ]
+ column: 1,
+ },
+ },
+ },
+ ],
])
expect(ast).toMatchSnapshot()
const butSrc = ` but `
const bazSrc = `{{ baz }}`
const [foo, bar, but, baz] = baseParse(
- fooSrc + barSrc + butSrc + bazSrc
+ fooSrc + barSrc + butSrc + bazSrc,
).children
let offset = 0
const ast = baseParse(
`<template><Hello\n</template><script>console.log(1)</script>`,
{
- onError: spy
- }
+ onError: spy,
+ },
)
//
expect(ast.children.length).toBe(2)
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
- tag: 'script'
+ tag: 'script',
})
})
type: NodeTypes.DIRECTIVE,
name: 'slot',
exp: undefined,
- arg: undefined
+ arg: undefined,
})
})
test('should warn in non-browser build', () => {
baseParse('&∪︀', {
decodeEntities: text => text.replace('∪︀', '\u222A\uFE00'),
- onError: () => {} // Ignore errors
+ onError: () => {}, // Ignore errors
})
expect(
- `decodeEntities option is passed but will be ignored`
+ `decodeEntities option is passed but will be ignored`,
).toHaveBeenWarned()
})
})
const parse = (content: string, options?: ParserOptions) =>
baseParse(content, {
whitespace: 'condense',
- ...options
+ ...options,
})
test('should remove whitespaces at start/end inside an element', () => {
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
expect(ast.children[1]).toMatchObject({
type: NodeTypes.TEXT,
- content: ' '
+ content: ' ',
})
expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
})
expect(ast.children[0].type).toBe(NodeTypes.COMMENT)
expect(ast.children[1]).toMatchObject({
type: NodeTypes.TEXT,
- content: ' '
+ content: ' ',
})
expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
})
NodeTypes.TEXT,
NodeTypes.ELEMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
test('should remove leading newline character immediately following the pre element start tag', () => {
const ast = baseParse(`<pre>\n foo bar </pre>`, {
- isPreTag: tag => tag === 'pre'
+ isPreTag: tag => tag === 'pre',
})
expect(ast.children).toHaveLength(1)
const preElement = ast.children[0] as ElementNode
test('should NOT remove leading newline character immediately following child-tag of pre element', () => {
const ast = baseParse(`<pre><span></span>\n foo bar </pre>`, {
- isPreTag: tag => tag === 'pre'
+ isPreTag: tag => tag === 'pre',
})
const preElement = ast.children[0] as ElementNode
expect(preElement.children).toHaveLength(2)
expect((preElement.children[1] as TextNode).content).toBe(
- `\n foo bar `
+ `\n foo bar `,
)
})
test('self-closing pre tag', () => {
const ast = baseParse(`<pre/><span>\n foo bar</span>`, {
- isPreTag: tag => tag === 'pre'
+ isPreTag: tag => tag === 'pre',
})
const elementAfterPre = ast.children[1] as ElementNode
// should not affect the <span> and condense its whitespace inside
test('should NOT condense whitespaces in RCDATA text mode', () => {
const ast = baseParse(`<textarea>Text:\n foo</textarea>`, {
- parseMode: 'html'
+ parseMode: 'html',
})
const preElement = ast.children[0] as ElementNode
expect(preElement.children).toHaveLength(1)
const parse = (content: string, options?: ParserOptions) =>
baseParse(content, {
whitespace: 'preserve',
- ...options
+ ...options,
})
test('should still remove whitespaces at start/end inside an element', () => {
NodeTypes.TEXT,
NodeTypes.ELEMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
NodeTypes.TEXT,
NodeTypes.COMMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
NodeTypes.TEXT,
NodeTypes.COMMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
expect(ast.children[1]).toMatchObject({
type: NodeTypes.TEXT,
- content: ' '
+ content: ' ',
})
expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
})
NodeTypes.TEXT,
NodeTypes.ELEMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
describe('expression parsing', () => {
test('interpolation', () => {
const ast = baseParse(`{{ a + b }}`, { prefixIdentifiers: true })
- // @ts-ignore
+ // @ts-expect-error
expect((ast.children[0] as InterpolationNode).content.ast?.type).toBe(
- 'BinaryExpression'
+ 'BinaryExpression',
)
})
test('v-bind', () => {
const ast = baseParse(`<div :[key+1]="foo()" />`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const dir = (ast.children[0] as ElementNode).props[0] as DirectiveNode
- // @ts-ignore
+ // @ts-expect-error
expect(dir.arg?.ast?.type).toBe('BinaryExpression')
- // @ts-ignore
+ // @ts-expect-error
expect(dir.exp?.ast?.type).toBe('CallExpression')
})
test('v-on multi statements', () => {
const ast = baseParse(`<div @click="a++;b++" />`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const dir = (ast.children[0] as ElementNode).props[0] as DirectiveNode
- // @ts-ignore
+ // @ts-expect-error
expect(dir.exp?.ast?.type).toBe('Program')
expect((dir.exp?.ast as Program).body).toMatchObject([
{ type: 'ExpressionStatement' },
- { type: 'ExpressionStatement' }
+ { type: 'ExpressionStatement' },
])
})
test('v-slot', () => {
const ast = baseParse(`<Comp #foo="{ a, b }" />`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const dir = (ast.children[0] as ElementNode).props[0] as DirectiveNode
- // @ts-ignore
+ // @ts-expect-error
expect(dir.exp?.ast?.type).toBe('ArrowFunctionExpression')
})
test('v-for', () => {
const ast = baseParse(`<div v-for="({ a, b }, key, index) of a.b" />`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const dir = (ast.children[0] as ElementNode).props[0] as DirectiveNode
const { source, value, key, index } = dir.forParseResult!
- // @ts-ignore
+ // @ts-expect-error
expect(source.ast?.type).toBe('MemberExpression')
- // @ts-ignore
+ // @ts-expect-error
expect(value?.ast?.type).toBe('ArrowFunctionExpression')
expect(key?.ast).toBeNull() // simple ident
expect(index?.ast).toBeNull() // simple ident
errors: [
{
type: ErrorCodes.CDATA_IN_HTML_CONTENT,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
+ loc: { offset: 10, line: 1, column: 11 },
+ },
+ ],
},
{
code: '<template><svg><![CDATA[cdata]]></svg></template>',
- errors: []
- }
+ errors: [],
+ },
],
DUPLICATE_ATTRIBUTE: [
{
errors: [
{
type: ErrorCodes.DUPLICATE_ATTRIBUTE,
- loc: { offset: 21, line: 1, column: 22 }
- }
- ]
- }
+ loc: { offset: 21, line: 1, column: 22 },
+ },
+ ],
+ },
],
// END_TAG_WITH_ATTRIBUTES: [
// {
errors: [
{
type: ErrorCodes.EOF_BEFORE_TAG_NAME,
- loc: { offset: 11, line: 1, column: 12 }
+ loc: { offset: 11, line: 1, column: 12 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template></',
errors: [
{
type: ErrorCodes.EOF_BEFORE_TAG_NAME,
- loc: { offset: 12, line: 1, column: 13 }
+ loc: { offset: 12, line: 1, column: 13 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
- }
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
+ },
],
EOF_IN_CDATA: [
{
errors: [
{
type: ErrorCodes.EOF_IN_CDATA,
- loc: { offset: 29, line: 1, column: 30 }
+ loc: { offset: 29, line: 1, column: 30 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
+ loc: { offset: 10, line: 1, column: 11 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><svg><![CDATA[',
errors: [
{
type: ErrorCodes.EOF_IN_CDATA,
- loc: { offset: 24, line: 1, column: 25 }
+ loc: { offset: 24, line: 1, column: 25 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
+ loc: { offset: 10, line: 1, column: 11 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
- }
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
+ },
],
EOF_IN_COMMENT: [
{
errors: [
{
type: ErrorCodes.EOF_IN_COMMENT,
- loc: { offset: 21, line: 1, column: 22 }
+ loc: { offset: 21, line: 1, column: 22 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><!--',
errors: [
{
type: ErrorCodes.EOF_IN_COMMENT,
- loc: { offset: 14, line: 1, column: 15 }
+ loc: { offset: 14, line: 1, column: 15 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
- }
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
+ },
// // Bogus comments don't throw eof-in-comment error.
// // https://html.spec.whatwg.org/multipage/parsing.html#bogus-comment-state
// {
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 14, line: 1, column: 15 }
+ loc: { offset: 14, line: 1, column: 15 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div ',
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 15, line: 1, column: 16 }
+ loc: { offset: 15, line: 1, column: 16 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div id',
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 17, line: 1, column: 18 }
+ loc: { offset: 17, line: 1, column: 18 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div id ',
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 18, line: 1, column: 19 }
+ loc: { offset: 18, line: 1, column: 19 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div id =',
// },
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 19, line: 1, column: 20 }
+ loc: { offset: 19, line: 1, column: 20 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: "<template><div id='abc",
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 22, line: 1, column: 23 }
+ loc: { offset: 22, line: 1, column: 23 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div id="abc',
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 22, line: 1, column: 23 }
+ loc: { offset: 22, line: 1, column: 23 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: "<template><div id='abc'",
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 23, line: 1, column: 24 }
+ loc: { offset: 23, line: 1, column: 24 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div id="abc"',
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 23, line: 1, column: 24 }
+ loc: { offset: 23, line: 1, column: 24 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div id=abc',
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 21, line: 1, column: 22 }
+ loc: { offset: 21, line: 1, column: 22 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: "<template><div id='abc'/",
errors: [
{
type: ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG,
- loc: { offset: 23, line: 1, column: 24 }
+ loc: { offset: 23, line: 1, column: 24 },
},
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 24, line: 1, column: 25 }
+ loc: { offset: 24, line: 1, column: 25 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div id="abc"/',
errors: [
{
type: ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG,
- loc: { offset: 23, line: 1, column: 24 }
+ loc: { offset: 23, line: 1, column: 24 },
},
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 24, line: 1, column: 25 }
+ loc: { offset: 24, line: 1, column: 25 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<template><div id=abc /',
errors: [
{
type: ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG,
- loc: { offset: 22, line: 1, column: 23 }
+ loc: { offset: 22, line: 1, column: 23 },
},
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 23, line: 1, column: 24 }
+ loc: { offset: 23, line: 1, column: 24 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<div></div',
errors: [
{
type: ErrorCodes.EOF_IN_TAG,
- loc: { offset: 10, line: 1, column: 11 }
+ loc: { offset: 10, line: 1, column: 11 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
- }
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
+ },
],
// INCORRECTLY_CLOSED_COMMENT: [
// {
errors: [
{
type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
- loc: { offset: 18, line: 1, column: 19 }
- }
- ]
+ loc: { offset: 18, line: 1, column: 19 },
+ },
+ ],
},
{
code: '<template><div id= ></div></template>',
errors: [
{
type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
- loc: { offset: 19, line: 1, column: 20 }
- }
- ]
+ loc: { offset: 19, line: 1, column: 20 },
+ },
+ ],
},
{
code: '<template><div id= /></div></template>',
- errors: []
- }
+ errors: [],
+ },
],
MISSING_END_TAG_NAME: [
{
errors: [
{
type: ErrorCodes.MISSING_END_TAG_NAME,
- loc: { offset: 12, line: 1, column: 13 }
- }
- ]
- }
+ loc: { offset: 12, line: 1, column: 13 },
+ },
+ ],
+ },
],
// MISSING_WHITESPACE_BETWEEN_ATTRIBUTES: [
// {
errors: [
{
type: ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
- loc: { offset: 16, line: 1, column: 17 }
- }
- ]
+ loc: { offset: 16, line: 1, column: 17 },
+ },
+ ],
},
{
code: "<template><div a'bc=''></div></template>",
errors: [
{
type: ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
- loc: { offset: 16, line: 1, column: 17 }
- }
- ]
+ loc: { offset: 16, line: 1, column: 17 },
+ },
+ ],
},
{
code: "<template><div a<bc=''></div></template>",
errors: [
{
type: ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
- loc: { offset: 16, line: 1, column: 17 }
- }
- ]
- }
+ loc: { offset: 16, line: 1, column: 17 },
+ },
+ ],
+ },
],
UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE: [
{
errors: [
{
type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
- loc: { offset: 22, line: 1, column: 23 }
- }
- ]
+ loc: { offset: 22, line: 1, column: 23 },
+ },
+ ],
},
{
code: "<template><div foo=bar'></div></template>",
errors: [
{
type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
- loc: { offset: 22, line: 1, column: 23 }
- }
- ]
+ loc: { offset: 22, line: 1, column: 23 },
+ },
+ ],
},
{
code: '<template><div foo=bar<div></div></template>',
errors: [
{
type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
- loc: { offset: 22, line: 1, column: 23 }
- }
- ]
+ loc: { offset: 22, line: 1, column: 23 },
+ },
+ ],
},
{
code: '<template><div foo=bar=baz></div></template>',
errors: [
{
type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
- loc: { offset: 22, line: 1, column: 23 }
- }
- ]
+ loc: { offset: 22, line: 1, column: 23 },
+ },
+ ],
},
{
code: '<template><div foo=bar`></div></template>',
errors: [
{
type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
- loc: { offset: 22, line: 1, column: 23 }
- }
- ]
- }
+ loc: { offset: 22, line: 1, column: 23 },
+ },
+ ],
+ },
],
UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME: [
{
errors: [
{
type: ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
- loc: { offset: 15, line: 1, column: 16 }
- }
- ]
+ loc: { offset: 15, line: 1, column: 16 },
+ },
+ ],
},
{
code: '<template><div =></div></template>',
errors: [
{
type: ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
- loc: { offset: 15, line: 1, column: 16 }
- }
- ]
- }
+ loc: { offset: 15, line: 1, column: 16 },
+ },
+ ],
+ },
],
UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME: [
{
errors: [
{
type: ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
- loc: { offset: 11, line: 1, column: 12 }
- }
- ]
- }
+ loc: { offset: 11, line: 1, column: 12 },
+ },
+ ],
+ },
],
UNEXPECTED_SOLIDUS_IN_TAG: [
{
errors: [
{
type: ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG,
- loc: { offset: 16, line: 1, column: 17 }
- }
- ]
- }
+ loc: { offset: 16, line: 1, column: 17 },
+ },
+ ],
+ },
],
X_INVALID_END_TAG: [
{
errors: [
{
type: ErrorCodes.X_INVALID_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
+ loc: { offset: 10, line: 1, column: 11 },
+ },
+ ],
},
{
code: '<template></div></div></template>',
errors: [
{
type: ErrorCodes.X_INVALID_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
+ loc: { offset: 10, line: 1, column: 11 },
},
{
type: ErrorCodes.X_INVALID_END_TAG,
- loc: { offset: 16, line: 1, column: 17 }
- }
- ]
+ loc: { offset: 16, line: 1, column: 17 },
+ },
+ ],
},
{
code: '<template>a </ b</template>',
errors: [
{
type: ErrorCodes.X_INVALID_END_TAG,
- loc: { offset: 12, line: 1, column: 13 }
+ loc: { offset: 12, line: 1, column: 13 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: "<template>{{'</div>'}}</template>",
- errors: []
+ errors: [],
},
{
code: '<textarea></div></textarea>',
- errors: []
+ errors: [],
},
{
code: '<svg><![CDATA[</div>]]></svg>',
- errors: []
+ errors: [],
},
{
code: '<svg><!--</div>--></svg>',
- errors: []
- }
+ errors: [],
+ },
],
X_MISSING_END_TAG: [
{
errors: [
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
+ loc: { offset: 10, line: 1, column: 11 },
+ },
+ ],
},
{
code: '<template><div>',
errors: [
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
+ loc: { offset: 10, line: 1, column: 11 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
- }
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
+ },
],
X_MISSING_INTERPOLATION_END: [
{
errors: [
{
type: ErrorCodes.X_MISSING_INTERPOLATION_END,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '{{',
errors: [
{
type: ErrorCodes.X_MISSING_INTERPOLATION_END,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '<div>{{ foo</div>',
errors: [
{
type: ErrorCodes.X_MISSING_INTERPOLATION_END,
- loc: { offset: 5, line: 1, column: 6 }
+ loc: { offset: 5, line: 1, column: 6 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '{{}}',
- errors: []
- }
+ errors: [],
+ },
],
X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END: [
{
errors: [
{
type: ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
- loc: { offset: 15, line: 1, column: 16 }
- }
- ]
- }
- ]
+ loc: { offset: 15, line: 1, column: 16 },
+ },
+ ],
+ },
+ ],
}
for (const key of Object.keys(patterns)) {
test(
code.replace(
/[\r\n]/g,
- c => `\\x0${c.codePointAt(0)!.toString(16)};`
+ c => `\\x0${c.codePointAt(0)!.toString(16)};`,
),
() => {
const spy = vi.fn()
getNamespace: tag =>
tag === 'svg' ? Namespaces.SVG : Namespaces.HTML,
...options,
- onError: spy
+ onError: spy,
})
expect(
spy.mock.calls.map(([err]) => ({
type: err.code,
- loc: err.loc.start
- }))
+ loc: err.loc.start,
+ })),
).toMatchObject(errors)
expect(ast).toMatchSnapshot()
- }
+ },
)
}
})
import { baseCompile } from '../src/compile'
-import { PUSH_SCOPE_ID, POP_SCOPE_ID } from '../src/runtimeHelpers'
+import { POP_SCOPE_ID, PUSH_SCOPE_ID } from '../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
import { genFlagText } from './testUtils'
test('should wrap default slot', () => {
const { code } = baseCompile(`<Child><div/></Child>`, {
mode: 'module',
- scopeId: 'test'
+ scopeId: 'test',
})
expect(code).toMatch(`default: _withCtx(() => [`)
expect(code).toMatchSnapshot()
`,
{
mode: 'module',
- scopeId: 'test'
- }
+ scopeId: 'test',
+ },
)
expect(code).toMatch(`foo: _withCtx(({ msg }) => [`)
expect(code).toMatch(`bar: _withCtx(() => [`)
`,
{
mode: 'module',
- scopeId: 'test'
- }
+ scopeId: 'test',
+ },
)
expect(code).toMatch(/name: "foo",\s+fn: _withCtx\(/)
expect(code).toMatch(/name: i,\s+fn: _withCtx\(/)
{
mode: 'module',
scopeId: 'test',
- hoistStatic: true
- }
+ hoistStatic: true,
+ },
)
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
expect(ast.helpers).toContain(POP_SCOPE_ID)
;[
`const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)`,
`const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText(
- PatchFlags.HOISTED
+ PatchFlags.HOISTED,
)}))`,
`const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText(
- PatchFlags.HOISTED
- )}))`
+ PatchFlags.HOISTED,
+ )}))`,
].forEach(c => expect(code).toMatch(c))
expect(code).toMatchSnapshot()
})
import {
+ type ElementNode,
+ ElementTypes,
+ Namespaces,
NodeTypes,
- ElementNode,
+ type VNodeCall,
locStub,
- Namespaces,
- ElementTypes,
- VNodeCall
} from '../src'
import {
- isString,
- PatchFlags,
PatchFlagNames,
+ type PatchFlags,
+ type ShapeFlags,
isArray,
- ShapeFlags
+ isString,
} from '@vue/shared'
const leadingBracketRE = /^\[/
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: key.replace(bracketsRE, ''),
- isStatic: !leadingBracketRE.test(key)
+ isStatic: !leadingBracketRE.test(key),
},
value: isString(obj[key])
? {
type: NodeTypes.SIMPLE_EXPRESSION,
content: obj[key].replace(bracketsRE, ''),
- isStatic: !leadingBracketRE.test(obj[key])
+ isStatic: !leadingBracketRE.test(obj[key]),
}
- : obj[key]
- }))
+ : obj[key],
+ })),
}
}
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'],
- dynamicProps?: VNodeCall['dynamicProps']
+ dynamicProps?: VNodeCall['dynamicProps'],
): ElementNode {
return {
type: NodeTypes.ELEMENT,
isBlock: false,
disableTracking: false,
isComponent: false,
- loc: locStub
- }
+ loc: locStub,
+ },
}
}
type Flags = PatchFlags | ShapeFlags
export function genFlagText(
flag: Flags | Flags[],
- names: { [k: number]: string } = PatchFlagNames
+ names: { [k: number]: string } = PatchFlagNames,
) {
if (isArray(flag)) {
let f = 0
import { baseParse } from '../src/parser'
-import { transform, NodeTransform } from '../src/transform'
+import { type NodeTransform, transform } from '../src/transform'
import {
- ElementNode,
+ type DirectiveNode,
+ type ElementNode,
+ type ExpressionNode,
NodeTypes,
- DirectiveNode,
- ExpressionNode,
- VNodeCall
+ type VNodeCall,
} from '../src/ast'
import { ErrorCodes, createCompilerError } from '../src/errors'
import {
- TO_DISPLAY_STRING,
+ CREATE_COMMENT,
FRAGMENT,
RENDER_SLOT,
- CREATE_COMMENT
+ TO_DISPLAY_STRING,
} from '../src/runtimeHelpers'
import { transformIf } from '../src/transforms/vIf'
import { transformFor } from '../src/transforms/vFor'
}
transform(ast, {
- nodeTransforms: [plugin]
+ nodeTransforms: [plugin],
})
const div = ast.children[0] as ElementNode
ast,
{
parent: null,
- currentNode: ast
- }
+ currentNode: ast,
+ },
])
expect(calls[1]).toMatchObject([
div,
{
parent: ast,
- currentNode: div
- }
+ currentNode: div,
+ },
])
expect(calls[2]).toMatchObject([
div.children[0],
{
parent: div,
- currentNode: div.children[0]
- }
+ currentNode: div.children[0],
+ },
])
expect(calls[3]).toMatchObject([
div.children[1],
{
parent: div,
- currentNode: div.children[1]
- }
+ currentNode: div.children[1],
+ },
])
})
{
type: NodeTypes.TEXT,
content: 'hello',
- isEmpty: false
- }
- ]
- })
+ isEmpty: false,
+ },
+ ],
+ }),
)
}
}
const spy = vi.fn(plugin)
transform(ast, {
- nodeTransforms: [spy]
+ nodeTransforms: [spy],
})
expect(ast.children.length).toBe(2)
}
const spy = vi.fn(plugin)
transform(ast, {
- nodeTransforms: [spy]
+ nodeTransforms: [spy],
})
expect(ast.children.length).toBe(2)
}
const spy = vi.fn(plugin)
transform(ast, {
- nodeTransforms: [spy]
+ nodeTransforms: [spy],
})
expect(ast.children.length).toBe(1)
}
const spy = vi.fn(plugin)
transform(ast, {
- nodeTransforms: [spy]
+ nodeTransforms: [spy],
})
expect(ast.children.length).toBe(1)
}
}
transform(ast, {
- nodeTransforms: [mock]
+ nodeTransforms: [mock],
})
expect(ast.hoists).toMatchObject(hoisted)
expect((ast as any).children[0].props[0].exp.content).toBe(`_hoisted_1`)
transform(ast, {
filename: '/the/fileName.vue',
- nodeTransforms: [plugin]
+ nodeTransforms: [plugin],
})
expect(calls.length).toBe(2)
expect(calls[1]).toMatchObject({
filename: '/the/fileName.vue',
- selfName: 'FileName'
+ selfName: 'FileName',
})
})
const loc = ast.children[0].loc
const plugin: NodeTransform = (node, context) => {
context.onError(
- createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc)
+ createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc),
)
}
const spy = vi.fn()
transform(ast, {
nodeTransforms: [plugin],
- onError: spy
+ onError: spy,
})
expect(spy.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_INVALID_END_TAG,
- loc
- }
+ loc,
+ },
])
})
transformFor,
transformText,
transformSlotOutlet,
- transformElement
- ]
+ transformElement,
+ ],
})
return ast
}
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
- patchFlag?: VNodeCall['patchFlag']
+ patchFlag?: VNodeCall['patchFlag'],
) {
return {
type: NodeTypes.VNODE_CALL,
tag,
props,
children,
- patchFlag
+ patchFlag,
}
}
expect(ast.codegenNode).toMatchObject({
codegenNode: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ callee: RENDER_SLOT,
+ },
})
})
test('root v-if', () => {
const ast = transformWithCodegen(`<div v-if="ok" />`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.IF
+ type: NodeTypes.IF,
})
})
test('root v-for', () => {
const ast = transformWithCodegen(`<div v-for="i in list" />`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.FOR
+ type: NodeTypes.FOR,
})
})
const ast = transformWithCodegen(`<div v-foo/>`)
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
- directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
+ directives: { type: NodeTypes.JS_ARRAY_EXPRESSION },
})
})
test('single text', () => {
const ast = transformWithCodegen(`hello`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.TEXT
+ type: NodeTypes.TEXT,
})
})
test('single interpolation', () => {
const ast = transformWithCodegen(`{{ foo }}`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.INTERPOLATION
+ type: NodeTypes.INTERPOLATION,
})
})
test('single CompoundExpression', () => {
const ast = transformWithCodegen(`{{ foo }} bar baz`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.COMPOUND_EXPRESSION
+ type: NodeTypes.COMPOUND_EXPRESSION,
})
})
undefined,
[
{ type: NodeTypes.ELEMENT, tag: `div` },
- { type: NodeTypes.ELEMENT, tag: `div` }
+ { type: NodeTypes.ELEMENT, tag: `div` },
] as any,
- genFlagText(PatchFlags.STABLE_FRAGMENT)
- )
+ genFlagText(PatchFlags.STABLE_FRAGMENT),
+ ),
)
})
[
{ type: NodeTypes.COMMENT },
{ type: NodeTypes.ELEMENT, tag: `div` },
- { type: NodeTypes.COMMENT }
+ { type: NodeTypes.COMMENT },
] as any,
genFlagText([
PatchFlags.STABLE_FRAGMENT,
- PatchFlags.DEV_ROOT_FRAGMENT
- ])
- )
+ PatchFlags.DEV_ROOT_FRAGMENT,
+ ]),
+ ),
)
})
})
import {
- baseParse as parse,
- transform,
+ type CompilerOptions,
+ ConstantTypes,
+ type ElementNode,
+ type ForNode,
+ type IfNode,
NodeTypes,
+ type VNodeCall,
generate,
- CompilerOptions,
- VNodeCall,
- IfNode,
- ElementNode,
- ForNode,
- ConstantTypes
+ baseParse as parse,
+ transform,
} from '../../src'
import {
FRAGMENT,
+ NORMALIZE_CLASS,
RENDER_LIST,
- NORMALIZE_CLASS
} from '../../src/runtimeHelpers'
import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_${startIndex + i}`
- }
- }))
+ content: `_hoisted_${startIndex + i}`,
+ },
+ })),
})
function transformWithHoist(template: string, options: CompilerOptions = {}) {
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement,
- transformText
+ transformText,
],
directiveTransforms: {
on: transformOn,
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
})
return ast
}
const root = transformWithHoist(`<div/>`)
expect(root.hoists.length).toBe(0)
expect(root.codegenNode).toMatchObject({
- tag: `"div"`
+ tag: `"div"`,
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist simple element', () => {
const root = transformWithHoist(
- `<div><span class="inline">hello</span></div>`
+ `<div><span class="inline">hello</span></div>`,
)
expect(root.hoists).toMatchObject([
{
props: createObjectMatcher({ class: 'inline' }),
children: {
type: NodeTypes.TEXT,
- content: `hello`
- }
+ content: `hello`,
+ },
},
- hoistedChildrenArrayMatcher()
+ hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
- children: { content: `_hoisted_2` }
+ children: { content: `_hoisted_2` },
})
expect(generate(root).code).toMatchSnapshot()
})
props: undefined,
children: [
{ type: NodeTypes.ELEMENT, tag: `span` },
- { type: NodeTypes.ELEMENT, tag: `span` }
- ]
+ { type: NodeTypes.ELEMENT, tag: `span` },
+ ],
},
- hoistedChildrenArrayMatcher()
+ hoistedChildrenArrayMatcher(),
])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: '_hoisted_2'
+ content: '_hoisted_2',
})
expect(generate(root).code).toMatchSnapshot()
})
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: undefined,
- children: [{ type: NodeTypes.COMMENT, content: `comment` }]
+ children: [{ type: NodeTypes.COMMENT, content: `comment` }],
},
- hoistedChildrenArrayMatcher()
+ hoistedChildrenArrayMatcher(),
])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: `_hoisted_2`
+ content: `_hoisted_2`,
})
expect(generate(root).code).toMatchSnapshot()
})
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
+ tag: `"span"`,
},
{
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
+ tag: `"div"`,
},
- hoistedChildrenArrayMatcher(1, 2)
+ hoistedChildrenArrayMatcher(1, 2),
])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: '_hoisted_3'
+ content: '_hoisted_3',
})
expect(generate(root).code).toMatchSnapshot()
})
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.VNODE_CALL,
- tag: `_component_Comp`
- }
- }
+ tag: `_component_Comp`,
+ },
+ },
])
expect(generate(root).code).toMatchSnapshot()
})
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
- id: `[foo]`
+ id: `[foo]`,
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
- isStatic: false
- }
- }
- }
+ isStatic: false,
+ },
+ },
+ },
])
expect(generate(root).code).toMatchSnapshot()
})
{
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
- props: createObjectMatcher({ key: 'foo' })
+ props: createObjectMatcher({ key: 'foo' }),
},
- hoistedChildrenArrayMatcher()
+ hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
- children: { content: `_hoisted_2` }
+ children: { content: `_hoisted_2` },
})
expect(generate(root).code).toMatchSnapshot()
})
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
- key: `[foo]`
- })
- }
- }
+ key: `[foo]`,
+ }),
+ },
+ },
])
expect(generate(root).code).toMatchSnapshot()
})
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
- ref: `[foo]`
+ ref: `[foo]`,
}),
children: undefined,
- patchFlag: genFlagText(PatchFlags.NEED_PATCH)
- }
- }
+ patchFlag: genFlagText(PatchFlags.NEED_PATCH),
+ },
+ },
])
expect(generate(root).code).toMatchSnapshot()
})
tag: `"div"`,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`
+ content: `_hoisted_1`,
},
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
directives: {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- }
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
+ },
+ },
])
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with dynamic text children', () => {
const root = transformWithHoist(
- `<div><div id="foo">{{ hello }}</div></div>`
+ `<div><div id="foo">{{ hello }}</div></div>`,
)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect((root.codegenNode as VNodeCall).children).toMatchObject([
tag: `"div"`,
props: { content: `_hoisted_1` },
children: { type: NodeTypes.INTERPOLATION },
- patchFlag: genFlagText(PatchFlags.TEXT)
- }
- }
+ patchFlag: genFlagText(PatchFlags.TEXT),
+ },
+ },
])
expect(generate(root).code).toMatchSnapshot()
})
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
- children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }]
- }
- }
+ children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }],
+ },
+ },
])
expect(generate(root).code).toMatchSnapshot()
})
test('should hoist v-if props/children if static', () => {
const root = transformWithHoist(
- `<div><div v-if="ok" id="foo"><span/></div></div>`
+ `<div><div v-if="ok" id="foo"><span/></div></div>`,
)
expect(root.hoists).toMatchObject([
createObjectMatcher({
key: `[0]`, // key injected by v-if branch
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
+ tag: `"span"`,
},
- hoistedChildrenArrayMatcher(2)
+ hoistedChildrenArrayMatcher(2),
])
expect(
- ((root.children[0] as ElementNode).children[0] as IfNode).codegenNode
+ ((root.children[0] as ElementNode).children[0] as IfNode).codegenNode,
).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
- children: { content: `_hoisted_3` }
- }
+ children: { content: `_hoisted_3` },
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('should hoist v-for children if static', () => {
const root = transformWithHoist(
- `<div><div v-for="i in list" id="foo"><span/></div></div>`
+ `<div><div v-for="i in list" id="foo"><span/></div></div>`,
)
expect(root.hoists).toMatchObject([
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
+ tag: `"span"`,
},
- hoistedChildrenArrayMatcher(2)
+ hoistedChildrenArrayMatcher(2),
])
const forBlockCodegen = (
(root.children[0] as ElementNode).children[0] as ForNode
props: undefined,
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_LIST
+ callee: RENDER_LIST,
},
- patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
+ patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
})
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
expect(innerBlockCodegen.returns).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
- children: { content: `_hoisted_3` }
+ children: { content: `_hoisted_3` },
})
expect(generate(root).code).toMatchSnapshot()
})
const root = transformWithHoist(
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.hoists).toMatchObject([
{
tag: `"span"`,
props: undefined,
children: {
- type: NodeTypes.COMPOUND_EXPRESSION
- }
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ },
},
- hoistedChildrenArrayMatcher()
+ hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_2`
- }
+ content: `_hoisted_2`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
const root = transformWithHoist(
`<div><span :foo="0">{{ 1 }}</span></div>`,
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.hoists).toMatchObject([
content: {
content: `1`,
isStatic: false,
- constType: ConstantTypes.CAN_STRINGIFY
- }
- }
+ constType: ConstantTypes.CAN_STRINGIFY,
+ },
+ },
},
- hoistedChildrenArrayMatcher()
+ hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_2`
- }
+ content: `_hoisted_2`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
const root = transformWithHoist(
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.hoists).toMatchObject([
key: {
content: `class`,
isStatic: true,
- constType: ConstantTypes.CAN_STRINGIFY
+ constType: ConstantTypes.CAN_STRINGIFY,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
{
content: `{ foo: true }`,
isStatic: false,
- constType: ConstantTypes.CAN_STRINGIFY
- }
- ]
- }
- }
- ]
- }
+ constType: ConstantTypes.CAN_STRINGIFY,
+ },
+ ],
+ },
+ },
+ ],
+ },
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
tag: `"span"`,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`
+ content: `_hoisted_1`,
},
children: {
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`,
isStatic: false,
- constType: ConstantTypes.NOT_CONSTANT
- }
+ constType: ConstantTypes.NOT_CONSTANT,
+ },
},
- patchFlag: `1 /* TEXT */`
- }
- }
- ]
+ patchFlag: `1 /* TEXT */`,
+ },
+ },
+ ],
})
expect(generate(root).code).toMatchSnapshot()
})
const root = transformWithHoist(
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.hoists.length).toBe(0)
const root = transformWithHoist(
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.hoists.length).toBe(0)
const root = transformWithHoist(
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.hoists.length).toBe(0)
`<div><div><div @click="foo"/></div></div>`,
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
)
expect(root.cached).toBe(1)
expect(
generate(root, {
mode: 'module',
- prefixIdentifiers: true
- }).code
+ prefixIdentifiers: true,
+ }).code,
).toMatchSnapshot()
})
`<div><div><div :class="{}" @click="foo"/></div></div>`,
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
)
expect(root.cached).toBe(1)
expect(
generate(root, {
mode: 'module',
- prefixIdentifiers: true
- }).code
+ prefixIdentifiers: true,
+ }).code,
).toMatchSnapshot()
})
test('should NOT hoist keyed template v-for with plain element child', () => {
const root = transformWithHoist(
- `<div><template v-for="item in items" :key="item"><span/></template></div>`
+ `<div><template v-for="item in items" :key="item"><span/></template></div>`,
)
expect(root.hoists.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
test('should NOT hoist SVG with directives', () => {
const root = transformWithHoist(
- `<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`
+ `<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`,
)
expect(root.hoists.length).toBe(2)
expect(generate(root).code).toMatchSnapshot()
test('clone hoisted array children in HMR mode', () => {
const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
- hmr: true
+ hmr: true,
})
expect(root.hoists.length).toBe(2)
expect(root.codegenNode).toMatchObject({
children: {
- content: '[..._hoisted_2]'
- }
+ content: '[..._hoisted_2]',
+ },
})
})
})
import {
+ type ElementNode,
+ type VNodeCall,
+ noopDirectiveTransform,
baseParse as parse,
transform,
- ElementNode,
- noopDirectiveTransform,
- VNodeCall
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- noop: noopDirectiveTransform
- }
+ noop: noopDirectiveTransform,
+ },
})
const node = ast.children[0] as ElementNode
// As v-noop adds no properties the codegen should be identical to
import {
- CompilerOptions,
+ BindingTypes,
+ type CompilerOptions,
+ ErrorCodes,
+ type NodeTransform,
+ baseCompile,
baseParse as parse,
transform,
- ErrorCodes,
- BindingTypes,
- NodeTransform,
transformExpression,
- baseCompile
} from '../../src'
import {
- RESOLVE_COMPONENT,
+ BASE_TRANSITION,
CREATE_VNODE,
+ GUARD_REACTIVE_PROPS,
+ KEEP_ALIVE,
MERGE_PROPS,
+ NORMALIZE_CLASS,
+ NORMALIZE_PROPS,
+ NORMALIZE_STYLE,
+ RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
- TO_HANDLERS,
- helperNameMap,
- TELEPORT,
RESOLVE_DYNAMIC_COMPONENT,
SUSPENSE,
- KEEP_ALIVE,
- BASE_TRANSITION,
- NORMALIZE_CLASS,
- NORMALIZE_STYLE,
- NORMALIZE_PROPS,
- GUARD_REACTIVE_PROPS
+ TELEPORT,
+ TO_HANDLERS,
+ helperNameMap,
} from '../../src/runtimeHelpers'
import {
+ type DirectiveNode,
NodeTypes,
+ type RootNode,
+ type VNodeCall,
createObjectProperty,
- DirectiveNode,
- RootNode,
- VNodeCall
} from '../../src/ast'
import { transformElement } from '../../src/transforms/transformElement'
import { transformStyle } from '../../../compiler-dom/src/transforms/transformStyle'
function parseWithElementTransform(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
): {
root: RootNode
node: VNodeCall
const ast = parse(`<div>${template}</div>`, options)
transform(ast, {
nodeTransforms: [transformElement, transformText],
- ...options
+ ...options,
})
const codegenNode = (ast as any).children[0].children[0]
.codegenNode as VNodeCall
expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
return {
root: ast,
- node: codegenNode
+ node: codegenNode,
}
}
...options,
directiveTransforms: {
...options?.directiveTransforms,
- bind: transformBind
- }
+ bind: transformBind,
+ },
})
}
test('resolve implicitly self-referencing component', () => {
const { root } = parseWithElementTransform(`<Example/>`, {
- filename: `/foo/bar/Example.vue?vue&type=template`
+ filename: `/foo/bar/Example.vue?vue&type=template`,
})
expect(root.helpers).toContain(RESOLVE_COMPONENT)
expect(root.components).toContain(`Example__self`)
test('resolve component from setup bindings', () => {
const { root, node } = parseWithElementTransform(`<Example/>`, {
bindingMetadata: {
- Example: BindingTypes.SETUP_MAYBE_REF
- }
+ Example: BindingTypes.SETUP_MAYBE_REF,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`$setup["Example"]`)
const { root, node } = parseWithElementTransform(`<Example/>`, {
inline: true,
bindingMetadata: {
- Example: BindingTypes.SETUP_MAYBE_REF
- }
+ Example: BindingTypes.SETUP_MAYBE_REF,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`_unref(Example)`)
const { root, node } = parseWithElementTransform(`<Example/>`, {
inline: true,
bindingMetadata: {
- Example: BindingTypes.SETUP_CONST
- }
+ Example: BindingTypes.SETUP_CONST,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`Example`)
test('resolve namespaced component from setup bindings', () => {
const { root, node } = parseWithElementTransform(`<Foo.Example/>`, {
bindingMetadata: {
- Foo: BindingTypes.SETUP_MAYBE_REF
- }
+ Foo: BindingTypes.SETUP_MAYBE_REF,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`$setup["Foo"].Example`)
const { root, node } = parseWithElementTransform(`<Foo.Example/>`, {
inline: true,
bindingMetadata: {
- Foo: BindingTypes.SETUP_MAYBE_REF
- }
+ Foo: BindingTypes.SETUP_MAYBE_REF,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`_unref(Foo).Example`)
const { root, node } = parseWithElementTransform(`<Foo.Example/>`, {
inline: true,
bindingMetadata: {
- Foo: BindingTypes.SETUP_CONST
- }
+ Foo: BindingTypes.SETUP_CONST,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`Foo.Example`)
const { root, node } = parseWithElementTransform(`<Foo.Example/>`, {
inline: true,
bindingMetadata: {
- Foo: BindingTypes.PROPS
- }
+ Foo: BindingTypes.PROPS,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`_unref(__props["Foo"]).Example`)
const { root, node } = parseWithElementTransform(`<Foo.Example/>`, {
inline: false,
bindingMetadata: {
- Foo: BindingTypes.PROPS
- }
+ Foo: BindingTypes.PROPS,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe('_unref($props["Foo"]).Example')
test('do not resolve component from non-script-setup bindings', () => {
const bindingMetadata = {
- Example: BindingTypes.SETUP_MAYBE_REF
+ Example: BindingTypes.SETUP_MAYBE_REF,
}
Object.defineProperty(bindingMetadata, '__isScriptSetup', { value: false })
const { root } = parseWithElementTransform(`<Example/>`, {
- bindingMetadata
+ bindingMetadata,
})
expect(root.helpers).toContain(RESOLVE_COMPONENT)
expect(root.components).toContain(`Example`)
tag: `"div"`,
props: createObjectMatcher({
id: 'foo',
- class: 'bar'
+ class: 'bar',
}),
- children: undefined
+ children: undefined,
})
})
expect(node).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
children: [
{
tag: 'span',
codegenNode: {
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- }
- }
- ]
+ tag: `"span"`,
+ },
+ },
+ ],
})
})
tag: 'span',
codegenNode: {
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- }
- }
- ]
+ tag: `"span"`,
+ },
+ },
+ ],
})
})
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
- }
- ]
- }
- ]
+ content: `obj`,
+ },
+ ],
+ },
+ ],
})
})
test('v-bind="obj" after static prop', () => {
const { root, node } = parseWithElementTransform(
- `<div id="foo" v-bind="obj" />`
+ `<div id="foo" v-bind="obj" />`,
)
expect(root.helpers).toContain(MERGE_PROPS)
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
- }
- ]
+ content: `obj`,
+ },
+ ],
})
})
test('v-bind="obj" before static prop', () => {
const { root, node } = parseWithElementTransform(
- `<div v-bind="obj" id="foo" />`
+ `<div v-bind="obj" id="foo" />`,
)
expect(root.helpers).toContain(MERGE_PROPS)
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
+ content: `obj`,
},
createObjectMatcher({
- id: 'foo'
- })
- ]
+ id: 'foo',
+ }),
+ ],
})
})
test('v-bind="obj" between static props', () => {
const { root, node } = parseWithElementTransform(
- `<div id="foo" v-bind="obj" class="bar" />`
+ `<div id="foo" v-bind="obj" class="bar" />`,
)
expect(root.helpers).toContain(MERGE_PROPS)
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
+ content: `obj`,
},
createObjectMatcher({
- class: 'bar'
- })
- ]
+ class: 'bar',
+ }),
+ ],
})
})
test('v-on="obj"', () => {
const { root, node } = parseWithElementTransform(
- `<div id="foo" v-on="obj" class="bar" />`
+ `<div id="foo" v-on="obj" class="bar" />`,
)
expect(root.helpers).toContain(MERGE_PROPS)
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.JS_CALL_EXPRESSION,
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
+ content: `obj`,
},
- `true`
- ]
+ `true`,
+ ],
},
createObjectMatcher({
- class: 'bar'
- })
- ]
+ class: 'bar',
+ }),
+ ],
})
})
test('v-on="obj" on component', () => {
const { root, node } = parseWithElementTransform(
- `<Foo id="foo" v-on="obj" class="bar" />`
+ `<Foo id="foo" v-on="obj" class="bar" />`,
)
expect(root.helpers).toContain(MERGE_PROPS)
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.JS_CALL_EXPRESSION,
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
- }
- ]
+ content: `obj`,
+ },
+ ],
},
createObjectMatcher({
- class: 'bar'
- })
- ]
+ class: 'bar',
+ }),
+ ],
})
})
test('v-on="obj" + v-bind="obj"', () => {
const { root, node } = parseWithElementTransform(
- `<div id="foo" v-on="handlers" v-bind="obj" />`
+ `<div id="foo" v-on="handlers" v-bind="obj" />`,
)
expect(root.helpers).toContain(MERGE_PROPS)
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.JS_CALL_EXPRESSION,
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `handlers`
+ content: `handlers`,
},
- `true`
- ]
+ `true`,
+ ],
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
- }
- ]
+ content: `obj`,
+ },
+ ],
})
})
expect(node).toMatchObject({
tag: `"template"`,
props: createObjectMatcher({
- id: 'foo'
- })
+ id: 'foo',
+ }),
})
})
test('should handle <Teleport> with normal children', () => {
function assert(tag: string) {
const { root, node } = parseWithElementTransform(
- `<${tag} target="#foo"><span /></${tag}>`
+ `<${tag} target="#foo"><span /></${tag}>`,
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(TELEPORT)
expect(node).toMatchObject({
tag: TELEPORT,
props: createObjectMatcher({
- target: '#foo'
+ target: '#foo',
}),
children: [
{
tag: 'span',
codegenNode: {
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- }
- }
- ]
+ tag: `"span"`,
+ },
+ },
+ ],
})
}
test('should handle <Suspense>', () => {
function assert(tag: string, content: string, hasFallback?: boolean) {
const { root, node } = parseWithElementTransform(
- `<${tag}>${content}</${tag}>`
+ `<${tag}>${content}</${tag}>`,
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(SUSPENSE)
children: hasFallback
? createObjectMatcher({
default: {
- type: NodeTypes.JS_FUNCTION_EXPRESSION
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
},
fallback: {
- type: NodeTypes.JS_FUNCTION_EXPRESSION
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
},
- _: `[1 /* STABLE */]`
+ _: `[1 /* STABLE */]`,
})
: createObjectMatcher({
default: {
- type: NodeTypes.JS_FUNCTION_EXPRESSION
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
},
- _: `[1 /* STABLE */]`
- })
+ _: `[1 /* STABLE */]`,
+ }),
})
}
assert(
`suspense`,
`<template #default>foo</template><template #fallback>fallback</template>`,
- true
+ true,
)
})
function assert(tag: string) {
const root = parse(`<div><${tag}><span /></${tag}></div>`)
transform(root, {
- nodeTransforms: [transformElement, transformText]
+ nodeTransforms: [transformElement, transformText],
})
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(KEEP_ALIVE)
// keep-alive should not compile content to slots
children: [{ type: NodeTypes.ELEMENT, tag: 'span' }],
// should get a dynamic slots flag to force updates
- patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
+ patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS),
})
}
test('should handle <BaseTransition>', () => {
function assert(tag: string) {
const { root, node } = parseWithElementTransform(
- `<${tag}><span /></${tag}>`
+ `<${tag}><span /></${tag}>`,
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(BASE_TRANSITION)
props: undefined,
children: createObjectMatcher({
default: {
- type: NodeTypes.JS_FUNCTION_EXPRESSION
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
},
- _: `[1 /* STABLE */]`
- })
+ _: `[1 /* STABLE */]`,
+ }),
})
}
parseWithElementTransform(`<div v-bind/>`, { onError })
expect(onError.mock.calls[0]).toMatchObject([
{
- code: ErrorCodes.X_V_BIND_NO_EXPRESSION
- }
+ code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
+ },
])
})
foo(dir) {
_dir = dir
return {
- props: [createObjectProperty(dir.arg!, dir.exp!)]
+ props: [createObjectProperty(dir.arg!, dir.exp!)],
}
- }
- }
+ },
+ },
})
expect(node.props).toMatchObject({
{
type: NodeTypes.JS_PROPERTY,
key: _dir!.arg,
- value: _dir!.exp
- }
- ]
+ value: _dir!.exp,
+ },
+ ],
})
// should factor in props returned by custom directive transforms
// in patchFlag analysis
foo() {
return {
props: [],
- needRuntime: true
+ needRuntime: true,
}
- }
- }
- }
+ },
+ },
+ },
)
expect(root.helpers).toContain(RESOLVE_DIRECTIVE)
expect(root.directives).toContain(`foo`)
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `hello`,
- isStatic: false
+ isStatic: false,
},
// arg
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`,
- isStatic: true
- }
- ]
- }
- ]
- }
+ isStatic: true,
+ },
+ ],
+ },
+ ],
+ },
})
})
foo() {
return {
props: [],
- needRuntime: CREATE_VNODE
+ needRuntime: CREATE_VNODE,
}
- }
- }
- }
+ },
+ },
+ },
)
expect(root.helpers).toContain(CREATE_VNODE)
expect(root.helpers).not.toContain(RESOLVE_DIRECTIVE)
expect(root.directives.length).toBe(0)
expect(node.directives!.elements[0].elements[0]).toBe(
- `_${helperNameMap[CREATE_VNODE]}`
+ `_${helperNameMap[CREATE_VNODE]}`,
)
})
test('runtime directives', () => {
const { root, node } = parseWithElementTransform(
- `<div v-foo v-bar="x" v-baz:[arg].mod.mad="y" />`
+ `<div v-foo v-bar="x" v-baz:[arg].mod.mad="y" />`,
)
expect(root.helpers).toContain(RESOLVE_DIRECTIVE)
expect(root.directives).toContain(`foo`)
elements: [
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
- elements: [`_directive_foo`]
+ elements: [`_directive_foo`],
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
// exp
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `x`
- }
- ]
+ content: `x`,
+ },
+ ],
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `y`,
- isStatic: false
+ isStatic: false,
},
// arg
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `arg`,
- isStatic: false
+ isStatic: false,
},
// modifiers
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `mod`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `true`,
- isStatic: false
- }
+ isStatic: false,
+ },
},
{
type: NodeTypes.JS_PROPERTY,
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `mad`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `true`,
- isStatic: false
- }
- }
- ]
- }
- ]
- }
- ]
- }
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
})
})
`<div @click.foo="a" @click.bar="b" />`,
{
directiveTransforms: {
- on: transformOn
- }
- }
+ on: transformOn,
+ },
+ },
)
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `onClick`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a`,
- isStatic: false
+ isStatic: false,
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `b`,
- isStatic: false
- }
- ]
- }
- }
- ]
+ isStatic: false,
+ },
+ ],
+ },
+ },
+ ],
})
})
{
nodeTransforms: [transformStyle, transformElement],
directiveTransforms: {
- bind: transformBind
- }
- }
+ bind: transformBind,
+ },
+ },
)
expect(root.helpers).toContain(NORMALIZE_STYLE)
expect(node.props).toMatchObject({
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"green"}`,
- isStatic: false
+ isStatic: false,
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ color: 'red' }`,
- isStatic: false
- }
- ]
- }
- ]
- }
- }
- ]
+ isStatic: false,
+ },
+ ],
+ },
+ ],
+ },
+ },
+ ],
})
})
{
nodeTransforms: [transformExpression, transformStyle, transformElement],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.helpers).toContain(NORMALIZE_STYLE)
expect(node.props).toMatchObject({
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: NORMALIZE_STYLE
- }
- }
- ]
+ callee: NORMALIZE_STYLE,
+ },
+ },
+ ],
})
})
{
nodeTransforms: [transformExpression, transformStyle, transformElement],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.helpers).toContain(NORMALIZE_STYLE)
expect(node.props).toMatchObject({
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: NORMALIZE_STYLE
- }
- }
- ]
+ callee: NORMALIZE_STYLE,
+ },
+ },
+ ],
})
})
`<div class="foo" :class="{ bar: isBar }" />`,
{
directiveTransforms: {
- bind: transformBind
- }
- }
+ bind: transformBind,
+ },
+ },
)
expect(root.helpers).toContain(NORMALIZE_CLASS)
expect(node.props).toMatchObject({
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `class`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
- isStatic: true
+ isStatic: true,
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ bar: isBar }`,
- isStatic: false
- }
- ]
- }
- ]
- }
- }
- ]
+ isStatic: false,
+ },
+ ],
+ },
+ ],
+ },
+ },
+ ],
})
})
test('CLASS + STYLE + PROPS', () => {
const { node } = parseWithBind(
- `<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`
+ `<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`,
)
expect(node.patchFlag).toBe(
- genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS])
+ genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS]),
)
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
})
// should treat `class` and `style` as PROPS
test('PROPS on component', () => {
const { node } = parseWithBind(
- `<Foo :id="foo" :class="cls" :style="styl" />`
+ `<Foo :id="foo" :class="cls" :style="styl" />`,
)
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
expect(node.dynamicProps).toBe(`["id", "class", "style"]`)
test('FULL_PROPS (w/ others)', () => {
const { node } = parseWithBind(
- `<div id="foo" v-bind="bar" :class="cls" />`
+ `<div id="foo" v-bind="bar" :class="cls" />`,
)
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
})
test('NEED_PATCH (vnode hooks)', () => {
const root = baseCompile(`<div @vue:updated="foo" />`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
}).ast
const node = (root as any).children[0].codegenNode
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
const { node } = parseWithElementTransform(`<input ref="input"/>`, {
inline: true,
bindingMetadata: {
- input: BindingTypes.SETUP_REF
- }
+ input: BindingTypes.SETUP_REF,
+ },
})
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
type: NodeTypes.JS_PROPERTY,
key: {
content: 'ref_key',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'input',
- isStatic: true
- }
+ isStatic: true,
+ },
},
{
type: NodeTypes.JS_PROPERTY,
key: {
content: 'ref',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'input',
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
test('script setup inline mode template ref (binding does not exist)', () => {
const { node } = parseWithElementTransform(`<input ref="input"/>`, {
- inline: true
+ inline: true,
})
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
type: NodeTypes.JS_PROPERTY,
key: {
content: 'ref',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'input',
- isStatic: true
- }
- }
- ]
+ isStatic: true,
+ },
+ },
+ ],
})
})
inline: true,
bindingMetadata: {
msg: BindingTypes.PROPS,
- ref: BindingTypes.SETUP_CONST
- }
+ ref: BindingTypes.SETUP_CONST,
+ },
})
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
type: NodeTypes.JS_PROPERTY,
key: {
content: 'ref',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'msg',
- isStatic: true
- }
- }
- ]
+ isStatic: true,
+ },
+ },
+ ],
})
})
// ignore click events (has dedicated fast path)
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
directiveTransforms: {
- on: transformOn
- }
+ on: transformOn,
+ },
})
// should only have props flag
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
`<div @keyup="foo" />`,
{
directiveTransforms: {
- on: transformOn
- }
- }
+ on: transformOn,
+ },
+ },
)
expect(node2.patchFlag).toBe(
- genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
+ genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
)
})
test('NEED_HYDRATION for v-bind.prop', () => {
const { node } = parseWithBind(`<div v-bind:id.prop="id" />`)
expect(node.patchFlag).toBe(
- genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
+ genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
)
const { node: node2 } = parseWithBind(`<div .id="id" />`)
expect(node2.patchFlag).toBe(
- genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
+ genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
)
})
`<component :is="foo" @input="foo" />`,
{
directiveTransforms: {
- on: transformOn
- }
- }
+ on: transformOn,
+ },
+ },
)
expect(node.patchFlag).toBe(
- genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
+ genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
)
})
const { node } = parseWithElementTransform(`<div @keydown="foo" />`, {
prefixIdentifiers: true,
bindingMetadata: {
- foo: BindingTypes.SETUP_CONST
+ foo: BindingTypes.SETUP_CONST,
},
directiveTransforms: {
- on: transformOn
- }
+ on: transformOn,
+ },
})
// should only have hydration flag
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
- isStatic: true
- }
- ]
- }
+ isStatic: true,
+ },
+ ],
+ },
})
})
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
- isStatic: true
- }
- ]
- }
+ isStatic: true,
+ },
+ ],
+ },
})
})
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
- isStatic: false
- }
- ]
- }
+ isStatic: false,
+ },
+ ],
+ },
})
})
expect(root.helpers).toContain(RESOLVE_COMPONENT)
expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL,
- tag: '_component_foo'
+ tag: '_component_foo',
})
})
// #3934
test('normal component with is prop', () => {
const { node, root } = parseWithBind(`<custom-input is="foo" />`, {
- isNativeTag: () => false
+ isNativeTag: () => false,
})
expect(root.helpers).toContain(RESOLVE_COMPONENT)
expect(root.helpers).not.toContain(RESOLVE_DYNAMIC_COMPONENT)
expect(node).toMatchObject({
- tag: '_component_custom_input'
+ tag: '_component_custom_input',
})
})
})
test('<svg> should be forced into blocks', () => {
const ast = parse(`<div><svg/></div>`)
transform(ast, {
- nodeTransforms: [transformElement]
+ nodeTransforms: [transformElement],
})
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"svg"`,
- isBlock: true
+ isBlock: true,
})
})
test('force block for inline before-update handlers w/ children', () => {
expect(
parseWithElementTransform(`<div @vue:before-update>hello</div>`).node
- .isBlock
+ .isBlock,
).toBe(true)
})
test('element with dynamic keys should be forced into blocks', () => {
const ast = parse(`<div><div :key="foo" /></div>`)
transform(ast, {
- nodeTransforms: [transformElement]
+ nodeTransforms: [transformElement],
})
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
- isBlock: true
+ isBlock: true,
})
})
prop.type === NodeTypes.ATTRIBUTE &&
prop.name === 'id' &&
prop.value &&
- prop.value.content === 'foo'
+ prop.value.content === 'foo',
)
) {
context.replaceNode({
...node,
- tag: 'span'
+ tag: 'span',
})
}
}
const ast = parse(`<div><div id="foo" /></div>`)
transform(ast, {
- nodeTransforms: [transformElement, transformText, customNodeTransform]
+ nodeTransforms: [transformElement, transformText, customNodeTransform],
})
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: '"span"',
- isBlock: false
+ isBlock: false,
})
})
})
import {
+ BindingTypes,
+ type CompilerOptions,
+ ConstantTypes,
+ type DirectiveNode,
+ type ElementNode,
+ type InterpolationNode,
+ NodeTypes,
+ baseCompile,
baseParse as parse,
transform,
- ElementNode,
- DirectiveNode,
- NodeTypes,
- CompilerOptions,
- InterpolationNode,
- ConstantTypes,
- BindingTypes,
- baseCompile
} from '../../src'
import { transformIf } from '../../src/transforms/vIf'
import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithExpressionTransform(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
) {
const ast = parse(template, options)
transform(ast, {
prefixIdentifiers: true,
nodeTransforms: [transformIf, transformExpression],
- ...options
+ ...options,
})
return ast.children[0]
}
const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.foo`
+ content: `_ctx.foo`,
})
})
const node = parseWithExpressionTransform(`{{}}`) as InterpolationNode
const node2 = parseWithExpressionTransform(`{{ }}`) as InterpolationNode
const node3 = parseWithExpressionTransform(
- `<div>{{ }}</div>`
+ `<div>{{ }}</div>`,
) as ElementNode
const objectToBeMatched = {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: ``
+ content: ``,
}
expect(node.content).toMatchObject(objectToBeMatched)
expect(node2.content).toMatchObject(objectToBeMatched)
expect((node3.children[0] as InterpolationNode).content).toMatchObject(
- objectToBeMatched
+ objectToBeMatched,
)
})
test('interpolation (children)', () => {
const el = parseWithExpressionTransform(
- `<div>{{ foo }}</div>`
+ `<div>{{ foo }}</div>`,
) as ElementNode
const node = el.children[0] as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.foo`
+ content: `_ctx.foo`,
})
})
test('interpolation (complex)', () => {
const el = parseWithExpressionTransform(
- `<div>{{ foo + bar(baz.qux) }}</div>`
+ `<div>{{ foo + bar(baz.qux) }}</div>`,
) as ElementNode
const node = el.children[0] as InterpolationNode
expect(node.content).toMatchObject({
{ content: `_ctx.baz` },
`.`,
{ content: `qux` },
- `)`
- ]
+ `)`,
+ ],
})
})
test('directive value', () => {
const node = parseWithExpressionTransform(
- `<div v-foo:arg="baz"/>`
+ `<div v-foo:arg="baz"/>`,
) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg!
expect(arg).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `arg`
+ content: `arg`,
})
const exp = (node.props[0] as DirectiveNode).exp!
expect(exp).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.baz`
+ content: `_ctx.baz`,
})
})
test('dynamic directive arg', () => {
const node = parseWithExpressionTransform(
- `<div v-foo:[arg]="baz"/>`
+ `<div v-foo:[arg]="baz"/>`,
) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg!
expect(arg).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.arg`
+ content: `_ctx.arg`,
})
const exp = (node.props[0] as DirectiveNode).exp!
expect(exp).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.baz`
+ content: `_ctx.baz`,
})
})
test('should prefix complex expressions', () => {
const node = parseWithExpressionTransform(
- `{{ foo(baz + 1, { key: kuz }) }}`
+ `{{ foo(baz + 1, { key: kuz }) }}`,
) as InterpolationNode
// should parse into compound expression
expect(node.content).toMatchObject({
content: `_ctx.foo`,
loc: {
start: { offset: 3, line: 1, column: 4 },
- end: { offset: 6, line: 1, column: 7 }
- }
+ end: { offset: 6, line: 1, column: 7 },
+ },
},
`(`,
{
content: `_ctx.baz`,
loc: {
start: { offset: 7, line: 1, column: 8 },
- end: { offset: 10, line: 1, column: 11 }
- }
+ end: { offset: 10, line: 1, column: 11 },
+ },
},
` + 1, { key: `,
{
content: `_ctx.kuz`,
loc: {
start: { offset: 23, line: 1, column: 24 },
- end: { offset: 26, line: 1, column: 27 }
- }
+ end: { offset: 26, line: 1, column: 27 },
+ },
},
- ` })`
- ]
+ ` })`,
+ ],
})
})
test('should not prefix whitelisted globals', () => {
const node = parseWithExpressionTransform(
- `{{ Math.max(1, 2) }}`
+ `{{ Math.max(1, 2) }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`]
+ children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`],
})
expect(
(parseWithExpressionTransform(`{{ new Error() }}`) as InterpolationNode)
- .content
+ .content,
).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: ['new ', { content: 'Error' }, '()']
+ children: ['new ', { content: 'Error' }, '()'],
})
})
test('should not prefix reserved literals', () => {
function assert(exp: string) {
const node = parseWithExpressionTransform(
- `{{ ${exp} }}`
+ `{{ ${exp} }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: exp
+ content: exp,
})
}
assert(`true`)
test('should not prefix id of a function declaration', () => {
const node = parseWithExpressionTransform(
- `{{ function foo() { return bar } }}`
+ `{{ function foo() { return bar } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
{ content: `foo` },
`() { return `,
{ content: `_ctx.bar` },
- ` }`
- ]
+ ` }`,
+ ],
})
})
test('should not prefix params of a function expression', () => {
const node = parseWithExpressionTransform(
- `{{ foo => foo + bar }}`
+ `{{ foo => foo + bar }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
` => `,
{ content: `foo` },
` + `,
- { content: `_ctx.bar` }
- ]
+ { content: `_ctx.bar` },
+ ],
})
})
test('should prefix default value of a function expression param', () => {
const node = parseWithExpressionTransform(
- `{{ (foo = baz) => foo + bar }}`
+ `{{ (foo = baz) => foo + bar }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
`) => `,
{ content: `foo` },
` + `,
- { content: `_ctx.bar` }
- ]
+ { content: `_ctx.bar` },
+ ],
})
})
test('should not prefix function param destructuring', () => {
const node = parseWithExpressionTransform(
- `{{ ({ foo }) => foo + bar }}`
+ `{{ ({ foo }) => foo + bar }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
` }) => `,
{ content: `foo` },
` + `,
- { content: `_ctx.bar` }
- ]
+ { content: `_ctx.bar` },
+ ],
})
})
test('function params should not affect out of scope identifiers', () => {
const node = parseWithExpressionTransform(
- `{{ { a: foo => foo, b: foo } }}`
+ `{{ { a: foo => foo, b: foo } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
{ content: `foo` },
`, b: `,
{ content: `_ctx.foo` },
- ` }`
- ]
+ ` }`,
+ ],
})
})
test('should prefix default value of function param destructuring', () => {
const node = parseWithExpressionTransform(
- `{{ ({ foo = bar }) => foo + bar }}`
+ `{{ ({ foo = bar }) => foo + bar }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
` }) => `,
{ content: `foo` },
` + `,
- { content: `_ctx.bar` }
- ]
+ { content: `_ctx.bar` },
+ ],
})
})
test('should not prefix an object property key', () => {
const node = parseWithExpressionTransform(
- `{{ { foo() { baz() }, value: bar } }}`
+ `{{ { foo() { baz() }, value: bar } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
{ content: `_ctx.baz` },
`() }, value: `,
{ content: `_ctx.bar` },
- ` }`
- ]
+ ` }`,
+ ],
})
})
test('should not duplicate object key with same name as value', () => {
const node = parseWithExpressionTransform(
- `{{ { foo: foo } }}`
+ `{{ { foo: foo } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ foo: `, { content: `_ctx.foo` }, ` }`]
+ children: [`{ foo: `, { content: `_ctx.foo` }, ` }`],
})
})
test('should prefix a computed object property key', () => {
const node = parseWithExpressionTransform(
- `{{ { [foo]: bar } }}`
+ `{{ { [foo]: bar } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
{ content: `_ctx.foo` },
`]: `,
{ content: `_ctx.bar` },
- ` }`
- ]
+ ` }`,
+ ],
})
})
test('should prefix object property shorthand value', () => {
const node = parseWithExpressionTransform(
- `{{ { foo } }}`
+ `{{ { foo } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ foo: `, { content: `_ctx.foo` }, ` }`]
+ children: [`{ foo: `, { content: `_ctx.foo` }, ` }`],
})
})
test('should not prefix id in a member expression', () => {
const node = parseWithExpressionTransform(
- `{{ foo.bar.baz }}`
+ `{{ foo.bar.baz }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
`.`,
{ content: `bar` },
`.`,
- { content: `baz` }
- ]
+ { content: `baz` },
+ ],
})
})
test('should prefix computed id in a member expression', () => {
const node = parseWithExpressionTransform(
- `{{ foo[bar][baz] }}`
+ `{{ foo[bar][baz] }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
{ content: `_ctx.bar` },
`][`,
{ content: '_ctx.baz' },
- `]`
- ]
+ `]`,
+ ],
})
})
const onError = vi.fn()
parseWithExpressionTransform(`{{ a( }}`, { onError })
expect(onError.mock.calls[0][0].message).toMatch(
- `Error parsing JavaScript expression: Unexpected token`
+ `Error parsing JavaScript expression: Unexpected token`,
)
})
test('should prefix in assignment', () => {
const node = parseWithExpressionTransform(
- `{{ x = 1 }}`
+ `{{ x = 1 }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `_ctx.x` }, ` = 1`]
+ children: [{ content: `_ctx.x` }, ` = 1`],
})
})
test('should prefix in assignment pattern', () => {
const node = parseWithExpressionTransform(
- `{{ { x, y: [z] } = obj }}`
+ `{{ { x, y: [z] } = obj }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
`, y: [`,
{ content: `_ctx.z` },
`] } = `,
- { content: `_ctx.obj` }
- ]
+ { content: `_ctx.obj` },
+ ],
})
})
// #8295
test('should treat floating point number literals as constant', () => {
const node = parseWithExpressionTransform(
- `{{ [1, 2.1] }}`
+ `{{ [1, 2.1] }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
- constType: ConstantTypes.CAN_STRINGIFY
+ constType: ConstantTypes.CAN_STRINGIFY,
})
})
describe('ES Proposals support', () => {
test('bigInt', () => {
const node = parseWithExpressionTransform(
- `{{ 13000n }}`
+ `{{ 13000n }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `13000n`,
isStatic: false,
- constType: ConstantTypes.CAN_STRINGIFY
+ constType: ConstantTypes.CAN_STRINGIFY,
})
})
test('nullish coalescing', () => {
const node = parseWithExpressionTransform(
- `{{ a ?? b }}`
+ `{{ a ?? b }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }]
+ children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }],
})
})
test('optional chaining', () => {
const node = parseWithExpressionTransform(
- `{{ a?.b?.c }}`
+ `{{ a?.b?.c }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
`?.`,
{ content: `b` },
`?.`,
- { content: `c` }
- ]
+ { content: `c` },
+ ],
})
})
[
'pipelineOperator',
{
- proposal: 'minimal'
- }
- ]
- ]
+ proposal: 'minimal',
+ },
+ ],
+ ],
}) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `_ctx.a` }, ` |> `, { content: `_ctx.uppercase` }]
+ children: [
+ { content: `_ctx.a` },
+ ` |> `,
+ { content: `_ctx.uppercase` },
+ ],
})
})
})
options: BindingTypes.OPTIONS,
reactive: BindingTypes.SETUP_REACTIVE_CONST,
literal: BindingTypes.LITERAL_CONST,
- isNaN: BindingTypes.SETUP_REF
+ isNaN: BindingTypes.SETUP_REF,
}
function compileWithBindingMetadata(
template: string,
- options?: CompilerOptions
+ options?: CompilerOptions,
) {
return baseCompile(template, {
prefixIdentifiers: true,
bindingMetadata,
- ...options
+ ...options,
})
}
test('non-inline mode', () => {
const { code } = compileWithBindingMetadata(
- `<div>{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}</div>`
+ `<div>{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}</div>`,
)
expect(code).toMatch(`$props.props`)
expect(code).toMatch(`$setup.setup`)
for (const x in list) {
log(x)
}
- }"/>`
+ }"/>`,
)
expect(code).not.toMatch(`_ctx.x`)
expect(code).toMatchSnapshot()
for (const x of list) {
log(x)
}
- }"/>`
+ }"/>`,
)
expect(code).not.toMatch(`_ctx.x`)
expect(code).toMatchSnapshot()
for (let i = 0; i < list.length; i++) {
log(i)
}
- }"/>`
+ }"/>`,
)
expect(code).not.toMatch(`_ctx.i`)
expect(code).toMatchSnapshot()
test('inline mode', () => {
const { code } = compileWithBindingMetadata(
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`,
- { inline: true }
+ { inline: true },
)
expect(code).toMatch(`__props.props`)
expect(code).toMatch(`_unref(setup)`)
test('literal const handling', () => {
const { code } = compileWithBindingMetadata(`<div>{{ literal }}</div>`, {
- inline: true
+ inline: true,
})
expect(code).toMatch(`toDisplayString(literal)`)
// #7973 should skip patch for literal const
expect(code).not.toMatch(
- `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
+ `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
)
})
expect(code).toMatch(`toDisplayString($setup.literal)`)
// #7973 should skip patch for literal const
expect(code).not.toMatch(
- `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
+ `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
)
})
test('reactive const handling', () => {
const { code } = compileWithBindingMetadata(`<div>{{ reactive }}</div>`, {
- inline: true
+ inline: true,
})
// #7973 should not skip patch for reactive const
expect(code).toMatch(
- `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
+ `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
)
})
})
import {
- CompilerOptions,
+ type CompilerOptions,
+ type ElementNode,
+ ErrorCodes,
+ NodeTypes,
baseParse as parse,
transform,
- ElementNode,
- NodeTypes,
- ErrorCodes
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet,
- transformElement
+ transformElement,
],
directiveTransforms: {
on: transformOn,
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
return ast
}
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: [`$slots`, `"default"`]
+ arguments: [`$slots`, `"default"`],
})
})
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: [`$slots`, `"foo"`]
+ arguments: [`$slots`, `"foo"`],
})
})
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
- isStatic: false
- }
- ]
+ isStatic: false,
+ },
+ ],
})
})
test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo`,
- isStatic: false
+ isStatic: false,
},
` + `,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.bar`,
- isStatic: false
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ ],
+ },
+ ],
})
})
test('default slot outlet with props', () => {
const ast = parseWithSlots(
- `<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`
+ `<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`,
)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: true
- }
+ isStatic: true,
+ },
},
{
key: {
content: `baz`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `qux`,
- isStatic: false
- }
+ isStatic: false,
+ },
},
{
key: {
content: `fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `foo-bar`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: true
- }
+ isStatic: true,
+ },
},
{
key: {
content: `baz`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `qux`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: true
- }
+ isStatic: true,
+ },
},
{
key: {
content: `baz`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `qux`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- ]
+ tag: `div`,
+ },
+ ],
+ },
+ ],
})
})
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- ]
+ tag: `div`,
+ },
+ ],
+ },
+ ],
})
})
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
},
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- ]
+ tag: `div`,
+ },
+ ],
+ },
+ ],
})
})
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
},
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- ]
+ tag: `div`,
+ },
+ ],
+ },
+ ],
})
})
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: [`$slots`, `"default"`, `{}`, `undefined`, `true`]
+ arguments: [`$slots`, `"default"`, `{}`, `undefined`, `true`],
})
const fallback = parseWithSlots(`<slot>fallback</slot>`, {
slotted: false,
- scopeId: 'foo'
+ scopeId: 'foo',
})
const child = {
returns: [
{
type: NodeTypes.TEXT,
- content: `fallback`
- }
- ]
+ content: `fallback`,
+ },
+ ],
}
expect((fallback.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: [`$slots`, `"default"`, `{}`, child, `true`]
+ arguments: [`$slots`, `"default"`, `{}`, child, `true`],
})
})
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 5,
line: 1,
- column: index + 6
- }
- }
+ column: index + 6,
+ },
+ },
})
})
})
import {
- CompilerOptions,
- baseParse as parse,
- transform,
+ type CompilerOptions,
+ type ElementNode,
+ type ForNode,
NodeTypes,
generate,
- ForNode,
- ElementNode
+ baseParse as parse,
+ transform,
} from '../../src'
import { transformFor } from '../../src/transforms/vFor'
import { transformText } from '../../src/transforms/transformText'
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement,
- transformText
+ transformText,
],
- ...options
+ ...options,
})
return ast
}
expect(root.children[0]).toMatchObject({
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
` + `,
{ type: NodeTypes.TEXT, content: ` bar ` },
` + `,
- { type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
- ]
+ { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
+ ],
})
expect(generate(root).code).toMatchSnapshot()
})
` + `,
{ type: NodeTypes.TEXT, content: ` bar ` },
` + `,
- { type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
- ]
+ { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
+ ],
},
- genFlagText(PatchFlags.TEXT)
- ]
- }
+ genFlagText(PatchFlags.TEXT),
+ ],
+ },
})
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot()
arguments: [
{
type: NodeTypes.TEXT,
- content: `hello`
- }
+ content: `hello`,
+ },
// should have no flag
- ]
- }
+ ],
+ },
})
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot()
test('consecutive text mixed with elements', () => {
const root = transformWithTextOpt(
- `<div/>{{ foo }} bar {{ baz }}<div/>hello<div/>`
+ `<div/>{{ foo }} bar {{ baz }}<div/>hello<div/>`,
)
expect(root.children.length).toBe(5)
expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
` + `,
{ type: NodeTypes.TEXT, content: ` bar ` },
` + `,
- { type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
- ]
+ { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
+ ],
},
- genFlagText(PatchFlags.TEXT)
- ]
- }
+ genFlagText(PatchFlags.TEXT),
+ ],
+ },
})
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(root.children[3]).toMatchObject({
arguments: [
{
type: NodeTypes.TEXT,
- content: `hello`
- }
- ]
- }
+ content: `hello`,
+ },
+ ],
+ },
})
expect(root.children[4].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot()
test('<template v-for>', () => {
const root = transformWithTextOpt(
- `<template v-for="i in list">foo</template>`
+ `<template v-for="i in list">foo</template>`,
)
expect(root.children[0].type).toBe(NodeTypes.FOR)
const forNode = root.children[0] as ForNode
// should convert template v-for text children because they are inside
// fragments
expect(forNode.children[0]).toMatchObject({
- type: NodeTypes.TEXT_CALL
+ type: NodeTypes.TEXT_CALL,
})
expect(generate(root).code).toMatchSnapshot()
})
test('with prefixIdentifiers: true', () => {
const root = transformWithTextOpt(`{{ foo }} bar {{ baz + qux }}`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(root.children.length).toBe(1)
expect(root.children[0]).toMatchObject({
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `_ctx.baz` }, ` + `, { content: `_ctx.qux` }]
- }
- }
- ]
+ children: [{ content: `_ctx.baz` }, ` + `, { content: `_ctx.qux` }],
+ },
+ },
+ ],
})
expect(
generate(root, {
- prefixIdentifiers: true
- }).code
+ prefixIdentifiers: true,
+ }).code,
).toMatchSnapshot()
})
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: 'foo'
- }
+ content: 'foo',
+ },
},
- genFlagText(PatchFlags.TEXT)
- ]
- }
+ genFlagText(PatchFlags.TEXT),
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
import {
- baseParse as parse,
- transform,
- ElementNode,
- ObjectExpression,
- CompilerOptions,
+ type CallExpression,
+ type CompilerOptions,
+ type ElementNode,
ErrorCodes,
- VNodeCall,
NodeTypes,
- CallExpression
+ type ObjectExpression,
+ type VNodeCall,
+ baseParse as parse,
+ transform,
} from '../../src'
import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement'
import {
CAMELIZE,
+ NORMALIZE_PROPS,
helperNameMap,
- NORMALIZE_PROPS
} from '../../src/runtimeHelpers'
import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithVBind(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
): ElementNode {
const ast = parse(template)
transform(ast, {
nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []),
- transformElement
+ transformElement,
],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
return ast.children[0] as ElementNode
}
loc: {
start: {
line: 1,
- column: 13
+ column: 13,
},
end: {
line: 1,
- column: 15
- }
- }
+ column: 15,
+ },
+ },
},
value: {
content: `id`,
loc: {
start: {
line: 1,
- column: 17
+ column: 17,
},
end: {
line: 1,
- column: 19
- }
- }
- }
+ column: 19,
+ },
+ },
+ },
})
})
isStatic: true,
loc: {
start: { line: 1, column: 13, offset: 12 },
- end: { line: 1, column: 15, offset: 14 }
- }
+ end: { line: 1, column: 15, offset: 14 },
+ },
},
value: {
content: `id`,
isStatic: false,
loc: {
start: { line: 1, column: 13, offset: 12 },
- end: { line: 1, column: 15, offset: 14 }
- }
- }
+ end: { line: 1, column: 15, offset: 14 },
+ },
+ },
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `id`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
{
key: {
content: `id || ""`,
- isStatic: false
+ isStatic: false,
},
value: {
content: `id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
loc: {
start: {
line: 1,
- column: 6
+ column: 6,
},
end: {
line: 1,
- column: 19
- }
- }
+ column: 19,
+ },
+ },
})
expect(props.properties[0]).toMatchObject({
key: {
content: `arg`,
- isStatic: true
+ isStatic: true,
},
value: {
content: ``,
- isStatic: true
- }
+ isStatic: true,
+ },
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `fooBar`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
{
key: {
content: `_${helperNameMap[CAMELIZE]}(foo || "")`,
- isStatic: false
+ isStatic: false,
},
value: {
content: `id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
test('.camel modifier w/ dynamic arg + prefixIdentifiers', () => {
const node = parseWithVBind(`<div v-bind:[foo(bar)].camel="id"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const props = (node.codegenNode as VNodeCall).props as CallExpression
expect(props).toMatchObject({
{ content: `_ctx.bar` },
`)`,
`) || ""`,
- `)`
- ]
+ `)`,
+ ],
},
value: {
content: `_ctx.id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `.fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `.fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `fooBar`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
{
key: {
content: '`.${fooBar || ""}`',
- isStatic: false
+ isStatic: false,
},
value: {
content: `id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
test('.prop modifier w/ dynamic arg + prefixIdentifiers', () => {
const node = parseWithVBind(`<div v-bind:[foo(bar)].prop="id"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const props = (node.codegenNode as VNodeCall).props as CallExpression
expect(props).toMatchObject({
{ content: `_ctx.bar` },
`)`,
`) || ""`,
- `)`
- ]
+ `)`,
+ ],
},
value: {
content: `_ctx.id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `.fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `.fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `fooBar`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `^foo-bar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
expect(props.properties[0]).toMatchObject({
key: {
content: `^foo-bar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `fooBar`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
})
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import { transformExpression } from '../../src/transforms/transformExpression'
import {
- ForNode,
+ ConstantTypes,
+ type ElementNode,
+ type ForCodegenNode,
+ type ForNode,
+ type InterpolationNode,
NodeTypes,
- SimpleExpressionNode,
- ElementNode,
- InterpolationNode,
- ForCodegenNode,
- ConstantTypes
+ type SimpleExpressionNode,
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
-import { CompilerOptions, generate } from '../../src'
+import { type CompilerOptions, generate } from '../../src'
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
import { createObjectMatcher, genFlagText } from '../testUtils'
function parseWithForTransform(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
) {
const ast = parse(template, options)
transform(ast, {
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet,
- transformElement
+ transformElement,
],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
return {
root: ast,
- node: ast.children[0] as ForNode & { codegenNode: ForCodegenNode }
+ node: ast.children[0] as ForNode & { codegenNode: ForCodegenNode },
}
}
describe('transform', () => {
test('number expression', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="index in 5" />'
+ '<span v-for="index in 5" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
test('value', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="(item) in items" />'
+ '<span v-for="(item) in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
test('object de-structured value', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="({ id, value }) in items" />'
+ '<span v-for="({ id, value }) in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe(
- '{ id, value }'
+ '{ id, value }',
)
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('array de-structured value', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="([ id, value ]) in items" />'
+ '<span v-for="([ id, value ]) in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe(
- '[ id, value ]'
+ '[ id, value ]',
)
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('value and key', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="(item, key) in items" />'
+ '<span v-for="(item, key) in items" />',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
test('value, key and index', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="(value, key, index) in items" />'
+ '<span v-for="(value, key, index) in items" />',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
test('skipped key', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="(value,,index) in items" />'
+ '<span v-for="(value,,index) in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
test('skipped value and key', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="(,,index) in items" />'
+ '<span v-for="(,,index) in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect(forNode.valueAlias).toBeUndefined()
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
test('unbracketed value', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="item in items" />'
+ '<span v-for="item in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
test('unbracketed value and key', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="item, key in items" />'
+ '<span v-for="item, key in items" />',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
test('unbracketed value, key and index', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="value, key, index in items" />'
+ '<span v-for="value, key, index in items" />',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
test('unbracketed skipped key', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for="value, , index in items" />'
+ '<span v-for="value, , index in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
test('unbracketed skipped value and key', () => {
const { node: forNode } = parseWithForTransform(
- '<span v-for=", , index in items" />'
+ '<span v-for=", , index in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect(forNode.valueAlias).toBeUndefined()
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_NO_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_NO_EXPRESSION,
+ }),
)
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
)
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
)
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
)
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
)
})
<template v-for="item in items">
<div :key="item.id"/>
</template>`,
- { onError }
+ { onError },
)
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT
- })
+ code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
+ }),
)
// should not warn on nested v-for keys
<template v-for="item in items">
<div v-for="c in item.children" :key="c.id"/>
</template>`,
- { onError }
+ { onError },
)
expect(onError).toHaveBeenCalledTimes(1)
})
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
})
describe('prefixIdentifiers: true', () => {
test('should prefix v-for source', () => {
const { node } = parseWithForTransform(`<div v-for="i in list"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(node.source).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.list`
+ content: `_ctx.list`,
})
})
test('should prefix v-for source w/ complex expression', () => {
const { node } = parseWithForTransform(
`<div v-for="i in list.concat([foo])"/>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(node.source).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
{ content: `concat` },
`([`,
{ content: `_ctx.foo` },
- `])`
- ]
+ `])`,
+ ],
})
})
test('should not prefix v-for alias', () => {
const { node } = parseWithForTransform(
`<div v-for="i in list">{{ i }}{{ j }}</div>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `i`
+ content: `i`,
})
expect((div.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.j`
+ content: `_ctx.j`,
})
})
test('should not prefix v-for aliases (multiple)', () => {
const { node } = parseWithForTransform(
`<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
` + `,
{ content: `j` },
` + `,
- { content: `k` }
- ]
+ { content: `k` },
+ ],
})
expect((div.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.l`
+ content: `_ctx.l`,
})
})
test('should prefix id outside of v-for', () => {
const { node } = parseWithForTransform(
`<div><div v-for="i in list" />{{ i }}</div>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect((node.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.i`
+ content: `_ctx.i`,
})
})
`<div v-for="i in list">
<div v-for="i in list">{{ i + j }}</div>{{ i }}
</div>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const outerDiv = node.children[0] as ElementNode
const innerFor = outerDiv.children[0] as ForNode
.children[0] as InterpolationNode
expect(innerExp.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }]
+ children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }],
})
// when an inner v-for shadows a variable of an outer v-for and exit,
const outerExp = outerDiv.children[1] as InterpolationNode
expect(outerExp.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `i`
+ content: `i`,
})
})
`<div v-for="({ foo = bar, baz: [qux = quux] }) in list">
{{ foo + bar + baz + qux + quux }}
</div>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(node.valueAlias!).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
{ content: `qux` },
` = `,
{ content: `_ctx.quux` },
- `] }`
- ]
+ `] }`,
+ ],
})
const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
` + `,
{ content: `qux` },
` + `,
- { content: `_ctx.quux` }
- ]
+ { content: `_ctx.quux` },
+ ],
})
})
test('element v-for key expression prefixing', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
'<div v-for="item in items" :key="itemKey(item)">test</div>',
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
`(`,
// should NOT prefix in scope variables
{ content: `item` },
- `)`
- ]
- }
- })
+ `)`,
+ ],
+ },
+ }),
})
})
// #2085
test('template v-for key expression prefixing', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
'<template v-for="item in items" :key="itemKey(item)">test</template>',
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
`(`,
// should NOT prefix in scope variables
{ content: `item` },
- `)`
- ]
- }
- })
+ `)`,
+ ],
+ },
+ }),
})
})
test('template v-for key no prefixing on attribute key', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
'<template v-for="item in items" key="key">test</template>',
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
props: createObjectMatcher({
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: 'key'
- }
- })
+ content: 'key',
+ },
+ }),
})
})
})
node: ForCodegenNode,
keyed: boolean = false,
customReturn: boolean = false,
- disableTracking: boolean = true
+ disableTracking: boolean = true,
) {
expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL,
? {}
: {
type: NodeTypes.VNODE_CALL,
- isBlock: disableTracking
- }
- }
- ]
- }
+ isBlock: disableTracking,
+ },
+ },
+ ],
+ },
})
const renderListArgs = node.children.arguments
return {
source: renderListArgs[0] as SimpleExpressionNode,
params: (renderListArgs[1] as any).params,
returns: (renderListArgs[1] as any).returns,
- innerVNodeCall: customReturn ? null : (renderListArgs[1] as any).returns
+ innerVNodeCall: customReturn
+ ? null
+ : (renderListArgs[1] as any).returns,
}
}
test('basic v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<span v-for="(item) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
innerVNodeCall: {
- tag: `"span"`
- }
+ tag: `"span"`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('value + key + index', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<span v-for="(item, key, index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
- params: [{ content: `item` }, { content: `key` }, { content: `index` }]
+ params: [{ content: `item` }, { content: `key` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
test('skipped value', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<span v-for="(, key, index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
- params: [{ content: `_` }, { content: `key` }, { content: `index` }]
+ params: [{ content: `_` }, { content: `key` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
test('skipped key', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<span v-for="(item,,index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
- params: [{ content: `item` }, { content: `__` }, { content: `index` }]
+ params: [{ content: `item` }, { content: `__` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
test('skipped value & key', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<span v-for="(,,index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
- params: [{ content: `_` }, { content: `__` }, { content: `index` }]
+ params: [{ content: `_` }, { content: `__` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-for with constant expression', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<p v-for="item in 10">{{item}}</p>', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(
codegenNode,
false /* keyed */,
false /* customReturn */,
- false /* disableTracking */
- )
+ false /* disableTracking */,
+ ),
).toMatchObject({
source: { content: `10`, constType: ConstantTypes.CAN_STRINGIFY },
params: [{ content: `item` }],
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
isStatic: false,
- constType: ConstantTypes.NOT_CONSTANT
- }
+ constType: ConstantTypes.NOT_CONSTANT,
+ },
},
- patchFlag: genFlagText(PatchFlags.TEXT)
- }
+ patchFlag: genFlagText(PatchFlags.TEXT),
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('template v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
- '<template v-for="item in items">hello<span/></template>'
+ '<template v-for="item in items">hello<span/></template>',
)
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
isBlock: true,
children: [
{ type: NodeTypes.TEXT, content: `hello` },
- { type: NodeTypes.ELEMENT, tag: `span` }
+ { type: NodeTypes.ELEMENT, tag: `span` },
],
- patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
- }
+ patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('template v-for w/ <slot/>', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
- '<template v-for="item in items"><slot/></template>'
+ '<template v-for="item in items"><slot/></template>',
)
expect(
- assertSharedCodegen(codegenNode, false, true /* custom return */)
+ assertSharedCodegen(codegenNode, false, true /* custom return */),
).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
returns: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ callee: RENDER_SLOT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('template v-for key injection with single child', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
- '<template v-for="item in items" :key="item.id"><span :id="item.id" /></template>'
+ '<template v-for="item in items" :key="item.id"><span :id="item.id" /></template>',
)
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
tag: `"span"`,
props: createObjectMatcher({
key: '[item.id]',
- id: '[item.id]'
- })
- }
+ id: '[item.id]',
+ }),
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-for on <slot/>', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<slot v-for="item in items"></slot>')
expect(
- assertSharedCodegen(codegenNode, false, true /* custom return */)
+ assertSharedCodegen(codegenNode, false, true /* custom return */),
).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
returns: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ callee: RENDER_SLOT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('keyed v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<span v-for="(item) in items" :key="item" />')
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
innerVNodeCall: {
tag: `"span"`,
props: createObjectMatcher({
- key: `[item]`
- })
- }
+ key: `[item]`,
+ }),
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('keyed template v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
- '<template v-for="item in items" :key="item">hello<span/></template>'
+ '<template v-for="item in items" :key="item">hello<span/></template>',
)
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
innerVNodeCall: {
tag: FRAGMENT,
props: createObjectMatcher({
- key: `[item]`
+ key: `[item]`,
}),
children: [
{ type: NodeTypes.TEXT, content: `hello` },
- { type: NodeTypes.ELEMENT, tag: `span` }
+ { type: NodeTypes.ELEMENT, tag: `span` },
],
- patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
- }
+ patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-if + v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(`<div v-if="ok" v-for="i in list"/>`)
expect(codegenNode).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
type: NodeTypes.VNODE_CALL,
props: createObjectMatcher({
- key: `[0]`
+ key: `[0]`,
}),
isBlock: true,
disableTracking: true,
returns: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
- isBlock: true
- }
- }
- ]
- }
- }
+ isBlock: true,
+ },
+ },
+ ],
+ },
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-if + v-for on <template>', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(`<template v-if="ok" v-for="i in list"/>`)
expect(codegenNode).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
type: NodeTypes.VNODE_CALL,
props: createObjectMatcher({
- key: `[0]`
+ key: `[0]`,
}),
isBlock: true,
disableTracking: true,
returns: {
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
- isBlock: true
- }
- }
- ]
- }
- }
+ isBlock: true,
+ },
+ },
+ ],
+ },
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-for on element with custom directive', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('<div v-for="i in list" v-foo/>')
const { returns } = assertSharedCodegen(codegenNode, false, true)
expect(returns).toMatchObject({
type: NodeTypes.VNODE_CALL,
- directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
+ directives: { type: NodeTypes.JS_ARRAY_EXPRESSION },
})
expect(generate(root).code).toMatchSnapshot()
})
import { transformElement } from '../../src/transforms/transformElement'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import {
- CommentNode,
- ConditionalExpression,
- ElementNode,
+ type CommentNode,
+ type ConditionalExpression,
+ type ElementNode,
ElementTypes,
- IfBranchNode,
- IfConditionalExpression,
- IfNode,
+ type IfBranchNode,
+ type IfConditionalExpression,
+ type IfNode,
NodeTypes,
- SimpleExpressionNode,
- TextNode,
- VNodeCall
+ type SimpleExpressionNode,
+ type TextNode,
+ type VNodeCall,
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
-import { CompilerOptions, generate, TO_HANDLERS } from '../../src'
+import { type CompilerOptions, TO_HANDLERS, generate } from '../../src'
import {
CREATE_COMMENT,
FRAGMENT,
MERGE_PROPS,
NORMALIZE_PROPS,
- RENDER_SLOT
+ RENDER_SLOT,
} from '../../src/runtimeHelpers'
import { createObjectMatcher } from '../testUtils'
template: string,
options: CompilerOptions = {},
returnIndex: number = 0,
- childrenLen: number = 1
+ childrenLen: number = 1,
) {
const ast = parse(template, options)
transform(ast, {
nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
- ...options
+ ...options,
})
if (!options.onError) {
expect(ast.children.length).toBe(childrenLen)
root: ast,
node: ast.children[returnIndex] as IfNode & {
codegenNode: IfConditionalExpression
- }
+ },
}
}
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
- `ok`
+ `ok`,
)
expect(node.branches[0].children.length).toBe(1)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
test('template v-if', () => {
const { node } = parseWithIfTransform(
- `<template v-if="ok"><div/>hello<p/></template>`
+ `<template v-if="ok"><div/>hello<p/></template>`,
)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
- `ok`
+ `ok`,
)
expect(node.branches[0].children.length).toBe(3)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
expect((node.branches[0].children[0] as ElementNode).tag).toBe(
- `Component`
+ `Component`,
)
expect((node.branches[0].children[0] as ElementNode).tagType).toBe(
- ElementTypes.COMPONENT
+ ElementTypes.COMPONENT,
)
// #2058 since a component may fail to resolve and fallback to a plain
// element, it still needs to be made a block
expect(
((node.branches[0].children[0] as ElementNode)!
- .codegenNode as VNodeCall)!.isBlock
+ .codegenNode as VNodeCall)!.isBlock,
).toBe(true)
})
test('v-if + v-else-if', () => {
const { node } = parseWithIfTransform(
- `<div v-if="ok"/><p v-else-if="orNot"/>`
+ `<div v-if="ok"/><p v-else-if="orNot"/>`,
)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(2)
test('v-if + v-else-if + v-else', () => {
const { node } = parseWithIfTransform(
- `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
+ `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`,
)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(3)
test('should prefix v-if condition', () => {
const { node } = parseWithIfTransform(`<div v-if="ok"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(node.branches[0].condition).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.ok`
+ content: `_ctx.ok`,
})
})
})
expect(onError.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node1.loc
- }
+ loc: node1.loc,
+ },
])
const { node: node2 } = parseWithIfTransform(
`<div/><div v-else/>`,
{ onError },
- 1
+ 1,
)
expect(onError.mock.calls[1]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node2.loc
- }
+ loc: node2.loc,
+ },
])
const { node: node3 } = parseWithIfTransform(
`<div/>foo<div v-else/>`,
{ onError },
- 2
+ 2,
)
expect(onError.mock.calls[2]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node3.loc
- }
+ loc: node3.loc,
+ },
])
})
const onError = vi.fn()
const { node: node1 } = parseWithIfTransform(`<div v-else-if="foo"/>`, {
- onError
+ onError,
})
expect(onError.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node1.loc
- }
+ loc: node1.loc,
+ },
])
const { node: node2 } = parseWithIfTransform(
`<div/><div v-else-if="foo"/>`,
{ onError },
- 1
+ 1,
)
expect(onError.mock.calls[1]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node2.loc
- }
+ loc: node2.loc,
+ },
])
const { node: node3 } = parseWithIfTransform(
`<div/>foo<div v-else-if="foo"/>`,
{ onError },
- 2
+ 2,
)
expect(onError.mock.calls[2]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node3.loc
- }
+ loc: node3.loc,
+ },
])
const {
- node: { branches }
+ node: { branches },
} = parseWithIfTransform(
`<div v-if="notOk"/><div v-else/><div v-else-if="ok"/>`,
{ onError },
- 0
+ 0,
)
expect(onError.mock.calls[3]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: branches[branches.length - 1].loc
- }
+ loc: branches[branches.length - 1].loc,
+ },
])
})
// dynamic
parseWithIfTransform(
`<div v-if="ok" :key="a + 1" /><div v-else :key="a + 1" />`,
- { onError }
+ { onError },
)
expect(onError.mock.calls[0]).toMatchObject([
{
- code: ErrorCodes.X_V_IF_SAME_KEY
- }
+ code: ErrorCodes.X_V_IF_SAME_KEY,
+ },
])
// static
parseWithIfTransform(`<div v-if="ok" key="1" /><div v-else key="1" />`, {
- onError
+ onError,
})
expect(onError.mock.calls[1]).toMatchObject([
{
- code: ErrorCodes.X_V_IF_SAME_KEY
- }
+ code: ErrorCodes.X_V_IF_SAME_KEY,
+ },
])
})
})
function assertSharedCodegen(
node: IfConditionalExpression,
depth: number = 0,
- hasElse: boolean = false
+ hasElse: boolean = false,
) {
expect(node).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
- content: `ok`
+ content: `ok`,
},
consequent: {
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
},
alternate:
depth < 1
? hasElse
? {
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
}
: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_COMMENT
+ callee: CREATE_COMMENT,
}
: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
- content: `orNot`
+ content: `orNot`,
},
consequent: {
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
},
alternate: hasElse
? {
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
}
: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_COMMENT
- }
- }
+ callee: CREATE_COMMENT,
+ },
+ },
})
}
test('basic v-if', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok"/>`)
assertSharedCodegen(codegenNode)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_COMMENT
+ callee: CREATE_COMMENT,
})
expect(generate(root).code).toMatchSnapshot()
})
test('template v-if', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<template v-if="ok"><div/>hello<p/></template>`)
assertSharedCodegen(codegenNode)
expect(codegenNode.consequent).toMatchObject({
children: [
{ type: NodeTypes.ELEMENT, tag: 'div' },
{ type: NodeTypes.TEXT, content: `hello` },
- { type: NodeTypes.ELEMENT, tag: 'p' }
- ]
+ { type: NodeTypes.ELEMENT, tag: 'p' },
+ ],
})
expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_COMMENT
+ callee: CREATE_COMMENT,
})
expect(generate(root).code).toMatchSnapshot()
})
test('template v-if w/ single <slot/> child', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
+ arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })],
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-if on <slot/>', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<slot v-if="ok"></slot>`)
expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
+ arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })],
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-if + v-else', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
assertSharedCodegen(codegenNode, 0, true)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
expect(codegenNode.alternate).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-if + v-else-if', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot" />`)
assertSharedCodegen(codegenNode, 1)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(generate(root).code).toMatchSnapshot()
})
test('v-if + v-else-if + v-else', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(
- `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
+ `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`,
)
assertSharedCodegen(codegenNode, 1, true)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(branch2.alternate).toMatchObject({
tag: FRAGMENT,
children: [
{
type: NodeTypes.TEXT,
- content: `fine`
- }
- ]
+ content: `fine`,
+ },
+ ],
})
expect(generate(root).code).toMatchSnapshot()
})
`<div v-if="ok"/><p v-if="orNot"/>`,
{},
0 /* returnIndex, just give the default value */,
- 2 /* childrenLen */
+ 2 /* childrenLen */,
)
const ifNode = root.children[0] as IfNode & {
}
expect(ifNode.codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
const ifNode2 = root.children[1] as IfNode & {
codegenNode: IfConditionalExpression
}
expect(ifNode2.codegenNode.consequent).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(generate(root).code).toMatchSnapshot()
})
`<div v-if="ok"/><p v-else/><div v-if="another"/><p v-else-if="orNot"/><p v-else/>`,
{},
0 /* returnIndex, just give the default value */,
- 2 /* childrenLen */
+ 2 /* childrenLen */,
)
const ifNode = root.children[0] as IfNode & {
codegenNode: IfConditionalExpression
}
expect(ifNode.codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
expect(ifNode.codegenNode.alternate).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
const ifNode2 = root.children[1] as IfNode & {
codegenNode: IfConditionalExpression
}
expect(ifNode2.codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[2]` })
+ props: createObjectMatcher({ key: `[2]` }),
})
const branch = ifNode2.codegenNode.alternate as IfConditionalExpression
expect(branch.consequent).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[3]` })
+ props: createObjectMatcher({ key: `[3]` }),
})
expect(branch.alternate).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[4]` })
+ props: createObjectMatcher({ key: `[4]` }),
})
expect(generate(root).code).toMatchSnapshot()
})
test('key injection (only v-bind)', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj"/>`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
- arguments: [createObjectMatcher({ key: `[0]` }), { content: `obj` }]
- }
- ]
+ arguments: [
+ createObjectMatcher({ key: `[0]` }),
+ { content: `obj` },
+ ],
+ },
+ ],
})
})
test('key injection (before v-bind)', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" id="foo" v-bind="obj"/>`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
arguments: [
createObjectMatcher({
key: '[0]',
- id: 'foo'
+ id: 'foo',
}),
- { content: `obj` }
- ]
+ { content: `obj` },
+ ],
})
})
test('key injection (after v-bind)', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj" id="foo"/>`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
createObjectMatcher({ key: `[0]` }),
{ content: `obj` },
createObjectMatcher({
- id: 'foo'
- })
- ]
+ id: 'foo',
+ }),
+ ],
})
})
test('key injection (w/ custom directive)', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" v-foo />`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.directives).not.toBeUndefined()
// #6631
test('avoid duplicate keys', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" key="custom_key" v-bind="obj"/>`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- key: 'custom_key'
+ key: 'custom_key',
}),
- { content: `obj` }
- ]
+ { content: `obj` },
+ ],
})
})
test('with spaces between branches', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(
- `<div v-if="ok"/> <div v-else-if="no"/> <div v-else/>`
+ `<div v-if="ok"/> <div v-else-if="no"/> <div v-else/>`,
)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
const branch = codegenNode.alternate as ConditionalExpression
expect(branch.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(branch.alternate).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[2]` })
+ props: createObjectMatcher({ key: `[2]` }),
})
})
<p/>
</template>
`,
- { comments: true }
+ { comments: true },
)
__DEV__ = true
})
test('v-on with v-if', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(
- `<button v-on="{ click: clickEvent }" v-if="true">w/ v-if</button>`
+ `<button v-on="{ click: clickEvent }" v-if="true">w/ v-if</button>`,
)
expect((codegenNode.consequent as any).props.type).toBe(
- NodeTypes.JS_CALL_EXPRESSION
+ NodeTypes.JS_CALL_EXPRESSION,
)
expect((codegenNode.consequent as any).props.callee).toBe(MERGE_PROPS)
expect(
(codegenNode.consequent as any).props.arguments[0].properties[0].value
- .content
+ .content,
).toBe('0')
expect((codegenNode.consequent as any).props.arguments[1].callee).toBe(
- TO_HANDLERS
+ TO_HANDLERS,
)
})
})
function compile(content: string) {
return baseCompile(`<div>${content}</div>`, {
mode: 'module',
- prefixIdentifiers: true
+ prefixIdentifiers: true,
}).code
}
expect(
baseCompile(`<div v-memo="[x]"></div>`, {
mode: 'module',
- prefixIdentifiers: true
- }).code
+ prefixIdentifiers: true,
+ }).code,
).toMatchSnapshot()
})
expect(
compile(
`<div v-if="ok" v-memo="[x]"><span>foo</span>bar</div>
- <Comp v-else v-memo="[x]"></Comp>`
- )
+ <Comp v-else v-memo="[x]"></Comp>`,
+ ),
).toMatchSnapshot()
})
compile(
`<div v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
<span>foobar</span>
- </div>`
- )
+ </div>`,
+ ),
).toMatchSnapshot()
})
compile(
`<template v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
<span>foobar</span>
- </template>`
- )
+ </template>`,
+ ),
).toMatchSnapshot()
})
})
import {
+ BindingTypes,
+ type CompilerOptions,
+ type ComponentNode,
+ type ElementNode,
+ type ForNode,
+ NORMALIZE_PROPS,
+ NodeTypes,
+ type ObjectExpression,
+ type PlainElementNode,
+ type VNodeCall,
+ generate,
baseParse as parse,
transform,
- generate,
- ElementNode,
- ObjectExpression,
- CompilerOptions,
- ForNode,
- PlainElementNode,
- ComponentNode,
- NodeTypes,
- VNodeCall,
- NORMALIZE_PROPS,
- BindingTypes
} from '../../src'
import { ErrorCodes } from '../../src/errors'
import { transformModel } from '../../src/transforms/vModel'
import { transformExpression } from '../../src/transforms/transformExpression'
import { transformFor } from '../../src/transforms/vFor'
import { trackSlotScopes } from '../../src/transforms/vSlot'
-import { CallExpression } from '@babel/types'
+import type { CallExpression } from '@babel/types'
function parseWithVModel(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transformFor,
transformExpression,
transformElement,
- trackSlotScopes
+ trackSlotScopes,
],
directiveTransforms: {
...options.directiveTransforms,
- model: transformModel
+ model: transformModel,
},
- ...options
+ ...options,
})
return ast
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'model',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: 'model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
test('simple expression (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model="model" />', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
content: '_ctx.model',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: '_ctx.model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
content: '\n model\n.\nfoo \n',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: '\n model\n.\nfoo \n',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'model[index]',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: 'model[index]',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
test('compound expression (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model="model[index]" />', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
{
content: '_ctx.model',
- isStatic: false
+ isStatic: false,
},
'[',
{
content: '_ctx.index',
- isStatic: false
+ isStatic: false,
},
- ']'
- ]
- }
+ ']',
+ ],
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
children: [
{
content: '_ctx.model',
- isStatic: false
+ isStatic: false,
},
'[',
{
content: '_ctx.index',
- isStatic: false
+ isStatic: false,
},
- ']'
- ]
+ ']',
+ ],
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
expect(props[0]).toMatchObject({
key: {
content: 'foo-value',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'model',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:fooValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: 'model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
{
key: {
content: 'value',
- isStatic: false
+ isStatic: false,
},
value: {
content: 'model',
- isStatic: false
- }
+ isStatic: false,
+ },
},
{
key: {
'"onUpdate:" + ',
{
content: 'value',
- isStatic: false
- }
- ]
+ isStatic: false,
+ },
+ ],
},
value: {
children: [
'$event => ((',
{
content: 'model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
- }
- ]
- }
- ]
+ ') = $event)',
+ ],
+ },
+ },
+ ],
+ },
+ ],
})
expect(generate(root).code).toMatchSnapshot()
test('with dynamic argument (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model:[value]="model" />', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = (node.codegenNode as VNodeCall)
{
key: {
content: '_ctx.value',
- isStatic: false
+ isStatic: false,
},
value: {
content: '_ctx.model',
- isStatic: false
- }
+ isStatic: false,
+ },
},
{
key: {
'"onUpdate:" + ',
{
content: '_ctx.value',
- isStatic: false
- }
- ]
+ isStatic: false,
+ },
+ ],
},
value: {
children: [
'$event => ((',
{
content: '_ctx.model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
- }
- ]
- }
- ]
+ ') = $event)',
+ ],
+ },
+ },
+ ],
+ },
+ ],
})
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
test('should cache update handler w/ cacheHandlers: true', () => {
const root = parseWithVModel('<input v-model="foo" />', {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).toBe(1)
const codegen = (root.children[0] as PlainElementNode)
// should not list cached prop in dynamicProps
expect(codegen.dynamicProps).toBe(`["modelValue"]`)
expect((codegen.props as ObjectExpression).properties[1].value.type).toBe(
- NodeTypes.JS_CACHE_EXPRESSION
+ NodeTypes.JS_CACHE_EXPRESSION,
)
})
'<input v-for="i in list" v-model="foo[i]" />',
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
)
expect(root.cached).toBe(0)
const codegen = (
).codegenNode as VNodeCall
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
expect(
- (codegen.props as ObjectExpression).properties[1].value.type
+ (codegen.props as ObjectExpression).properties[1].value.type,
).not.toBe(NodeTypes.JS_CACHE_EXPRESSION)
})
test('should not cache update handler if it inside v-once', () => {
const root = parseWithVModel('<div v-once><input v-model="foo" /></div>', {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1)
const root = parseWithVModel(
'<Comp v-slot="{ foo }"><input v-model="foo.bar"/></Comp>',
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
const codegen = (
(root.children[0] as ComponentNode).children[0] as PlainElementNode
test('should generate modelModifiers for component v-model', () => {
const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
{ key: { content: `onUpdate:modelValue` } },
{
key: { content: 'modelModifiers' },
- value: { content: `{ trim: true, "bar-baz": true }`, isStatic: false }
- }
- ]
+ value: {
+ content: `{ trim: true, "bar-baz": true }`,
+ isStatic: false,
+ },
+ },
+ ],
})
// should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change
const root = parseWithVModel(
'<Comp v-model:foo.trim="foo" v-model:bar.number="bar" />',
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
{ key: { content: `onUpdate:foo` } },
{
key: { content: 'fooModifiers' },
- value: { content: `{ trim: true }`, isStatic: false }
+ value: { content: `{ trim: true }`, isStatic: false },
},
{ key: { content: `bar` } },
{ key: { content: `onUpdate:bar` } },
{
key: { content: 'barModifiers' },
- value: { content: `{ number: true }`, isStatic: false }
- }
- ]
+ value: { content: `{ number: true }`, isStatic: false },
+ },
+ ],
})
// should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change
expect(vnodeCall.dynamicProps).toBe(
- `["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`
+ `["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`,
)
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_NO_EXPRESSION
- })
+ code: ErrorCodes.X_V_MODEL_NO_EXPRESSION,
+ }),
)
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION,
+ }),
)
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION,
+ }),
)
})
const onError = vi.fn()
parseWithVModel('<span v-for="i in list" v-model="i" />', {
onError,
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE
- })
+ code: ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE,
+ }),
)
})
parseWithVModel('<div v-model="p" />', {
onError,
bindingMetadata: {
- p: BindingTypes.PROPS
- }
+ p: BindingTypes.PROPS,
+ },
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_ON_PROPS
- })
+ code: ErrorCodes.X_V_MODEL_ON_PROPS,
+ }),
)
})
})
import {
- baseParse as parse,
- CompilerOptions,
- ElementNode,
+ type CompilerOptions,
+ type ElementNode,
ErrorCodes,
+ NodeTypes,
+ type ObjectExpression,
TO_HANDLER_KEY,
+ type VNodeCall,
helperNameMap,
- NodeTypes,
- ObjectExpression,
+ baseParse as parse,
transform,
- VNodeCall
} from '../../src'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
transform(ast, {
nodeTransforms: [transformExpression, transformElement],
directiveTransforms: {
- on: transformOn
+ on: transformOn,
},
- ...options
+ ...options,
})
return {
root: ast,
- node: ast.children[0] as ElementNode
+ node: ast.children[0] as ElementNode,
}
}
loc: {
start: {
line: 1,
- column: 11
+ column: 11,
},
end: {
line: 1,
- column: 16
- }
- }
+ column: 16,
+ },
+ },
},
value: {
content: `onClick`,
loc: {
start: {
line: 1,
- column: 18
+ column: 18,
},
end: {
line: 1,
- column: 25
- }
- }
- }
- }
- ]
+ column: 25,
+ },
+ },
+ },
+ },
+ ],
})
})
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `event` },
- `)`
- ]
+ `)`,
+ ],
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
test('dynamic arg with prefixing', () => {
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `_ctx.event` },
- `)`
- ]
+ `)`,
+ ],
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
test('dynamic arg with complex exp prefixing', () => {
const { node } = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
`(`,
{ content: `_ctx.foo` },
`)`,
- `)`
- ]
+ `)`,
+ ],
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`$event => (`, { content: `i++` }, `)`]
- }
- }
- ]
+ children: [`$event => (`, { content: `i++` }, `)`],
+ },
+ },
+ ],
})
})
// should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is
// consistent with 2.x
- children: [`$event => {`, { content: `foo();bar()` }, `}`]
- }
- }
- ]
+ children: [`$event => {`, { content: `foo();bar()` }, `}`],
+ },
+ },
+ ],
})
})
// should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is
// consistent with 2.x
- children: [`$event => {`, { content: `\nfoo();\nbar()\n` }, `}`]
- }
- }
- ]
+ children: [`$event => {`, { content: `\nfoo();\nbar()\n` }, `}`],
+ },
+ },
+ ],
})
})
test('inline statement w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="foo($event)"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
`(`,
// should NOT prefix $event
{ content: `$event` },
- `)`
- ]
+ `)`,
+ ],
},
- `)`
- ]
- }
- }
- ]
+ `)`,
+ ],
+ },
+ },
+ ],
})
})
test('multiple inline statements w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="foo($event);bar()"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{ content: `$event` },
`);`,
{ content: `_ctx.bar` },
- `()`
- ]
+ `()`,
+ ],
},
- `}`
- ]
- }
- }
- ]
+ `}`,
+ ],
+ },
+ },
+ ],
})
})
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `$event => foo($event)`
- }
- }
- ]
+ content: `$event => foo($event)`,
+ },
+ },
+ ],
})
})
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `(e: any): any => foo(e)`
- }
- }
- ]
+ content: `(e: any): any => foo(e)`,
+ },
+ },
+ ],
})
})
$event => {
foo($event)
}
- "/>`
+ "/>`,
)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
$event => {
foo($event)
}
- `
- }
- }
- ]
+ `,
+ },
+ },
+ ],
})
})
function($event) {
foo($event)
}
- "/>`
+ "/>`,
)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
function($event) {
foo($event)
}
- `
- }
- }
- ]
+ `,
+ },
+ },
+ ],
})
})
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `a['b' + c]`
- }
- }
- ]
+ content: `a['b' + c]`,
+ },
+ },
+ ],
})
})
test('complex member expression w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{ content: `_ctx.a` },
`['b' + `,
{ content: `_ctx.c` },
- `]`
- ]
- }
- }
- ]
+ `]`,
+ ],
+ },
+ },
+ ],
})
})
test('function expression w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="e => foo(e)"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{ content: `_ctx.foo` },
`(`,
{ content: `e` },
- `)`
- ]
- }
- }
- ]
+ `)`,
+ ],
+ },
+ },
+ ],
})
})
loc: {
start: {
line: 1,
- column: 6
+ column: 6,
},
end: {
line: 1,
- column: 16
- }
- }
+ column: 16,
+ },
+ },
})
})
properties: [
{
key: {
- content: `onFooBar`
+ content: `onFooBar`,
},
value: {
- content: `onMount`
- }
- }
- ]
+ content: `onMount`,
+ },
+ },
+ ],
})
})
loc: {
start: {
line: 1,
- column: 11
+ column: 11,
},
end: {
line: 1,
- column: 24
- }
- }
+ column: 24,
+ },
+ },
})
})
test('vue: prefixed events', () => {
const { node } = parseWithVOn(
- `<div v-on:vue:mounted="onMount" @vue:before-update="onBeforeUpdate" />`
+ `<div v-on:vue:mounted="onMount" @vue:before-update="onBeforeUpdate" />`,
)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
- content: `onVnodeMounted`
+ content: `onVnodeMounted`,
},
value: {
- content: `onMount`
- }
+ content: `onMount`,
+ },
},
{
key: {
- content: `onVnodeBeforeUpdate`
+ content: `onVnodeBeforeUpdate`,
},
value: {
- content: `onBeforeUpdate`
- }
- }
- ]
+ content: `onBeforeUpdate`,
+ },
+ },
+ ],
})
})
test('empty handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `() => {}`
- }
+ content: `() => {}`,
+ },
})
})
test('member expression handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="foo" />`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
children: [
`(...args) => (`,
{ content: `_ctx.foo && _ctx.foo(...args)` },
- `)`
- ]
- }
+ `)`,
+ ],
+ },
})
})
test('compound member expression handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="foo.bar" />`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
{ content: `_ctx.foo` },
`.`,
{ content: `bar` },
- `(...args)`
- ]
+ `(...args)`,
+ ],
},
- `)`
- ]
- }
+ `)`,
+ ],
+ },
})
})
const { root } = parseWithVOn(`<comp v-on:click="foo" />`, {
prefixIdentifiers: true,
cacheHandlers: true,
- isNativeTag: tag => tag === 'div'
+ isNativeTag: tag => tag === 'div',
})
expect(root.cached).toBe(0)
})
`<div v-once><div v-on:click="foo"/></div>`,
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
)
expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1)
test('inline function expression handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="() => foo()" />`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`() => `, { content: `_ctx.foo` }, `()`]
- }
+ children: [`() => `, { content: `_ctx.foo` }, `()`],
+ },
})
})
`<div v-on:click="async () => await foo()" />`,
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
)
expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`async () => await `, { content: `_ctx.foo` }, `()`]
- }
+ children: [`async () => await `, { content: `_ctx.foo` }, `()`],
+ },
})
})
`<div v-on:click="async function () { await foo() } " />`,
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
)
expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
children: [
`async function () { await `,
{ content: `_ctx.foo` },
- `() } `
- ]
- }
+ `() } `,
+ ],
+ },
})
})
test('inline statement handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="foo++" />`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached).toBe(1)
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
children: [
`$event => (`,
{ children: [{ content: `_ctx.foo` }, `++`] },
- `)`
- ]
- }
+ `)`,
+ ],
+ },
})
})
})
import {
- baseParse as parse,
- transform,
+ type CompilerOptions,
NodeTypes,
generate,
- CompilerOptions,
- getBaseTransformPreset
+ getBaseTransformPreset,
+ baseParse as parse,
+ transform,
} from '../../src'
import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers'
transform(ast, {
nodeTransforms,
directiveTransforms,
- ...options
+ ...options,
})
return ast
}
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
+ tag: `"div"`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
+ tag: `"div"`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
- tag: `_component_Comp`
- }
+ tag: `_component_Comp`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
index: 0,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ callee: RENDER_SLOT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
// cached nodes should be ignored by hoistStatic transform
test('with hoistStatic: true', () => {
const root = transformWithOnce(`<div><div v-once /></div>`, {
- hoistStatic: true
+ hoistStatic: true,
})
expect(root.cached).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
+ tag: `"div"`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
+ tag: `"div"`,
},
alternate: {
type: NodeTypes.VNODE_CALL,
- tag: `"p"`
- }
- }
- }
+ tag: `"p"`,
+ },
+ },
+ },
})
})
type: NodeTypes.FOR,
// should cache the entire v-for expression, not just a single branch
codegenNode: {
- type: NodeTypes.JS_CACHE_EXPRESSION
- }
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ },
})
})
})
import {
- CompilerOptions,
+ type CompilerOptions,
+ type ComponentNode,
+ type ElementNode,
+ ErrorCodes,
+ type ForNode,
+ NodeTypes,
+ type ObjectExpression,
+ type RenderSlotCall,
+ type SimpleExpressionNode,
+ type SlotsExpression,
+ type VNodeCall,
+ generate,
baseParse as parse,
transform,
- generate,
- ElementNode,
- NodeTypes,
- ErrorCodes,
- ForNode,
- ComponentNode,
- VNodeCall,
- SlotsExpression,
- ObjectExpression,
- SimpleExpressionNode,
- RenderSlotCall
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import {
trackSlotScopes,
- trackVForSlotScopes
+ trackVForSlotScopes,
} from '../../src/transforms/vSlot'
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeHelpers'
import { createObjectMatcher, genFlagText } from '../testUtils'
function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template, {
- whitespace: options.whitespace
+ whitespace: options.whitespace,
})
transform(ast, {
nodeTransforms: [
: []),
transformSlotOutlet,
transformElement,
- trackSlotScopes
+ trackSlotScopes,
],
directiveTransforms: {
on: transformOn,
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
return {
root: ast,
ast.children[0].type === NodeTypes.ELEMENT
? ((ast.children[0].codegenNode as VNodeCall)
.children as SlotsExpression)
- : null
+ : null,
}
}
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: !/^\[/.test(key),
- content: key.replace(/^\[|\]$/g, '')
+ content: key.replace(/^\[|\]$/g, ''),
},
- value: obj[key]
+ value: obj[key],
} as any
})
.concat({
key: { content: `_` },
value: {
content: isDynamic ? `2 /* DYNAMIC */` : `1 /* STABLE */`,
- isStatic: false
- }
- })
+ isStatic: false,
+ },
+ }),
}
}
describe('compiler: transform component slots', () => {
test('implicit default slot', () => {
const { root, slots } = parseWithSlots(`<Comp><div/></Comp>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(slots).toMatchObject(
createSlotMatcher({
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- })
+ tag: `div`,
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('on-component default slot', () => {
const { root, slots } = parseWithSlots(
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher({
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
- }
- })
+ content: `_ctx.bar`,
+ },
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('on component named slot', () => {
const { root, slots } = parseWithSlots(
`<Comp v-slot:named="{ foo }">{{ foo }}{{ bar }}</Comp>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher({
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
- }
- })
+ content: `_ctx.bar`,
+ },
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
{{ foo }}{{ bar }}
</template>
</Comp>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher({
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
+ content: `_ctx.bar`,
+ },
+ },
+ ],
},
two: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `bar` }, ` }`]
+ children: [`{ `, { content: `bar` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.foo`
- }
+ content: `_ctx.foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `bar`
- }
- }
- ]
- }
- })
+ content: `bar`,
+ },
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('on component dynamically named slot', () => {
const { root, slots } = parseWithSlots(
`<Comp v-slot:[named]="{ foo }">{{ foo }}{{ bar }}</Comp>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher(
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
- }
+ content: `_ctx.bar`,
+ },
+ },
+ ],
+ },
},
- true
- )
+ true,
+ ),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
const { root, slots } = parseWithSlots(
`<Comp>
<template #one>foo</template>bar<span/>
- </Comp>`
+ </Comp>`,
)
expect(slots).toMatchObject(
createSlotMatcher({
returns: [
{
type: NodeTypes.TEXT,
- content: `foo`
- }
- ]
+ content: `foo`,
+ },
+ ],
},
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: [
{
type: NodeTypes.TEXT,
- content: `bar`
+ content: `bar`,
},
{
type: NodeTypes.ELEMENT,
- tag: `span`
- }
- ]
- }
- })
+ tag: `span`,
+ },
+ ],
+ },
+ }),
)
expect(generate(root).code).toMatchSnapshot()
})
{{ foo }}{{ bar }}
</template>
</Comp>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher(
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
+ content: `_ctx.bar`,
+ },
+ },
+ ],
},
'[_ctx.two]': {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `bar` }, ` }`]
+ children: [`{ `, { content: `bar` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.foo`
- }
+ content: `_ctx.foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `bar`
- }
- }
- ]
- }
+ content: `bar`,
+ },
+ },
+ ],
+ },
},
- true
- )
+ true,
+ ),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
{{ foo }}{{ bar }}{{ baz }}
</template>
</Comp>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher({
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `bar` }, ` }`]
+ children: [`{ `, { content: `bar` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `bar`
- }
+ content: `bar`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.baz`
- }
- }
- ]
- }
+ content: `_ctx.baz`,
+ },
+ },
+ ],
+ },
},
- true
+ true,
),
// nested slot should be forced dynamic, since scope variables
// are not tracked as dependencies of the slot.
- patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
- }
+ patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS),
+ },
},
// test scope
{
type: NodeTypes.TEXT,
- content: ` `
+ content: ` `,
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
+ content: `_ctx.bar`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.baz`
- }
- }
- ]
- }
- })
+ content: `_ctx.baz`,
+ },
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
const { root } = parseWithSlots(
`<div v-for="i in list">
<Comp v-slot="bar">foo</Comp>
- </div>`
+ </div>`,
)
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
.codegenNode as any
const comp = div.children[0]
expect(comp.codegenNode.patchFlag).toBe(
- genFlagText(PatchFlags.DYNAMIC_SLOTS)
+ genFlagText(PatchFlags.DYNAMIC_SLOTS),
)
})
`<div v-for="i in list">
<Comp v-slot="bar">foo</Comp>
</div>`,
- false
+ false,
)
assertDynamicSlots(
`<div v-for="i in list">
<Comp v-slot="bar">{{ i }}</Comp>
</div>`,
- true
+ true,
)
// reference the component's own slot variable should not force dynamic slots
`<Comp v-slot="foo">
<Comp v-slot="bar">{{ bar }}</Comp>
</Comp>`,
- false
+ false,
)
assertDynamicSlots(
`<Comp v-slot="foo">
<Comp v-slot="bar">{{ foo }}</Comp>
</Comp>`,
- true
+ true,
)
// #2564
`<div v-for="i in list">
<Comp v-slot="bar"><button @click="fn(i)" /></Comp>
</div>`,
- true
+ true,
)
assertDynamicSlots(
`<div v-for="i in list">
<Comp v-slot="bar"><button @click="fn()" /></Comp>
</div>`,
- false
+ false,
)
})
const { root, slots } = parseWithSlots(
`<Comp>
<template #one v-if="ok">hello</template>
- </Comp>`
+ </Comp>`,
)
expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
- _: `[2 /* DYNAMIC */]`
+ _: `[2 /* DYNAMIC */]`,
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
name: `one`,
fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
- returns: [{ type: NodeTypes.TEXT, content: `hello` }]
+ returns: [{ type: NodeTypes.TEXT, content: `hello` }],
},
- key: `0`
+ key: `0`,
}),
alternate: {
content: `undefined`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
- PatchFlags.DYNAMIC_SLOTS + ''
+ PatchFlags.DYNAMIC_SLOTS + '',
)
expect(generate(root).code).toMatchSnapshot()
})
`<Comp>
<template #one="props" v-if="ok">{{ props }}</template>
</Comp>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
- _: `[2 /* DYNAMIC */]`
+ _: `[2 /* DYNAMIC */]`,
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
returns: [
{
type: NodeTypes.INTERPOLATION,
- content: { content: `props` }
- }
- ]
+ content: { content: `props` },
+ },
+ ],
},
- key: `0`
+ key: `0`,
}),
alternate: {
content: `undefined`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
- PatchFlags.DYNAMIC_SLOTS + ''
+ PatchFlags.DYNAMIC_SLOTS + '',
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
<template #one v-if="ok">foo</template>
<template #two="props" v-else-if="orNot">bar</template>
<template #one v-else>baz</template>
- </Comp>`
+ </Comp>`,
)
expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
- _: `[2 /* DYNAMIC */]`
+ _: `[2 /* DYNAMIC */]`,
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: undefined,
- returns: [{ type: NodeTypes.TEXT, content: `foo` }]
+ returns: [{ type: NodeTypes.TEXT, content: `foo` }],
},
- key: `0`
+ key: `0`,
}),
alternate: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { content: `props` },
- returns: [{ type: NodeTypes.TEXT, content: `bar` }]
+ returns: [{ type: NodeTypes.TEXT, content: `bar` }],
},
- key: `1`
+ key: `1`,
}),
alternate: createObjectMatcher({
name: `one`,
fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: undefined,
- returns: [{ type: NodeTypes.TEXT, content: `baz` }]
+ returns: [{ type: NodeTypes.TEXT, content: `baz` }],
},
- key: `2`
- })
- }
- }
- ]
- }
- ]
+ key: `2`,
+ }),
+ },
+ },
+ ],
+ },
+ ],
})
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
- PatchFlags.DYNAMIC_SLOTS + ''
+ PatchFlags.DYNAMIC_SLOTS + '',
)
expect(generate(root).code).toMatchSnapshot()
})
`<Comp>
<template v-for="name in list" #[name]>{{ name }}</template>
</Comp>`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
- _: `[2 /* DYNAMIC */]`
+ _: `[2 /* DYNAMIC */]`,
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
returns: [
{
type: NodeTypes.INTERPOLATION,
- content: { content: `name`, isStatic: false }
- }
- ]
- }
- })
- }
- ]
- }
- ]
- }
- ]
+ content: { content: `name`, isStatic: false },
+ },
+ ],
+ },
+ }),
+ },
+ ],
+ },
+ ],
+ },
+ ],
})
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
- PatchFlags.DYNAMIC_SLOTS + ''
+ PatchFlags.DYNAMIC_SLOTS + '',
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
properties: [
{
key: { content: `default` },
- value: { type: NodeTypes.JS_FUNCTION_EXPRESSION }
+ value: { type: NodeTypes.JS_FUNCTION_EXPRESSION },
},
{
key: { content: `_` },
- value: { content: `3 /* FORWARDED */` }
- }
- ]
+ value: { content: `3 /* FORWARDED */` },
+ },
+ ],
}
test('<slot> tag only', () => {
const { slots } = parseWithSlots(`<Comp><slot/></Comp>`)
test('<slot> tag w/ template', () => {
const { slots } = parseWithSlots(
- `<Comp><template #default><slot/></template></Comp>`
+ `<Comp><template #default><slot/></template></Comp>`,
)
expect(slots).toMatchObject(toMatch)
})
// # fix: #6900
test('consistent behavior of @xxx:modelValue and @xxx:model-value', () => {
const { root: rootUpper } = parseWithSlots(
- `<div><slot @foo:modelValue="handler" /></div>`
+ `<div><slot @foo:modelValue="handler" /></div>`,
)
const slotNodeUpper = (rootUpper.codegenNode! as VNodeCall)
.children as ElementNode[]
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: 'onFoo:modelValue'
+ content: 'onFoo:modelValue',
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
const { root } = parseWithSlots(
- `<div><slot @foo:model-Value="handler" /></div>`
+ `<div><slot @foo:model-Value="handler" /></div>`,
)
const slotNode = (root.codegenNode! as VNodeCall)
.children as ElementNode[]
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: 'onFoo:modelValue'
+ content: 'onFoo:modelValue',
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
})
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 3,
line: 1,
- column: index + 4
- }
- }
+ column: index + 4,
+ },
+ },
})
})
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 4,
line: 1,
- column: index + 5
- }
- }
+ column: index + 5,
+ },
+ },
})
})
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 4,
line: 1,
- column: index + 5
- }
- }
+ column: index + 5,
+ },
+ },
})
})
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 6,
line: 1,
- column: index + 7
- }
- }
+ column: index + 7,
+ },
+ },
})
})
})
</Comp>
`
const { root } = parseWithSlots(source, {
- whitespace: 'preserve'
+ whitespace: 'preserve',
})
expect(
- `Extraneous children found when component already has explicitly named default slot.`
+ `Extraneous children found when component already has explicitly named default slot.`,
).not.toHaveBeenWarned()
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
</Comp>
`
const { root } = parseWithSlots(source, {
- whitespace: 'preserve'
+ whitespace: 'preserve',
})
expect(
- `Extraneous children found when component already has explicitly named default slot.`
+ `Extraneous children found when component already has explicitly named default slot.`,
).not.toHaveBeenWarned()
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
</Comp>
`
const { root } = parseWithSlots(source, {
- whitespace: 'preserve'
+ whitespace: 'preserve',
})
// slots is vnodeCall's children as an ObjectExpression
// should be: header, footer, _ (no default)
expect(slots.length).toBe(3)
expect(
- slots.some(p => (p.key as SimpleExpressionNode).content === 'default')
+ slots.some(p => (p.key as SimpleExpressionNode).content === 'default'),
).toBe(false)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
-import { TransformContext } from '../src'
-import { Position } from '../src/ast'
+import type { TransformContext } from '../src'
+import type { Position } from '../src/ast'
import {
advancePositionWithClone,
- isMemberExpressionNode,
isMemberExpressionBrowser,
- toValidAssetId
+ isMemberExpressionNode,
+ toValidAssetId,
} from '../src/utils'
function p(line: number, column: number, offset: number): Position {
expect(toValidAssetId('div', 'filter')).toBe('_filter_div')
expect(toValidAssetId('foo-bar', 'component')).toBe('_component_foo_bar')
expect(toValidAssetId('test-测试-1', 'component')).toBe(
- '_component_test_2797935797_1'
+ '_component_test_2797935797_1',
)
})
import { isString } from '@vue/shared'
import {
- RENDER_SLOT,
- CREATE_SLOTS,
- RENDER_LIST,
+ CREATE_BLOCK,
+ CREATE_ELEMENT_BLOCK,
+ CREATE_ELEMENT_VNODE,
+ type CREATE_SLOTS,
+ CREATE_VNODE,
+ type FRAGMENT,
OPEN_BLOCK,
- FRAGMENT,
+ type RENDER_LIST,
+ type RENDER_SLOT,
WITH_DIRECTIVES,
- WITH_MEMO,
- CREATE_VNODE,
- CREATE_ELEMENT_VNODE,
- CREATE_BLOCK,
- CREATE_ELEMENT_BLOCK
+ type WITH_MEMO,
} from './runtimeHelpers'
-import { PropsExpression } from './transforms/transformElement'
-import { ImportItem, TransformContext } from './transform'
-import { Node as BabelNode } from '@babel/types'
+import type { PropsExpression } from './transforms/transformElement'
+import type { ImportItem, TransformContext } from './transform'
+import type { Node as BabelNode } from '@babel/types'
// Vue template is a platform-agnostic superset of HTML (syntax only).
// More namespaces can be declared by platform specific compilers.
export enum Namespaces {
HTML,
SVG,
- MATH_ML
+ MATH_ML,
}
export enum NodeTypes {
JS_IF_STATEMENT,
JS_ASSIGNMENT_EXPRESSION,
JS_SEQUENCE_EXPRESSION,
- JS_RETURN_STATEMENT
+ JS_RETURN_STATEMENT,
}
export enum ElementTypes {
ELEMENT,
COMPONENT,
SLOT,
- TEMPLATE
+ TEMPLATE,
}
export interface Node {
NOT_CONSTANT = 0,
CAN_SKIP_PATCH,
CAN_HOIST,
- CAN_STRINGIFY
+ CAN_STRINGIFY,
}
export interface SimpleExpressionNode extends Node {
string,
string | ExpressionNode,
PropsExpression | '{}',
- TemplateChildNode[]
+ TemplateChildNode[],
]
}
export const locStub: SourceLocation = {
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 },
- source: ''
+ source: '',
}
export function createRoot(
children: TemplateChildNode[],
- source = ''
+ source = '',
): RootNode {
return {
type: NodeTypes.ROOT,
cached: 0,
temps: 0,
codegenNode: undefined,
- loc: locStub
+ loc: locStub,
}
}
isBlock: VNodeCall['isBlock'] = false,
disableTracking: VNodeCall['disableTracking'] = false,
isComponent: VNodeCall['isComponent'] = false,
- loc = locStub
+ loc = locStub,
): VNodeCall {
if (context) {
if (isBlock) {
isBlock,
disableTracking,
isComponent,
- loc
+ loc,
}
}
export function createArrayExpression(
elements: ArrayExpression['elements'],
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): ArrayExpression {
return {
type: NodeTypes.JS_ARRAY_EXPRESSION,
loc,
- elements
+ elements,
}
}
export function createObjectExpression(
properties: ObjectExpression['properties'],
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): ObjectExpression {
return {
type: NodeTypes.JS_OBJECT_EXPRESSION,
loc,
- properties
+ properties,
}
}
export function createObjectProperty(
key: Property['key'] | string,
- value: Property['value']
+ value: Property['value'],
): Property {
return {
type: NodeTypes.JS_PROPERTY,
loc: locStub,
key: isString(key) ? createSimpleExpression(key, true) : key,
- value
+ value,
}
}
content: SimpleExpressionNode['content'],
isStatic: SimpleExpressionNode['isStatic'] = false,
loc: SourceLocation = locStub,
- constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
+ constType: ConstantTypes = ConstantTypes.NOT_CONSTANT,
): SimpleExpressionNode {
return {
type: NodeTypes.SIMPLE_EXPRESSION,
loc,
content,
isStatic,
- constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType
+ constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType,
}
}
export function createInterpolation(
content: InterpolationNode['content'] | string,
- loc: SourceLocation
+ loc: SourceLocation,
): InterpolationNode {
return {
type: NodeTypes.INTERPOLATION,
loc,
content: isString(content)
? createSimpleExpression(content, false, loc)
- : content
+ : content,
}
}
export function createCompoundExpression(
children: CompoundExpressionNode['children'],
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): CompoundExpressionNode {
return {
type: NodeTypes.COMPOUND_EXPRESSION,
loc,
- children
+ children,
}
}
export function createCallExpression<T extends CallExpression['callee']>(
callee: T,
args: CallExpression['arguments'] = [],
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): InferCodegenNodeType<T> {
return {
type: NodeTypes.JS_CALL_EXPRESSION,
loc,
callee,
- arguments: args
+ arguments: args,
} as InferCodegenNodeType<T>
}
returns: FunctionExpression['returns'] = undefined,
newline: boolean = false,
isSlot: boolean = false,
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): FunctionExpression {
return {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns,
newline,
isSlot,
- loc
+ loc,
}
}
test: ConditionalExpression['test'],
consequent: ConditionalExpression['consequent'],
alternate: ConditionalExpression['alternate'],
- newline = true
+ newline = true,
): ConditionalExpression {
return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent,
alternate,
newline,
- loc: locStub
+ loc: locStub,
}
}
export function createCacheExpression(
index: number,
value: JSChildNode,
- isVNode: boolean = false
+ isVNode: boolean = false,
): CacheExpression {
return {
type: NodeTypes.JS_CACHE_EXPRESSION,
index,
value,
isVNode,
- loc: locStub
+ loc: locStub,
}
}
export function createBlockStatement(
- body: BlockStatement['body']
+ body: BlockStatement['body'],
): BlockStatement {
return {
type: NodeTypes.JS_BLOCK_STATEMENT,
body,
- loc: locStub
+ loc: locStub,
}
}
export function createTemplateLiteral(
- elements: TemplateLiteral['elements']
+ elements: TemplateLiteral['elements'],
): TemplateLiteral {
return {
type: NodeTypes.JS_TEMPLATE_LITERAL,
elements,
- loc: locStub
+ loc: locStub,
}
}
export function createIfStatement(
test: IfStatement['test'],
consequent: IfStatement['consequent'],
- alternate?: IfStatement['alternate']
+ alternate?: IfStatement['alternate'],
): IfStatement {
return {
type: NodeTypes.JS_IF_STATEMENT,
test,
consequent,
alternate,
- loc: locStub
+ loc: locStub,
}
}
export function createAssignmentExpression(
left: AssignmentExpression['left'],
- right: AssignmentExpression['right']
+ right: AssignmentExpression['right'],
): AssignmentExpression {
return {
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION,
left,
right,
- loc: locStub
+ loc: locStub,
}
}
export function createSequenceExpression(
- expressions: SequenceExpression['expressions']
+ expressions: SequenceExpression['expressions'],
): SequenceExpression {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions,
- loc: locStub
+ loc: locStub,
}
}
export function createReturnStatement(
- returns: ReturnStatement['returns']
+ returns: ReturnStatement['returns'],
): ReturnStatement {
return {
type: NodeTypes.JS_RETURN_STATEMENT,
returns,
- loc: locStub
+ loc: locStub,
}
}
export function convertToBlock(
node: VNodeCall,
- { helper, removeHelper, inSSR }: TransformContext
+ { helper, removeHelper, inSSR }: TransformContext,
) {
if (!node.isBlock) {
node.isBlock = true
// should only use types from @babel/types
// do not import runtime methods
import type {
+ BlockStatement,
+ Function,
Identifier,
Node,
- Function,
ObjectProperty,
- BlockStatement,
- Program
+ Program,
} from '@babel/types'
import { walk } from 'estree-walker'
parent: Node,
parentStack: Node[],
isReference: boolean,
- isLocal: boolean
+ isLocal: boolean,
) => void,
includeAll = false,
parentStack: Node[] = [],
- knownIds: Record<string, number> = Object.create(null)
+ knownIds: Record<string, number> = Object.create(null),
) {
if (__BROWSER__) {
return
// walk function expressions and add its arguments to known identifiers
// so that we don't prefix them
walkFunctionParams(node, id =>
- markScopeIdentifier(node, id, knownIds)
+ markScopeIdentifier(node, id, knownIds),
)
}
} else if (node.type === 'BlockStatement') {
} else {
// #3445 record block-level local variables
walkBlockDeclarations(node, id =>
- markScopeIdentifier(node, id, knownIds)
+ markScopeIdentifier(node, id, knownIds),
)
}
}
}
}
}
- }
+ },
})
}
export function isReferencedIdentifier(
id: Identifier,
parent: Node | null,
- parentStack: Node[]
+ parentStack: Node[],
) {
if (__BROWSER__) {
return false
export function isInDestructureAssignment(
parent: Node,
- parentStack: Node[]
+ parentStack: Node[],
): boolean {
if (
parent &&
export function walkFunctionParams(
node: Function,
- onIdent: (id: Identifier) => void
+ onIdent: (id: Identifier) => void,
) {
for (const p of node.params) {
for (const id of extractIdentifiers(p)) {
export function walkBlockDeclarations(
block: BlockStatement | Program,
- onIdent: (node: Identifier) => void
+ onIdent: (node: Identifier) => void,
) {
for (const stmt of block.body) {
if (stmt.type === 'VariableDeclaration') {
export function extractIdentifiers(
param: Node,
- nodes: Identifier[] = []
+ nodes: Identifier[] = [],
): Identifier[] {
switch (param.type) {
case 'Identifier':
function markScopeIdentifier(
node: Node & { scopeIds?: Set<string> },
child: Identifier,
- knownIds: Record<string, number>
+ knownIds: Record<string, number>,
) {
const { name } = child
if (node.scopeIds && node.scopeIds.has(name)) {
'TSTypeAssertion', // (<number>foo)
'TSNonNullExpression', // foo!
'TSInstantiationExpression', // foo<string>
- 'TSSatisfiesExpression' // foo satisfies T
+ 'TSSatisfiesExpression', // foo satisfies T
]
export function unwrapTSNode(node: Node): Node {
-import { CodegenOptions } from './options'
+import type { CodegenOptions } from './options'
import {
- RootNode,
- TemplateChildNode,
- TextNode,
- CommentNode,
- ExpressionNode,
+ type ArrayExpression,
+ type AssignmentExpression,
+ type CacheExpression,
+ type CallExpression,
+ type CommentNode,
+ type CompoundExpressionNode,
+ type ConditionalExpression,
+ type ExpressionNode,
+ type FunctionExpression,
+ type IfStatement,
+ type InterpolationNode,
+ type JSChildNode,
NodeTypes,
- JSChildNode,
- CallExpression,
- ArrayExpression,
- ObjectExpression,
- Position,
- InterpolationNode,
- CompoundExpressionNode,
- SimpleExpressionNode,
- FunctionExpression,
- ConditionalExpression,
- CacheExpression,
- locStub,
- SSRCodegenNode,
- TemplateLiteral,
- IfStatement,
- AssignmentExpression,
- ReturnStatement,
- VNodeCall,
- SequenceExpression,
+ type ObjectExpression,
+ type Position,
+ type ReturnStatement,
+ type RootNode,
+ type SSRCodegenNode,
+ type SequenceExpression,
+ type SimpleExpressionNode,
+ type TemplateChildNode,
+ type TemplateLiteral,
+ type TextNode,
+ type VNodeCall,
getVNodeBlockHelper,
- getVNodeHelper
+ getVNodeHelper,
+ locStub,
} from './ast'
-import { SourceMapGenerator, RawSourceMap } from 'source-map-js'
+import { type RawSourceMap, SourceMapGenerator } from 'source-map-js'
import {
advancePositionWithMutation,
assert,
isSimpleIdentifier,
- toValidAssetId
+ toValidAssetId,
} from './utils'
-import { isString, isArray, isSymbol } from '@vue/shared'
+import { isArray, isString, isSymbol } from '@vue/shared'
import {
- helperNameMap,
- TO_DISPLAY_STRING,
+ CREATE_COMMENT,
+ CREATE_ELEMENT_VNODE,
+ CREATE_STATIC,
+ CREATE_TEXT,
CREATE_VNODE,
+ OPEN_BLOCK,
+ POP_SCOPE_ID,
+ PUSH_SCOPE_ID,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
+ RESOLVE_FILTER,
SET_BLOCK_TRACKING,
- CREATE_COMMENT,
- CREATE_TEXT,
- PUSH_SCOPE_ID,
- POP_SCOPE_ID,
- WITH_DIRECTIVES,
- CREATE_ELEMENT_VNODE,
- OPEN_BLOCK,
- CREATE_STATIC,
+ TO_DISPLAY_STRING,
WITH_CTX,
- RESOLVE_FILTER
+ WITH_DIRECTIVES,
+ helperNameMap,
} from './runtimeHelpers'
-import { ImportItem } from './transform'
+import type { ImportItem } from './transform'
const PURE_ANNOTATION = `/*#__PURE__*/`
Start = 0,
End = -1,
None = -2,
- Unknown = -3
+ Unknown = -3,
}
export interface CodegenContext
ssrRuntimeModuleName = 'vue/server-renderer',
ssr = false,
isTS = false,
- inSSR = false
- }: CodegenOptions
+ inSSR = false,
+ }: CodegenOptions,
): CodegenContext {
const context: CodegenContext = {
mode,
if (__TEST__ && code.includes('\n')) {
throw new Error(
`CodegenContext.push() called newlineIndex: none, but contains` +
- `newlines: ${code.replace(/\n/g, '\\n')}`
+ `newlines: ${code.replace(/\n/g, '\\n')}`,
)
}
context.column += code.length
) {
throw new Error(
`CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
- `but does not conform: ${code.replace(/\n/g, '\\n')}`
+ `but does not conform: ${code.replace(/\n/g, '\\n')}`,
)
}
context.line++
},
newline() {
newline(context.indentLevel)
- }
+ },
}
function newline(n: number) {
generatedLine: context.line,
generatedColumn: context.column - 1,
source: filename,
- // @ts-ignore it is possible to be null
- name
+ // @ts-expect-error it is possible to be null
+ name,
})
}
ast: RootNode,
options: CodegenOptions & {
onContextCreated?: (context: CodegenContext) => void
- } = {}
+ } = {},
): CodegenResult {
const context = createCodegenContext(ast, options)
if (options.onContextCreated) options.onContextCreated(context)
deindent,
newline,
scopeId,
- ssr
+ ssr,
} = context
const helpers = Array.from(ast.helpers)
if (hasHelpers) {
push(
`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
- NewlineType.End
+ NewlineType.End,
)
newline()
}
ast,
code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``,
- map: context.map ? context.map.toJSON() : undefined
+ map: context.map ? context.map.toJSON() : undefined,
}
}
newline,
runtimeModuleName,
runtimeGlobalName,
- ssrRuntimeModuleName
+ ssrRuntimeModuleName,
} = context
const VueBinding =
!__BROWSER__ && ssr
if (!__BROWSER__ && prefixIdentifiers) {
push(
`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`,
- NewlineType.End
+ NewlineType.End,
)
} else {
// "with" mode.
CREATE_ELEMENT_VNODE,
CREATE_COMMENT,
CREATE_TEXT,
- CREATE_STATIC
+ CREATE_STATIC,
]
.filter(helper => helpers.includes(helper))
.map(aliasHelper)
`const { ${ast.ssrHelpers
.map(aliasHelper)
.join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
- NewlineType.End
+ NewlineType.End,
)
}
genHoists(ast.hoists, context)
ast: RootNode,
context: CodegenContext,
genScopeId: boolean,
- inline?: boolean
+ inline?: boolean,
) {
const {
push,
newline,
optimizeImports,
runtimeModuleName,
- ssrRuntimeModuleName
+ ssrRuntimeModuleName,
} = context
if (genScopeId && ast.hoists.length) {
`import { ${helpers
.map(s => helperNameMap[s])
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
- NewlineType.End
+ NewlineType.End,
)
push(
`\n// Binding optimization for webpack code-split\nconst ${helpers
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
.join(', ')}\n`,
- NewlineType.End
+ NewlineType.End,
)
} else {
push(
`import { ${helpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
- NewlineType.End
+ NewlineType.End,
)
}
}
`import { ${ast.ssrHelpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from "${ssrRuntimeModuleName}"\n`,
- NewlineType.End
+ NewlineType.End,
)
}
function genAssets(
assets: string[],
type: 'component' | 'directive' | 'filter',
- { helper, push, newline, isTS }: CodegenContext
+ { helper, push, newline, isTS }: CodegenContext,
) {
const resolver = helper(
__COMPAT__ && type === 'filter'
? RESOLVE_FILTER
: type === 'component'
? RESOLVE_COMPONENT
- : RESOLVE_DIRECTIVE
+ : RESOLVE_DIRECTIVE,
)
for (let i = 0; i < assets.length; i++) {
let id = assets[i]
push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
maybeSelfReference ? `, true` : ``
- })${isTS ? `!` : ``}`
+ })${isTS ? `!` : ``}`,
)
if (i < assets.length - 1) {
newline()
if (genScopeId) {
push(
`const _withScopeId = n => (${helper(
- PUSH_SCOPE_ID
- )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`
+ PUSH_SCOPE_ID,
+ )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
)
newline()
}
push(
`const _hoisted_${i + 1} = ${
needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
- }`
+ }`,
)
genNode(exp, context)
if (needScopeIdWrapper) {
function genNodeListAsArray(
nodes: (string | CodegenNode | TemplateChildNode[])[],
- context: CodegenContext
+ context: CodegenContext,
) {
const multilines =
nodes.length > 3 ||
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
context: CodegenContext,
multilines: boolean = false,
- comma: boolean = true
+ comma: boolean = true,
) {
const { push, newline } = context
for (let i = 0; i < nodes.length; i++) {
assert(
node.codegenNode != null,
`Codegen node is missing for element/if/for node. ` +
- `Apply appropriate transforms first.`
+ `Apply appropriate transforms first.`,
)
genNode(node.codegenNode!, context)
break
function genText(
node: TextNode | SimpleExpressionNode,
- context: CodegenContext
+ context: CodegenContext,
) {
context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
}
context.push(
isStatic ? JSON.stringify(content) : content,
NewlineType.Unknown,
- node
+ node,
)
}
function genCompoundExpression(
node: CompoundExpressionNode,
- context: CodegenContext
+ context: CodegenContext,
) {
for (let i = 0; i < node.children!.length; i++) {
const child = node.children![i]
function genExpressionAsPropertyKey(
node: ExpressionNode,
- context: CodegenContext
+ context: CodegenContext,
) {
const { push } = context
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
push(
`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
NewlineType.Unknown,
- node
+ node,
)
}
directives,
isBlock,
disableTracking,
- isComponent
+ isComponent,
} = node
if (directives) {
push(helper(WITH_DIRECTIVES) + `(`)
push(helper(callHelper) + `(`, NewlineType.None, node)
genNodeList(
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
- context
+ context,
)
push(`)`)
if (isBlock) {
function genFunctionExpression(
node: FunctionExpression,
- context: CodegenContext
+ context: CodegenContext,
) {
const { push, indent, deindent } = context
const { params, returns, body, newline, isSlot } = node
function genConditionalExpression(
node: ConditionalExpression,
- context: CodegenContext
+ context: CodegenContext,
) {
const { test, consequent, alternate, newline: needNewline } = node
const { push, indent, deindent, newline } = context
function genAssignmentExpression(
node: AssignmentExpression,
- context: CodegenContext
+ context: CodegenContext,
) {
genNode(node.left, context)
context.push(` = `)
function genSequenceExpression(
node: SequenceExpression,
- context: CodegenContext
+ context: CodegenContext,
) {
context.push(`(`)
genNodeList(node.expressions, context)
function genReturnStatement(
{ returns }: ReturnStatement,
- context: CodegenContext
+ context: CodegenContext,
) {
context.push(`return `)
if (isArray(returns)) {
-import { SourceLocation } from '../ast'
-import { CompilerError } from '../errors'
-import { MergedParserOptions } from '../parser'
-import { TransformContext } from '../transform'
+import type { SourceLocation } from '../ast'
+import type { CompilerError } from '../errors'
+import type { MergedParserOptions } from '../parser'
+import type { TransformContext } from '../transform'
export type CompilerCompatConfig = Partial<
Record<CompilerDeprecationTypes, boolean | 'suppress-warning'>
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
- COMPILER_FILTERS = 'COMPILER_FILTER'
+ COMPILER_FILTERS = 'COMPILER_FILTER',
}
type DeprecationData = {
`Platform-native elements with "is" prop will no longer be ` +
`treated as components in Vue 3 unless the "is" value is explicitly ` +
`prefixed with "vue:".`,
- link: `https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html`,
},
[CompilerDeprecationTypes.COMPILER_V_BIND_SYNC]: {
`.sync modifier for v-bind has been removed. Use v-model with ` +
`argument instead. \`v-bind:${key}.sync\` should be changed to ` +
`\`v-model:${key}\`.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`,
},
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
`that appears before v-bind in the case of conflict. ` +
`To retain 2.x behavior, move v-bind to make it the first attribute. ` +
`You can also suppress this warning if the usage is intended.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/v-bind.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-bind.html`,
},
[CompilerDeprecationTypes.COMPILER_V_ON_NATIVE]: {
message: `.native modifier for v-on has been removed as is no longer necessary.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html`,
},
[CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE]: {
`access to v-for scope variables. It is best to avoid the ambiguity ` +
`with <template> tags or use a computed property that filters v-for ` +
`data source.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html`,
},
[CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: {
message:
`<template> with no special directives will render as a native template ` +
- `element instead of its inner content in Vue 3.`
+ `element instead of its inner content in Vue 3.`,
},
[CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE]: {
message: `"inline-template" has been removed in Vue 3.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute.html`,
},
[CompilerDeprecationTypes.COMPILER_FILTERS]: {
`filters have been removed in Vue 3. ` +
`The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
`Use method calls or computed properties instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/filters.html`
- }
+ link: `https://v3-migration.vuejs.org/breaking-changes/filters.html`,
+ },
}
function getCompatValue(
key: CompilerDeprecationTypes | 'MODE',
- { compatConfig }: MergedParserOptions | TransformContext
+ { compatConfig }: MergedParserOptions | TransformContext,
) {
const value = compatConfig && compatConfig[key]
if (key === 'MODE') {
export function isCompatEnabled(
key: CompilerDeprecationTypes,
- context: MergedParserOptions | TransformContext
+ context: MergedParserOptions | TransformContext,
) {
const mode = getCompatValue('MODE', context)
const value = getCompatValue(key, context)
import { RESOLVE_FILTER } from '../runtimeHelpers'
import {
- ExpressionNode,
- AttributeNode,
- DirectiveNode,
+ type AttributeNode,
+ type DirectiveNode,
+ type ExpressionNode,
NodeTypes,
- SimpleExpressionNode
+ type SimpleExpressionNode,
} from '../ast'
import {
CompilerDeprecationTypes,
isCompatEnabled,
- warnDeprecation
+ warnDeprecation,
} from './compatConfig'
-import { NodeTransform, TransformContext } from '../transform'
+import type { NodeTransform, TransformContext } from '../transform'
import { toValidAssetId } from '../utils'
const validDivisionCharRE = /[\w).+\-_$\]]/
warnDeprecation(
CompilerDeprecationTypes.COMPILER_FILTERS,
context,
- node.loc
+ node.loc,
)
for (i = 0; i < filters.length; i++) {
expression = wrapFilter(expression, filters[i], context)
function wrapFilter(
exp: string,
filter: string,
- context: TransformContext
+ context: TransformContext,
): string {
context.helper(RESOLVE_FILTER)
const i = filter.indexOf('(')
-import { CompilerOptions } from './options'
+import type { CompilerOptions } from './options'
import { baseParse } from './parser'
-import { transform, NodeTransform, DirectiveTransform } from './transform'
-import { generate, CodegenResult } from './codegen'
-import { RootNode } from './ast'
-import { isString, extend } from '@vue/shared'
+import {
+ type DirectiveTransform,
+ type NodeTransform,
+ transform,
+} from './transform'
+import { type CodegenResult, generate } from './codegen'
+import type { RootNode } from './ast'
+import { extend, isString } from '@vue/shared'
import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor'
import { transformExpression } from './transforms/transformExpression'
import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel'
import { transformFilter } from './compat/transformFilter'
-import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
+import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
import { transformMemo } from './transforms/vMemo'
export type TransformPreset = [
NodeTransform[],
- Record<string, DirectiveTransform>
+ Record<string, DirectiveTransform>,
]
export function getBaseTransformPreset(
- prefixIdentifiers?: boolean
+ prefixIdentifiers?: boolean,
): TransformPreset {
return [
[
? [
// order is important
trackVForSlotScopes,
- transformExpression
+ transformExpression,
]
: __BROWSER__ && __DEV__
? [transformExpression]
transformSlotOutlet,
transformElement,
trackSlotScopes,
- transformText
+ transformText,
],
{
on: transformOn,
bind: transformBind,
- model: transformModel
- }
+ model: transformModel,
+ },
]
}
// @vue/compiler-dom can export `compile` while re-exporting everything else.
export function baseCompile(
source: string | RootNode,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
): CodegenResult {
const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module'
}
const resolvedOptions = extend({}, options, {
- prefixIdentifiers
+ prefixIdentifiers,
})
const ast = isString(source) ? baseParse(source, resolvedOptions) : source
const [nodeTransforms, directiveTransforms] =
extend({}, resolvedOptions, {
nodeTransforms: [
...nodeTransforms,
- ...(options.nodeTransforms || []) // user transforms
+ ...(options.nodeTransforms || []), // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
- options.directiveTransforms || {} // user transforms
- )
- })
+ options.directiveTransforms || {}, // user transforms
+ ),
+ }),
)
return generate(ast, resolvedOptions)
-import { SourceLocation } from './ast'
+import type { SourceLocation } from './ast'
export interface CompilerError extends SyntaxError {
code: number | string
code: T,
loc?: SourceLocation,
messages?: { [code: number]: string },
- additionalMessage?: string
+ additionalMessage?: string,
): InferCompilerError<T> {
const msg =
__DEV__ || !__BROWSER__
// 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
// item.
- __EXTEND_POINT__
+ __EXTEND_POINT__,
}
export const errorMessages: Record<ErrorCodes, string> = {
[ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`,
// just to fulfill types
- [ErrorCodes.__EXTEND_POINT__]: ``
+ [ErrorCodes.__EXTEND_POINT__]: ``,
}
type CodegenOptions,
type HoistTransform,
type BindingMetadata,
- BindingTypes
+ BindingTypes,
} from './options'
export { baseParse } from './parser'
export {
createStructuralDirectiveTransform,
type NodeTransform,
type StructuralDirectiveTransform,
- type DirectiveTransform
+ type DirectiveTransform,
} from './transform'
export { generate, type CodegenContext, type CodegenResult } from './codegen'
export {
errorMessages,
createCompilerError,
type CoreCompilerError,
- type CompilerError
+ type CompilerError,
} from './errors'
export * from './ast'
export {
transformExpression,
processExpression,
- stringifyExpression
+ stringifyExpression,
} from './transforms/transformExpression'
export {
buildSlots,
type SlotFnBuilder,
trackVForSlotScopes,
- trackSlotScopes
+ trackSlotScopes,
} from './transforms/vSlot'
export {
transformElement,
resolveComponentType,
buildProps,
buildDirectiveArgs,
- type PropsExpression
+ type PropsExpression,
} from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
export { getConstantType } from './transforms/hoistStatic'
export {
checkCompatEnabled,
warnDeprecation,
- CompilerDeprecationTypes
+ CompilerDeprecationTypes,
} from './compat/compatConfig'
-import {
+import type {
ElementNode,
Namespace,
- TemplateChildNode,
+ Namespaces,
ParentNode,
- Namespaces
+ TemplateChildNode,
} from './ast'
-import { CompilerError } from './errors'
-import {
- NodeTransform,
+import type { CompilerError } from './errors'
+import type {
DirectiveTransform,
- TransformContext
+ NodeTransform,
+ TransformContext,
} from './transform'
-import { CompilerCompatOptions } from './compat/compatConfig'
-import { ParserPlugin } from '@babel/parser'
+import type { CompilerCompatOptions } from './compat/compatConfig'
+import type { ParserPlugin } from '@babel/parser'
export interface ErrorHandlingOptions {
onWarn?: (warning: CompilerError) => void
getNamespace?: (
tag: string,
parent: ElementNode | undefined,
- rootNamespace: Namespace
+ rootNamespace: Namespace,
) => Namespace
/**
* @default ['{{', '}}']
export type HoistTransform = (
children: TemplateChildNode[],
context: TransformContext,
- parent: ParentNode
+ parent: ParentNode,
) => void
export enum BindingTypes {
/**
* a literal constant, e.g. 'foo', 1, true
*/
- LITERAL_CONST = 'literal-const'
+ LITERAL_CONST = 'literal-const',
}
export type BindingMetadata = {
import {
- AttributeNode,
+ type AttributeNode,
ConstantTypes,
- DirectiveNode,
- ElementNode,
+ type DirectiveNode,
+ type ElementNode,
ElementTypes,
- ForParseResult,
+ type ForParseResult,
Namespaces,
NodeTypes,
- RootNode,
- SimpleExpressionNode,
- SourceLocation,
- TemplateChildNode,
+ type RootNode,
+ type SimpleExpressionNode,
+ type SourceLocation,
+ type TemplateChildNode,
createRoot,
- createSimpleExpression
+ createSimpleExpression,
} from './ast'
-import { ParserOptions } from './options'
+import type { ParserOptions } from './options'
import Tokenizer, {
CharCodes,
ParseMode,
Sequences,
State,
isWhitespace,
- toCharCodes
+ toCharCodes,
} from './tokenizer'
import {
- CompilerCompatOptions,
+ type CompilerCompatOptions,
CompilerDeprecationTypes,
checkCompatEnabled,
isCompatEnabled,
- warnDeprecation
+ warnDeprecation,
} from './compat/compatConfig'
import { NO, extend } from '@vue/shared'
import {
ErrorCodes,
createCompilerError,
defaultOnError,
- defaultOnWarn
+ defaultOnWarn,
} from './errors'
import {
forAliasRE,
isCoreComponent,
isSimpleIdentifier,
- isStaticArgOf
+ isStaticArgOf,
} from './utils'
import { decodeHTML } from 'entities/lib/decode.js'
import {
+ type ParserOptions as BabelOptions,
parse,
parseExpression,
- type ParserOptions as BabelOptions
} from '@babel/parser'
type OptionalOptions =
onError: defaultOnError,
onWarn: defaultOnWarn,
comments: __DEV__,
- prefixIdentifiers: false
+ prefixIdentifiers: false,
}
let currentOptions: MergedParserOptions = defaultParserOptions
addNode({
type: NodeTypes.INTERPOLATION,
content: createExp(exp, false, getLoc(innerStart, innerEnd)),
- loc: getLoc(start, end)
+ loc: getLoc(start, end),
})
},
props: [],
children: [],
loc: getLoc(start - 1, end),
- codegenNode: undefined
+ codegenNode: undefined,
}
},
name: getSlice(start, end),
nameLoc: getLoc(start, end),
value: undefined,
- loc: getLoc(start)
+ loc: getLoc(start),
}
},
name: raw,
nameLoc: getLoc(start, end),
value: undefined,
- loc: getLoc(start)
+ loc: getLoc(start),
}
} else {
currentProp = {
exp: undefined,
arg: undefined,
modifiers: raw === '.' ? ['prop'] : [],
- loc: getLoc(start)
+ loc: getLoc(start),
}
if (name === 'pre') {
inVPre = tokenizer.inVPre = true
isStatic ? arg : arg.slice(1, -1),
isStatic,
getLoc(start, end),
- isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
+ isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT,
)
}
},
// check duplicate attrs
if (
currentOpenTag!.props.some(
- p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name
+ p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name,
)
) {
emitError(ErrorCodes.DUPLICATE_ATTRIBUTE, start)
if (__BROWSER__ && currentAttrValue.includes('&')) {
currentAttrValue = currentOptions.decodeEntities!(
currentAttrValue,
- true
+ true,
)
}
loc:
quote === QuoteType.Unquoted
? getLoc(currentAttrStartIndex, currentAttrEndIndex)
- : getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1)
+ : getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1),
}
if (
tokenizer.inSFCRoot &&
false,
getLoc(currentAttrStartIndex, currentAttrEndIndex),
ConstantTypes.NOT_CONSTANT,
- expParseMode
+ expParseMode,
)
if (currentProp.name === 'for') {
currentProp.forParseResult = parseForExpression(currentProp.exp)
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
currentOptions,
currentProp.loc,
- currentProp.rawName
+ currentProp.rawName,
)
) {
currentProp.name = 'model'
addNode({
type: NodeTypes.COMMENT,
content: getSlice(start, end),
- loc: getLoc(start - 4, end + 3)
+ loc: getLoc(start - 4, end + 3),
})
}
},
case State.InterpolationClose:
emitError(
ErrorCodes.X_MISSING_INTERPOLATION_END,
- tokenizer.sectionStart
+ tokenizer.sectionStart,
)
break
case State.InCommentLike:
if ((stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML) {
emitError(
ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
- start - 1
+ start - 1,
)
}
- }
+ },
})
// This regex doesn't cover the case if key or index aliases have destructuring,
const stripParensRE = /^\(|\)$/g
function parseForExpression(
- input: SimpleExpressionNode
+ input: SimpleExpressionNode,
): ForParseResult | undefined {
const loc = input.loc
const exp = input.content
const createAliasExpression = (
content: string,
offset: number,
- asParam = false
+ asParam = false,
) => {
const start = loc.start.offset + offset
const end = start + content.length
false,
getLoc(start, end),
ConstantTypes.NOT_CONSTANT,
- asParam ? ExpParseMode.Params : ExpParseMode.Normal
+ asParam ? ExpParseMode.Params : ExpParseMode.Normal,
)
}
value: undefined,
key: undefined,
index: undefined,
- finalized: false
+ finalized: false,
}
let valueContent = LHS.trim().replace(stripParensRE, '').trim()
indexContent,
result.key
? keyOffset! + keyContent.length
- : trimmedOffset + valueContent.length
+ : trimmedOffset + valueContent.length,
),
- true
+ true,
)
}
}
parent.children.push({
type: NodeTypes.TEXT,
content,
- loc: getLoc(start, end)
+ loc: getLoc(start, end),
})
}
}
}
el.innerLoc!.source = getSlice(
el.innerLoc!.start.offset,
- el.innerLoc!.end.offset
+ el.innerLoc!.end.offset,
)
}
__DEV__ &&
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
- currentOptions
+ currentOptions,
)
) {
let hasIf = false
warnDeprecation(
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
currentOptions,
- el.loc
+ el.loc,
)
break
}
if (
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
- currentOptions
+ currentOptions,
) &&
el.tag === 'template' &&
!isFragmentTemplate(el)
warnDeprecation(
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
currentOptions,
- el.loc
+ el.loc,
)
// unwrap
const parent = stack[0] || currentRoot
}
const inlineTemplateProp = props.find(
- p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template'
+ p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template',
) as AttributeNode
if (
inlineTemplateProp &&
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
currentOptions,
- inlineTemplateProp.loc
+ inlineTemplateProp.loc,
) &&
el.children.length
) {
type: NodeTypes.TEXT,
content: getSlice(
el.children[0].loc.start.offset,
- el.children[el.children.length - 1].loc.end.offset
+ el.children[el.children.length - 1].loc.end.offset,
),
- loc: inlineTemplateProp.loc
+ loc: inlineTemplateProp.loc,
}
}
}
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
currentOptions,
- p.loc
+ p.loc,
)
) {
return true
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
currentOptions,
- p.loc
+ p.loc,
)
) {
return true
const windowsNewlineRE = /\r\n/g
function condenseWhitespace(
nodes: TemplateChildNode[],
- tag?: string
+ tag?: string,
): TemplateChildNode[] {
const shouldCondense = currentOptions.whitespace !== 'preserve'
let removedWhitespace = false
// @ts-expect-error allow late attachment
end: end == null ? end : tokenizer.getPos(end),
// @ts-expect-error allow late attachment
- source: end == null ? end : getSlice(start, end)
+ source: end == null ? end : getSlice(start, end),
}
}
name: dir.rawName!,
nameLoc: getLoc(
dir.loc.start.offset,
- dir.loc.start.offset + dir.rawName!.length
+ dir.loc.start.offset + dir.rawName!.length,
),
value: undefined,
- loc: dir.loc
+ loc: dir.loc,
}
if (dir.exp) {
// account for quotes
attr.value = {
type: NodeTypes.TEXT,
content: (dir.exp as SimpleExpressionNode).content,
- loc
+ loc,
}
}
return attr
Normal,
Params,
Statements,
- Skip
+ Skip,
}
function createExp(
isStatic: SimpleExpressionNode['isStatic'] = false,
loc: SourceLocation,
constType: ConstantTypes = ConstantTypes.NOT_CONSTANT,
- parseMode = ExpParseMode.Normal
+ parseMode = ExpParseMode.Normal,
) {
const exp = createSimpleExpression(content, isStatic, loc, constType)
if (
try {
const plugins = currentOptions.expressionPlugins
const options: BabelOptions = {
- plugins: plugins ? [...plugins, 'typescript'] : ['typescript']
+ plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
}
if (parseMode === ExpParseMode.Statements) {
// v-on with multi-inline-statements, pad 1 char
function emitError(code: ErrorCodes, index: number, message?: string) {
currentOptions.onError(
- createCompilerError(code, getLoc(index, index), undefined, message)
+ createCompilerError(code, getLoc(index, index), undefined, message),
)
}
let key: keyof ParserOptions
for (key in options) {
if (options[key] != null) {
- // @ts-ignore
+ // @ts-expect-error
currentOptions[key] = options[key]
}
}
if (!__BROWSER__ && currentOptions.decodeEntities) {
console.warn(
`[@vue/compiler-core] decodeEntities option is passed but will be ` +
- `ignored in non-browser builds.`
+ `ignored in non-browser builds.`,
)
} else if (__BROWSER__ && !currentOptions.decodeEntities) {
throw new Error(
- `[@vue/compiler-core] decodeEntities option is required in browser builds.`
+ `[@vue/compiler-core] decodeEntities option is required in browser builds.`,
)
}
}
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``)
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
- __DEV__ ? `resolveDynamicComponent` : ``
+ __DEV__ ? `resolveDynamicComponent` : ``,
)
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``)
[UNREF]: `unref`,
[IS_REF]: `isRef`,
[WITH_MEMO]: `withMemo`,
- [IS_MEMO_SAME]: `isMemoSame`
+ [IS_MEMO_SAME]: `isMemoSame`,
}
export function registerRuntimeHelpers(helpers: Record<symbol, string>) {
*/
import { ErrorCodes } from './errors'
-import { ElementNode, Position } from './ast'
+import type { ElementNode, Position } from './ast'
/**
* Note: entities is a non-browser-build-only dependency.
* so that it can be properly treeshaken.
*/
import {
- EntityDecoder,
DecodingMode,
+ EntityDecoder,
+ fromCodePoint,
htmlDecodeTree,
- fromCodePoint
} from 'entities/lib/decode.js'
export enum ParseMode {
BASE,
HTML,
- SFC
+ SFC,
}
export enum CharCodes {
Colon = 0x3a, // ":"
At = 0x40, // "@"
LeftSquare = 91, // "["
- RightSquare = 93 // "]"
+ RightSquare = 93, // "]"
}
const defaultDelimitersOpen = new Uint8Array([123, 123]) // "{{"
InEntity,
- InSFCRootTagName
+ InSFCRootTagName,
}
/**
NoValue = 0,
Unquoted = 1,
Single = 2,
- Double = 3
+ Double = 3,
}
export interface Callbacks {
StyleEnd: new Uint8Array([0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, 0x65]), // `</style`
TitleEnd: new Uint8Array([0x3c, 0x2f, 0x74, 0x69, 0x74, 0x6c, 0x65]), // `</title`
TextareaEnd: new Uint8Array([
- 0x3c, 0x2f, 116, 101, 120, 116, 97, 114, 101, 97
- ]) // `</textarea
+ 0x3c, 0x2f, 116, 101, 120, 116, 97, 114, 101, 97,
+ ]), // `</textarea
}
export default class Tokenizer {
constructor(
private readonly stack: ElementNode[],
- private readonly cbs: Callbacks
+ private readonly cbs: Callbacks,
) {
if (!__BROWSER__) {
this.entityDecoder = new EntityDecoder(htmlDecodeTree, (cp, consumed) =>
- this.emitCodePoint(cp, consumed)
+ this.emitCodePoint(cp, consumed),
)
}
}
return {
column,
line,
- offset: index
+ offset: index,
}
}
if ((__DEV__ || !__BROWSER__) && c === CharCodes.Eq) {
this.cbs.onerr(
ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
- this.index
+ this.index,
)
}
this.handleAttrStart(c)
) {
this.cbs.onerr(
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
- this.index
+ this.index,
)
}
}
if (__DEV__ || !__BROWSER__) {
this.cbs.onerr(
ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
- this.index
+ this.index,
)
}
}
this.sectionStart = -1
this.cbs.onattribend(
quote === CharCodes.DoubleQuote ? QuoteType.Double : QuoteType.Single,
- this.index + 1
+ this.index + 1,
)
this.state = State.BeforeAttrName
} else if (!__BROWSER__ && c === CharCodes.Amp) {
) {
this.cbs.onerr(
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
- this.index
+ this.index,
)
} else if (!__BROWSER__ && c === CharCodes.Amp) {
this.startEntity()
this.entityDecoder!.startEntity(
this.baseState === State.Text || this.baseState === State.InRCDATA
? DecodingMode.Legacy
- : DecodingMode.Attribute
+ : DecodingMode.Attribute,
)
}
}
this.cbs.onattribentity(
fromCodePoint(cp),
this.entityStart,
- this.sectionStart
+ this.sectionStart,
)
} else {
if (this.sectionStart < this.entityStart) {
this.cbs.ontextentity(
fromCodePoint(cp),
this.entityStart,
- this.sectionStart
+ this.sectionStart,
)
}
}
-import { TransformOptions } from './options'
+import type { TransformOptions } from './options'
import {
- RootNode,
- NodeTypes,
- ParentNode,
- TemplateChildNode,
- ElementNode,
- DirectiveNode,
- Property,
- ExpressionNode,
- createSimpleExpression,
- JSChildNode,
- SimpleExpressionNode,
+ type ArrayExpression,
+ type CacheExpression,
+ ConstantTypes,
+ type DirectiveNode,
+ type ElementNode,
ElementTypes,
- CacheExpression,
+ type ExpressionNode,
+ type JSChildNode,
+ NodeTypes,
+ type ParentNode,
+ type Property,
+ type RootNode,
+ type SimpleExpressionNode,
+ type TemplateChildNode,
+ type TemplateLiteral,
+ convertToBlock,
createCacheExpression,
- TemplateLiteral,
+ createSimpleExpression,
createVNodeCall,
- ConstantTypes,
- ArrayExpression,
- convertToBlock
} from './ast'
import {
- isString,
- isArray,
+ EMPTY_OBJ,
NOOP,
- PatchFlags,
PatchFlagNames,
- EMPTY_OBJ,
+ PatchFlags,
+ camelize,
capitalize,
- camelize
+ isArray,
+ isString,
} from '@vue/shared'
import { defaultOnError, defaultOnWarn } from './errors'
import {
- TO_DISPLAY_STRING,
+ CREATE_COMMENT,
FRAGMENT,
+ TO_DISPLAY_STRING,
helperNameMap,
- CREATE_COMMENT
} from './runtimeHelpers'
import { isVSlot } from './utils'
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
-import { CompilerCompatOptions } from './compat/compatConfig'
+import type { CompilerCompatOptions } from './compat/compatConfig'
// There are two types of transforms:
//
// replace or remove the node being processed.
export type NodeTransform = (
node: RootNode | TemplateChildNode,
- context: TransformContext
+ context: TransformContext,
) => void | (() => void) | (() => void)[]
// - DirectiveTransform:
context: TransformContext,
// a platform specific compiler can import the base transform and augment
// it by passing in this optional argument.
- augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult
+ augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,
) => DirectiveTransformResult
export interface DirectiveTransformResult {
export type StructuralDirectiveTransform = (
node: ElementNode,
dir: DirectiveNode,
- context: TransformContext
+ context: TransformContext,
) => void | (() => void)
export interface ImportItem {
isTS = false,
onError = defaultOnError,
onWarn = defaultOnWarn,
- compatConfig
- }: TransformOptions
+ compatConfig,
+ }: TransformOptions,
): TransformContext {
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
const context: TransformContext = {
vFor: 0,
vSlot: 0,
vPre: 0,
- vOnce: 0
+ vOnce: 0,
},
parent: null,
currentNode: root,
`_hoisted_${context.hoists.length}`,
false,
exp.loc,
- ConstantTypes.CAN_HOIST
+ ConstantTypes.CAN_HOIST,
)
identifier.hoisted = exp
return identifier
},
cache(exp, isVNode = false) {
return createCacheExpression(context.cached++, exp, isVNode)
- }
+ },
}
if (__COMPAT__) {
undefined,
true,
undefined,
- false /* isComponent */
+ false /* isComponent */,
)
} else {
// no children = noop. codegen will return null.
export function traverseChildren(
parent: ParentNode,
- context: TransformContext
+ context: TransformContext,
) {
let i = 0
const nodeRemoved = () => {
export function traverseNode(
node: RootNode | TemplateChildNode,
- context: TransformContext
+ context: TransformContext,
) {
context.currentNode = node
// apply transform plugins
export function createStructuralDirectiveTransform(
name: string | RegExp,
- fn: StructuralDirectiveTransform
+ fn: StructuralDirectiveTransform,
): NodeTransform {
const matches = isString(name)
? (n: string) => n === name
import {
+ type CallExpression,
+ type ComponentNode,
ConstantTypes,
- RootNode,
- NodeTypes,
- TemplateChildNode,
- SimpleExpressionNode,
ElementTypes,
- PlainElementNode,
- ComponentNode,
- TemplateNode,
- VNodeCall,
- ParentNode,
- JSChildNode,
- CallExpression,
+ type JSChildNode,
+ NodeTypes,
+ type ParentNode,
+ type PlainElementNode,
+ type RootNode,
+ type SimpleExpressionNode,
+ type TemplateChildNode,
+ type TemplateNode,
+ type VNodeCall,
createArrayExpression,
getVNodeBlockHelper,
- getVNodeHelper
+ getVNodeHelper,
} from '../ast'
-import { TransformContext } from '../transform'
-import { PatchFlags, isString, isSymbol, isArray } from '@vue/shared'
+import type { TransformContext } from '../transform'
+import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet } from '../utils'
import {
- OPEN_BLOCK,
GUARD_REACTIVE_PROPS,
NORMALIZE_CLASS,
NORMALIZE_PROPS,
- NORMALIZE_STYLE
+ NORMALIZE_STYLE,
+ OPEN_BLOCK,
} from '../runtimeHelpers'
export function hoistStatic(root: RootNode, context: TransformContext) {
context,
// Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes.
- isSingleElementRoot(root, root.children[0])
+ isSingleElementRoot(root, root.children[0]),
)
}
export function isSingleElementRoot(
root: RootNode,
- child: TemplateChildNode
+ child: TemplateChildNode,
): child is PlainElementNode | ComponentNode | TemplateNode {
const { children } = root
return (
function walk(
node: ParentNode,
context: TransformContext,
- doNotHoistNode: boolean = false
+ doNotHoistNode: boolean = false,
) {
const { children } = node
const originalCount = children.length
walk(
child.branches[i],
context,
- child.branches[i].children.length === 1
+ child.branches[i].children.length === 1,
)
}
}
isArray(node.codegenNode.children)
) {
const hoisted = context.hoist(
- createArrayExpression(node.codegenNode.children)
+ createArrayExpression(node.codegenNode.children),
)
// #6978, #7138, #7114
// a hoisted children array inside v-for can caused HMR errors since
export function getConstantType(
node: TemplateChildNode | SimpleExpressionNode,
- context: TransformContext
+ context: TransformContext,
): ConstantTypes {
const { constantCache } = context
switch (node.type) {
context.removeHelper(OPEN_BLOCK)
context.removeHelper(
- getVNodeBlockHelper(context.inSSR, codegenNode.isComponent)
+ getVNodeBlockHelper(context.inSSR, codegenNode.isComponent),
)
codegenNode.isBlock = false
context.helper(getVNodeHelper(context.inSSR, codegenNode.isComponent))
NORMALIZE_CLASS,
NORMALIZE_STYLE,
NORMALIZE_PROPS,
- GUARD_REACTIVE_PROPS
+ GUARD_REACTIVE_PROPS,
])
function getConstantTypeOfHelperCall(
value: CallExpression,
- context: TransformContext
+ context: TransformContext,
): ConstantTypes {
if (
value.type === NodeTypes.JS_CALL_EXPRESSION &&
function getGeneratedPropsConstantType(
node: PlainElementNode,
- context: TransformContext
+ context: TransformContext,
): ConstantTypes {
let returnType = ConstantTypes.CAN_STRINGIFY
const props = getNodeProps(node)
-import { DirectiveTransform } from '../transform'
+import type { DirectiveTransform } from '../transform'
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })
-import { NodeTransform, TransformContext } from '../transform'
+import type { NodeTransform, TransformContext } from '../transform'
import {
- NodeTypes,
+ type ArrayExpression,
+ type CallExpression,
+ type ComponentNode,
+ ConstantTypes,
+ type DirectiveArguments,
+ type DirectiveNode,
+ type ElementNode,
ElementTypes,
- CallExpression,
- ObjectExpression,
- ElementNode,
- DirectiveNode,
- ExpressionNode,
- ArrayExpression,
- createCallExpression,
+ type ExpressionNode,
+ type JSChildNode,
+ NodeTypes,
+ type ObjectExpression,
+ type Property,
+ type TemplateTextChildNode,
+ type VNodeCall,
createArrayExpression,
+ createCallExpression,
+ createObjectExpression,
createObjectProperty,
createSimpleExpression,
- createObjectExpression,
- Property,
- ComponentNode,
- VNodeCall,
- TemplateTextChildNode,
- DirectiveArguments,
createVNodeCall,
- ConstantTypes,
- JSChildNode
} from '../ast'
import {
- PatchFlags,
PatchFlagNames,
- isSymbol,
- isOn,
+ PatchFlags,
+ camelize,
+ capitalize,
+ isBuiltInDirective,
isObject,
+ isOn,
isReservedProp,
- capitalize,
- camelize,
- isBuiltInDirective
+ isSymbol,
} from '@vue/shared'
-import { createCompilerError, ErrorCodes } from '../errors'
+import { ErrorCodes, createCompilerError } from '../errors'
import {
- RESOLVE_DIRECTIVE,
- RESOLVE_COMPONENT,
- RESOLVE_DYNAMIC_COMPONENT,
+ GUARD_REACTIVE_PROPS,
+ KEEP_ALIVE,
MERGE_PROPS,
NORMALIZE_CLASS,
- NORMALIZE_STYLE,
NORMALIZE_PROPS,
- TO_HANDLERS,
- TELEPORT,
- KEEP_ALIVE,
+ NORMALIZE_STYLE,
+ RESOLVE_COMPONENT,
+ RESOLVE_DIRECTIVE,
+ RESOLVE_DYNAMIC_COMPONENT,
SUSPENSE,
+ TELEPORT,
+ TO_HANDLERS,
UNREF,
- GUARD_REACTIVE_PROPS
} from '../runtimeHelpers'
import {
- toValidAssetId,
findProp,
isCoreComponent,
isStaticArgOf,
- isStaticExp
+ isStaticExp,
+ toValidAssetId,
} from '../utils'
import { buildSlots } from './vSlot'
import { getConstantType } from './hoistStatic'
import { BindingTypes } from '../options'
import {
- checkCompatEnabled,
CompilerDeprecationTypes,
- isCompatEnabled
+ checkCompatEnabled,
+ isCompatEnabled,
} from '../compat/compatConfig'
// some directive transforms (e.g. v-model) may return a symbol for runtime
context,
undefined,
isComponent,
- isDynamicComponent
+ isDynamicComponent,
)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag
vnodeDirectives =
directives && directives.length
? (createArrayExpression(
- directives.map(dir => buildDirectiveArgs(dir, context))
+ directives.map(dir => buildDirectiveArgs(dir, context)),
) as DirectiveArguments)
: undefined
createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end,
- source: ''
- })
+ source: '',
+ }),
)
}
}
!!shouldUseBlock,
false /* disableTracking */,
isComponent,
- node.loc
+ node.loc,
)
}
}
export function resolveComponentType(
node: ComponentNode,
context: TransformContext,
- ssr = false
+ ssr = false,
) {
let { tag } = node
(__COMPAT__ &&
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
- context
+ context,
))
) {
const exp =
: isProp.exp
if (exp) {
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
- exp
+ exp,
])
}
} else if (
props: ElementNode['props'] = node.props,
isComponent: boolean,
isDynamicComponent: boolean,
- ssr = false
+ ssr = false,
): {
props: PropsExpression | undefined
directives: DirectiveNode[]
const pushMergeArg = (arg?: PropsExpression) => {
if (properties.length) {
mergeArgs.push(
- createObjectExpression(dedupeProperties(properties), elementLoc)
+ createObjectExpression(dedupeProperties(properties), elementLoc),
)
properties = []
}
properties.push(
createObjectProperty(
createSimpleExpression('ref_for', true),
- createSimpleExpression('true')
- )
+ createSimpleExpression('true'),
+ ),
)
}
// in inline mode there is no setupState object, so we can't use string
properties.push(
createObjectProperty(
createSimpleExpression('ref_key', true),
- createSimpleExpression(value.content, true, value.loc)
- )
+ createSimpleExpression(value.content, true, value.loc),
+ ),
)
}
}
(__COMPAT__ &&
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
- context
+ context,
)))
) {
continue
createSimpleExpression(
value ? value.content : '',
isStatic,
- value ? value.loc : loc
- )
- )
+ value ? value.loc : loc,
+ ),
+ ),
)
} else {
// directives
if (name === 'slot') {
if (!isComponent) {
context.onError(
- createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, loc)
+ createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, loc),
)
}
continue
(__COMPAT__ &&
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
- context
+ context,
))))
) {
continue
properties.push(
createObjectProperty(
createSimpleExpression('ref_for', true),
- createSimpleExpression('true')
- )
+ createSimpleExpression('true'),
+ ),
)
}
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
context,
- loc
+ loc,
)
}
}
if (
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
- context
+ context,
)
) {
mergeArgs.unshift(exp)
type: NodeTypes.JS_CALL_EXPRESSION,
loc,
callee: context.helper(TO_HANDLERS),
- arguments: isComponent ? [exp] : [exp, `true`]
+ arguments: isComponent ? [exp] : [exp, `true`],
})
}
} else {
isVBind
? ErrorCodes.X_V_BIND_NO_EXPRESSION
: ErrorCodes.X_V_ON_NO_EXPRESSION,
- loc
- )
+ loc,
+ ),
)
}
continue
propsExpression = createCallExpression(
context.helper(MERGE_PROPS),
mergeArgs,
- elementLoc
+ elementLoc,
)
} else {
// single v-bind with nothing else - no need for a mergeProps call
} else if (properties.length) {
propsExpression = createObjectExpression(
dedupeProperties(properties),
- elementLoc
+ elementLoc,
)
}
if (classProp && !isStaticExp(classProp.value)) {
classProp.value = createCallExpression(
context.helper(NORMALIZE_CLASS),
- [classProp.value]
+ [classProp.value],
)
}
if (
) {
styleProp.value = createCallExpression(
context.helper(NORMALIZE_STYLE),
- [styleProp.value]
+ [styleProp.value],
)
}
} else {
// dynamic key binding, wrap with `normalizeProps`
propsExpression = createCallExpression(
context.helper(NORMALIZE_PROPS),
- [propsExpression]
+ [propsExpression],
)
}
break
context.helper(NORMALIZE_PROPS),
[
createCallExpression(context.helper(GUARD_REACTIVE_PROPS), [
- propsExpression
- ])
- ]
+ propsExpression,
+ ]),
+ ],
)
break
}
directives: runtimeDirectives,
patchFlag,
dynamicPropNames,
- shouldUseBlock
+ shouldUseBlock,
}
}
} else {
existing.value = createArrayExpression(
[existing.value, incoming.value],
- existing.loc
+ existing.loc,
)
}
}
export function buildDirectiveArgs(
dir: DirectiveNode,
- context: TransformContext
+ context: TransformContext,
): ArrayExpression {
const dirArgs: ArrayExpression['elements'] = []
const runtime = directiveImportMap.get(dir)
dirArgs.push(
createObjectExpression(
dir.modifiers.map(modifier =>
- createObjectProperty(modifier, trueExpression)
+ createObjectProperty(modifier, trueExpression),
),
- loc
- )
+ loc,
+ ),
)
}
return createArrayExpression(dirArgs, dir.loc)
// - This transform is only applied in non-browser builds because it relies on
// an additional JavaScript parser. In the browser, there is no source-map
// support and the code is wrapped in `with (this) { ... }`.
-import { NodeTransform, TransformContext } from '../transform'
+import type { NodeTransform, TransformContext } from '../transform'
import {
+ type CompoundExpressionNode,
+ ConstantTypes,
+ type ExpressionNode,
NodeTypes,
- createSimpleExpression,
- ExpressionNode,
- SimpleExpressionNode,
- CompoundExpressionNode,
+ type SimpleExpressionNode,
createCompoundExpression,
- ConstantTypes
+ createSimpleExpression,
} from '../ast'
import {
isInDestructureAssignment,
isStaticProperty,
isStaticPropertyKey,
- walkIdentifiers
+ walkIdentifiers,
} from '../babelUtils'
import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
import {
- isGloballyAllowed,
- makeMap,
+ genPropsAccessExp,
hasOwn,
+ isGloballyAllowed,
isString,
- genPropsAccessExp
+ makeMap,
} from '@vue/shared'
-import { createCompilerError, ErrorCodes } from '../errors'
-import {
- Node,
- Identifier,
+import { ErrorCodes, createCompilerError } from '../errors'
+import type {
AssignmentExpression,
- UpdateExpression
+ Identifier,
+ Node,
+ UpdateExpression,
} from '@babel/types'
import { validateBrowserExpression } from '../validateExpression'
import { parse } from '@babel/parser'
if (node.type === NodeTypes.INTERPOLATION) {
node.content = processExpression(
node.content as SimpleExpressionNode,
- context
+ context,
)
} else if (node.type === NodeTypes.ELEMENT) {
// handle directives on element
exp,
context,
// slot args must be processed as function params
- dir.name === 'slot'
+ dir.name === 'slot',
)
}
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
asParams = false,
// v-on handler values may contain multiple statements
asRawStatements = false,
- localVars: Record<string, number> = Object.create(context.identifiers)
+ localVars: Record<string, number> = Object.create(context.identifiers),
): ExpressionNode {
if (__BROWSER__) {
if (__DEV__) {
context,
false,
false,
- knownIds
- )
+ knownIds,
+ ),
)
return `${context.helperString(IS_REF)}(${raw})${
context.isTS ? ` //@ts-ignore\n` : ``
: `(${rawExp})${asParams ? `=>{}` : ``}`
try {
ast = parse(source, {
- plugins: context.expressionPlugins
+ plugins: context.expressionPlugins,
}).program
} catch (e: any) {
context.onError(
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
- e.message
- )
+ e.message,
+ ),
)
return node
}
},
true, // invoke on ALL identifiers
parentStack,
- knownIds
+ knownIds,
)
// We break up the compound expression into an array of strings and sub
{
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end),
- source
+ source,
},
- id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
- )
+ id.isConstant
+ ? ConstantTypes.CAN_STRINGIFY
+ : ConstantTypes.NOT_CONSTANT,
+ ),
)
if (i === ids.length - 1 && end < rawExp.length) {
children.push(rawExp.slice(end))
-import { NodeTransform, TransformContext } from '../transform'
+import type { NodeTransform, TransformContext } from '../transform'
import {
+ type CallExpression,
+ type ExpressionNode,
NodeTypes,
- CallExpression,
+ type SlotOutletNode,
createCallExpression,
- ExpressionNode,
- SlotOutletNode,
- createFunctionExpression
+ createFunctionExpression,
} from '../ast'
import { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'
-import { buildProps, PropsExpression } from './transformElement'
-import { createCompilerError, ErrorCodes } from '../errors'
+import { type PropsExpression, buildProps } from './transformElement'
+import { ErrorCodes, createCompilerError } from '../errors'
import { RENDER_SLOT } from '../runtimeHelpers'
import { camelize } from '@vue/shared'
slotName,
'{}',
'undefined',
- 'true'
+ 'true',
]
let expectedLen = 2
node.codegenNode = createCallExpression(
context.helper(RENDER_SLOT),
slotArgs,
- loc
+ loc,
)
}
}
export function processSlotOutlet(
node: SlotOutletNode,
- context: TransformContext
+ context: TransformContext,
): SlotOutletProcessResult {
let slotName: string | ExpressionNode = `"default"`
let slotProps: PropsExpression | undefined = undefined
context,
nonNameProps,
false,
- false
+ false,
)
slotProps = props
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
- directives[0].loc
- )
+ directives[0].loc,
+ ),
)
}
}
return {
slotName,
- slotProps
+ slotProps,
}
}
-import { NodeTransform } from '../transform'
+import type { NodeTransform } from '../transform'
import {
+ type CallExpression,
+ type CompoundExpressionNode,
+ ConstantTypes,
+ ElementTypes,
NodeTypes,
- CompoundExpressionNode,
createCallExpression,
- CallExpression,
- ElementTypes,
- ConstantTypes,
- createCompoundExpression
+ createCompoundExpression,
} from '../ast'
import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers'
-import { PatchFlags, PatchFlagNames } from '@vue/shared'
+import { PatchFlagNames, PatchFlags } from '@vue/shared'
import { getConstantType } from './hoistStatic'
// Merge adjacent text nodes and expressions into a single expression
if (!currentContainer) {
currentContainer = children[i] = createCompoundExpression(
[child],
- child.loc
+ child.loc,
)
}
// merge adjacent text node into current
!node.props.find(
p =>
p.type === NodeTypes.DIRECTIVE &&
- !context.directiveTransforms[p.name]
+ !context.directiveTransforms[p.name],
) &&
// in compat mode, <template> tags with no special directives
// will be rendered as a fragment so its children must be
) {
callArgs.push(
PatchFlags.TEXT +
- (__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``)
+ (__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``),
)
}
children[i] = {
loc: child.loc,
codegenNode: createCallExpression(
context.helper(CREATE_TEXT),
- callArgs
- )
+ callArgs,
+ ),
}
}
}
-import { DirectiveTransform } from '../transform'
+import type { DirectiveTransform } from '../transform'
import {
+ type ExpressionNode,
+ NodeTypes,
createObjectProperty,
createSimpleExpression,
- ExpressionNode,
- NodeTypes
} from '../ast'
-import { createCompilerError, ErrorCodes } from '../errors'
+import { ErrorCodes, createCompilerError } from '../errors'
import { camelize } from '@vue/shared'
import { CAMELIZE } from '../runtimeHelpers'
import { processExpression } from './transformExpression'
) {
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
return {
- props: [createObjectProperty(arg, createSimpleExpression('', true, loc))]
+ props: [createObjectProperty(arg, createSimpleExpression('', true, loc))],
}
}
return {
- props: [createObjectProperty(arg, exp)]
+ props: [createObjectProperty(arg, exp)],
}
}
import {
+ type TransformContext,
createStructuralDirectiveTransform,
- TransformContext
} from '../transform'
import {
+ type BlockCodegenNode,
+ ConstantTypes,
+ type DirectiveNode,
+ type ElementNode,
+ type ExpressionNode,
+ type ForCodegenNode,
+ type ForIteratorExpression,
+ type ForNode,
+ type ForParseResult,
+ type ForRenderListExpression,
NodeTypes,
- ExpressionNode,
- createSimpleExpression,
- SimpleExpressionNode,
+ type PlainElementNode,
+ type RenderSlotCall,
+ type SimpleExpressionNode,
+ type SlotOutletNode,
+ type VNodeCall,
+ createBlockStatement,
createCallExpression,
+ createCompoundExpression,
createFunctionExpression,
createObjectExpression,
createObjectProperty,
- ForCodegenNode,
- RenderSlotCall,
- SlotOutletNode,
- ElementNode,
- DirectiveNode,
- ForNode,
- PlainElementNode,
+ createSimpleExpression,
createVNodeCall,
- VNodeCall,
- ForRenderListExpression,
- BlockCodegenNode,
- ForIteratorExpression,
- ConstantTypes,
- createBlockStatement,
- createCompoundExpression,
getVNodeBlockHelper,
getVNodeHelper,
- ForParseResult
} from '../ast'
-import { createCompilerError, ErrorCodes } from '../errors'
+import { ErrorCodes, createCompilerError } from '../errors'
import {
+ findDir,
findProp,
- isTemplateNode,
- isSlotOutlet,
injectProp,
- findDir
+ isSlotOutlet,
+ isTemplateNode,
} from '../utils'
import {
- RENDER_LIST,
- OPEN_BLOCK,
FRAGMENT,
- IS_MEMO_SAME
+ IS_MEMO_SAME,
+ OPEN_BLOCK,
+ RENDER_LIST,
} from '../runtimeHelpers'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
-import { PatchFlags, PatchFlagNames } from '@vue/shared'
+import { PatchFlagNames, PatchFlags } from '@vue/shared'
export const transformFor = createStructuralDirectiveTransform(
'for',
// create the loop render function expression now, and add the
// iterator on exit after all children have been traversed
const renderExp = createCallExpression(helper(RENDER_LIST), [
- forNode.source
+ forNode.source,
]) as ForRenderListExpression
const isTemplate = isTemplateNode(node)
const memo = findDir(node, 'memo')
if (memo) {
memo.exp = processExpression(
memo.exp! as SimpleExpressionNode,
- context
+ context,
)
}
if (keyProperty && keyProp!.type !== NodeTypes.ATTRIBUTE) {
keyProperty.value = processExpression(
keyProperty.value as SimpleExpressionNode,
- context
+ context,
)
}
}
true /* isBlock */,
!isStableFragment /* disableTracking */,
false /* isComponent */,
- node.loc
+ node.loc,
) as ForCodegenNode
return () => {
context.onError(
createCompilerError(
ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
- key.loc
- )
+ key.loc,
+ ),
)
return true
}
undefined,
true,
undefined,
- false /* isComponent */
+ false /* isComponent */,
)
} else {
// Normal element v-for. Directly use the child's codegenNode
// switch from block to vnode
removeHelper(OPEN_BLOCK)
removeHelper(
- getVNodeBlockHelper(context.inSSR, childBlock.isComponent)
+ getVNodeBlockHelper(context.inSSR, childBlock.isComponent),
)
} else {
// switch from vnode to block
removeHelper(
- getVNodeHelper(context.inSSR, childBlock.isComponent)
+ getVNodeHelper(context.inSSR, childBlock.isComponent),
)
}
}
if (memo) {
const loop = createFunctionExpression(
createForLoopParams(forNode.parseResult, [
- createSimpleExpression(`_cached`)
- ])
+ createSimpleExpression(`_cached`),
+ ]),
)
loop.body = createBlockStatement([
createCompoundExpression([`const _memo = (`, memo.exp!, `)`]),
`if (_cached`,
...(keyExp ? [` && _cached.key === `, keyExp] : []),
` && ${context.helperString(
- IS_MEMO_SAME
- )}(_cached, _memo)) return _cached`
+ IS_MEMO_SAME,
+ )}(_cached, _memo)) return _cached`,
]),
createCompoundExpression([`const _item = `, childBlock as any]),
createSimpleExpression(`_item.memo = _memo`),
- createSimpleExpression(`return _item`)
+ createSimpleExpression(`return _item`),
])
renderExp.arguments.push(
loop as ForIteratorExpression,
createSimpleExpression(`_cache`),
- createSimpleExpression(String(context.cached++))
+ createSimpleExpression(String(context.cached++)),
)
} else {
renderExp.arguments.push(
createFunctionExpression(
createForLoopParams(forNode.parseResult),
childBlock,
- true /* force newline */
- ) as ForIteratorExpression
+ true /* force newline */,
+ ) as ForIteratorExpression,
)
}
}
})
- }
+ },
)
// target-agnostic transform used for both Client and SSR
node: ElementNode,
dir: DirectiveNode,
context: TransformContext,
- processCodegen?: (forNode: ForNode) => (() => void) | undefined
+ processCodegen?: (forNode: ForNode) => (() => void) | undefined,
) {
if (!dir.exp) {
context.onError(
- createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
+ createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc),
)
return
}
if (!parseResult) {
context.onError(
- createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
+ createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc),
)
return
}
keyAlias: key,
objectIndexAlias: index,
parseResult,
- children: isTemplateNode(node) ? node.children : [node]
+ children: isTemplateNode(node) ? node.children : [node],
}
context.replaceNode(forNode)
export function finalizeForParseResult(
result: ForParseResult,
- context: TransformContext
+ context: TransformContext,
) {
if (result.finalized) return
if (!__BROWSER__ && context.prefixIdentifiers) {
result.source = processExpression(
result.source as SimpleExpressionNode,
- context
+ context,
)
if (result.key) {
result.key = processExpression(
result.key as SimpleExpressionNode,
context,
- true
+ true,
)
}
if (result.index) {
result.index = processExpression(
result.index as SimpleExpressionNode,
context,
- true
+ true,
)
}
if (result.value) {
result.value = processExpression(
result.value as SimpleExpressionNode,
context,
- true
+ true,
)
}
}
validateBrowserExpression(
result.key as SimpleExpressionNode,
context,
- true
+ true,
)
}
if (result.index) {
validateBrowserExpression(
result.index as SimpleExpressionNode,
context,
- true
+ true,
)
}
if (result.value) {
validateBrowserExpression(
result.value as SimpleExpressionNode,
context,
- true
+ true,
)
}
}
export function createForLoopParams(
{ value, key, index }: ForParseResult,
- memoArgs: ExpressionNode[] = []
+ memoArgs: ExpressionNode[] = [],
): ExpressionNode[] {
return createParamsList([value, key, index, ...memoArgs])
}
function createParamsList(
- args: (ExpressionNode | undefined)[]
+ args: (ExpressionNode | undefined)[],
): ExpressionNode[] {
let i = args.length
while (i--) {
import {
+ type TransformContext,
createStructuralDirectiveTransform,
- TransformContext,
- traverseNode
+ traverseNode,
} from '../transform'
import {
- NodeTypes,
+ type AttributeNode,
+ type BlockCodegenNode,
+ type CacheExpression,
+ ConstantTypes,
+ type DirectiveNode,
+ type ElementNode,
ElementTypes,
- ElementNode,
- DirectiveNode,
- IfBranchNode,
- SimpleExpressionNode,
+ type IfBranchNode,
+ type IfConditionalExpression,
+ type IfNode,
+ type MemoExpression,
+ NodeTypes,
+ type SimpleExpressionNode,
+ convertToBlock,
createCallExpression,
createConditionalExpression,
- createSimpleExpression,
- createObjectProperty,
createObjectExpression,
- IfConditionalExpression,
- BlockCodegenNode,
- IfNode,
+ createObjectProperty,
+ createSimpleExpression,
createVNodeCall,
- AttributeNode,
locStub,
- CacheExpression,
- ConstantTypes,
- MemoExpression,
- convertToBlock
} from '../ast'
-import { createCompilerError, ErrorCodes } from '../errors'
+import { ErrorCodes, createCompilerError } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
-import { FRAGMENT, CREATE_COMMENT } from '../runtimeHelpers'
-import { injectProp, findDir, findProp, getMemoedVNodeCall } from '../utils'
-import { PatchFlags, PatchFlagNames } from '@vue/shared'
+import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
+import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
+import { PatchFlagNames, PatchFlags } from '@vue/shared'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
- context
+ context,
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
- context
+ context,
)
}
}
})
- }
+ },
)
// target-agnostic transform used for both Client and SSR
processCodegen?: (
node: IfNode,
branch: IfBranchNode,
- isRoot: boolean
- ) => (() => void) | undefined
+ isRoot: boolean,
+ ) => (() => void) | undefined,
) {
if (
dir.name !== 'else' &&
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(
- createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
+ createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc),
)
dir.exp = createSimpleExpression(`true`, false, loc)
}
const ifNode: IfNode = {
type: NodeTypes.IF,
loc: node.loc,
- branches: [branch]
+ branches: [branch],
}
context.replaceNode(ifNode)
if (processCodegen) {
sibling.branches[sibling.branches.length - 1].condition === undefined
) {
context.onError(
- createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
+ createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc),
)
}
context.onError(
createCompilerError(
ErrorCodes.X_V_IF_SAME_KEY,
- branch.userKey!.loc
- )
+ branch.userKey!.loc,
+ ),
)
}
})
context.currentNode = null
} else {
context.onError(
- createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
+ createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc),
)
}
break
condition: dir.name === 'else' ? undefined : dir.exp,
children: isTemplateIf && !findDir(node, 'for') ? node.children : [node],
userKey: findProp(node, `key`),
- isTemplateIf
+ isTemplateIf,
}
}
function createCodegenNodeForBranch(
branch: IfBranchNode,
keyIndex: number,
- context: TransformContext
+ context: TransformContext,
): IfConditionalExpression | BlockCodegenNode | MemoExpression {
if (branch.condition) {
return createConditionalExpression(
// closes the current block.
createCallExpression(context.helper(CREATE_COMMENT), [
__DEV__ ? '"v-if"' : '""',
- 'true'
- ])
+ 'true',
+ ]),
) as IfConditionalExpression
} else {
return createChildrenCodegenNode(branch, keyIndex, context)
function createChildrenCodegenNode(
branch: IfBranchNode,
keyIndex: number,
- context: TransformContext
+ context: TransformContext,
): BlockCodegenNode | MemoExpression {
const { helper } = context
const keyProperty = createObjectProperty(
`${keyIndex}`,
false,
locStub,
- ConstantTypes.CAN_HOIST
- )
+ ConstantTypes.CAN_HOIST,
+ ),
)
const { children } = branch
const firstChild = children[0]
true,
false,
false /* isComponent */,
- branch.loc
+ branch.loc,
)
}
} else {
function isSameKey(
a: AttributeNode | DirectiveNode | undefined,
- b: AttributeNode | DirectiveNode
+ b: AttributeNode | DirectiveNode,
): boolean {
if (!a || a.type !== b.type) {
return false
}
function getParentCondition(
- node: IfConditionalExpression | CacheExpression
+ node: IfConditionalExpression | CacheExpression,
): IfConditionalExpression {
while (true) {
if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
-import { NodeTransform } from '../transform'
+import type { NodeTransform } from '../transform'
import { findDir } from '../utils'
import {
+ ElementTypes,
+ type MemoExpression,
+ NodeTypes,
+ type PlainElementNode,
convertToBlock,
createCallExpression,
createFunctionExpression,
- ElementTypes,
- MemoExpression,
- NodeTypes,
- PlainElementNode
} from '../ast'
import { WITH_MEMO } from '../runtimeHelpers'
dir.exp!,
createFunctionExpression(undefined, codegenNode),
`_cache`,
- String(context.cached++)
+ String(context.cached++),
]) as MemoExpression
}
}
-import { DirectiveTransform } from '../transform'
+import type { DirectiveTransform } from '../transform'
import {
- createSimpleExpression,
- createObjectProperty,
- createCompoundExpression,
- NodeTypes,
- Property,
+ ConstantTypes,
ElementTypes,
- ExpressionNode,
- ConstantTypes
+ type ExpressionNode,
+ NodeTypes,
+ type Property,
+ createCompoundExpression,
+ createObjectProperty,
+ createSimpleExpression,
} from '../ast'
-import { createCompilerError, ErrorCodes } from '../errors'
+import { ErrorCodes, createCompilerError } from '../errors'
import {
+ hasScopeRef,
isMemberExpression,
isSimpleIdentifier,
- hasScopeRef,
- isStaticExp
+ isStaticExp,
} from '../utils'
import { IS_REF } from '../runtimeHelpers'
import { BindingTypes } from '../options'
const { exp, arg } = dir
if (!exp) {
context.onError(
- createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc)
+ createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc),
)
return createTransformProps()
}
(!isMemberExpression(expString, context) && !maybeRef)
) {
context.onError(
- createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
+ createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc),
)
return createTransformProps()
}
context.identifiers[expString]
) {
context.onError(
- createCompilerError(ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE, exp.loc)
+ createCompilerError(ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE, exp.loc),
)
return createTransformProps()
}
assignmentExp = createCompoundExpression([
`${eventArg} => ((`,
createSimpleExpression(rawExp, false, exp.loc),
- `).value = $event)`
+ `).value = $event)`,
])
} else {
// v-model used on a potentially ref binding in <script setup> inline mode.
assignmentExp = createCompoundExpression([
`${eventArg} => (${context.helperString(IS_REF)}(${rawExp}) ? (`,
createSimpleExpression(rawExp, false, exp.loc),
- `).value = $event : ${altAssignment})`
+ `).value = $event : ${altAssignment})`,
])
}
} else {
assignmentExp = createCompoundExpression([
`${eventArg} => ((`,
exp,
- `) = $event)`
+ `) = $event)`,
])
}
// modelValue: foo
createObjectProperty(propName, dir.exp!),
// "onUpdate:modelValue": $event => (foo = $event)
- createObjectProperty(eventName, assignmentExp)
+ createObjectProperty(eventName, assignmentExp),
]
// cache v-model handler if applicable (when it doesn't refer any scope vars)
`{ ${modifiers} }`,
false,
dir.loc,
- ConstantTypes.CAN_HOIST
- )
- )
+ ConstantTypes.CAN_HOIST,
+ ),
+ ),
)
}
-import { DirectiveTransform, DirectiveTransformResult } from '../transform'
+import type { DirectiveTransform, DirectiveTransformResult } from '../transform'
import {
+ type DirectiveNode,
+ ElementTypes,
+ type ExpressionNode,
+ NodeTypes,
+ type SimpleExpressionNode,
createCompoundExpression,
createObjectProperty,
createSimpleExpression,
- DirectiveNode,
- ElementTypes,
- ExpressionNode,
- NodeTypes,
- SimpleExpressionNode
} from '../ast'
import { camelize, toHandlerKey } from '@vue/shared'
-import { createCompilerError, ErrorCodes } from '../errors'
+import { ErrorCodes, createCompilerError } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { hasScopeRef, isMemberExpression } from '../utils'
dir,
node,
context,
- augmentor
+ augmentor,
) => {
const { loc, modifiers, arg } = dir as VOnDirectiveNode
if (!dir.exp && !modifiers.length) {
eventName = createCompoundExpression([
`${context.helperString(TO_HANDLER_KEY)}(`,
arg,
- `)`
+ `)`,
])
}
} else {
exp,
context,
false,
- hasMultipleStatements
+ hasMultipleStatements,
)
isInlineStatement && context.removeIdentifiers(`$event`)
// with scope analysis, the function is hoistable if it has no reference
exp as SimpleExpressionNode,
context,
false,
- hasMultipleStatements
+ hasMultipleStatements,
)
}
}(...args)`
} => ${hasMultipleStatements ? `{` : `(`}`,
exp,
- hasMultipleStatements ? `}` : `)`
+ hasMultipleStatements ? `}` : `)`,
])
}
}
props: [
createObjectProperty(
eventName,
- exp || createSimpleExpression(`() => {}`, false, loc)
- )
- ]
+ exp || createSimpleExpression(`() => {}`, false, loc),
+ ),
+ ],
}
// apply extended compiler augmentor
-import { NodeTransform } from '../transform'
+import type { NodeTransform } from '../transform'
import { findDir } from '../utils'
-import { ElementNode, ForNode, IfNode, NodeTypes } from '../ast'
+import { type ElementNode, type ForNode, type IfNode, NodeTypes } from '../ast'
import { SET_BLOCK_TRACKING } from '../runtimeHelpers'
const seen = new WeakSet()
import {
- ElementNode,
- ObjectExpression,
- createObjectExpression,
+ type CallExpression,
+ type ConditionalExpression,
+ type DirectiveNode,
+ type ElementNode,
+ ElementTypes,
+ type ExpressionNode,
+ type FunctionExpression,
NodeTypes,
+ type ObjectExpression,
+ type Property,
+ type SlotsExpression,
+ type SourceLocation,
+ type TemplateChildNode,
+ createArrayExpression,
+ createCallExpression,
+ createConditionalExpression,
+ createFunctionExpression,
+ createObjectExpression,
createObjectProperty,
createSimpleExpression,
- createFunctionExpression,
- DirectiveNode,
- ElementTypes,
- ExpressionNode,
- Property,
- TemplateChildNode,
- SourceLocation,
- createConditionalExpression,
- ConditionalExpression,
- FunctionExpression,
- CallExpression,
- createCallExpression,
- createArrayExpression,
- SlotsExpression
} from '../ast'
-import { TransformContext, NodeTransform } from '../transform'
-import { createCompilerError, ErrorCodes } from '../errors'
+import type { NodeTransform, TransformContext } from '../transform'
+import { ErrorCodes, createCompilerError } from '../errors'
import {
+ assert,
findDir,
+ hasScopeRef,
+ isStaticExp,
isTemplateNode,
- assert,
isVSlot,
- hasScopeRef,
- isStaticExp
} from '../utils'
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
import { createForLoopParams, finalizeForParseResult } from './vFor'
slotProps: ExpressionNode | undefined,
vFor: DirectiveNode | undefined,
slotChildren: TemplateChildNode[],
- loc: SourceLocation
+ loc: SourceLocation,
) => FunctionExpression
const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
children,
false /* newline */,
true /* isSlot */,
- children.length ? children[0].loc : loc
+ children.length ? children[0].loc : loc,
)
// Instead of being a DirectiveTransform, v-slot processing is called during
export function buildSlots(
node: ElementNode,
context: TransformContext,
- buildSlotFn: SlotFnBuilder = buildClientSlotFn
+ buildSlotFn: SlotFnBuilder = buildClientSlotFn,
): {
slots: SlotsExpression
hasDynamicSlots: boolean
slotsProperties.push(
createObjectProperty(
arg || createSimpleExpression('default', true),
- buildSlotFn(exp, undefined, children, loc)
- )
+ buildSlotFn(exp, undefined, children, loc),
+ ),
)
}
if (onComponentSlot) {
// already has on-component slot - this is incorrect usage.
context.onError(
- createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
+ createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc),
)
break
}
const {
arg: slotName = createSimpleExpression(`default`, true),
exp: slotProps,
- loc: dirLoc
+ loc: dirLoc,
} = slotDir
// check if name is dynamic.
createConditionalExpression(
vIf.exp!,
buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++),
- defaultFallback
- )
+ defaultFallback,
+ ),
)
} else if (
(vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))
buildDynamicSlot(
slotName,
slotFunction,
- conditionalBranchIndex++
+ conditionalBranchIndex++,
),
- defaultFallback
+ defaultFallback,
)
: buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++)
} else {
context.onError(
- createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
+ createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc),
)
}
} else if (vFor) {
createFunctionExpression(
createForLoopParams(parseResult),
buildDynamicSlot(slotName, slotFunction),
- true /* force newline */
- )
- ])
+ true /* force newline */,
+ ),
+ ]),
)
} else {
context.onError(
- createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, vFor.loc)
+ createCompilerError(
+ ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ vFor.loc,
+ ),
)
}
} else {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
- dirLoc
- )
+ dirLoc,
+ ),
)
continue
}
if (!onComponentSlot) {
const buildDefaultSlotProperty = (
props: ExpressionNode | undefined,
- children: TemplateChildNode[]
+ children: TemplateChildNode[],
) => {
const fn = buildSlotFn(props, undefined, children, loc)
if (__COMPAT__ && context.compatConfig) {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
- implicitDefaultChildren[0].loc
- )
+ implicitDefaultChildren[0].loc,
+ ),
)
} else {
slotsProperties.push(
- buildDefaultSlotProperty(undefined, implicitDefaultChildren)
+ buildDefaultSlotProperty(undefined, implicitDefaultChildren),
)
}
}
// 1 = compiled and static = can skip normalization AND diff as optimized
createSimpleExpression(
slotFlag + (__DEV__ ? ` /* ${slotFlagsText[slotFlag]} */` : ``),
- false
- )
- )
+ false,
+ ),
+ ),
),
- loc
+ loc,
) as SlotsExpression
if (dynamicSlots.length) {
slots = createCallExpression(context.helper(CREATE_SLOTS), [
slots,
- createArrayExpression(dynamicSlots)
+ createArrayExpression(dynamicSlots),
]) as SlotsExpression
}
return {
slots,
- hasDynamicSlots
+ hasDynamicSlots,
}
}
function buildDynamicSlot(
name: ExpressionNode,
fn: FunctionExpression,
- index?: number
+ index?: number,
): ObjectExpression {
const props = [
createObjectProperty(`name`, name),
- createObjectProperty(`fn`, fn)
+ createObjectProperty(`fn`, fn),
]
if (index != null) {
props.push(
- createObjectProperty(`key`, createSimpleExpression(String(index), true))
+ createObjectProperty(`key`, createSimpleExpression(String(index), true)),
)
}
return createObjectExpression(props)
import {
- Position,
- ElementNode,
+ type BlockCodegenNode,
+ type CallExpression,
+ type DirectiveNode,
+ type ElementNode,
+ ElementTypes,
+ type ExpressionNode,
+ type IfBranchNode,
+ type InterpolationNode,
+ type JSChildNode,
+ type MemoExpression,
NodeTypes,
- CallExpression,
+ type ObjectExpression,
+ type Position,
+ type Property,
+ type RenderSlotCall,
+ type RootNode,
+ type SimpleExpressionNode,
+ type SlotOutletNode,
+ type TemplateChildNode,
+ type TemplateNode,
+ type TextNode,
+ type VNodeCall,
createCallExpression,
- DirectiveNode,
- ElementTypes,
- TemplateChildNode,
- RootNode,
- ObjectExpression,
- Property,
- JSChildNode,
createObjectExpression,
- SlotOutletNode,
- TemplateNode,
- RenderSlotCall,
- ExpressionNode,
- IfBranchNode,
- TextNode,
- InterpolationNode,
- VNodeCall,
- SimpleExpressionNode,
- BlockCodegenNode,
- MemoExpression
} from './ast'
-import { TransformContext } from './transform'
+import type { TransformContext } from './transform'
import {
+ BASE_TRANSITION,
+ GUARD_REACTIVE_PROPS,
+ KEEP_ALIVE,
MERGE_PROPS,
- TELEPORT,
+ NORMALIZE_PROPS,
SUSPENSE,
- KEEP_ALIVE,
- BASE_TRANSITION,
+ TELEPORT,
TO_HANDLERS,
- NORMALIZE_PROPS,
- GUARD_REACTIVE_PROPS,
- WITH_MEMO
+ WITH_MEMO,
} from './runtimeHelpers'
-import { isString, isObject, NOOP } from '@vue/shared'
-import { PropsExpression } from './transforms/transformElement'
+import { NOOP, isObject, isString } from '@vue/shared'
+import type { PropsExpression } from './transforms/transformElement'
import { parseExpression } from '@babel/parser'
-import { Expression } from '@babel/types'
+import type { Expression } from '@babel/types'
import { unwrapTSNode } from './babelUtils'
export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
inMemberExp,
inBrackets,
inParens,
- inString
+ inString,
}
const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/
: (path: string, context: TransformContext): boolean => {
try {
let ret: Expression = parseExpression(path, {
- plugins: context.expressionPlugins
+ plugins: context.expressionPlugins,
})
ret = unwrapTSNode(ret) as Expression
return (
export function advancePositionWithClone(
pos: Position,
source: string,
- numberOfCharacters: number = source.length
+ numberOfCharacters: number = source.length,
): Position {
return advancePositionWithMutation(
{
offset: pos.offset,
line: pos.line,
- column: pos.column
+ column: pos.column,
},
source,
- numberOfCharacters
+ numberOfCharacters,
)
}
export function advancePositionWithMutation(
pos: Position,
source: string,
- numberOfCharacters: number = source.length
+ numberOfCharacters: number = source.length,
): Position {
let linesCount = 0
let lastNewLinePos = -1
export function findDir(
node: ElementNode,
name: string | RegExp,
- allowEmpty: boolean = false
+ allowEmpty: boolean = false,
): DirectiveNode | undefined {
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
node: ElementNode,
name: string,
dynamicOnly: boolean = false,
- allowEmpty: boolean = false
+ allowEmpty: boolean = false,
): ElementNode['props'][0] | undefined {
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
export function isStaticArgOf(
arg: DirectiveNode['arg'],
- name: string
+ name: string,
): boolean {
return !!(arg && isStaticExp(arg) && arg.content === name)
}
p.name === 'bind' &&
(!p.arg || // v-bind="obj"
p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
- !p.arg.isStatic) // v-bind:[foo]
+ !p.arg.isStatic), // v-bind:[foo]
)
}
export function isText(
- node: TemplateChildNode
+ node: TemplateChildNode,
): node is TextNode | InterpolationNode {
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
}
}
export function isTemplateNode(
- node: RootNode | TemplateChildNode
+ node: RootNode | TemplateChildNode,
): node is TemplateNode {
return (
node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE
}
export function isSlotOutlet(
- node: RootNode | TemplateChildNode
+ node: RootNode | TemplateChildNode,
): node is SlotOutletNode {
return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
}
function getUnnormalizedProps(
props: PropsExpression | '{}',
- callPath: CallExpression[] = []
+ callPath: CallExpression[] = [],
): [PropsExpression | '{}', CallExpression[]] {
if (
props &&
if (!isString(callee) && propsHelperSet.has(callee)) {
return getUnnormalizedProps(
props.arguments[0] as PropsExpression,
- callPath.concat(props)
+ callPath.concat(props),
)
}
}
export function injectProp(
node: VNodeCall | RenderSlotCall,
prop: Property,
- context: TransformContext
+ context: TransformContext,
) {
let propsWithInjection: ObjectExpression | CallExpression | undefined
/**
// #2366
propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
createObjectExpression([prop]),
- props
+ props,
])
} else {
props.arguments.unshift(createObjectExpression([prop]))
// single v-bind with expression, return a merged replacement
propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
createObjectExpression([prop]),
- props
+ props,
])
// in the case of nested helper call, e.g. `normalizeProps(guardReactiveProps(props))`,
// it will be rewritten as `normalizeProps(mergeProps({ key: 0 }, props))`,
result = props.properties.some(
p =>
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
- p.key.content === propKeyName
+ p.key.content === propKeyName,
)
}
return result
export function toValidAssetId(
name: string,
- type: 'component' | 'directive' | 'filter'
+ type: 'component' | 'directive' | 'filter',
): string {
// see issue#4422, we need adding identifier on validAssetId if variable `name` has specific character
return `_${type}_${name.replace(/[^\w]/g, (searchValue, replaceValue) => {
// Check if a node contains expressions that reference current context scope ids
export function hasScopeRef(
node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined,
- ids: TransformContext['identifiers']
+ ids: TransformContext['identifiers'],
): boolean {
if (!node || Object.keys(ids).length === 0) {
return false
-import { SimpleExpressionNode } from './ast'
-import { TransformContext } from './transform'
-import { createCompilerError, ErrorCodes } from './errors'
+import type { SimpleExpressionNode } from './ast'
+import type { TransformContext } from './transform'
+import { ErrorCodes, createCompilerError } from './errors'
// these keywords should not appear inside expressions, but operators like
// 'typeof', 'instanceof', and 'in' are allowed
)
.split(',')
.join('\\b|\\b') +
- '\\b'
+ '\\b',
)
// strip strings in expressions
node: SimpleExpressionNode,
context: TransformContext,
asParams = false,
- asRawStatements = false
+ asRawStatements = false,
) {
const exp = node.content
new Function(
asRawStatements
? ` ${exp} `
- : `return ${asParams ? `(${exp}) => {}` : `(${exp})`}`
+ : `return ${asParams ? `(${exp}) => {}` : `(${exp})`}`,
)
} catch (e: any) {
let message = e.message
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
- message
- )
+ message,
+ ),
)
}
}
// #3001 html tags inside attribute values
expect(decodeHtmlBrowser('<strong>Text</strong>', true)).toBe(
- '<strong>Text</strong>'
+ '<strong>Text</strong>',
)
expect(decodeHtmlBrowser('<strong>&</strong>', true)).toBe(
- '<strong>&</strong>'
+ '<strong>&</strong>',
)
expect(
decodeHtmlBrowser(
'<strong><strong>&</strong></strong>',
- true
- )
+ true,
+ ),
).toBe('<strong><strong>&</strong></strong>')
})
})
import {
- baseParse as parse,
- NodeTypes,
- ElementNode,
- TextNode,
- ElementTypes,
- InterpolationNode,
- AttributeNode,
+ type AttributeNode,
ConstantTypes,
- Namespaces
+ type ElementNode,
+ ElementTypes,
+ type InterpolationNode,
+ Namespaces,
+ NodeTypes,
+ type TextNode,
+ baseParse as parse,
} from '@vue/compiler-core'
import { parserOptions } from '../src/parserOptions'
test('textarea handles comments/elements as just text', () => {
const ast = parse(
'<textarea>some<div>text</div>and<!--comment--></textarea>',
- parserOptions
+ parserOptions,
)
const element = ast.children[0] as ElementNode
const text = element.children[0] as TextNode
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 46, line: 1, column: 47 },
- source: 'some<div>text</div>and<!--comment-->'
- }
+ source: 'some<div>text</div>and<!--comment-->',
+ },
})
})
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 15, line: 1, column: 16 },
- source: '&'
- }
+ source: '&',
+ },
})
})
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
- isStatic: false
- }
- }
+ isStatic: false,
+ },
+ },
])
})
test('style handles comments/elements as just a text', () => {
const ast = parse(
'<style>some<div>text</div>and<!--comment--></style>',
- parserOptions
+ parserOptions,
)
const element = ast.children[0] as ElementNode
const text = element.children[0] as TextNode
loc: {
start: { offset: 7, line: 1, column: 8 },
end: { offset: 43, line: 1, column: 44 },
- source: 'some<div>text</div>and<!--comment-->'
- }
+ source: 'some<div>text</div>and<!--comment-->',
+ },
})
})
loc: {
start: { offset: 7, line: 1, column: 8 },
end: { offset: 12, line: 1, column: 13 },
- source: '&'
- }
+ source: '&',
+ },
})
})
loc: {
start: { offset: 14, line: 1, column: 15 },
end: { offset: 23, line: 1, column: 24 },
- source: 'some text'
- }
+ source: 'some text',
+ },
})
})
expect((ast.children[0] as ElementNode).children).toMatchObject([
{
type: NodeTypes.TEXT,
- content: ` \na `
+ content: ` \na `,
},
{
type: NodeTypes.ELEMENT,
children: [
{
type: NodeTypes.TEXT,
- content: `foo \n bar`
- }
- ]
+ content: `foo \n bar`,
+ },
+ ],
},
{
type: NodeTypes.TEXT,
- content: ` \n c`
- }
+ content: ` \n c`,
+ },
])
})
expect((ast.children[0] as ElementNode).children).toMatchObject([
{
type: NodeTypes.TEXT,
- content: `hello`
+ content: `hello`,
},
{
type: NodeTypes.ELEMENT,
{
type: NodeTypes.TEXT,
// should not remove the leading newline for nested elements
- content: `\nbye`
- }
- ]
- }
+ content: `\nbye`,
+ },
+ ],
+ },
])
})
const ast = parse(`foo bar`, parserOptions)
expect(ast.children[0]).toMatchObject({
type: NodeTypes.TEXT,
- content: `foo${nbsp}${nbsp}bar`
+ content: `foo${nbsp}${nbsp}bar`,
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 11, line: 1, column: 12 },
- source: '&ersand;'
- }
+ source: '&ersand;',
+ },
})
})
test('HTML entities compatibility in attribute', () => {
const ast = parse(
'<div a="&ersand;" b="&ersand;" c="&!"></div>',
- parserOptions
+ parserOptions,
)
const element = ast.children[0] as ElementNode
const text1 = (element.props[0] as AttributeNode).value
loc: {
start: { offset: 7, line: 1, column: 8 },
end: { offset: 20, line: 1, column: 21 },
- source: '"&ersand;"'
- }
+ source: '"&ersand;"',
+ },
})
expect(text2).toStrictEqual({
type: NodeTypes.TEXT,
loc: {
start: { offset: 23, line: 1, column: 24 },
end: { offset: 37, line: 1, column: 38 },
- source: '"&ersand;"'
- }
+ source: '"&ersand;"',
+ },
})
expect(text3).toStrictEqual({
type: NodeTypes.TEXT,
loc: {
start: { offset: 40, line: 1, column: 41 },
end: { offset: 47, line: 1, column: 48 },
- source: '"&!"'
- }
+ source: '"&!"',
+ },
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 6, line: 1, column: 7 },
- source: '†'
- }
+ source: '†',
+ },
})
})
})
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 16, line: 1, column: 17 },
- source: 'a < b'
- }
+ source: 'a < b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 19, line: 1, column: 20 },
- source: '{{ a < b }}'
- }
+ source: '{{ a < b }}',
+ },
})
})
})
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: '<img>'
+ source: '<img>',
},
- codegenNode: undefined
+ codegenNode: undefined,
})
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
test('Strict end tag detection for textarea.', () => {
const ast = parse(
'<textarea>hello</textarea</textarea0></texTArea>',
- parserOptions
+ parserOptions,
)
const element = ast.children[0] as ElementNode
const text = element.children[0] as TextNode
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 37, line: 1, column: 38 },
- source: 'hello</textarea</textarea0>'
- }
+ source: 'hello</textarea</textarea0>',
+ },
})
})
})
test('SVG in MATH_ML namespace', () => {
const ast = parse(
'<math><annotation-xml><svg></svg></annotation-xml></math>',
- parserOptions
+ parserOptions,
)
const elementMath = ast.children[0] as ElementNode
const elementAnnotation = elementMath.children[0] as ElementNode
test('html text/html in MATH_ML namespace', () => {
const ast = parse(
'<math><annotation-xml encoding="text/html"><test/></annotation-xml></math>',
- parserOptions
+ parserOptions,
)
const elementMath = ast.children[0] as ElementNode
test('html application/xhtml+xml in MATH_ML namespace', () => {
const ast = parse(
'<math><annotation-xml encoding="application/xhtml+xml"><test/></annotation-xml></math>',
- parserOptions
+ parserOptions,
)
const elementMath = ast.children[0] as ElementNode
const elementAnnotation = elementMath.children[0] as ElementNode
test('mtext malignmark in MATH_ML namespace', () => {
const ast = parse(
'<math><mtext><malignmark/></mtext></math>',
- parserOptions
+ parserOptions,
)
const elementMath = ast.children[0] as ElementNode
const elementText = elementMath.children[0] as ElementNode
test('foreignObject tag in SVG namespace', () => {
const ast = parse(
'<svg><foreignObject><test/></foreignObject></svg>',
- parserOptions
+ parserOptions,
)
const elementSvg = ast.children[0] as ElementNode
const elementForeignObject = elementSvg.children[0] as ElementNode
test('root ns', () => {
const ast = parse('<foreignObject><test/></foreignObject>', {
...parserOptions,
- ns: Namespaces.SVG
+ ns: Namespaces.SVG,
})
const elementForieng = ast.children[0] as ElementNode
const element = elementForieng.children[0] as ElementNode
// treatment for <script>, <style>, <textarea> etc.
const ast = parse('<script><g/><g/></script>', {
...parserOptions,
- ns: Namespaces.SVG
+ ns: Namespaces.SVG,
})
const elementSvg = ast.children[0] as ElementNode
// should parse as nodes instead of text
expect(elementSvg.children).toMatchObject([
{ type: NodeTypes.ELEMENT, tag: 'g' },
- { type: NodeTypes.ELEMENT, tag: 'g' }
+ { type: NodeTypes.ELEMENT, tag: 'g' },
])
})
})
function checkWarning(
template: string,
shouldWarn: boolean,
- message = `<Transition> expects exactly one child element or component.`
+ message = `<Transition> expects exactly one child element or component.`,
) {
const spy = vi.fn()
compile(template.trim(), {
transformHoist: null,
onError: err => {
spy(err.message)
- }
+ },
})
if (shouldWarn) expect(spy).toHaveBeenCalledWith(message)
<div>hey</div>
</transition>
`,
- true
+ true,
)
})
<div v-for="i in items">hey</div>
</transition>
`,
- true
+ true,
)
})
<div v-else v-for="i in items">hey</div>
</transition>
`,
- true
+ true,
)
})
<template v-if="ok"></template>
</transition>
`,
- true
+ true,
)
})
<template v-else></template>
</transition>
`,
- true
+ true,
)
})
<div v-if="other">hey</div>
</transition>
`,
- true
+ true,
)
})
<div>hey</div>
</transition>
`,
- false
+ false,
)
})
<div v-if="a">hey</div>
</transition>
`,
- false
+ false,
)
})
<div v-else>hey</div>
</transition>
`,
- false
+ false,
)
})
<div v-else>hey</div>
</transition>
`,
- false
+ false,
)
})
})
<transition>
<div v-show="ok" />
</transition>
- `).code
+ `).code,
).toMatchSnapshot()
})
<p v-else/>
</div>
</transition>
- `).code
+ `).code,
).toMatchSnapshot()
})
-import { compile, CompilerError } from '../../src'
+import { type CompilerError, compile } from '../../src'
describe('compiler: ignore side effect tags', () => {
it('should ignore script', () => {
const { code } = compile(`<script>console.log(1)</script>`, {
onError(e) {
err = e
- }
+ },
})
expect(code).not.toMatch('script')
expect(err).toBeDefined()
const { code } = compile(`<style>h1 { color: red }</style>`, {
onError(e) {
err = e
- }
+ },
})
expect(code).not.toMatch('style')
expect(err).toBeDefined()
import {
- compile,
- NodeTypes,
CREATE_STATIC,
+ ConstantTypes,
+ NodeTypes,
+ compile,
createSimpleExpression,
- ConstantTypes
} from '../../src'
import {
+ StringifyThresholds,
stringifyStatic,
- StringifyThresholds
} from '../../src/transforms/stringifyStatic'
describe('stringify static html', () => {
return compile(template, {
hoistStatic: true,
prefixIdentifiers: true,
- transformHoist: stringifyStatic
+ transformHoist: stringifyStatic,
})
}
test('should bail on non-eligible static trees', () => {
const { ast } = compileWithStringify(
- `<div><div><div>hello</div><div>hello</div></div></div>`
+ `<div><div><div>hello</div><div>hello</div></div></div>`,
)
// should be a normal vnode call
expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
const { ast } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo"/>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div></div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
JSON.stringify(
`<div>${repeat(
`<span class="foo"></span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
),
- '1'
- ]
+ '1',
+ ],
}, // the children array is hoisted as well
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
})
const { ast } = compileWithStringify(
`<div><div>${repeat(
`<span/>`,
- StringifyThresholds.NODE_COUNT
- )}</div></div>`
+ StringifyThresholds.NODE_COUNT,
+ )}</div></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
JSON.stringify(
`<div>${repeat(
`<span></span>`,
- StringifyThresholds.NODE_COUNT
- )}</div>`
+ StringifyThresholds.NODE_COUNT,
+ )}</div>`,
),
- '1'
- ]
+ '1',
+ ],
},
// the children array is hoisted as well
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
})
const { ast } = compileWithStringify(
`<div>${repeat(
`<span class="foo"/>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
)
// should have 6 hoisted nodes (including the entire array),
// but 2~5 should be null because they are merged into 1
JSON.stringify(
repeat(
`<span class="foo"></span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ ),
),
- '5'
- ]
+ '5',
+ ],
},
null,
null,
null,
null,
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
})
const { ast } = compileWithStringify(
`<div><div :style="{ color: 'red' }">${repeat(
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div></div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
JSON.stringify(
`<div style="color:red;">${repeat(
`<span class="foo bar">1 + false</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
),
- '1'
- ]
+ '1',
+ ],
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
})
`<div><div>${repeat(
`<span :class="'foo' + '>ar'">{{ 1 }} + {{ '<' }}</span>` +
`<span>&</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div></div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div></div>`,
)
// should be optimized now
expect(ast.hoists).toMatchObject([
JSON.stringify(
`<div>${repeat(
`<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
),
- '1'
- ]
+ '1',
+ ],
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
})
const { ast, code } = compile(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<img src="./foo" /></div></div>`,
{
hoistStatic: true,
'_imports_0_',
false,
node.loc,
- ConstantTypes.CAN_HOIST
+ ConstantTypes.CAN_HOIST,
)
node.props[0] = {
type: NodeTypes.DIRECTIVE,
arg: createSimpleExpression('src', true),
exp,
modifiers: [],
- loc: node.loc
+ loc: node.loc,
}
}
- }
- ]
- }
+ },
+ ],
+ },
)
expect(ast.hoists).toMatchObject([
{
// the expression and the tree are still hoistable
// but should stay NodeTypes.VNODE_CALL
// if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION
- type: NodeTypes.VNODE_CALL
+ type: NodeTypes.VNODE_CALL,
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
expect(code).toMatchSnapshot()
})
const { ast, code } = compile(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<img src="./foo" /></div></div>`,
{
hoistStatic: true,
'_imports_0_',
false,
node.loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
node.props[0] = {
type: NodeTypes.DIRECTIVE,
arg: createSimpleExpression('src', true),
exp,
modifiers: [],
- loc: node.loc
+ loc: node.loc,
}
}
- }
- ]
- }
+ },
+ ],
+ },
)
expect(ast.hoists).toMatchObject([
{
// the hoisted node should be NodeTypes.JS_CALL_EXPRESSION
// of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL
- type: NodeTypes.JS_CALL_EXPRESSION
+ type: NodeTypes.JS_CALL_EXPRESSION,
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
expect(code).toMatchSnapshot()
})
const { ast } = compileWithStringify(
`<div><div><input indeterminate>${repeat(
`<span class="foo">foo</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div></div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div></div>`,
)
expect(ast.hoists).toMatchObject([
{
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
+ type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
const { ast: ast2 } = compileWithStringify(
`<div><div><input :indeterminate="true">${repeat(
`<span class="foo">foo</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div></div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div></div>`,
)
expect(ast2.hoists).toMatchObject([
{
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
+ type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
})
const { ast } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}<input indeterminate></div></div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}<input indeterminate></div></div>`,
)
expect(ast.hoists).toMatchObject([
{
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
+ type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
const { ast: ast2 } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}<input :indeterminate="true"></div></div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}<input :indeterminate="true"></div></div>`,
)
expect(ast2.hoists).toMatchObject([
{
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
+ type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
})
const { ast } = compileWithStringify(
`<table><tbody>${repeat(
`<tr class="foo"><td>foo</td></tr>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</tbody></table>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</tbody></table>`,
)
expect(ast.hoists).toMatchObject([
{
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
+ type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
},
{
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
])
})
const { ast } = compileWithStringify(
`<foo>${repeat(
`<div class="foo"></div>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</foo>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</foo>`,
)
expect(ast.hoists.length).toBe(
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)
ast.hoists.forEach(node => {
expect(node).toMatchObject({
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
+ type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
})
})
const { ast: ast2 } = compileWithStringify(
`<foo><template #foo>${repeat(
`<div class="foo"></div>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</template></foo>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</template></foo>`,
)
expect(ast2.hoists.length).toBe(
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)
ast2.hoists.forEach(node => {
expect(node).toMatchObject({
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
+ type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
})
})
})
const { ast } = compileWithStringify(
`<div>${repeat(
`<span :title="null"></span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
)
expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
JSON.stringify(
`${repeat(
`<span></span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}`,
),
- '5'
- ]
+ '5',
+ ],
})
})
const { ast } = compileWithStringify(
`<button :disabled="false">enable</button>${repeat(
`<div></div>`,
- StringifyThresholds.NODE_COUNT
- )}`
+ StringifyThresholds.NODE_COUNT,
+ )}`,
)
expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
JSON.stringify(
`<button>enable</button>${repeat(
`<div></div>`,
- StringifyThresholds.NODE_COUNT
- )}`
+ StringifyThresholds.NODE_COUNT,
+ )}`,
),
- '21'
- ]
+ '21',
+ ],
})
})
const { ast } = compileWithStringify(
`<div>${svg}${repeat(
repeated,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</svg></div>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</svg></div>`,
)
expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
JSON.stringify(
`${svg}${repeat(
repeated,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}</svg>`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</svg>`,
),
- '1'
- ]
+ '1',
+ ],
})
})
import {
+ type CompilerOptions,
+ type ElementNode,
+ NodeTypes,
+ type VNodeCall,
baseParse as parse,
transform,
- CompilerOptions,
- ElementNode,
- NodeTypes,
- VNodeCall
} from '@vue/compiler-core'
import { transformBind } from '../../../compiler-core/src/transforms/vBind'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
function transformWithStyleTransform(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
) {
const ast = parse(template)
transform(ast, {
nodeTransforms: [transformStyle],
- ...options
+ ...options,
})
return {
root: ast,
- node: ast.children[0] as ElementNode
+ node: ast.children[0] as ElementNode,
}
}
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
const { node } = transformWithStyleTransform(`<div style="color: red"/>`, {
nodeTransforms: [transformStyle, transformElement],
directiveTransforms: {
- bind: transformBind
- }
+ bind: transformBind,
+ },
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
// should not cause the STYLE patchFlag to be attached
expect((node.codegenNode as VNodeCall).patchFlag).toBeUndefined()
import {
+ type CompilerOptions,
+ type PlainElementNode,
baseParse as parse,
transform,
- PlainElementNode,
- CompilerOptions
} from '@vue/compiler-core'
import { transformVHtml } from '../../src/transforms/vHtml'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import {
createObjectMatcher,
- genFlagText
+ genFlagText,
} from '../../../compiler-core/__tests__/testUtils'
import { PatchFlags } from '@vue/shared'
import { DOMErrorCodes } from '../../src/errors'
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- html: transformVHtml
+ html: transformVHtml,
},
- ...options
+ ...options,
})
return ast
}
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
- innerHTML: `[test]`
+ innerHTML: `[test]`,
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: `["innerHTML"]`
+ dynamicProps: `["innerHTML"]`,
})
})
it('should raise error and ignore children when v-html is present', () => {
const onError = vi.fn()
const ast = transformWithVHtml(`<div v-html="test">hello</div>`, {
- onError
+ onError,
})
expect(onError.mock.calls).toMatchObject([
- [{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }]
+ [{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }],
])
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
- innerHTML: `[test]`
+ innerHTML: `[test]`,
}),
children: undefined, // <-- children should have been removed
patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: `["innerHTML"]`
+ dynamicProps: `["innerHTML"]`,
})
})
it('should raise error if has no expression', () => {
const onError = vi.fn()
transformWithVHtml(`<div v-html></div>`, {
- onError
+ onError,
})
expect(onError.mock.calls).toMatchObject([
- [{ code: DOMErrorCodes.X_V_HTML_NO_EXPRESSION }]
+ [{ code: DOMErrorCodes.X_V_HTML_NO_EXPRESSION }],
])
})
})
import {
+ type CompilerOptions,
+ generate,
baseParse as parse,
transform,
- CompilerOptions,
- generate
} from '@vue/compiler-core'
import { transformModel } from '../../src/transforms/vModel'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
V_MODEL_DYNAMIC,
V_MODEL_RADIO,
V_MODEL_SELECT,
- V_MODEL_TEXT
+ V_MODEL_TEXT,
} from '../../src/runtimeHelpers'
function transformWithModel(template: string, options: CompilerOptions = {}) {
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- model: transformModel
+ model: transformModel,
},
- ...options
+ ...options,
})
return ast
}
expect(generate(root).code).toMatchSnapshot()
const root2 = transformWithModel(
- '<input v-bind:[key]="val" v-model="model" />'
+ '<input v-bind:[key]="val" v-model="model" />',
)
expect(root2.helpers).toContain(V_MODEL_DYNAMIC)
expect(generate(root2).code).toMatchSnapshot()
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT
- })
+ code: DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
+ }),
)
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT
- })
+ code: DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
+ }),
)
})
const onError = vi.fn()
const root = transformWithModel('<my-input v-model="model" />', {
onError,
- isCustomElement: tag => tag.startsWith('my-')
+ isCustomElement: tag => tag.startsWith('my-'),
})
expect(root.helpers).toContain(V_MODEL_TEXT)
expect(onError).not.toHaveBeenCalled()
test('should raise error if used file input element', () => {
const onError = vi.fn()
transformWithModel(`<input type="file" v-model="test"/>`, {
- onError
+ onError,
})
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT
- })
+ code: DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
+ }),
)
})
test('should error on dynamic value binding alongside v-model', () => {
const onError = vi.fn()
transformWithModel(`<input v-model="test" :value="test" />`, {
- onError
+ onError,
})
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE
- })
+ code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
+ }),
)
})
test('should NOT error on static value binding alongside v-model', () => {
const onError = vi.fn()
transformWithModel(`<input v-model="test" value="test" />`, {
- onError
+ onError,
})
expect(onError).not.toHaveBeenCalled()
})
import {
- baseParse as parse,
- CompilerOptions,
- ElementNode,
+ BindingTypes,
+ type CompilerOptions,
+ type ElementNode,
+ NodeTypes,
+ type ObjectExpression,
TO_HANDLER_KEY,
+ type VNodeCall,
helperNameMap,
- NodeTypes,
- ObjectExpression,
+ baseParse as parse,
transform,
- VNodeCall,
- BindingTypes
} from '@vue/compiler-core'
import { transformOn } from '../../src/transforms/vOn'
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers'
transform(ast, {
nodeTransforms: [transformExpression, transformElement],
directiveTransforms: {
- on: transformOn
+ on: transformOn,
},
- ...options
+ ...options,
})
const node = (ast.children[0] as ElementNode).codegenNode as VNodeCall
return {
root: ast,
node,
- props: (node.props as ObjectExpression).properties
+ props: (node.props as ObjectExpression).properties,
}
}
describe('compiler-dom: transform v-on', () => {
it('should support multiple modifiers w/ prefixIdentifiers: true', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @click.stop.prevent="test"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["stop","prevent"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["stop","prevent"]'],
+ },
})
})
const { props } = parseWithVOn(
`<div @click.stop="test" @keyup.enter="test" />`,
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
const [clickProp, keyUpProp] = props
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["stop"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["stop"]'],
+ },
})
expect(keyUpProp).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_KEYS,
- arguments: [{ content: '_ctx.test' }, '["enter"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["enter"]'],
+ },
})
})
it('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @click.stop.capture.once="test"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
key: {
- content: `onClickCaptureOnce`
+ content: `onClickCaptureOnce`,
},
value: {
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["stop"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["stop"]'],
+ },
})
})
it('should wrap keys guard for keyboard events or dynamic events', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @keydown.stop.capture.ctrl.a="test"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
key: {
- content: `onKeydownCapture`
+ content: `onKeydownCapture`,
},
value: {
callee: V_ON_WITH_KEYS,
arguments: [
{
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["stop","ctrl"]']
+ arguments: [{ content: '_ctx.test' }, '["stop","ctrl"]'],
},
- '["a"]'
- ]
- }
+ '["a"]',
+ ],
+ },
})
})
it('should not wrap keys guard if no key modifier is present', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @keyup.exact="test"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["exact"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["exact"]'],
+ },
})
})
it('should wrap keys guard for static key event w/ left/right modifiers', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @keyup.left="test"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_KEYS,
- arguments: [{ content: '_ctx.test' }, '["left"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["left"]'],
+ },
})
})
it('should wrap both for dynamic key event w/ left/right modifiers', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @[e].left="test"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
arguments: [
{
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: `_ctx.test` }, `["left"]`]
+ arguments: [{ content: `_ctx.test` }, `["left"]`],
},
- '["left"]'
- ]
- }
+ '["left"]',
+ ],
+ },
})
})
it('should not wrap normal guard if there is only keys guard', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @keyup.enter="test"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_KEYS,
- arguments: [{ content: '_ctx.test' }, '["enter"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["enter"]'],
+ },
})
})
test('should transform click.right', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @click.right="test"/>`)
expect(prop.key).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `onContextmenu`
+ content: `onContextmenu`,
})
// dynamic
const {
- props: [prop2]
+ props: [prop2],
} = parseWithVOn(`<div @[event].right="test"/>`)
// (_toHandlerKey(event)).toLowerCase() === "onclick" ? "onContextmenu" : (_toHandlerKey(event))
expect(prop2.key).toMatchObject({
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
- `)`
- ]
+ `)`,
+ ],
},
`) === "onClick" ? "onContextmenu" : (`,
{
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
- `)`
- ]
+ `)`,
+ ],
},
- `)`
- ]
+ `)`,
+ ],
})
})
test('should transform click.middle', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @click.middle="test"/>`)
expect(prop.key).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `onMouseup`
+ content: `onMouseup`,
})
// dynamic
const {
- props: [prop2]
+ props: [prop2],
} = parseWithVOn(`<div @[event].middle="test"/>`)
// (_eventNaming(event)).toLowerCase() === "onclick" ? "onMouseup" : (_eventNaming(event))
expect(prop2.key).toMatchObject({
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
- `)`
- ]
+ `)`,
+ ],
},
`) === "onClick" ? "onMouseup" : (`,
{
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
- `)`
- ]
+ `)`,
+ ],
},
- `)`
- ]
+ `)`,
+ ],
})
})
test('cache handler w/ modifiers', () => {
const {
root,
- props: [prop]
+ props: [prop],
} = parseWithVOn(`<div @keyup.enter.capture="foo" />`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).toBe(1)
// should not treat cached handler as dynamicProp, so it should have no
// dynamicProps flags and only the hydration flag
expect((root as any).children[0].codegenNode.patchFlag).toBe(
- genFlagText(PatchFlags.NEED_HYDRATION)
+ genFlagText(PatchFlags.NEED_HYDRATION),
)
expect(prop).toMatchObject({
key: {
- content: `onKeyupCapture`
+ content: `onKeyupCapture`,
},
value: {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: V_ON_WITH_KEYS
- }
- }
+ callee: V_ON_WITH_KEYS,
+ },
+ },
})
})
const { node } = parseWithVOn(`<div @keydown.up="foo" />`, {
prefixIdentifiers: true,
bindingMetadata: {
- foo: BindingTypes.SETUP_CONST
+ foo: BindingTypes.SETUP_CONST,
},
directiveTransforms: {
- on: transformOn
- }
+ on: transformOn,
+ },
})
// should only have hydration flag
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))
import {
+ type CompilerOptions,
+ generate,
baseParse as parse,
transform,
- generate,
- CompilerOptions
} from '@vue/compiler-core'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import { transformShow } from '../../src/transforms/vShow'
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- show: transformShow
+ show: transformShow,
},
- ...options
+ ...options,
})
return ast
}
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_SHOW_NO_EXPRESSION
- })
+ code: DOMErrorCodes.X_V_SHOW_NO_EXPRESSION,
+ }),
)
})
})
import {
+ type CompilerOptions,
+ type PlainElementNode,
baseParse as parse,
transform,
- PlainElementNode,
- CompilerOptions
} from '@vue/compiler-core'
import { transformVText } from '../../src/transforms/vText'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import {
createObjectMatcher,
- genFlagText
+ genFlagText,
} from '../../../compiler-core/__tests__/testUtils'
import { PatchFlags } from '@vue/shared'
import { DOMErrorCodes } from '../../src/errors'
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- text: transformVText
+ text: transformVText,
},
- ...options
+ ...options,
})
return ast
}
tag: `"div"`,
props: createObjectMatcher({
textContent: {
- arguments: [{ content: 'test' }]
- }
+ arguments: [{ content: 'test' }],
+ },
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: `["textContent"]`
+ dynamicProps: `["textContent"]`,
})
})
it('should raise error and ignore children when v-text is present', () => {
const onError = vi.fn()
const ast = transformWithVText(`<div v-text="test">hello</div>`, {
- onError
+ onError,
})
expect(onError.mock.calls).toMatchObject([
- [{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }]
+ [{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }],
])
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
textContent: {
- arguments: [{ content: 'test' }]
- }
+ arguments: [{ content: 'test' }],
+ },
}),
children: undefined, // <-- children should have been removed
patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: `["textContent"]`
+ dynamicProps: `["textContent"]`,
})
})
it('should raise error if has no expression', () => {
const onError = vi.fn()
transformWithVText(`<div v-text></div>`, {
- onError
+ onError,
})
expect(onError.mock.calls).toMatchObject([
- [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }]
+ [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }],
])
})
})
import {
- SourceLocation,
- CompilerError,
+ type CompilerError,
+ ErrorCodes,
+ type SourceLocation,
createCompilerError,
- ErrorCodes
} from '@vue/compiler-core'
export interface DOMCompilerError extends CompilerError {
export function createDOMCompilerError(
code: DOMErrorCodes,
- loc?: SourceLocation
+ loc?: SourceLocation,
) {
return createCompilerError(
code,
loc,
- __DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined
+ __DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined,
) as DOMCompilerError
}
X_V_SHOW_NO_EXPRESSION,
X_TRANSITION_INVALID_CHILDREN,
X_IGNORED_SIDE_EFFECT_TAG,
- __EXTEND_POINT__
+ __EXTEND_POINT__,
}
if (__TEST__) {
throw new Error(
`DOMErrorCodes need to be updated to ${
ErrorCodes.__EXTEND_POINT__ + 1
- } to match extension point from core ErrorCodes.`
+ } to match extension point from core ErrorCodes.`,
)
}
}
[DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,
[DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`,
[DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN]: `<Transition> expects exactly one child element or component.`,
- [DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG]: `Tags with side effect (<script> and <style>) are ignored in client component templates.`
+ [DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG]: `Tags with side effect (<script> and <style>) are ignored in client component templates.`,
}
import {
+ type CodegenResult,
+ type CompilerOptions,
+ type DirectiveTransform,
+ type NodeTransform,
+ type ParserOptions,
+ type RootNode,
baseCompile,
baseParse,
- CompilerOptions,
- CodegenResult,
- ParserOptions,
- RootNode,
noopDirectiveTransform,
- NodeTransform,
- DirectiveTransform
} from '@vue/compiler-core'
import { parserOptions } from './parserOptions'
import { transformStyle } from './transforms/transformStyle'
export const DOMNodeTransforms: NodeTransform[] = [
transformStyle,
- ...(__DEV__ ? [transformTransition] : [])
+ ...(__DEV__ ? [transformTransition] : []),
]
export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
text: transformVText,
model: transformModel, // override compiler-core
on: transformOn, // override compiler-core
- show: transformShow
+ show: transformShow,
}
export function compile(
src: string | RootNode,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
): CodegenResult {
return baseCompile(
src,
// by compiler-ssr to generate vnode fallback branches
ignoreSideEffectTags,
...DOMNodeTransforms,
- ...(options.nodeTransforms || [])
+ ...(options.nodeTransforms || []),
],
directiveTransforms: extend(
{},
DOMDirectiveTransforms,
- options.directiveTransforms || {}
+ options.directiveTransforms || {},
),
- transformHoist: __BROWSER__ ? null : stringifyStatic
- })
+ transformHoist: __BROWSER__ ? null : stringifyStatic,
+ }),
)
}
export {
createDOMCompilerError,
DOMErrorCodes,
- DOMErrorMessages
+ DOMErrorMessages,
} from './errors'
export * from '@vue/compiler-core'
-import { ParserOptions, NodeTypes, Namespaces } from '@vue/compiler-core'
-import { isVoidTag, isHTMLTag, isSVGTag, isMathMLTag } from '@vue/shared'
+import { Namespaces, NodeTypes, type ParserOptions } from '@vue/compiler-core'
+import { isHTMLTag, isMathMLTag, isSVGTag, isVoidTag } from '@vue/shared'
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
import { decodeHtmlBrowser } from './decodeHtmlBrowser'
a.name === 'encoding' &&
a.value != null &&
(a.value.content === 'text/html' ||
- a.value.content === 'application/xhtml+xml')
+ a.value.content === 'application/xhtml+xml'),
)
) {
ns = Namespaces.HTML
}
}
return ns
- }
+ },
}
[V_ON_WITH_KEYS]: `withKeys`,
[V_SHOW]: `vShow`,
[TRANSITION]: `Transition`,
- [TRANSITION_GROUP]: `TransitionGroup`
+ [TRANSITION_GROUP]: `TransitionGroup`,
})
import {
- NodeTransform,
- NodeTypes,
+ type ComponentNode,
ElementTypes,
- ComponentNode,
- IfBranchNode
+ type IfBranchNode,
+ type NodeTransform,
+ NodeTypes,
} from '@vue/compiler-core'
import { TRANSITION } from '../runtimeHelpers'
-import { createDOMCompilerError, DOMErrorCodes } from '../errors'
+import { DOMErrorCodes, createDOMCompilerError } from '../errors'
export const transformTransition: NodeTransform = (node, context) => {
if (
{
start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end,
- source: ''
- }
- )
+ source: '',
+ },
+ ),
)
}
name: 'persisted',
nameLoc: node.loc,
value: undefined,
- loc: node.loc
+ loc: node.loc,
})
}
}
const children = (node.children = node.children.filter(
c =>
c.type !== NodeTypes.COMMENT &&
- !(c.type === NodeTypes.TEXT && !c.content.trim())
+ !(c.type === NodeTypes.TEXT && !c.content.trim()),
))
const child = children[0]
return (
-import { NodeTransform, NodeTypes, ElementTypes } from '@vue/compiler-core'
+import { ElementTypes, type NodeTransform, NodeTypes } from '@vue/compiler-core'
import { DOMErrorCodes, createDOMCompilerError } from '../errors'
export const ignoreSideEffectTags: NodeTransform = (node, context) => {
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG,
- node.loc
- )
+ node.loc,
+ ),
)
context.removeNode()
}
* This module is Node-only.
*/
import {
- NodeTypes,
- ElementNode,
- TransformContext,
- TemplateChildNode,
- SimpleExpressionNode,
- createCallExpression,
- HoistTransform,
CREATE_STATIC,
- ExpressionNode,
- ElementTypes,
- PlainElementNode,
- JSChildNode,
- TextCallNode,
ConstantTypes,
- Namespaces
+ type ElementNode,
+ ElementTypes,
+ type ExpressionNode,
+ type HoistTransform,
+ type JSChildNode,
+ Namespaces,
+ NodeTypes,
+ type PlainElementNode,
+ type SimpleExpressionNode,
+ type TemplateChildNode,
+ type TextCallNode,
+ type TransformContext,
+ createCallExpression,
} from '@vue/compiler-core'
import {
- isVoidTag,
+ escapeHtml,
+ isBooleanAttr,
+ isKnownHtmlAttr,
+ isKnownSvgAttr,
isString,
isSymbol,
- isKnownHtmlAttr,
- escapeHtml,
- toDisplayString,
+ isVoidTag,
+ makeMap,
normalizeClass,
normalizeStyle,
stringifyStyle,
- makeMap,
- isKnownSvgAttr,
- isBooleanAttr
+ toDisplayString,
} from '@vue/shared'
export enum StringifyThresholds {
ELEMENT_WITH_BINDING_COUNT = 5,
- NODE_COUNT = 20
+ NODE_COUNT = 20,
}
type StringifiableNode = PlainElementNode | TextCallNode
// combine all currently eligible nodes into a single static vnode call
const staticCall = createCallExpression(context.helper(CREATE_STATIC), [
JSON.stringify(
- currentChunk.map(node => stringifyNode(node, context)).join('')
+ currentChunk.map(node => stringifyNode(node, context)).join(''),
).replace(expReplaceRE, `" + $1 + "`),
// the 2nd argument indicates the number of DOM nodes this static vnode
// will insert / hydrate
- String(currentChunk.length)
+ String(currentChunk.length),
])
// replace the first node's hoisted expression with the static vnode call
replaceHoist(currentChunk[0], staticCall, context)
const replaceHoist = (
node: StringifiableNode,
replacement: JSChildNode | null,
- context: TransformContext
+ context: TransformContext,
) => {
const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted!
context.hoists[context.hoists.indexOf(hoistToReplace)] = replacement
}
const isNonStringifiable = /*#__PURE__*/ makeMap(
- `caption,thead,tr,th,tbody,td,tfoot,colgroup,col`
+ `caption,thead,tr,th,tbody,td,tfoot,colgroup,col`,
)
/**
function stringifyNode(
node: string | TemplateChildNode,
- context: TransformContext
+ context: TransformContext,
): string {
if (isString(node)) {
return node
function stringifyElement(
node: ElementNode,
- context: TransformContext
+ context: TransformContext,
): string {
let res = `<${node.tag}`
let innerHTML = ''
evaluated = stringifyStyle(normalizeStyle(evaluated))
}
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
- evaluated
+ evaluated,
)}"`
}
} else if (p.name === 'html') {
innerHTML = evaluateConstant(p.exp as SimpleExpressionNode)
} else if (p.name === 'text') {
innerHTML = escapeHtml(
- toDisplayString(evaluateConstant(p.exp as SimpleExpressionNode))
+ toDisplayString(evaluateConstant(p.exp as SimpleExpressionNode)),
)
}
}
import {
- NodeTransform,
+ ConstantTypes,
+ type NodeTransform,
NodeTypes,
+ type SimpleExpressionNode,
+ type SourceLocation,
createSimpleExpression,
- SimpleExpressionNode,
- SourceLocation,
- ConstantTypes
} from '@vue/compiler-core'
import { parseStringStyle } from '@vue/shared'
arg: createSimpleExpression(`style`, true, p.loc),
exp: parseInlineCSS(p.value.content, p.loc),
modifiers: [],
- loc: p.loc
+ loc: p.loc,
}
}
})
const parseInlineCSS = (
cssText: string,
- loc: SourceLocation
+ loc: SourceLocation,
): SimpleExpressionNode => {
const normalized = parseStringStyle(cssText)
return createSimpleExpression(
JSON.stringify(normalized),
false,
loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
}
import {
- DirectiveTransform,
+ type DirectiveTransform,
createObjectProperty,
- createSimpleExpression
+ createSimpleExpression,
} from '@vue/compiler-core'
-import { createDOMCompilerError, DOMErrorCodes } from '../errors'
+import { DOMErrorCodes, createDOMCompilerError } from '../errors'
export const transformVHtml: DirectiveTransform = (dir, node, context) => {
const { exp, loc } = dir
if (!exp) {
context.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc)
+ createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc),
)
}
if (node.children.length) {
context.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc)
+ createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc),
)
node.children.length = 0
}
props: [
createObjectProperty(
createSimpleExpression(`innerHTML`, true, loc),
- exp || createSimpleExpression('', true)
- )
- ]
+ exp || createSimpleExpression('', true),
+ ),
+ ],
}
}
import {
- transformModel as baseTransform,
- DirectiveTransform,
+ type DirectiveTransform,
ElementTypes,
- findProp,
NodeTypes,
- hasDynamicKeyVBind,
+ transformModel as baseTransform,
findDir,
- isStaticArgOf
+ findProp,
+ hasDynamicKeyVBind,
+ isStaticArgOf,
} from '@vue/compiler-core'
-import { createDOMCompilerError, DOMErrorCodes } from '../errors'
+import { DOMErrorCodes, createDOMCompilerError } from '../errors'
import {
V_MODEL_CHECKBOX,
+ V_MODEL_DYNAMIC,
V_MODEL_RADIO,
V_MODEL_SELECT,
V_MODEL_TEXT,
- V_MODEL_DYNAMIC
} from '../runtimeHelpers'
export const transformModel: DirectiveTransform = (dir, node, context) => {
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
- dir.arg.loc
- )
+ dir.arg.loc,
+ ),
)
}
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
- value.loc
- )
+ value.loc,
+ ),
)
}
}
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
- dir.loc
- )
+ dir.loc,
+ ),
)
break
default:
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
- dir.loc
- )
+ dir.loc,
+ ),
)
}
!(
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
p.key.content === 'modelValue'
- )
+ ),
)
return baseResult
import {
+ CompilerDeprecationTypes,
+ type DirectiveTransform,
+ type ExpressionNode,
+ NodeTypes,
+ type SimpleExpressionNode,
+ type SourceLocation,
+ type TransformContext,
transformOn as baseTransform,
- DirectiveTransform,
- createObjectProperty,
+ checkCompatEnabled,
createCallExpression,
- createSimpleExpression,
- NodeTypes,
createCompoundExpression,
- ExpressionNode,
- SimpleExpressionNode,
+ createObjectProperty,
+ createSimpleExpression,
isStaticExp,
- CompilerDeprecationTypes,
- TransformContext,
- SourceLocation,
- checkCompatEnabled
} from '@vue/compiler-core'
-import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
-import { makeMap, capitalize } from '@vue/shared'
+import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../runtimeHelpers'
+import { capitalize, makeMap } from '@vue/shared'
const isEventOptionModifier = /*#__PURE__*/ makeMap(`passive,once,capture`)
const isNonKeyModifier = /*#__PURE__*/ makeMap(
// system modifiers + exact
`ctrl,shift,alt,meta,exact,` +
// mouse
- `middle`
+ `middle`,
)
// left & right could be mouse or key modifiers based on event type
const maybeKeyModifier = /*#__PURE__*/ makeMap('left,right')
const isKeyboardEvent = /*#__PURE__*/ makeMap(
`onkeyup,onkeydown,onkeypress`,
- true
+ true,
)
const resolveModifiers = (
key: ExpressionNode,
modifiers: string[],
context: TransformContext,
- loc: SourceLocation
+ loc: SourceLocation,
) => {
const keyModifiers = []
const nonKeyModifiers = []
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_ON_NATIVE,
context,
- loc
+ loc,
)
) {
eventOptionModifiers.push(modifier)
return {
keyModifiers,
nonKeyModifiers,
- eventOptionModifiers
+ eventOptionModifiers,
}
}
key,
`) === "onClick" ? "${event}" : (`,
key,
- `)`
+ `)`,
])
: key
}
if (nonKeyModifiers.length) {
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
handlerExp,
- JSON.stringify(nonKeyModifiers)
+ JSON.stringify(nonKeyModifiers),
])
}
) {
handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [
handlerExp,
- JSON.stringify(keyModifiers)
+ JSON.stringify(keyModifiers),
])
}
}
return {
- props: [createObjectProperty(key, handlerExp)]
+ props: [createObjectProperty(key, handlerExp)],
}
})
}
-import { DirectiveTransform } from '@vue/compiler-core'
-import { createDOMCompilerError, DOMErrorCodes } from '../errors'
+import type { DirectiveTransform } from '@vue/compiler-core'
+import { DOMErrorCodes, createDOMCompilerError } from '../errors'
import { V_SHOW } from '../runtimeHelpers'
export const transformShow: DirectiveTransform = (dir, node, context) => {
const { exp, loc } = dir
if (!exp) {
context.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, loc)
+ createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, loc),
)
}
return {
props: [],
- needRuntime: context.helper(V_SHOW)
+ needRuntime: context.helper(V_SHOW),
}
}
import {
- DirectiveTransform,
- createObjectProperty,
- createSimpleExpression,
+ type DirectiveTransform,
TO_DISPLAY_STRING,
createCallExpression,
- getConstantType
+ createObjectProperty,
+ createSimpleExpression,
+ getConstantType,
} from '@vue/compiler-core'
-import { createDOMCompilerError, DOMErrorCodes } from '../errors'
+import { DOMErrorCodes, createDOMCompilerError } from '../errors'
export const transformVText: DirectiveTransform = (dir, node, context) => {
const { exp, loc } = dir
if (!exp) {
context.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_TEXT_NO_EXPRESSION, loc)
+ createDOMCompilerError(DOMErrorCodes.X_V_TEXT_NO_EXPRESSION, loc),
)
}
if (node.children.length) {
context.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_TEXT_WITH_CHILDREN, loc)
+ createDOMCompilerError(DOMErrorCodes.X_V_TEXT_WITH_CHILDREN, loc),
)
node.children.length = 0
}
: createCallExpression(
context.helperString(TO_DISPLAY_STRING),
[exp],
- loc
+ loc,
)
- : createSimpleExpression('', true)
- )
- ]
+ : createSimpleExpression('', true),
+ ),
+ ],
}
}
import { BindingTypes } from '@vue/compiler-core'
-import { compileSFCScript as compile, assertCode, mockId } from './utils'
+import { assertCode, compileSFCScript as compile, mockId } from './utils'
describe('SFC compile <script setup>', () => {
test('should compile JS syntax', () => {
expect(content).toMatch(
`return { get aa() { return aa }, set aa(v) { aa = v }, ` +
`bb, cc, dd, get a() { return a }, set a(v) { a = v }, b, c, d, ` +
- `get xx() { return xx }, get x() { return x } }`
+ `get xx() { return xx }, get x() { return x } }`,
)
expect(bindings).toStrictEqual({
x: BindingTypes.SETUP_MAYBE_REF,
aa: BindingTypes.SETUP_LET,
bb: BindingTypes.LITERAL_CONST,
cc: BindingTypes.SETUP_CONST,
- dd: BindingTypes.SETUP_CONST
+ dd: BindingTypes.SETUP_CONST,
})
assertCode(content)
})
bar: BindingTypes.SETUP_MAYBE_REF,
baz: BindingTypes.SETUP_MAYBE_REF,
y: BindingTypes.SETUP_MAYBE_REF,
- z: BindingTypes.SETUP_MAYBE_REF
+ z: BindingTypes.SETUP_MAYBE_REF,
})
assertCode(content)
})
compile(`<script setup>
import { ref } from 'vue'
import 'foo/css'
- </script>`).content
+ </script>`).content,
)
})
import a from 'a' // comment
import b from 'b'
</script>
- `).content
+ `).content,
)
})
defineProps(['foo'])
defineEmits(['bar'])
const r = ref(0)
- </script>`).content
+ </script>`).content,
)
})
color: v-bind(msg)
}
</style>
- `
+ `,
)
assertCode(content)
expect(content).toMatch(
- `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`
+ `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`,
)
expect(content).toMatch(`import { useCssVars, ref } from 'vue'`)
})
`)
assertCode(content)
expect(content.indexOf(`import { x }`)).toEqual(
- content.lastIndexOf(`import { x }`)
+ content.lastIndexOf(`import { x }`),
)
})
ref: BindingTypes.SETUP_MAYBE_REF,
reactive: BindingTypes.SETUP_MAYBE_REF,
foo: BindingTypes.SETUP_MAYBE_REF,
- bar: BindingTypes.SETUP_MAYBE_REF
+ bar: BindingTypes.SETUP_MAYBE_REF,
})
})
_reactive: BindingTypes.SETUP_MAYBE_REF,
_ref: BindingTypes.SETUP_MAYBE_REF,
foo: BindingTypes.SETUP_MAYBE_REF,
- bar: BindingTypes.SETUP_MAYBE_REF
+ bar: BindingTypes.SETUP_MAYBE_REF,
})
})
`)
expect(bindings).toStrictEqual({
bar: BindingTypes.SETUP_REACTIVE_CONST,
- x: BindingTypes.SETUP_CONST
+ x: BindingTypes.SETUP_CONST,
})
})
})
`)
assertCode(content)
expect(bindings).toStrictEqual({
- foo: BindingTypes.SETUP_MAYBE_REF
+ foo: BindingTypes.SETUP_MAYBE_REF,
})
})
})
// foo: lowercase component
expect(content).toMatch(
`return { fooBar, get FooBaz() { return FooBaz }, ` +
- `get FooQux() { return FooQux }, get foo() { return foo } }`
+ `get FooQux() { return FooQux }, get foo() { return foo } }`,
)
assertCode(content)
})
`)
expect(content).toMatch(
`return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
- `get bar() { return bar }, get baz() { return baz } }`
+ `get bar() { return bar }, get baz() { return baz } }`,
)
assertCode(content)
})
</template>
`)
expect(content).toMatch(
- `return { cond, get bar() { return bar }, get baz() { return baz } }`
+ `return { cond, get bar() { return bar }, get baz() { return baz } }`,
)
assertCode(content)
})
// y: should not be matched by {{ yy }} or 'y' in binding exps
// x$y: #4274 should escape special chars when creating Regex
expect(content).toMatch(
- `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`
+ `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`,
)
assertCode(content)
})
`)
// VAR2 should not be matched
expect(content).toMatch(
- `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`
+ `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`,
)
assertCode(content)
})
</template>
`)
expect(content).toMatch(
- `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`
+ `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`,
)
assertCode(content)
})
</template>
`)
expect(content).toMatch(
- 'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }'
+ 'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }',
)
assertCode(content)
})
<div>static</div>
</template>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// check snapshot and make sure helper imports and
// hoists are placed correctly.
defineExpose({ count })
</script>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
assertCode(content)
expect(content).toMatch(`setup(__props, { expose: __expose })`)
<some-other-comp/>
</template>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
expect(content).toMatch('[_unref(vMyDir)]')
expect(content).toMatch('_createVNode(ChildComp)')
{{ tree.foo() }}
</template>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// no need to unref vue component import
expect(content).toMatch(`createVNode(Foo,`)
<input v-model="lett">
</template>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// known const ref: set value
expect(content).toMatch(`(count).value = $event`)
expect(content).toMatch(`_isRef(maybe) ? (maybe).value = $event : null`)
// let: handle both cases
expect(content).toMatch(
- `_isRef(lett) ? (lett).value = $event : lett = $event`
+ `_isRef(lett) ? (lett).value = $event : lett = $event`,
)
assertCode(content)
})
<input v-model="foo">
</template>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
expect(content).not.toMatch(`_isRef(foo)`)
})
}"/>
</template>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// known const ref: set value
expect(content).toMatch(`count.value = 1`)
expect(content).toMatch(`maybe.value = count.value`)
// let: handle both cases
expect(content).toMatch(
- `_isRef(lett) ? lett.value = count.value : lett = count.value`
+ `_isRef(lett) ? lett.value = count.value : lett = count.value`,
)
expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
<div @click="--lett"/>
</template>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// known const ref: set value
expect(content).toMatch(`count.value++`)
<div @click="({ lett } = val)"/>
</template>
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// known const ref: set value
expect(content).toMatch(`({ count: count.value } = val)`)
{
inlineTemplate: true,
templateOptions: {
- ssr: true
- }
- }
+ ssr: true,
+ },
+ },
)
expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
expect(content).toMatch(`return (_ctx, _push`)
</template>
`,
{
- inlineTemplate: false
- }
- )
+ inlineTemplate: false,
+ },
+ ),
).not.toThrowError()
})
})
const { content, bindings } = compile(
`<script setup lang="ts">
enum Foo { A = 123 }
- </script>`
+ </script>`,
)
assertCode(content)
expect(bindings).toStrictEqual({
- Foo: BindingTypes.LITERAL_CONST
+ Foo: BindingTypes.LITERAL_CONST,
})
})
</script>
<script setup lang="ts">
enum Foo { A = 123 }
- </script>`
+ </script>`,
)
assertCode(content)
expect(bindings).toStrictEqual({
D: BindingTypes.LITERAL_CONST,
C: BindingTypes.LITERAL_CONST,
B: BindingTypes.LITERAL_CONST,
- Foo: BindingTypes.LITERAL_CONST
+ Foo: BindingTypes.LITERAL_CONST,
})
})
`<script setup lang="ts">
const enum Foo { A = 123 }
</script>`,
- { hoistStatic: true }
+ { hoistStatic: true },
)
assertCode(content)
expect(bindings).toStrictEqual({
- Foo: BindingTypes.LITERAL_CONST
+ Foo: BindingTypes.LITERAL_CONST,
})
})
`<script setup lang="ts">
import type { Foo } from './main.ts'
import { type Bar, Baz } from './main.ts'
- </script>`
+ </script>`,
)
expect(content).toMatch(`return { get Baz() { return Baz } }`)
assertCode(content)
// class method
assertAwaitDetection(
`const cls = class Foo { async method() { await bar }}`,
- false
+ false,
)
})
})
describe('errors', () => {
test('<script> and <script setup> must have same lang', () => {
expect(() =>
- compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
+ compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
).toThrow(`<script> and <script setup> must have the same language type`)
})
expect(() =>
compile(`<script setup>
export const a = 1
- </script>`)
+ </script>`),
).toThrow(moduleErrorMsg)
expect(() =>
compile(`<script setup>
export * from './foo'
- </script>`)
+ </script>`),
).toThrow(moduleErrorMsg)
expect(() =>
compile(`<script setup>
const bar = 1
export { bar as default }
- </script>`)
+ </script>`),
).toThrow(moduleErrorMsg)
})
default: () => bar
}
})
- </script>`)
+ </script>`),
).toThrow(`cannot reference locally declared variables`)
expect(() =>
compile(`<script setup>
let bar = 'hello'
defineEmits([bar])
- </script>`)
+ </script>`),
).toThrow(`cannot reference locally declared variables`)
// #4644
default: () => bar
}
})
- </script>`)
+ </script>`),
).not.toThrow(`cannot reference locally declared variables`)
})
defineEmits({
foo: bar => bar > 1
})
- </script>`).content
+ </script>`).content,
)
})
defineEmits({
foo: () => bar > 1
})
- </script>`).content
+ </script>`).content,
)
})
})
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
- bar: BindingTypes.PROPS
+ bar: BindingTypes.PROPS,
})
expect(bindings!.__isScriptSetup).toBe(false)
})
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
baz: BindingTypes.PROPS,
- qux: BindingTypes.PROPS
+ qux: BindingTypes.PROPS,
})
expect(bindings!.__isScriptSetup).toBe(false)
})
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_MAYBE_REF,
- bar: BindingTypes.SETUP_MAYBE_REF
+ bar: BindingTypes.SETUP_MAYBE_REF,
})
expect(bindings!.__isScriptSetup).toBe(false)
})
</script>
`)
expect(bindings).toStrictEqual({
- foo: BindingTypes.LITERAL_CONST
+ foo: BindingTypes.LITERAL_CONST,
})
})
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_MAYBE_REF,
- bar: BindingTypes.SETUP_MAYBE_REF
+ bar: BindingTypes.SETUP_MAYBE_REF,
})
expect(bindings!.__isScriptSetup).toBe(false)
})
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.DATA,
- bar: BindingTypes.DATA
+ bar: BindingTypes.DATA,
})
})
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS,
- bar: BindingTypes.OPTIONS
+ bar: BindingTypes.OPTIONS,
})
})
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS,
- bar: BindingTypes.OPTIONS
+ bar: BindingTypes.OPTIONS,
})
})
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS,
- bar: BindingTypes.OPTIONS
+ bar: BindingTypes.OPTIONS,
})
})
baz: BindingTypes.SETUP_MAYBE_REF,
qux: BindingTypes.DATA,
quux: BindingTypes.OPTIONS,
- quuz: BindingTypes.OPTIONS
+ quuz: BindingTypes.OPTIONS,
})
})
c: BindingTypes.LITERAL_CONST,
d: BindingTypes.SETUP_MAYBE_REF,
e: BindingTypes.SETUP_LET,
- foo: BindingTypes.PROPS
+ foo: BindingTypes.PROPS,
})
})
<template>{{ a }}</template>`,
undefined,
{
- filename: 'FooBar.vue'
- }
+ filename: 'FooBar.vue',
+ },
)
expect(content).toMatch(`export default {
__name: 'FooBar'`)
<template>{{ a }}</template>`,
undefined,
{
- filename: 'FooBar.vue'
- }
+ filename: 'FooBar.vue',
+ },
)
expect(content).not.toMatch(`name: 'FooBar'`)
expect(content).toMatch(`name: 'Baz'`)
<template>{{ a }}</template>`,
undefined,
{
- filename: 'FooBar.vue'
- }
+ filename: 'FooBar.vue',
+ },
)
expect(content).not.toMatch(`name: 'FooBar'`)
expect(content).toMatch(`name: 'Baz'`)
export default {}
</script>`,
{
- genDefaultAs: '_sfc_'
- }
+ genDefaultAs: '_sfc_',
+ },
)
expect(content).not.toMatch('export default')
expect(content).toMatch(`const _sfc_ = {}`)
.foo { color: v-bind(x) }
</style>`,
{
- genDefaultAs: '_sfc_'
- }
+ genDefaultAs: '_sfc_',
+ },
)
expect(content).not.toMatch('export default')
expect(content).not.toMatch('__default__')
const a = 1
</script>`,
{
- genDefaultAs: '_sfc_'
- }
+ genDefaultAs: '_sfc_',
+ },
)
expect(content).not.toMatch('export default')
expect(content).toMatch(
- `const _sfc_ = /*#__PURE__*/Object.assign(__default__`
+ `const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
)
assertCode(content)
})
const a = 1
</script>`,
{
- genDefaultAs: '_sfc_'
- }
+ genDefaultAs: '_sfc_',
+ },
)
expect(content).not.toMatch('export default')
expect(content).toMatch(
- `const _sfc_ = /*#__PURE__*/Object.assign(__default__`
+ `const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
)
assertCode(content)
})
const a = 1
</script>`,
{
- genDefaultAs: '_sfc_'
- }
+ genDefaultAs: '_sfc_',
+ },
)
expect(content).not.toMatch('export default')
expect(content).toMatch(`const _sfc_ = {\n setup`)
const a = 1
</script>`,
{
- genDefaultAs: '_sfc_'
- }
+ genDefaultAs: '_sfc_',
+ },
)
expect(content).not.toMatch('export default')
expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`)
const a = 1
</script>`,
{
- genDefaultAs: '_sfc_'
- }
+ genDefaultAs: '_sfc_',
+ },
)
expect(content).not.toMatch('export default')
expect(content).toMatch(
- `const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__`
+ `const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__`,
)
assertCode(content)
})
import { toRef } from 'vue'
const props = defineProps<{foo: string}>()
const foo = toRef(() => props.foo)
- </script>`
+ </script>`,
)
expect(bindings).toStrictEqual({
toRef: BindingTypes.SETUP_CONST,
props: BindingTypes.SETUP_REACTIVE_CONST,
- foo: BindingTypes.SETUP_REF
+ foo: BindingTypes.SETUP_REF,
})
})
compile(`
<script setup>
import { foo } from './foo.js' assert { type: 'foobar' }
- </script>`)
+ </script>`),
).toThrow()
})
`,
{
babelParserPlugins: [
- ['importAttributes', { deprecatedAssertSyntax: true }]
- ]
- }
+ ['importAttributes', { deprecatedAssertSyntax: true }],
+ ],
+ },
)
assertCode(content)
})
import { BindingTypes } from '@vue/compiler-core'
-import { compileSFCScript as compile, assertCode } from '../utils'
+import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineEmits', () => {
test('basic usage', () => {
`)
assertCode(content)
expect(bindings).toStrictEqual({
- myEmit: BindingTypes.SETUP_CONST
+ myEmit: BindingTypes.SETUP_CONST,
})
// should remove defineEmits import and call
expect(content).not.toMatch('defineEmits')
// should generate correct setup signature
expect(content).toMatch(
- `setup(__props, { expose: __expose, emit: __emit }) {`
+ `setup(__props, { expose: __expose, emit: __emit }) {`,
)
expect(content).toMatch('const myEmit = __emit')
// should include context options in default export
foo: []
(e: 'hi'): void
}>()
- </script>`)
+ </script>`),
).toThrow(
- `defineEmits() type cannot mixed call signature and property syntax.`
+ `defineEmits() type cannot mixed call signature and property syntax.`,
)
})
})
-import { compileSFCScript as compile, assertCode } from '../utils'
+import { assertCode, compileSFCScript as compile } from '../utils'
test('defineExpose()', () => {
const { content } = compile(`
import { BindingTypes } from '@vue/compiler-core'
-import { compileSFCScript as compile, assertCode } from '../utils'
+import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineModel()', () => {
test('basic usage', () => {
const c = defineModel('count')
const toString = defineModel('toString', { type: Function })
</script>
- `
+ `,
)
assertCode(content)
expect(content).toMatch('props: {')
expect(content).toMatch('"count": {},')
expect(content).toMatch('"toString": { type: Function },')
expect(content).toMatch(
- 'emits: ["update:modelValue", "update:count", "update:toString"],'
+ 'emits: ["update:modelValue", "update:count", "update:toString"],',
)
expect(content).toMatch(
- `const modelValue = _useModel(__props, "modelValue")`
+ `const modelValue = _useModel(__props, "modelValue")`,
)
expect(content).toMatch(`const c = _useModel(__props, "count")`)
expect(content).toMatch(`return { modelValue, c, toString }`)
modelValue: BindingTypes.SETUP_REF,
count: BindingTypes.PROPS,
c: BindingTypes.SETUP_REF,
- toString: BindingTypes.SETUP_REF
+ toString: BindingTypes.SETUP_REF,
})
})
defineEmits(['change'])
const count = defineModel({ default: 0 })
</script>
- `
+ `,
)
assertCode(content)
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels({ foo: String }`)
expect(bindings).toStrictEqual({
count: BindingTypes.SETUP_REF,
foo: BindingTypes.PROPS,
- modelValue: BindingTypes.PROPS
+ modelValue: BindingTypes.PROPS,
})
})
defineProps(['foo', 'bar'])
const count = defineModel('count')
</script>
- `
+ `,
)
assertCode(content)
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
- count: BindingTypes.SETUP_REF
+ count: BindingTypes.SETUP_REF,
})
})
const local = true
const hoist = defineModel('hoist', { local })
- </script>`
+ </script>`,
)
assertCode(content)
expect(content).toMatch(`_useModel(__props, "modelValue", { local: true })`)
const disabled = defineModel<number>('disabled', { required: false })
const any = defineModel<any | boolean>('any')
</script>
- `
+ `,
)
assertCode(content)
expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
expect(content).toMatch('"count": { type: Number }')
expect(content).toMatch(
- '"disabled": { type: Number, ...{ required: false } }'
+ '"disabled": { type: Number, ...{ required: false } }',
)
expect(content).toMatch('"any": { type: Boolean, skipCheck: true }')
expect(content).toMatch(
- 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]'
+ 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]',
)
expect(content).toMatch(
- `const modelValue = _useModel(__props, "modelValue")`
+ `const modelValue = _useModel(__props, "modelValue")`,
)
expect(content).toMatch(`const count = _useModel(__props, "count")`)
expect(content).toMatch(`const disabled = _useModel(__props, "disabled")`)
modelValue: BindingTypes.SETUP_REF,
count: BindingTypes.SETUP_REF,
disabled: BindingTypes.SETUP_REF,
- any: BindingTypes.SETUP_REF
+ any: BindingTypes.SETUP_REF,
})
})
const optional = defineModel<string>('optional', { required: false })
</script>
`,
- { isProd: true }
+ { isProd: true },
)
assertCode(content)
expect(content).toMatch('"modelValue": { type: Boolean }')
expect(content).toMatch('"fn": {}')
expect(content).toMatch(
- '"fnWithDefault": { type: Function, ...{ default: () => null } },'
+ '"fnWithDefault": { type: Function, ...{ default: () => null } },',
)
expect(content).toMatch('"str": {}')
expect(content).toMatch('"optional": { required: false }')
expect(content).toMatch(
- 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]'
+ 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]',
)
expect(content).toMatch(
- `const modelValue = _useModel(__props, "modelValue")`
+ `const modelValue = _useModel(__props, "modelValue")`,
)
expect(content).toMatch(`const fn = _useModel(__props, "fn")`)
expect(content).toMatch(`const str = _useModel(__props, "str")`)
fn: BindingTypes.SETUP_REF,
fnWithDefault: BindingTypes.SETUP_REF,
str: BindingTypes.SETUP_REF,
- optional: BindingTypes.SETUP_REF
+ optional: BindingTypes.SETUP_REF,
})
})
})
-import { compileSFCScript as compile, assertCode } from '../utils'
+import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineOptions()', () => {
test('basic usage', () => {
expect(content).not.toMatch('defineOptions')
// should include context options in default export
expect(content).toMatch(
- `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, `
+ `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, `,
)
})
defineOptions({ name: 'FooApp' })
defineOptions({ name: 'BarApp' })
</script>
- `)
+ `),
).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call')
})
<script setup>
defineOptions({ props: { foo: String } })
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.',
)
expect(() =>
<script setup>
defineOptions({ emits: ['update'] })
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.',
)
expect(() =>
<script setup>
defineOptions({ expose: ['foo'] })
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.',
)
expect(() =>
<script setup>
defineOptions({ slots: ['foo'] })
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.',
)
})
<script setup lang="ts">
defineOptions<{ name: 'FooApp' }>()
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot accept type arguments'
+ '[@vue/compiler-sfc] defineOptions() cannot accept type arguments',
)
})
<script setup lang="ts">
defineOptions({ props: [] } as any)
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.',
)
})
<script setup>
defineOptions({ props: ['foo'] })
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead',
)
expect(() =>
<script setup>
defineOptions({ emits: ['update'] })
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead',
)
expect(() =>
<script setup>
defineOptions({ expose: ['foo'] })
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead',
)
expect(() =>
<script setup lang="ts">
defineOptions({ slots: Object })
</script>
- `)
+ `),
).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead'
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead',
)
})
})
import { BindingTypes } from '@vue/compiler-core'
-import { compileSFCScript as compile, assertCode } from '../utils'
+import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineProps', () => {
test('basic usage', () => {
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.LITERAL_CONST,
- props: BindingTypes.SETUP_REACTIVE_CONST
+ props: BindingTypes.SETUP_REACTIVE_CONST,
})
// should remove defineOptions import and call
expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
expect(content).toMatch(`error: { type: Error, required: true }`)
expect(content).toMatch(
- `objectOrFn: { type: [Function, Object], required: true },`
+ `objectOrFn: { type: [Function, Object], required: true },`,
)
expect(content).toMatch(`extract: { type: Number, required: true }`)
expect(content).toMatch(
- `exclude: { type: [Number, Boolean], required: true }`
+ `exclude: { type: [Number, Boolean], required: true }`,
)
expect(content).toMatch(`uppercase: { type: String, required: true }`)
expect(content).toMatch(`params: { type: Array, required: true }`)
expect(content).toMatch(`union: { type: [String, Number], required: true }`)
expect(content).toMatch(`literalUnion: { type: String, required: true }`)
expect(content).toMatch(
- `literalUnionNumber: { type: Number, required: true }`
+ `literalUnionNumber: { type: Number, required: true }`,
)
expect(content).toMatch(
- `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
+ `literalUnionMixed: { type: [String, Number, Boolean], required: true }`,
)
expect(content).toMatch(`intersection: { type: Object, required: true }`)
expect(content).toMatch(`intersection2: { type: String, required: true }`)
expect(content).toMatch(`unknownUnion: { type: null, required: true }`)
// intersection containing unknown type: narrow to the known types
expect(content).toMatch(
- `unknownIntersection: { type: Object, required: true },`
+ `unknownIntersection: { type: Object, required: true },`,
)
expect(content).toMatch(
- `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`
+ `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`,
)
expect(content).toMatch(
- `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`
+ `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`,
)
expect(bindings).toStrictEqual({
string: BindingTypes.PROPS,
unknownUnion: BindingTypes.PROPS,
unknownIntersection: BindingTypes.PROPS,
unknownUnionWithBoolean: BindingTypes.PROPS,
- unknownUnionWithFunction: BindingTypes.PROPS
+ unknownUnionWithFunction: BindingTypes.PROPS,
})
})
assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
+ x: BindingTypes.PROPS,
})
})
expect(bindings).toStrictEqual({
x: BindingTypes.PROPS,
y: BindingTypes.PROPS,
- z: BindingTypes.PROPS
+ z: BindingTypes.PROPS,
})
})
assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
+ x: BindingTypes.PROPS,
})
})
assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
+ x: BindingTypes.PROPS,
})
})
assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
+ x: BindingTypes.PROPS,
})
})
assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
+ x: BindingTypes.PROPS,
})
})
expect(content).toMatch(`props: ['foo']`)
assertCode(content)
expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS
+ foo: BindingTypes.PROPS,
})
})
`)
assertCode(content)
expect(content).toMatch(
- `foo: { type: String, required: false, default: 'hi' }`
+ `foo: { type: String, required: false, default: 'hi' }`,
)
expect(content).toMatch(`bar: { type: Number, required: false }`)
expect(content).toMatch(`baz: { type: Boolean, required: true }`)
expect(content).toMatch(
- `qux: { type: Function, required: false, default() { return 1 } }`
+ `qux: { type: Function, required: false, default() { return 1 } }`,
)
expect(content).toMatch(
- `quux: { type: Function, required: false, default() { } }`
+ `quux: { type: Function, required: false, default() { } }`,
)
expect(content).toMatch(
- `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }`
+ `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }`,
)
expect(content).toMatch(
- `fred: { type: String, required: false, get default() { return 'fred' } }`
+ `fred: { type: String, required: false, get default() { return 'fred' } }`,
)
expect(content).toMatch(`const props = __props`)
expect(bindings).toStrictEqual({
quux: BindingTypes.PROPS,
quuxx: BindingTypes.PROPS,
fred: BindingTypes.PROPS,
- props: BindingTypes.SETUP_CONST
+ props: BindingTypes.SETUP_CONST,
})
})
})
</script>
`,
- { isProd: true }
+ { isProd: true },
)
assertCode(content)
expect(content).toMatch(`const props = __props`)
foo: { type: String, required: false },
bar: { type: Number, required: false },
baz: { type: Boolean, required: true }
- }, { ...defaults })`.trim()
+ }, { ...defaults })`.trim(),
)
})
foo: { type: String, required: false },
bar: { type: Number, required: false },
baz: { type: Boolean, required: true }
- }, defaults)`.trim()
+ }, defaults)`.trim(),
)
})
}>(), { ...defaults })
</script>
`,
- { isProd: true }
+ { isProd: true },
)
assertCode(content)
expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
bar: { type: Boolean },
baz: { type: [Boolean, Function] },
qux: {}
- }, { ...defaults })`.trim()
+ }, { ...defaults })`.trim(),
)
})
foo: { type: Function, required: false }
}, {
['fo' + 'o']() { return 'foo' }
- })`.trim()
+ })`.trim(),
)
})
foo: Foo
}>()
</script>`,
- { hoistStatic: true }
- ).content
+ { hoistStatic: true },
+ ).content,
).toMatch(`foo: { type: Number`)
expect(
foo: Foo
}>()
</script>`,
- { hoistStatic: true }
- ).content
+ { hoistStatic: true },
+ ).content,
).toMatch(`foo: { type: String`)
expect(
foo: Foo
}>()
</script>`,
- { hoistStatic: true }
- ).content
+ { hoistStatic: true },
+ ).content,
).toMatch(`foo: { type: [String, Number]`)
expect(
foo: Foo
}>()
</script>`,
- { hoistStatic: true }
- ).content
+ { hoistStatic: true },
+ ).content,
).toMatch(`foo: { type: Number`)
})
`)
expect(bindings).toStrictEqual({
bar: BindingTypes.SETUP_REF,
- computed: BindingTypes.SETUP_CONST
+ computed: BindingTypes.SETUP_CONST,
})
})
const { foo } = defineProps<{
foo: Foo
}>()
- </script>`
+ </script>`,
)
expect(content).toMatch(`const { foo } = __props`)
assertCode(content)
assertCode(content)
expect(content).toMatch(`"spa ce": { type: null, required: true }`)
expect(content).toMatch(
- `"exclamation!mark": { type: null, required: true }`
+ `"exclamation!mark": { type: null, required: true }`,
)
expect(content).toMatch(`"double\\"quote": { type: null, required: true }`)
expect(content).toMatch(`"hash#tag": { type: null, required: true }`)
expect(content).toMatch(`"question?mark": { type: null, required: true }`)
expect(content).toMatch(`"at@sign": { type: null, required: true }`)
expect(content).toMatch(
- `"square[brack]ets": { type: null, required: true }`
+ `"square[brack]ets": { type: null, required: true }`,
)
expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`)
expect(content).toMatch(`"ca^ret": { type: null, required: true }`)
'curly{bra}ces': BindingTypes.PROPS,
'pi|pe': BindingTypes.PROPS,
'til~de': BindingTypes.PROPS,
- 'da-sh': BindingTypes.PROPS
+ 'da-sh': BindingTypes.PROPS,
})
})
const props = defineProps<{ foo: number}>()
</script>`,
{ isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
- { filename: 'app.ce.vue' }
+ { filename: 'app.ce.vue' },
)
expect(content).toMatch(`foo: {type: Number}`)
});
</script>`,
{ isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
- { filename: 'app.ce.vue' }
+ { filename: 'app.ce.vue' },
)
expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
assertCode(content)
import { BindingTypes } from '@vue/compiler-core'
-import { SFCScriptCompileOptions } from '../../src'
-import { compileSFCScript, assertCode } from '../utils'
+import type { SFCScriptCompileOptions } from '../../src'
+import { assertCode, compileSFCScript } from '../utils'
describe('sfc reactive props destructure', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, {
inlineTemplate: true,
propsDestructure: true,
- ...options
+ ...options,
})
}
expect(content).toMatch(`_toDisplayString(__props.foo)`)
assertCode(content)
expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS
+ foo: BindingTypes.PROPS,
})
})
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.LITERAL_CONST,
- hello: BindingTypes.LITERAL_CONST
+ hello: BindingTypes.LITERAL_CONST,
})
})
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
- test: BindingTypes.SETUP_CONST
+ test: BindingTypes.SETUP_CONST,
})
})
`)
expect(bindings).toStrictEqual({
__propsAliases: {
- fooBar: 'foo:bar'
+ fooBar: 'foo:bar',
},
foo: BindingTypes.PROPS,
'foo:bar': BindingTypes.PROPS,
- fooBar: BindingTypes.PROPS_ALIASED
+ fooBar: BindingTypes.PROPS_ALIASED,
})
expect(content).toMatch(`
`)
expect(bindings).toStrictEqual({
__propsAliases: {
- fooBar: 'foo:bar'
+ fooBar: 'foo:bar',
},
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
'foo:bar': BindingTypes.PROPS,
fooBar: BindingTypes.PROPS_ALIASED,
- 'onUpdate:modelValue': BindingTypes.PROPS
+ 'onUpdate:modelValue': BindingTypes.PROPS,
})
expect(content).toMatch(`
props: {
const { foo = 1, bar = {}, func = () => {} } = defineProps<{ foo?: number, bar?: object, baz?: any, boola?: boolean, boolb?: boolean | number, func?: Function }>()
</script>
`,
- { isProd: true }
+ { isProd: true },
)
assertCode(content)
// literals can be used as-is, non-literals are always returned from a
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS_ALIASED,
__propsAliases: {
- bar: 'foo'
- }
+ bar: 'foo',
+ },
})
})
'foo.bar': BindingTypes.PROPS,
fooBar: BindingTypes.PROPS_ALIASED,
__propsAliases: {
- fooBar: 'foo.bar'
- }
+ fooBar: 'foo.bar',
+ },
})
})
</script>
`)
expect(content).toMatch(
- `const rest = _createPropsRestProxy(__props, ["foo","bar"])`
+ `const rest = _createPropsRestProxy(__props, ["foo","bar"])`,
)
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
baz: BindingTypes.PROPS,
- rest: BindingTypes.SETUP_REACTIVE_CONST
+ rest: BindingTypes.SETUP_REACTIVE_CONST,
})
})
expect(content).toMatch(`_toDisplayString(__props.foo)`)
assertCode(content)
expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS
+ foo: BindingTypes.PROPS,
})
})
test('should error on deep destructure', () => {
expect(() =>
compile(
- `<script setup>const { foo: [bar] } = defineProps(['foo'])</script>`
- )
+ `<script setup>const { foo: [bar] } = defineProps(['foo'])</script>`,
+ ),
).toThrow(`destructure does not support nested patterns`)
expect(() =>
compile(
- `<script setup>const { foo: { bar } } = defineProps(['foo'])</script>`
- )
+ `<script setup>const { foo: { bar } } = defineProps(['foo'])</script>`,
+ ),
).toThrow(`destructure does not support nested patterns`)
})
test('should error on computed key', () => {
expect(() =>
compile(
- `<script setup>const { [foo]: bar } = defineProps(['foo'])</script>`
- )
+ `<script setup>const { [foo]: bar } = defineProps(['foo'])</script>`,
+ ),
).toThrow(`destructure cannot use computed key`)
})
compile(
`<script setup lang="ts">
const { foo } = withDefaults(defineProps<{ foo: string }>(), { foo: 'foo' })
- </script>`
- )
+ </script>`,
+ ),
).toThrow(`withDefaults() is unnecessary when using destructure`)
})
const {
foo = () => x
} = defineProps(['foo'])
- </script>`
- )
+ </script>`,
+ ),
).toThrow(`cannot reference locally declared variables`)
})
`<script setup>
const { foo } = defineProps(['foo'])
foo = 'bar'
- </script>`
- )
+ </script>`,
+ ),
).toThrow(`Cannot assign to destructured props`)
expect(() =>
`<script setup>
let { foo } = defineProps(['foo'])
foo = 'bar'
- </script>`
- )
+ </script>`,
+ ),
).toThrow(`Cannot assign to destructured props`)
})
import { watch } from 'vue'
const { foo } = defineProps(['foo'])
watch(foo, () => {})
- </script>`
- )
+ </script>`,
+ ),
).toThrow(
- `"foo" is a destructured prop and should not be passed directly to watch().`
+ `"foo" is a destructured prop and should not be passed directly to watch().`,
)
expect(() =>
import { watch as w } from 'vue'
const { foo } = defineProps(['foo'])
w(foo, () => {})
- </script>`
- )
+ </script>`,
+ ),
).toThrow(
- `"foo" is a destructured prop and should not be passed directly to watch().`
+ `"foo" is a destructured prop and should not be passed directly to watch().`,
)
expect(() =>
import { toRef } from 'vue'
const { foo } = defineProps(['foo'])
toRef(foo)
- </script>`
- )
+ </script>`,
+ ),
).toThrow(
- `"foo" is a destructured prop and should not be passed directly to toRef().`
+ `"foo" is a destructured prop and should not be passed directly to toRef().`,
)
expect(() =>
import { toRef as r } from 'vue'
const { foo } = defineProps(['foo'])
r(foo)
- </script>`
- )
+ </script>`,
+ ),
).toThrow(
- `"foo" is a destructured prop and should not be passed directly to toRef().`
+ `"foo" is a destructured prop and should not be passed directly to toRef().`,
)
})
compile(
`<script setup lang="ts">
const { foo = 'hello' } = defineProps<{ foo?: number }>()
- </script>`
- )
+ </script>`,
+ ),
).toThrow(`Default value of prop "foo" does not match declared type.`)
})
const { error: e, info } = useRequest();
watch(e, () => {});
watch(info, () => {});
- </script>`
- )
+ </script>`,
+ ),
).not.toThrowError()
})
})
-import { compileSFCScript as compile, assertCode } from '../utils'
+import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineSlots()', () => {
test('basic usage', () => {
import { BindingTypes } from '@vue/compiler-core'
-import { SFCScriptCompileOptions } from '../../src'
-import { compileSFCScript, assertCode } from '../utils'
+import type { SFCScriptCompileOptions } from '../../src'
+import { assertCode, compileSFCScript } from '../utils'
describe('sfc hoist static', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, {
inlineTemplate: true,
hoistStatic: true,
- ...options
+ ...options,
})
}
boolean: BindingTypes.LITERAL_CONST,
nil: BindingTypes.LITERAL_CONST,
bigint: BindingTypes.LITERAL_CONST,
- template: BindingTypes.LITERAL_CONST
+ template: BindingTypes.LITERAL_CONST,
})
assertCode(content)
})
binary: BindingTypes.LITERAL_CONST,
conditional: BindingTypes.LITERAL_CONST,
unary: BindingTypes.LITERAL_CONST,
- sequence: BindingTypes.LITERAL_CONST
+ sequence: BindingTypes.LITERAL_CONST,
})
assertCode(content)
})
expect(content.startsWith(hoistCode)).toBe(true)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
- defaultValue: BindingTypes.LITERAL_CONST
+ defaultValue: BindingTypes.LITERAL_CONST,
})
assertCode(content)
})
KEY1: BindingTypes.SETUP_LET,
KEY2: BindingTypes.SETUP_LET,
regex: BindingTypes.SETUP_CONST,
- undef: BindingTypes.SETUP_MAYBE_REF
+ undef: BindingTypes.SETUP_MAYBE_REF,
})
expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content)
KEY4: BindingTypes.SETUP_CONST,
KEY5: BindingTypes.SETUP_CONST,
KEY6: BindingTypes.SETUP_CONST,
- i: BindingTypes.SETUP_LET
+ i: BindingTypes.SETUP_LET,
})
expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content)
`)
expect(bindings).toStrictEqual({
arr: BindingTypes.SETUP_CONST,
- obj: BindingTypes.SETUP_CONST
+ obj: BindingTypes.SETUP_CONST,
})
expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content)
expect(bindings).toStrictEqual({
Foo: BindingTypes.SETUP_CONST,
fn: BindingTypes.SETUP_CONST,
- fn2: BindingTypes.SETUP_CONST
+ fn2: BindingTypes.SETUP_CONST,
})
expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content)
</script>
`)
expect(bindings).toStrictEqual({
- foo: BindingTypes.SETUP_CONST
+ foo: BindingTypes.SETUP_CONST,
})
assertCode(content)
})
const foo = 'bar'
</script>
`,
- { hoistStatic: false }
+ { hoistStatic: false },
)
expect(bindings).toStrictEqual({
- foo: BindingTypes.SETUP_CONST
+ foo: BindingTypes.SETUP_CONST,
})
assertCode(content)
})
const foo = 'bar'
</script>
<template>{{ foo }}</template>
- `
+ `,
)
expect(content).toMatch('_toDisplayString(foo)')
})
import { normalize } from 'node:path'
-import { Identifier } from '@babel/types'
-import { SFCScriptCompileOptions, parse } from '../../src'
+import type { Identifier } from '@babel/types'
+import { type SFCScriptCompileOptions, parse } from '../../src'
import { ScriptCompileContext } from '../../src/script/context'
import {
inferRuntimeType,
invalidateTypeCache,
recordImports,
+ registerTS,
resolveTypeElements,
- registerTS
} from '../../src/script/resolveType'
import ts from 'typescript'
expect(props).toStrictEqual({
foo: ['Number'],
bar: ['Function'],
- baz: ['String']
+ baz: ['String'],
})
expect(calls?.length).toBe(2)
})
resolve(`
type Aliased = { foo: number }
defineProps<Aliased>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['Number']
+ foo: ['Number'],
})
})
resolve(`
export type Aliased = { foo: number }
defineProps<Aliased>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['Number']
+ foo: ['Number'],
})
})
resolve(`
interface Aliased { foo: number }
defineProps<Aliased>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['Number']
+ foo: ['Number'],
})
})
resolve(`
export interface Aliased { foo: number }
defineProps<Aliased>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['Number']
+ foo: ['Number'],
})
})
interface C { c: string }
interface Aliased extends B, C { foo: number }
defineProps<Aliased>()
- `).props
+ `).props,
).toStrictEqual({
a: ['Function'],
b: ['Boolean'],
c: ['String'],
- foo: ['Number']
+ foo: ['Number'],
})
})
resolve(`
class Foo {}
defineProps<{ foo: Foo }>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['Object']
+ foo: ['Object'],
})
})
expect(
resolve(`
defineProps<(e: 'foo') => void>()
- `).calls?.length
+ `).calls?.length,
).toBe(1)
})
resolve(`
type Fn = (e: 'foo') => void
defineProps<Fn>()
- `).calls?.length
+ `).calls?.length,
).toBe(1)
})
type Bar = { bar: string }
type Baz = { bar: string | boolean }
defineProps<{ self: any } & Foo & Bar & Baz>()
- `).props
+ `).props,
).toStrictEqual({
self: ['Unknown'],
foo: ['Number'],
// both Bar & Baz has 'bar', but Baz['bar] is wider so it should be
// preferred
- bar: ['String', 'Boolean']
+ bar: ['String', 'Boolean'],
})
})
}
defineProps<CommonProps & ConditionalProps>()
- `).props
+ `).props,
).toStrictEqual({
size: ['String'],
color: ['String', 'Number'],
appearance: ['String'],
- note: ['String']
+ note: ['String'],
})
})
defineProps<{
[\`_\${T}_\${S}_\`]: string
}>()
- `).props
+ `).props,
).toStrictEqual({
_foo_x_: ['String'],
_foo_y_: ['String'],
_bar_x_: ['String'],
- _bar_y_: ['String']
+ _bar_y_: ['String'],
})
})
} & {
[K in \`x\${T}\`]: string
}>()
- `).props
+ `).props,
).toStrictEqual({
foo: ['String', 'Number'],
bar: ['String', 'Number'],
FOO: ['String'],
xfoo: ['String'],
xbar: ['String'],
- optional: ['Boolean']
+ optional: ['Boolean'],
})
})
resolve(`
type T = { foo: number, bar: string }
defineProps<Partial<T>>()
- `).raw.props
+ `).raw.props,
).toMatchObject({
foo: {
- optional: true
+ optional: true,
},
bar: {
- optional: true
- }
+ optional: true,
+ },
})
})
resolve(`
type T = { foo?: number, bar?: string }
defineProps<Required<T>>()
- `).raw.props
+ `).raw.props,
).toMatchObject({
foo: {
- optional: false
+ optional: false,
},
bar: {
- optional: false
- }
+ optional: false,
+ },
})
})
type T = { foo: number, bar: string, baz: boolean }
type K = 'foo' | 'bar'
defineProps<Pick<T, K>>()
- `).props
+ `).props,
).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
})
type T = { foo: number, bar: string, baz: boolean }
type K = 'foo' | 'bar'
defineProps<Omit<T, K>>()
- `).props
+ `).props,
).toStrictEqual({
- baz: ['Boolean']
+ baz: ['Boolean'],
})
})
type T = { bar: number }
type S = { nested: { foo: T['bar'] }}
defineProps<S['nested']>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['Number']
+ foo: ['Number'],
})
})
type T = { foo: string, bar: number }
type S = { foo: { foo: T[string] }, bar: { bar: string } }
defineProps<S[K]>()
- `).props
+ `).props,
).toStrictEqual({
foo: ['String', 'Number'],
- bar: ['String']
+ bar: ['String'],
})
})
type T = [1, 'foo']
type TT = [foo: 1, bar: 'foo']
defineProps<{ foo: A[number], bar: AA[number], tuple: T[number], namedTuple: TT[number] }>()
- `).props
+ `).props,
).toStrictEqual({
foo: ['String', 'Number'],
bar: ['String'],
tuple: ['Number', 'String'],
- namedTuple: ['Number', 'String']
+ namedTuple: ['Number', 'String'],
})
})
}
}
defineProps<Foo.Bar.A>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['Number']
+ foo: ['Number'],
})
})
foo: Foo['a'],
bar: Foo['b']
}>()
- `).props
+ `).props,
).toStrictEqual({
foo: ['String'],
- bar: ['Number']
+ bar: ['Number'],
})
})
foo: Foo.A,
bar: Foo.B
}>()
- `).props
+ `).props,
).toStrictEqual({
foo: ['String'],
- bar: ['Number']
+ bar: ['Number'],
})
})
foo: Foo.A,
bar: Foo['b']
}>()
- `).props
+ `).props,
).toStrictEqual({
foo: ['String'],
- bar: ['Number']
+ bar: ['Number'],
})
})
defineProps<{
foo: Foo
}>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['Number', 'String']
+ foo: ['Number', 'String'],
})
})
resolve(`
declare const a: string
defineProps<{ foo: typeof a }>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
})
}
type Props = ExtractPropTypes<typeof props>
defineProps<Props>()
- `
+ `,
)
expect(props).toStrictEqual({
foo: ['String'],
- bar: ['Boolean']
+ bar: ['Boolean'],
})
expect(raw.props.bar.optional).toBe(false)
})
}
type Props = Partial<import('vue').ExtractPropTypes<ReturnType<typeof props>>>
defineProps<Props>()
- `
+ `,
)
expect(props).toStrictEqual({
foo: ['String'],
- bar: ['Boolean']
+ bar: ['Boolean'],
})
})
resolve(`
type Props<T> = T
defineProps<Props<{ foo: string }>>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
})
type Bar = { bar: number; }
type Props<T,U> = T & U & { baz: boolean }
defineProps<Props<Foo, Bar>>()
- `).props
+ `).props,
).toStrictEqual({
foo: ['String'],
bar: ['Number'],
- baz: ['Boolean']
+ baz: ['Boolean'],
})
})
type Props<T> = Aliased<T>
type Foo = { foo: string; }
defineProps<Props<Foo>>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
})
resolve(`
type Aliased<T> = { foo: T }
defineProps<Aliased<string>>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
})
}
type Foo = string
defineProps<Props<Foo>>()
- `).props
+ `).props,
).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
})
test('generic from external-file', () => {
const files = {
- '/foo.ts': 'export type P<T> = { foo: T }'
+ '/foo.ts': 'export type P<T> = { foo: T }',
}
const { props } = resolve(
`
import { P } from './foo'
defineProps<P<string>>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
})
})
'/bar.d.ts':
'type X = { bar: string }; export { X as Y };' +
// verify that we can parse syntax that is only valid in d.ts
- 'export const baz: boolean'
+ 'export const baz: boolean',
}
const { props, deps } = resolve(
`
import { Y as PP } from './bar'
defineProps<P & PP>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
'type X = { bar: string }; export { X as Y };' +
// verify that we can parse syntax that is only valid in d.ts
'export const baz: boolean',
- 'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }'
+ 'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }',
}
const { props, deps } = resolve(
`
`,
files,
{},
- 'C:\\Test\\FolderA\\Test.vue'
+ 'C:\\Test\\FolderA\\Test.vue',
)
expect(props).toStrictEqual({
foo: ['Number'],
bar: ['String'],
- buz: ['String']
+ buz: ['String'],
})
expect(deps && [...deps].map(normalize)).toStrictEqual(
- Object.keys(files).map(normalize)
+ Object.keys(files).map(normalize),
)
})
// #8244
test('utility type in external file', () => {
const files = {
- '/foo.ts': 'type A = { n?: number }; export type B = Required<A>'
+ '/foo.ts': 'type A = { n?: number }; export type B = Required<A>',
}
const { props } = resolve(
`
import { B } from './foo'
defineProps<B>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- n: ['Number']
+ n: ['Number'],
})
})
'/foo.vue':
'<script lang="ts">export type P = { foo: number }</script>',
'/bar.vue':
- '<script setup lang="tsx">export type P = { bar: string }</script>'
+ '<script setup lang="tsx">export type P = { bar: string }</script>',
}
const { props, deps } = resolve(
`
import { P as PP } from './bar.vue'
defineProps<P & PP>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
'/foo.ts': `import type { P as PP } from './nested/bar.vue'
export type P = { foo: number } & PP`,
'/nested/bar.vue':
- '<script setup lang="ts">export type P = { bar: string }</script>'
+ '<script setup lang="ts">export type P = { bar: string }</script>',
}
const { props, deps } = resolve(
`
import { P } from './foo'
defineProps<P>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
test('relative (chained, re-export)', () => {
const files = {
'/foo.ts': `export { P as PP } from './bar'`,
- '/bar.ts': 'export type P = { bar: string }'
+ '/bar.ts': 'export type P = { bar: string }',
}
const { props, deps } = resolve(
`
import { PP as P } from './foo'
defineProps<P>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
test('relative (chained, export *)', () => {
const files = {
'/foo.ts': `export * from './bar'`,
- '/bar.ts': 'export type P = { bar: string }'
+ '/bar.ts': 'export type P = { bar: string }',
}
const { props, deps } = resolve(
`
import { P } from './foo'
defineProps<P>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
test('relative (default export)', () => {
const files = {
'/foo.ts': `export default interface P { foo: string }`,
- '/bar.ts': `type X = { bar: string }; export default X`
+ '/bar.ts': `type X = { bar: string }; export default X`,
}
const { props, deps } = resolve(
`
import X from './bar'
defineProps<P & X>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['String'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
const files = {
'/bar.ts': `export { default } from './foo'`,
'/foo.ts': `export default interface P { foo: string }; export interface PP { bar: number }`,
- '/baz.ts': `export { PP as default } from './foo'`
+ '/baz.ts': `export { PP as default } from './foo'`,
}
const { props, deps } = resolve(
`
import PP from './baz'
defineProps<P & PP>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['String'],
- bar: ['Number']
+ bar: ['Number'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
const files = {
'/foo.ts': `export default interface P { foo: string }`,
'/bar.ts': `export default interface PP { bar: number }`,
- '/baz.ts': `export { default as X } from './foo'; export { default as XX } from './bar'; `
+ '/baz.ts': `export { default as X } from './foo'; export { default as XX } from './bar'; `,
}
const { props, deps } = resolve(
`import { X, XX } from './baz'
defineProps<X & XX>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['String'],
- bar: ['Number']
+ bar: ['Number'],
})
expect(deps && [...deps]).toStrictEqual(['/baz.ts', '/foo.ts', '/bar.ts'])
})
test('relative (dynamic import)', () => {
const files = {
'/foo.ts': `export type P = { foo: string, bar: import('./bar').N }`,
- '/bar.ts': 'export type N = number'
+ '/bar.ts': 'export type N = number',
}
const { props, deps } = resolve(
`
defineProps<import('./foo').P>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['String'],
- bar: ['Number']
+ bar: ['Number'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
const files = {
'/foo.d.ts':
'import { PP } from "./bar.js"; export type P = { foo: PP }',
- '/bar.d.ts': 'export type PP = "foo" | "bar"'
+ '/bar.d.ts': 'export type PP = "foo" | "bar"',
}
const { props, deps } = resolve(
`
import { P } from './foo'
defineProps<P>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
test('ts module resolve', () => {
const files = {
'/node_modules/foo/package.json': JSON.stringify({
- types: 'index.d.ts'
+ types: 'index.d.ts',
}),
'/node_modules/foo/index.d.ts': 'export type P = { foo: number }',
'/tsconfig.json': JSON.stringify({
compilerOptions: {
paths: {
- bar: ['./pp.ts']
- }
- }
+ bar: ['./pp.ts'],
+ },
+ },
}),
- '/pp.ts': 'export type PP = { bar: string }'
+ '/pp.ts': 'export type PP = { bar: string }',
}
const { props, deps } = resolve(
import { PP } from 'bar'
defineProps<P & PP>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual([
'/node_modules/foo/index.d.ts',
- '/pp.ts'
+ '/pp.ts',
])
})
'/tsconfig.json': JSON.stringify({
references: [
{
- path: './tsconfig.app.json'
- }
- ]
+ path: './tsconfig.app.json',
+ },
+ ],
}),
'/tsconfig.app.json': JSON.stringify({
include: ['**/*.ts', '**/*.vue'],
- extends: './tsconfig.web.json'
+ extends: './tsconfig.web.json',
}),
'/tsconfig.web.json': JSON.stringify({
compilerOptions: {
composite: true,
paths: {
- bar: ['./user.ts']
- }
- }
+ bar: ['./user.ts'],
+ },
+ },
}),
- '/user.ts': 'export type User = { bar: string }'
+ '/user.ts': 'export type User = { bar: string }',
}
const { props, deps } = resolve(
import { User } from 'bar'
defineProps<User>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(['/user.ts'])
})
compilerOptions: {
include: ['**/*.ts', '**/*.vue'],
paths: {
- '@/*': ['./src/*']
- }
- }
+ '@/*': ['./src/*'],
+ },
+ },
}),
'/src/Foo.vue':
- '<script lang="ts">export type P = { bar: string }</script>'
+ '<script lang="ts">export type P = { bar: string }</script>',
}
const { props, deps } = resolve(
import { P } from '@/Foo.vue'
defineProps<P>()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(['/src/Foo.vue'])
})
type PP = { bar: string }
}
export {}
- `
+ `,
}
const { props, deps } = resolve(`defineProps<App.User & PP>()`, files, {
- globalTypeFiles: Object.keys(files)
+ globalTypeFiles: Object.keys(files),
})
expect(props).toStrictEqual({
name: ['String'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
id: string
}
}
- `
+ `,
}
const { props } = resolve(`defineProps<App.Data.AircraftData>()`, files, {
- globalTypeFiles: Object.keys(files)
+ globalTypeFiles: Object.keys(files),
})
expect(props).toStrictEqual({
id: ['String'],
- manufacturer: ['Object']
+ manufacturer: ['Object'],
})
})
// #9871
test('shared generics with different args', () => {
const files = {
- '/foo.ts': `export interface Foo<T> { value: T }`
+ '/foo.ts': `export interface Foo<T> { value: T }`,
}
const { props } = resolve(
`import type { Foo } from './foo'
defineProps<Foo<string>>()`,
files,
undefined,
- `/One.vue`
+ `/One.vue`,
)
expect(props).toStrictEqual({
- value: ['String']
+ value: ['String'],
})
const { props: props2 } = resolve(
`import type { Foo } from './foo'
files,
undefined,
`/Two.vue`,
- false /* do not invalidate cache */
+ false /* do not invalidate cache */,
)
expect(props2).toStrictEqual({
- value: ['Number']
+ value: ['Number'],
})
})
})
describe('errors', () => {
test('failed type reference', () => {
expect(() => resolve(`defineProps<X>()`)).toThrow(
- `Unresolvable type reference`
+ `Unresolvable type reference`,
)
})
test('unsupported computed keys', () => {
expect(() => resolve(`defineProps<{ [Foo]: string }>()`)).toThrow(
- `Unsupported computed key in type referenced by a macro`
+ `Unsupported computed key in type referenced by a macro`,
)
})
test('unsupported index type', () => {
expect(() => resolve(`defineProps<X[K]>()`)).toThrow(
- `Unsupported type when resolving index type`
+ `Unsupported type when resolving index type`,
)
})
test('failed import source resolve', () => {
expect(() =>
- resolve(`import { X } from './foo'; defineProps<X>()`)
+ resolve(`import { X } from './foo'; defineProps<X>()`),
).toThrow(`Failed to resolve import source "./foo"`)
})
resolve(`
import type P from 'unknown'
defineProps<{ foo: P }>()
- `)
+ `),
).not.toThrow()
})
import type Base from 'unknown'
interface Props extends Base {}
defineProps<Props>()
- `)
+ `),
).toThrow(`@vue-ignore`)
})
foo: string
}
defineProps<Props>()
- `))
+ `)),
).not.toThrow(`@vue-ignore`)
expect(res.props).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
})
})
files: Record<string, string> = {},
options?: Partial<SFCScriptCompileOptions>,
sourceFileName: string = '/Test.vue',
- invalidateCache = true
+ invalidateCache = true,
) {
const { descriptor } = parse(`<script setup lang="ts">\n${code}\n</script>`, {
- filename: sourceFileName
+ filename: sourceFileName,
})
const ctx = new ScriptCompileContext(descriptor, {
id: 'test',
},
readFile(file) {
return files[file] ?? files[normalize(file)]
- }
+ },
},
- ...options
+ ...options,
})
if (invalidateCache) {
props,
calls: raw.calls,
deps: ctx.deps,
- raw
+ raw,
}
}
import {
+ type SFCStyleCompileOptions,
compileStyle,
compileStyleAsync,
- SFCStyleCompileOptions
} from '../src/compileStyle'
-import path from 'path'
+import path from 'node:path'
export function compileScoped(
source: string,
- options?: Partial<SFCStyleCompileOptions>
+ options?: Partial<SFCStyleCompileOptions>,
): string {
const res = compileStyle({
source,
filename: 'test.css',
id: 'data-v-test',
scoped: true,
- ...options
+ ...options,
})
if (res.errors.length) {
res.errors.forEach(err => {
describe('SFC scoped CSS', () => {
test('simple selectors', () => {
expect(compileScoped(`h1 { color: red; }`)).toMatch(
- `h1[data-v-test] { color: red;`
+ `h1[data-v-test] { color: red;`,
)
expect(compileScoped(`.foo { color: red; }`)).toMatch(
- `.foo[data-v-test] { color: red;`
+ `.foo[data-v-test] { color: red;`,
)
})
test('descendent selector', () => {
expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
- `h1 .foo[data-v-test] { color: red;`
+ `h1 .foo[data-v-test] { color: red;`,
)
})
test('multiple selectors', () => {
expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
- `h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`
+ `h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`,
)
})
test('pseudo class', () => {
expect(compileScoped(`.foo:after { color: red; }`)).toMatch(
- `.foo[data-v-test]:after { color: red;`
+ `.foo[data-v-test]:after { color: red;`,
)
})
test('pseudo element', () => {
expect(compileScoped(`::selection { display: none; }`)).toMatch(
- '[data-v-test]::selection {'
+ '[data-v-test]::selection {',
)
})
to { opacity: 1; }
}
`,
- { id: 'data-v-test' }
+ { id: 'data-v-test' },
)
expect(style).toContain(
- `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`
+ `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`,
)
expect(style).toContain(
- `.anim-2[data-v-test] {\n animation-name: color-test`
+ `.anim-2[data-v-test] {\n animation-name: color-test`,
)
expect(style).toContain(
- `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`
+ `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`,
)
expect(style).toContain(`@keyframes color-test {`)
expect(style).toContain(`@-webkit-keyframes color-test {`)
expect(style).toContain(
- `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`
+ `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`,
)
expect(style).toContain(
- `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`
+ `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`,
)
expect(style).toContain(`@keyframes opacity-test {\nfrom { opacity: 0;`)
expect(style).toContain(
- `@-webkit-keyframes opacity-test {\nfrom { opacity: 0;`
+ `@-webkit-keyframes opacity-test {\nfrom { opacity: 0;`,
)
})
}"
`)
expect(
- `::v-deep usage as a combinator has been deprecated.`
+ `::v-deep usage as a combinator has been deprecated.`,
).toHaveBeenWarned()
})
}"
`)
expect(
- `the >>> and /deep/ combinators have been deprecated.`
+ `the >>> and /deep/ combinators have been deprecated.`,
).toHaveBeenWarned()
})
}"
`)
expect(
- `the >>> and /deep/ combinators have been deprecated.`
+ `the >>> and /deep/ combinators have been deprecated.`,
).toHaveBeenWarned()
})
})
source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`,
filename: `test.css`,
id: 'test',
- modules: true
+ modules: true,
})
expect(result.modules).toBeDefined()
expect(result.modules!.red).toMatch('_red_')
modulesOptions: {
scopeBehaviour: 'global',
generateScopedName: `[name]__[local]__[hash:base64:5]`,
- localsConvention: 'camelCaseOnly'
- }
+ localsConvention: 'camelCaseOnly',
+ },
})
expect(result.modules).toBeDefined()
expect(result.modules!.fooBar).toMatch('__foo-bar__')
`,
filename: path.resolve(__dirname, './fixture/test.scss'),
id: '',
- preprocessLang: 'scss'
+ preprocessLang: 'scss',
})
expect([...res.dependencies]).toStrictEqual([
- path.join(__dirname, './fixture/import.scss')
+ path.join(__dirname, './fixture/import.scss'),
])
})
@mixin square($size) {
width: $size;
height: $size;
- }`
+ }`,
},
source: `
.square {
`,
filename: path.resolve(__dirname, './fixture/test.scss'),
id: '',
- preprocessLang: 'scss'
+ preprocessLang: 'scss',
})
expect(res.errors.length).toBe(0)
width: $size;
height: $size;
}`
- }
+ },
},
source,
filename,
id: '',
- preprocessLang: 'scss'
+ preprocessLang: 'scss',
})
expect(res.errors.length).toBe(0)
-import { RawSourceMap, SourceMapConsumer } from 'source-map-js'
+import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
import {
+ type SFCTemplateCompileOptions,
compileTemplate,
- SFCTemplateCompileOptions
} from '../src/compileTemplate'
-import { parse, SFCTemplateBlock } from '../src/parse'
+import { type SFCTemplateBlock, parse } from '../src/parse'
import { compileScript } from '../src'
function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) {
return compileTemplate({
...opts,
- id: ''
+ id: '',
})
}
p Cool Pug example!
</template>
`,
- { filename: 'example.vue', sourceMap: true }
+ { filename: 'example.vue', sourceMap: true },
).descriptor.template as SFCTemplateBlock
const result = compile({
filename: 'example.vue',
source: template.content,
- preprocessLang: template.lang
+ preprocessLang: template.lang,
})
expect(result.errors.length).toBe(0)
p This is the last line.
</template>
`,
- { filename: 'example.vue', sourceMap: true }
+ { filename: 'example.vue', sourceMap: true },
).descriptor.template as SFCTemplateBlock
const result = compile({
filename: 'example.vue',
source: template.content,
- preprocessLang: template.lang
+ preprocessLang: template.lang,
})
expect(result.errors.length).toBe(0)
expect(result.source).toBe(
- '<body><h1>The next line contains four spaces.</h1><div class="container"><p>The next line is empty.</p></div><p>This is the last line.</p></body>'
+ '<body><h1>The next line contains four spaces.</h1><div class="container"><p>The next line is empty.</p></div><p>This is the last line.</p></body>',
)
})
test('warn missing preprocessor', () => {
const template = parse(`<template lang="unknownLang">hi</template>\n`, {
filename: 'example.vue',
- sourceMap: true
+ sourceMap: true,
}).descriptor.template as SFCTemplateBlock
const result = compile({
filename: 'example.vue',
source: template.content,
- preprocessLang: template.lang
+ preprocessLang: template.lang,
})
expect(result.errors.length).toBe(1)
const { code: code1 } = compile({
...input,
transformAssetUrls: {
- tags: { foo: ['bar'] }
- }
+ tags: { foo: ['bar'] },
+ },
})
expect(code1).toMatch(`import _imports_0 from 'baz'\n`)
const { code: code2 } = compile({
...input,
transformAssetUrls: {
- foo: ['bar']
- }
+ foo: ['bar'],
+ },
})
expect(code2).toMatch(`import _imports_0 from 'baz'\n`)
// false option
const { code: code3 } = compile({
...input,
- transformAssetUrls: false
+ transformAssetUrls: false,
})
expect(code3).not.toMatch(`import _imports_0 from 'baz'\n`)
})
<div><p>{{ foobar }}</p></div>
</template>
`,
- { filename: 'example.vue', sourceMap: true }
+ { filename: 'example.vue', sourceMap: true },
).descriptor.template!
const { code, map } = compile({
filename: 'example.vue',
- source: template.content
+ source: template.content,
})
expect(map!.sources).toEqual([`example.vue`])
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
+ consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
).toMatchObject(getPositionInCode(template.content, `foobar`))
})
`
const template = parse(source, {
filename: 'example.vue',
- sourceMap: true
+ sourceMap: true,
}).descriptor.template!
expect(template.ast!.source).toBe(source)
const { code, map } = compile({
filename: 'example.vue',
source: template.content,
- ast: template.ast
+ ast: template.ast,
})
expect(map!.sources).toEqual([`example.vue`])
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
+ consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
).toMatchObject(getPositionInCode(source, `foobar`))
expect(code).toBe(
compile({
filename: 'example.vue',
- source: template.content
- }).code
+ source: template.content,
+ }).code,
)
})
`
const template = parse(source, {
filename: 'example.vue',
- sourceMap: true
+ sourceMap: true,
}).descriptor.template!
expect(template.ast!.source).toBe(source)
filename: 'example.vue',
source: '', // make sure it's actually using the AST instead of source
ast: template.ast,
- ssr: true
+ ssr: true,
})
expect(map!.sources).toEqual([`example.vue`])
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
+ consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
).toMatchObject(getPositionInCode(source, `foobar`))
expect(code).toBe(
compile({
filename: 'example.vue',
source: template.content,
- ssr: true
- }).code
+ ssr: true,
+ }).code,
)
})
`
const template = parse(source, {
filename: 'example.vue',
- sourceMap: true
+ sourceMap: true,
}).descriptor.template!
const { code } = compile({
ast: template.ast,
compiler: {
parse: () => null as any,
- // @ts-ignore
- compile: input => ({ code: input })
- }
+ // @ts-expect-error
+ compile: input => ({ code: input }),
+ },
})
// what we really want to assert is that the `input` received by the custom
`
const template = parse(source, {
filename: 'example.vue',
- sourceMap: true
+ sourceMap: true,
}).descriptor.template!
// force set to empty, if this is reused then it won't generate proper code
const { code } = compile({
filename: 'example.vue',
source: '',
- ast: template.ast
+ ast: template.ast,
})
expect(code).toBe(
compile({
filename: 'example.vue',
- source: template.content
- }).code
+ source: template.content,
+ }).code,
)
})
`
const template = parse(source, {
filename: 'example.vue',
- sourceMap: true
+ sourceMap: true,
}).descriptor.template!
// force set to empty, if this is reused then it won't generate proper code
filename: 'example.vue',
source: '',
ast: template.ast,
- ssr: true
+ ssr: true,
})
expect(code).toBe(
compile({
filename: 'example.vue',
source: template.content,
- ssr: true
- }).code
+ ssr: true,
+ }).code,
)
})
const result = compile({
filename: 'example.vue',
source: `<div
- :bar="a[" v-model="baz"/>`
+ :bar="a[" v-model="baz"/>`,
})
expect(result.errors).toMatchSnapshot()
})
div(class='class)
</template>
`,
- { filename: 'example.vue', sourceMap: true }
+ { filename: 'example.vue', sourceMap: true },
).descriptor.template as SFCTemplateBlock
const result = compile({
filename: 'example.vue',
source: template.content,
- preprocessLang: template.lang
+ preprocessLang: template.lang,
})
expect(result.errors.length).toBe(1)
const message = result.errors[0].toString()
expect(message).toMatch(`Error: example.vue:3:1`)
expect(message).toMatch(
- `The end of the string reached with no closing bracket ) found.`
+ `The end of the string reached with no closing bracket ) found.`,
)
})
<img src="./bar.svg"/>
</Comp>
`,
- ssr: true
+ ssr: true,
})
expect(code).toMatch(`_ssrRenderAttr(\"src\", _imports_1)`)
expect(code).toMatch(`_createVNode(\"img\", { src: _imports_1 })`)
</picture>
</router-link>
`,
- ssr: true
+ ssr: true,
})
expect(code).toMatchSnapshot()
})
id: 'xxx',
filename: 'test.vue',
ast: descriptor.template!.ast,
- source: descriptor.template!.content
+ source: descriptor.template!.content,
})
expect(code).not.toMatch(`_ctx.t`)
})
function getPositionInCode(
code: string,
token: string,
- expectName: string | boolean = false
+ expectName: string | boolean = false,
): Pos {
const generatedOffset = code.indexOf(token)
let line = 1
column:
lastNewLinePos === -1
? generatedOffset
- : generatedOffset - lastNewLinePos - 1
+ : generatedOffset - lastNewLinePos - 1,
}
if (expectName) {
res.name = typeof expectName === 'string' ? expectName : token
import { compileStyle, parse } from '../src'
-import { mockId, compileSFCScript, assertCode } from './utils'
+import { assertCode, compileSFCScript, mockId } from './utils'
describe('CSS vars injection', () => {
test('generating correct code for nested paths', () => {
`<style>div{
color: v-bind(color);
font-size: v-bind('font.size');
- }</style>`
+ }</style>`,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-color": (_ctx.color),
div {
font-size: v-bind(size);
}
- </style>`
+ </style>`,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-size": (_ctx.size)
font-size: v-bind(size);
border: v-bind(foo);
}
- </style>`
+ </style>`,
)
// should handle:
// 1. local const bindings
"${mockId}-foo": (__props.foo)
})`)
expect(content).toMatch(
- `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`
+ `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`,
)
assertCode(content)
})
font-family: v-bind(フォント);
}`,
filename: 'test.css',
- id: 'data-v-test'
+ id: 'data-v-test',
})
expect(code).toMatchInlineSnapshot(`
".foo {
color: v-bind(color);
font-size: v-bind('font.size');
}</style>`,
- { isProd: true }
+ { isProd: true },
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"4003f1a6": (_ctx.color),
}`,
filename: 'test.css',
id: mockId,
- isProd: true
+ isProd: true,
})
expect(code).toMatchInlineSnapshot(`
".foo {
assertCode(
compileSFCScript(
`<script>const a = 1</script>\n` +
- `<style>div{ color: v-bind(color); }</style>`
- ).content
+ `<style>div{ color: v-bind(color); }</style>`,
+ ).content,
)
})
assertCode(
compileSFCScript(
`<script>export default { setup() {} }</script>\n` +
- `<style>div{ color: v-bind(color); }</style>`
- ).content
+ `<style>div{ color: v-bind(color); }</style>`,
+ ).content,
)
})
`<script>
// export default {}
export default {}
- </script>\n` + `<style>div{ color: v-bind(color); }</style>`
- ).content
+ </script>\n` + `<style>div{ color: v-bind(color); }</style>`,
+ ).content,
)
})
assertCode(
compileSFCScript(
`<script setup>const color = 'red'</script>\n` +
- `<style>div{ color: v-bind(color); }</style>`
- ).content
+ `<style>div{ color: v-bind(color); }</style>`,
+ ).content,
)
})
div{ /* color: v-bind(color); */ width:20; }
div{ width: v-bind(width); }
/* comment */
- </style>`
+ </style>`,
)
expect(content).not.toMatch(`"${mockId}-color": (color)`)
p {
color: v-bind(color);
}
- </style>`
+ </style>`,
)
// color should only be injected once, even if it is twice in style
expect(content).toMatch(`_useCssVars(_ctx => ({
p {
color: v-bind(((a + b)) / (2 * a));
}
- </style>`
+ </style>`,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-foo": (_unref(foo)),
// #6022
test('should be able to parse incomplete expressions', () => {
const {
- descriptor: { cssVars }
+ descriptor: { cssVars },
} = parse(
`<script setup>let xxx = 1</script>
<style scoped>
font-weight: v-bind("count.toString(");
font-weight: v-bind(xxx);
}
- </style>`
+ </style>`,
)
expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
})
label {
background: v-bind(background);
}
- </style>`
+ </style>`,
)
expect(content).toMatch(
- `export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`
+ `export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`,
)
})
{
inlineTemplate: true,
templateOptions: {
- ssr: true
- }
- }
+ ssr: true,
+ },
+ },
)
expect(content).not.toMatch(`_useCssVars`)
})
{
inlineTemplate: false,
templateOptions: {
- ssr: true
- }
- }
+ ssr: true,
+ },
+ },
)
expect(content).not.toMatch(`_useCssVars`)
})
</style>`,
{
templateOptions: {
- ssr: true
- }
- }
+ ssr: true,
+ },
+ },
)
expect(content).not.toMatch(`_useCssVars`)
})
}
</style>`
const {
- descriptor: { styles }
+ descriptor: { styles },
} = parse(src)
expect(styles[0].map).not.toBeUndefined()
// Padding determines how many blank lines will there be before the style block
const padding = Math.round(Math.random() * 10)
const script = parse(
- `${'\n'.repeat(padding)}<script>\nconsole.log(1)\n }\n</script>\n`
+ `${'\n'.repeat(padding)}<script>\nconsole.log(1)\n }\n</script>\n`,
).descriptor.script
expect(script!.map).not.toBeUndefined()
h1 foo
div bar
span baz
-</template>\n`
+</template>\n`,
).descriptor.template!
expect(template.map).not.toBeUndefined()
test('custom block', () => {
const padding = Math.round(Math.random() * 10)
const custom = parse(
- `${'\n'.repeat(padding)}<i18n>\n{\n "greeting": "hello"\n}\n</i18n>\n`
+ `${'\n'.repeat(padding)}<i18n>\n{\n "greeting": "hello"\n}\n</i18n>\n`,
).descriptor.customBlocks[0]
expect(custom!.map).not.toBeUndefined()
const padTrue = parse(content.trim(), { pad: true }).descriptor
expect(padTrue.script!.content).toBe(
- Array(3 + 1).join('//\n') + '\nexport default {}\n'
+ Array(3 + 1).join('//\n') + '\nexport default {}\n',
)
expect(padTrue.styles[0].content).toBe(
- Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
+ Array(6 + 1).join('\n') + '\nh1 { color: red }\n',
)
expect(padTrue.customBlocks[0].content).toBe(
- Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n'
+ Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n',
)
const padLine = parse(content.trim(), { pad: 'line' }).descriptor
expect(padLine.script!.content).toBe(
- Array(3 + 1).join('//\n') + '\nexport default {}\n'
+ Array(3 + 1).join('//\n') + '\nexport default {}\n',
)
expect(padLine.styles[0].content).toBe(
- Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
+ Array(6 + 1).join('\n') + '\nh1 { color: red }\n',
)
expect(padLine.customBlocks[0].content).toBe(
- Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n'
+ Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n',
)
const padSpace = parse(content.trim(), { pad: 'space' }).descriptor
expect(padSpace.script!.content).toBe(
`<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
- '\nexport default {}\n'
+ '\nexport default {}\n',
)
expect(padSpace.styles[0].content).toBe(
`<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>`.replace(
/./g,
- ' '
- ) + '\nh1 { color: red }\n'
+ ' ',
+ ) + '\nh1 { color: red }\n',
)
expect(padSpace.customBlocks[0].content).toBe(
`<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>\nh1 { color: red }\n</style>\n<i18n>`.replace(
/./g,
- ' '
- ) + '\n{ "greeting": "hello" }\n'
+ ' ',
+ ) + '\n{ "greeting": "hello" }\n',
)
})
end: {
line: 3,
column: 1,
- offset: 10 + content.length
- }
+ offset: 10 + content.length,
+ },
})
})
expect(descriptor.template!.content).toBeFalsy()
expect(descriptor.template!.loc).toMatchObject({
start: { line: 1, column: 12, offset: 11 },
- end: { line: 1, column: 12, offset: 11 }
+ end: { line: 1, column: 12, offset: 11 },
})
})
expect(descriptor.template!.content).toBeFalsy()
expect(descriptor.template!.loc).toMatchObject({
start: { line: 1, column: 11, offset: 10 },
- end: { line: 1, column: 11, offset: 10 }
+ end: { line: 1, column: 11, offset: 10 },
})
})
expect(parse(`<style> \n\t </style>`).descriptor.styles.length).toBe(0)
expect(parse(`<custom/>`).descriptor.customBlocks.length).toBe(0)
expect(
- parse(`<custom> \n\t </custom>`).descriptor.customBlocks.length
+ parse(`<custom> \n\t </custom>`).descriptor.customBlocks.length,
).toBe(0)
})
const { descriptor } = parse(
`<script></script>\n<script setup>\n</script>`,
{
- ignoreEmpty: false
- }
+ ignoreEmpty: false,
+ },
)
expect(descriptor.script).toBeTruthy()
expect(descriptor.script!.loc).toMatchObject({
start: { line: 1, column: 9, offset: 8 },
- end: { line: 1, column: 9, offset: 8 }
+ end: { line: 1, column: 9, offset: 8 },
})
expect(descriptor.scriptSetup).toBeTruthy()
expect(descriptor.scriptSetup!.loc).toMatchObject({
start: { line: 2, column: 15, offset: 32 },
- end: { line: 3, column: 1, offset: 33 }
+ end: { line: 3, column: 1, offset: 33 },
})
})
test('treat empty lang attribute as the html', () => {
const content = `<div><template v-if="ok">ok</template></div>`
const { descriptor, errors } = parse(
- `<template lang="">${content}</template>`
+ `<template lang="">${content}</template>`,
)
expect(descriptor.template!.content).toBe(content)
expect(errors.length).toBe(0)
test('template with preprocessor lang should be treated as plain text', () => {
const content = `p(v-if="1 < 2") test <div/>`
const { descriptor, errors } = parse(
- `<template lang="pug">` + content + `</template>`
+ `<template lang="pug">` + content + `</template>`,
)
expect(errors.length).toBe(0)
expect(descriptor.template!.content).toBe(content)
expect(parse(`<template>hi</template>`).descriptor.slotted).toBe(false)
expect(
parse(`<template>hi</template><style>h1{color:red;}</style>`).descriptor
- .slotted
+ .slotted,
).toBe(false)
expect(
parse(
- `<template>hi</template><style scoped>:slotted(h1){color:red;}</style>`
- ).descriptor.slotted
+ `<template>hi</template><style scoped>:slotted(h1){color:red;}</style>`,
+ ).descriptor.slotted,
).toBe(true)
expect(
parse(
- `<template>hi</template><style scoped>::v-slotted(h1){color:red;}</style>`
- ).descriptor.slotted
+ `<template>hi</template><style scoped>::v-slotted(h1){color:red;}</style>`,
+ ).descriptor.slotted,
).toBe(true)
})
options.onError!(new Error('foo') as any)
return createRoot([])
},
- compile: baseCompile
- }
+ compile: baseCompile,
+ },
})
expect(errors.length).toBe(2)
// error thrown by the custom parse
test('treat custom blocks as raw text', () => {
const { errors, descriptor } = parse(
- `<template><input></template><foo> <-& </foo>`
+ `<template><input></template><foo> <-& </foo>`,
)
expect(errors.length).toBe(0)
expect(descriptor.customBlocks[0].content).toBe(` <-& `)
test('should only allow single template element', () => {
assertWarning(
parse(`<template><div/></template><template><div/></template>`).errors,
- `Single file component can contain only one <template> element`
+ `Single file component can contain only one <template> element`,
)
})
assertWarning(
parse(`<script>console.log(1)</script><script>console.log(1)</script>`)
.errors,
- `Single file component can contain only one <script> element`
+ `Single file component can contain only one <script> element`,
)
})
test('should only allow single script setup element', () => {
assertWarning(
parse(
- `<script setup>console.log(1)</script><script setup>console.log(1)</script>`
+ `<script setup>console.log(1)</script><script setup>console.log(1)</script>`,
).errors,
- `Single file component can contain only one <script setup> element`
+ `Single file component can contain only one <script setup> element`,
)
})
test('should not warn script & script setup', () => {
expect(
parse(
- `<script setup>console.log(1)</script><script>console.log(1)</script>`
- ).errors.length
+ `<script setup>console.log(1)</script><script>console.log(1)</script>`,
+ ).errors.length,
).toBe(0)
})
test('should throw error if no <template> or <script> is present', () => {
assertWarning(
parse(`import { ref } from 'vue'`).errors,
- `At least one <template> or <script> is required in a single file component`
+ `At least one <template> or <script> is required in a single file component`,
)
})
})
test('rewrite export default', () => {
expect(
- rewriteDefault(`export default {}`, 'script')
+ rewriteDefault(`export default {}`, 'script'),
).toMatchInlineSnapshot(`"const script = {}"`)
})
expect(
rewriteDefault(
`const a = 1 \n export { a as b, a as default, a as c}`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"const a = 1
export { a as b, a as c}
expect(
rewriteDefault(
`const a = 1 \n export { a as b, a as default , a as c}`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"const a = 1
export { a as b, a as c}
expect(
rewriteDefault(
`const a = 1 \n export { a as b } \n export { a as default, a as c }`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"const a = 1
export { a as b }
test('export named default multiline', () => {
expect(
- rewriteDefault(`let App = {}\n export {\nApp as default\n}`, '_sfc_main')
+ rewriteDefault(`let App = {}\n export {\nApp as default\n}`, '_sfc_main'),
).toMatchInlineSnapshot(`
"let App = {}
export {
rewriteDefault(
`const a = 1 \n export {\n a as b,\n a as default,\n a as c}\n` +
`// export { myFunction as default }`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"const a = 1
export {
rewriteDefault(
`const a = 1 \n export {\n a as b,\n a as default ,\n a as c}\n` +
`// export { myFunction as default }`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"const a = 1
export {
test(`export { default } from '...'`, async () => {
expect(
- rewriteDefault(`export { default, foo } from './index.js'`, 'script')
+ rewriteDefault(`export { default, foo } from './index.js'`, 'script'),
).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js'
export { foo } from './index.js'
`)
expect(
- rewriteDefault(`export { default , foo } from './index.js'`, 'script')
+ rewriteDefault(`export { default , foo } from './index.js'`, 'script'),
).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js'
export { foo } from './index.js'
`)
expect(
- rewriteDefault(`export { foo, default } from './index.js'`, 'script')
+ rewriteDefault(`export { foo, default } from './index.js'`, 'script'),
).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js'
export { foo, } from './index.js'
expect(
rewriteDefault(
`export { foo as default, bar } from './index.js'`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"import { foo as __VUE_DEFAULT__ } from './index.js'
export { bar } from './index.js'
expect(
rewriteDefault(
`export { foo as default , bar } from './index.js'`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"import { foo as __VUE_DEFAULT__ } from './index.js'
export { bar } from './index.js'
expect(
rewriteDefault(
`export { bar, foo as default } from './index.js'`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"import { foo as __VUE_DEFAULT__ } from './index.js'
export { bar, } from './index.js'
expect(
rewriteDefault(
`export { foo as default } from './index.js' \n const foo = 1`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"import { foo as __VUE_DEFAULT__ } from './index.js'
export { } from './index.js'
expect(
rewriteDefault(
`const a = 1 \nexport { a as default } from 'xxx'`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"import { a as __VUE_DEFAULT__ } from 'xxx'
const a = 1
test('export default class w/ comments', async () => {
expect(
- rewriteDefault(`// export default\nexport default class Foo {}`, 'script')
+ rewriteDefault(
+ `// export default\nexport default class Foo {}`,
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"// export default
class Foo {}
expect(
rewriteDefault(
`export default {}\n` + `// export default class Foo {}`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"const script = {}
// export default class Foo {}"
expect(
rewriteDefault(
`/*\nexport default class Foo {}*/\n` + `export default class Bar {}`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"/*
export default class Foo {}*/
test('@Component\nexport default class', async () => {
expect(
rewriteDefault(`@Component\nexport default class Foo {}`, 'script', [
- 'decorators-legacy'
- ])
+ 'decorators-legacy',
+ ]),
).toMatchInlineSnapshot(`
"@Component class Foo {}
const script = Foo"
rewriteDefault(
`// export default\n@Component\nexport default class Foo {}`,
'script',
- ['decorators-legacy']
- )
+ ['decorators-legacy'],
+ ),
).toMatchInlineSnapshot(`
"// export default
@Component class Foo {}
expect(
rewriteDefault(
`export default {}\n` + `// @Component\n// export default class Foo {}`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"const script = {}
// @Component
rewriteDefault(
`/*\n@Component\nexport default class Foo {}*/\n` +
`export default class Bar {}`,
- 'script'
- )
+ 'script',
+ ),
).toMatchInlineSnapshot(`
"/*
@Component
import {
- generate,
+ type TransformOptions,
baseParse,
+ generate,
transform,
- TransformOptions
} from '@vue/compiler-core'
import {
- transformAssetUrl,
+ type AssetURLOptions,
createAssetUrlTransformWithOptions,
- AssetURLOptions,
- normalizeOptions
+ normalizeOptions,
+ transformAssetUrl,
} from '../src/template/transformAssetUrl'
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
import { transformBind } from '../../compiler-core/src/transforms/vBind'
function compileWithAssetUrls(
template: string,
options?: AssetURLOptions,
- transformOptions?: TransformOptions
+ transformOptions?: TransformOptions,
) {
const ast = baseParse(template)
const t = options
transform(ast, {
nodeTransforms: [t, transformElement],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- ...transformOptions
+ ...transformOptions,
})
return generate(ast, { mode: 'module' })
}
'<use href="~@svg/file.svg#fragment"></use>',
{},
{
- hoistStatic: true
- }
+ hoistStatic: true,
+ },
)
expect(result.code).toMatchSnapshot()
})
`<img src="~bar.png"></img>` + // -> still converts to import
`<img src="@theme/bar.png"></img>`, // -> still converts to import
{
- base: '/foo'
- }
+ base: '/foo',
+ },
)
expect(code).toMatch(`import _imports_0 from 'bar.png'`)
expect(code).toMatch(`import _imports_1 from '@theme/bar.png'`)
`<img src="https://foo.bar/baz.png"/>` +
`<img src="//foo.bar/baz.png"/>`,
{
- includeAbsolute: true
- }
+ includeAbsolute: true,
+ },
)
expect(code).toMatchSnapshot()
})
<circle id="myCircle" cx="0" cy="0" r="5" />
</defs>
<use x="5" y="5" xlink:href="#myCircle" />
- </svg>`
+ </svg>`,
)
// should not remove it
expect(code).toMatch(`"xlink:href": "#myCircle"`)
test('should allow for full base URLs, with paths', () => {
const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
- base: 'http://localhost:3000/src/'
+ base: 'http://localhost:3000/src/',
})
expect(code).toMatchSnapshot()
test('should allow for full base URLs, without paths', () => {
const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
- base: 'http://localhost:3000'
+ base: 'http://localhost:3000',
})
expect(code).toMatchSnapshot()
test('should allow for full base URLs, without port', () => {
const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
- base: 'http://localhost'
+ base: 'http://localhost',
})
expect(code).toMatchSnapshot()
test('should allow for full base URLs, without protocol', () => {
const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
- base: '//localhost'
+ base: '//localhost',
})
expect(code).toMatchSnapshot()
`<img src="./bar.png"/>` +
`</div>`,
{
- includeAbsolute: true
+ includeAbsolute: true,
},
{
hoistStatic: true,
- transformHoist: stringifyStatic
- }
+ transformHoist: stringifyStatic,
+ },
)
expect(code).toMatch(`_createStaticVNode`)
expect(code).toMatchSnapshot()
const { code } = compileWithAssetUrls(
`<div><img src="/foo bar.png"/></div>`,
{
- includeAbsolute: true
+ includeAbsolute: true,
},
{
hoistStatic: true,
- transformHoist: stringifyStatic
- }
+ transformHoist: stringifyStatic,
+ },
)
expect(code).toMatch(`_createElementVNode`)
expect(code).toContain(`import _imports_0 from '/foo bar.png'`)
const { code } = compileWithAssetUrls(
`<div><img src="./foo bar.png"/></div>`,
{
- includeAbsolute: true
+ includeAbsolute: true,
},
{
hoistStatic: true,
- transformHoist: stringifyStatic
- }
+ transformHoist: stringifyStatic,
+ },
)
expect(code).toMatch(`_createElementVNode`)
expect(code).toContain(`import _imports_0 from './foo bar.png'`)
import {
- generate,
+ type TransformOptions,
baseParse,
+ generate,
transform,
- TransformOptions
} from '@vue/compiler-core'
import {
+ createSrcsetTransformWithOptions,
transformSrcset,
- createSrcsetTransformWithOptions
} from '../src/template/transformSrcset'
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
import { transformBind } from '../../compiler-core/src/transforms/vBind'
import {
- AssetURLOptions,
- normalizeOptions
+ type AssetURLOptions,
+ normalizeOptions,
} from '../src/template/transformAssetUrl'
import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStatic'
function compileWithSrcset(
template: string,
options?: AssetURLOptions,
- transformOptions?: TransformOptions
+ transformOptions?: TransformOptions,
) {
const ast = baseParse(template)
const srcsetTransform = options
hoistStatic: true,
nodeTransforms: [srcsetTransform, transformElement],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- ...transformOptions
+ ...transformOptions,
})
return generate(ast, { mode: 'module' })
}
test('transform srcset w/ base', () => {
expect(
compileWithSrcset(src, {
- base: '/foo'
- }).code
+ base: '/foo',
+ }).code,
).toMatchSnapshot()
})
test('transform srcset w/ includeAbsolute: true', () => {
expect(
compileWithSrcset(src, {
- includeAbsolute: true
- }).code
+ includeAbsolute: true,
+ }).code,
).toMatchSnapshot()
})
const code = compileWithSrcset(
`<div>${src}</div>`,
{
- includeAbsolute: true
+ includeAbsolute: true,
},
{
hoistStatic: true,
- transformHoist: stringifyStatic
- }
+ transformHoist: stringifyStatic,
+ },
).code
expect(code).toMatch(`_createStaticVNode`)
expect(code).toMatchSnapshot()
<img srcset="@/logo.png 1x, ./logo.png 2x"/>
`,
{ base: '/foo/' },
- { hoistStatic: true }
+ { hoistStatic: true },
).code
expect(code).toMatchSnapshot()
})
import {
- isRelativeUrl,
+ isDataUrl,
isExternalUrl,
- isDataUrl
+ isRelativeUrl,
} from '../src/template/templateUtils'
describe('compiler sfc:templateUtils isRelativeUrl', () => {
import {
- parse,
- SFCScriptCompileOptions,
+ type SFCParseOptions,
+ type SFCScriptCompileOptions,
compileScript,
- SFCParseOptions
+ parse,
} from '../src'
import { parse as babelParse } from '@babel/parser'
export function compileSFCScript(
src: string,
options?: Partial<SFCScriptCompileOptions>,
- parseOptions?: SFCParseOptions
+ parseOptions?: SFCParseOptions,
) {
const { descriptor, errors } = parse(src, parseOptions)
if (errors.length) {
}
return compileScript(descriptor, {
...options,
- id: mockId
+ id: mockId,
})
}
sourceType: 'module',
plugins: [
'typescript',
- ['importAttributes', { deprecatedAssertSyntax: true }]
- ]
+ ['importAttributes', { deprecatedAssertSyntax: true }],
+ ],
})
} catch (e: any) {
console.log(code)
import { LRUCache } from 'lru-cache'
export function createCache<T extends {}>(
- max = 500
+ max = 500,
): Map<string, T> | LRUCache<string, T> {
if (__GLOBAL__ || __ESM_BROWSER__) {
return new Map<string, T>()
BindingTypes,
UNREF,
isFunctionType,
+ unwrapTSNode,
walkIdentifiers,
- unwrapTSNode
} from '@vue/compiler-dom'
-import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
-import { ParserPlugin } from '@babel/parser'
-import { generateCodeFrame } from '@vue/shared'
import {
- Node,
- Declaration,
- ObjectPattern,
+ DEFAULT_FILENAME,
+ type SFCDescriptor,
+ type SFCScriptBlock,
+} from './parse'
+import type { ParserPlugin } from '@babel/parser'
+import { generateCodeFrame } from '@vue/shared'
+import type {
ArrayPattern,
- Identifier,
+ CallExpression,
+ Declaration,
ExportSpecifier,
+ Identifier,
+ Node,
+ ObjectPattern,
Statement,
- CallExpression
} from '@babel/types'
import { walk } from 'estree-walker'
-import { RawSourceMap } from 'source-map-js'
+import type { RawSourceMap } from 'source-map-js'
import {
+ normalScriptDefaultVar,
processNormalScript,
- normalScriptDefaultVar
} from './script/normalScript'
import { CSS_VARS_HELPER, genCssVarsCode } from './style/cssVars'
-import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
+import {
+ type SFCTemplateCompileOptions,
+ compileTemplate,
+} from './compileTemplate'
import { warnOnce } from './warn'
import { transformDestructuredProps } from './script/definePropsDestructure'
import { ScriptCompileContext } from './script/context'
import {
- processDefineProps,
- genRuntimeProps,
DEFINE_PROPS,
- WITH_DEFAULTS
+ WITH_DEFAULTS,
+ genRuntimeProps,
+ processDefineProps,
} from './script/defineProps'
import {
- processDefineEmits,
+ DEFINE_EMITS,
genRuntimeEmits,
- DEFINE_EMITS
+ processDefineEmits,
} from './script/defineEmits'
import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
import { processDefineSlots } from './script/defineSlots'
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
-import { isLiteralNode, isCallOf, getImportedName } from './script/utils'
+import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
import { isImportUsed } from './script/importUsageCheck'
import { processAwait } from './script/topLevelAwait'
*/
export function compileScript(
sfc: SFCDescriptor,
- options: SFCScriptCompileOptions
+ options: SFCScriptCompileOptions,
): SFCScriptBlock {
if (!options.id) {
warnOnce(
`compileScript now requires passing the \`id\` option.\n` +
`Upgrade your vite or vue-loader version for compatibility with ` +
- `the latest experimental proposals.`
+ `the latest experimental proposals.`,
)
}
if (script && scriptLang !== scriptSetupLang) {
throw new Error(
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
- `language type.`
+ `language type.`,
)
}
imported: string,
isType: boolean,
isFromSetup: boolean,
- needTemplateUsageCheck: boolean
+ needTemplateUsageCheck: boolean,
) {
// template usage check is only needed in non-inline mode, so we can skip
// the work if inlineTemplate is true.
local,
source,
isFromSetup,
- isUsedInTemplate
+ isUsedInTemplate,
}
}
`setup() function. If your component options require initialization ` +
`in the module scope, use a separate normal <script> to export ` +
`the options instead.`,
- id
+ id,
)
}
})
(specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'),
false,
- !options.inlineTemplate
+ !options.inlineTemplate,
)
}
}
: current.start! + startOffset,
next && !removeLeft
? next.start! + startOffset
- : current.end! + startOffset
+ : current.end! + startOffset,
)
}
imported === DEFINE_EXPOSE)
) {
warnOnce(
- `\`${imported}\` is a compiler macro and no longer needs to be imported.`
+ `\`${imported}\` is a compiler macro and no longer needs to be imported.`,
)
removeSpecifier(i)
} else if (existing) {
} else {
ctx.error(
`different imports aliased to same local name.`,
- specifier
+ specifier,
)
}
} else {
(specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'),
true,
- !options.inlineTemplate
+ !options.inlineTemplate,
)
}
}
ctx.s.overwrite(start, end, `const ${normalScriptDefaultVar} = `)
} else if (node.type === 'ExportNamedDeclaration') {
const defaultSpecifier = node.specifiers.find(
- s => s.exported.type === 'Identifier' && s.exported.name === 'default'
+ s =>
+ s.exported.type === 'Identifier' && s.exported.name === 'default',
) as ExportSpecifier
if (defaultSpecifier) {
defaultExport = node
if (node.specifiers.length > 1) {
ctx.s.remove(
defaultSpecifier.start! + scriptStartOffset!,
- defaultSpecifier.end! + scriptStartOffset!
+ defaultSpecifier.end! + scriptStartOffset!,
)
} else {
ctx.s.remove(
node.start! + scriptStartOffset!,
- node.end! + scriptStartOffset!
+ node.end! + scriptStartOffset!,
)
}
if (node.source) {
// rewrite to `import { x as __default__ } from './x'` and
// add to top
ctx.s.prepend(
- `import { ${defaultSpecifier.local.name} as ${normalScriptDefaultVar} } from '${node.source.value}'\n`
+ `import { ${defaultSpecifier.local.name} as ${normalScriptDefaultVar} } from '${node.source.value}'\n`,
)
} else {
// export { x as default }
// rewrite to `const __default__ = x` and move to end
ctx.s.appendLeft(
scriptEndOffset!,
- `\nconst ${normalScriptDefaultVar} = ${defaultSpecifier.local.name}\n`
+ `\nconst ${normalScriptDefaultVar} = ${defaultSpecifier.local.name}\n`,
)
}
}
node.declaration,
scriptBindings,
vueImportAliases,
- hoistStatic
+ hoistStatic,
)
}
} else if (
node,
scriptBindings,
vueImportAliases,
- hoistStatic
+ hoistStatic,
)
}
}
ctx.s.overwrite(
callee.start! + startOffset,
callee.end! + startOffset,
- '__expose'
+ '__expose',
)
} else {
processDefineModel(ctx, expr)
if (processDefineOptions(ctx, init)) {
ctx.error(
`${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
- node
+ node,
)
}
ctx.s.overwrite(
startOffset + init.start!,
startOffset + init.end!,
- '__emit'
+ '__emit',
)
} else {
lastNonRemoved = i
node,
setupBindings,
vueImportAliases,
- hoistStatic
+ hoistStatic,
)
}
ctx,
child,
needsSemi,
- parent!.type === 'ExpressionStatement'
+ parent!.type === 'ExpressionStatement',
)
}
},
exit(node: Node) {
if (node.type === 'BlockStatement') scope.pop()
- }
+ },
})
}
`<script setup> cannot contain ES module exports. ` +
`If you are using a previous version of <script setup>, please ` +
`consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
- node
+ node,
)
}
Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
}
for (const [key, { isType, imported, source }] of Object.entries(
- ctx.userImports
+ ctx.userImports,
)) {
if (isType) continue
ctx.bindingMetadata[key] =
sfc.cssVars,
ctx.bindingMetadata,
scopeId,
- !!options.isProd
- )}\n`
+ !!options.isProd,
+ )}\n`,
)
}
startOffset + ctx.propsCall!.start!,
startOffset + ctx.propsCall!.end!,
`${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify(
- Object.keys(ctx.propsDestructuredBindings)
- )})`
+ Object.keys(ctx.propsDestructuredBindings),
+ )})`,
)
ctx.s.overwrite(
startOffset + ctx.propsDestructureDecl!.start!,
startOffset + ctx.propsDestructureDecl!.end!,
- ctx.propsDestructureRestId
+ ctx.propsDestructureRestId,
)
} else if (!ctx.propsDestructureDecl) {
ctx.s.overwrite(
startOffset + ctx.propsCall!.start!,
startOffset + ctx.propsCall!.end!,
- '__props'
+ '__props',
)
}
}
// return bindings from script and script setup
const allBindings: Record<string, any> = {
...scriptBindings,
- ...setupBindings
+ ...setupBindings,
}
for (const key in ctx.userImports) {
if (
options.templateOptions.compilerOptions),
inline: true,
isTS: ctx.isTS,
- bindingMetadata: ctx.bindingMetadata
- }
+ bindingMetadata: ctx.bindingMetadata,
+ },
})
if (tips.length) {
tips.forEach(warnOnce)
generateCodeFrame(
source,
err.loc.start.offset,
- err.loc.end.offset
+ err.loc.end.offset,
) +
`\n`
}
`\nconst __returned__ = ${returned}\n` +
`Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n` +
`return __returned__` +
- `\n}\n\n`
+ `\n}\n\n`,
)
} else {
ctx.s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
ctx.s.prependLeft(
startOffset,
`\n${genDefaultAs} /*#__PURE__*/${ctx.helper(
- `defineComponent`
+ `defineComponent`,
)}({${def}${runtimeOptions}\n ${
hasAwait ? `async ` : ``
- }setup(${args}) {\n${exposeCall}`
+ }setup(${args}) {\n${exposeCall}`,
)
ctx.s.appendRight(endOffset, `})`)
} else {
`\n${genDefaultAs} /*#__PURE__*/Object.assign(${
defaultExport ? `${normalScriptDefaultVar}, ` : ''
}${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
- `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
+ `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
)
ctx.s.appendRight(endOffset, `})`)
} else {
ctx.s.prependLeft(
startOffset,
`\n${genDefaultAs} {${runtimeOptions}\n ` +
- `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
+ `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
)
ctx.s.appendRight(endOffset, `}`)
}
ctx.s.prepend(
`import { ${[...ctx.helperImports]
.map(h => `${h} as _${h}`)
- .join(', ')} } from 'vue'\n`
+ .join(', ')} } from 'vue'\n`,
)
}
? (ctx.s.generateMap({
source: filename,
hires: true,
- includeContent: true
+ includeContent: true,
}) as unknown as RawSourceMap)
: undefined,
scriptAst: scriptAst?.body,
scriptSetupAst: scriptSetupAst?.body,
- deps: ctx.deps ? [...ctx.deps] : undefined
+ deps: ctx.deps ? [...ctx.deps] : undefined,
}
}
function registerBinding(
bindings: Record<string, BindingTypes>,
node: Identifier,
- type: BindingTypes
+ type: BindingTypes,
) {
bindings[node.name] = type
}
node: Declaration,
bindings: Record<string, BindingTypes>,
userImportAliases: Record<string, string>,
- hoistStatic: boolean
+ hoistStatic: boolean,
): boolean {
let isAllLiteral = false
isAllLiteral =
isConst &&
node.declarations.every(
- decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!)
+ decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!),
)
// export const foo = ...
isConst &&
isCallOf(
init,
- c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS
+ c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS,
)
)
if (id.type === 'Identifier') {
m === userImportAliases['shallowRef'] ||
m === userImportAliases['customRef'] ||
m === userImportAliases['toRef'] ||
- m === DEFINE_MODEL
+ m === DEFINE_MODEL,
)
) {
bindingType = BindingTypes.SETUP_REF
}
} else if (node.type === 'TSEnumDeclaration') {
isAllLiteral = node.members.every(
- member => !member.initializer || isStaticNode(member.initializer)
+ member => !member.initializer || isStaticNode(member.initializer),
)
bindings[node.id!.name] = isAllLiteral
? BindingTypes.LITERAL_CONST
node: ObjectPattern,
bindings: Record<string, BindingTypes>,
isConst: boolean,
- isDefineCall = false
+ isDefineCall = false,
) {
for (const p of node.properties) {
if (p.type === 'ObjectProperty') {
node: ArrayPattern,
bindings: Record<string, BindingTypes>,
isConst: boolean,
- isDefineCall = false
+ isDefineCall = false,
) {
for (const e of node.elements) {
e && walkPattern(e, bindings, isConst, isDefineCall)
node: Node,
bindings: Record<string, BindingTypes>,
isConst: boolean,
- isDefineCall = false
+ isDefineCall = false,
) {
if (node.type === 'Identifier') {
const type = isDefineCall
case 'SequenceExpression':
return canNeverBeRef(
node.expressions[node.expressions.length - 1],
- userReactiveImport
+ userReactiveImport,
)
default:
if (isLiteralNode(node)) {
import postcss, {
- ProcessOptions,
- Result,
- SourceMap,
- Message,
- LazyResult
+ type LazyResult,
+ type Message,
+ type ProcessOptions,
+ type Result,
+ type SourceMap,
} from 'postcss'
import trimPlugin from './style/pluginTrim'
import scopedPlugin from './style/pluginScoped'
import {
+ type PreprocessLang,
+ type StylePreprocessor,
+ type StylePreprocessorResults,
processors,
- StylePreprocessor,
- StylePreprocessorResults,
- PreprocessLang
} from './style/preprocessors'
-import { RawSourceMap } from 'source-map-js'
+import type { RawSourceMap } from 'source-map-js'
import { cssVarsPlugin } from './style/cssVars'
import postcssModules from 'postcss-modules'
}
export function compileStyle(
- options: SFCStyleCompileOptions
+ options: SFCStyleCompileOptions,
): SFCStyleCompileResults {
return doCompileStyle({
...options,
- isAsync: false
+ isAsync: false,
}) as SFCStyleCompileResults
}
export function compileStyleAsync(
- options: SFCAsyncStyleCompileOptions
+ options: SFCAsyncStyleCompileOptions,
): Promise<SFCStyleCompileResults> {
return doCompileStyle({
...options,
- isAsync: true
+ isAsync: true,
}) as Promise<SFCStyleCompileResults>
}
export function doCompileStyle(
- options: SFCAsyncStyleCompileOptions
+ options: SFCAsyncStyleCompileOptions,
): SFCStyleCompileResults | Promise<SFCStyleCompileResults> {
const {
filename,
modulesOptions = {},
preprocessLang,
postcssOptions,
- postcssPlugins
+ postcssPlugins,
} = options
const preprocessor = preprocessLang && processors[preprocessLang]
const preProcessedSource = preprocessor && preprocess(options, preprocessor)
if (modules) {
if (__GLOBAL__ || __ESM_BROWSER__) {
throw new Error(
- '[@vue/compiler-sfc] `modules` option is not supported in the browser build.'
+ '[@vue/compiler-sfc] `modules` option is not supported in the browser build.',
)
}
if (!options.isAsync) {
throw new Error(
- '[@vue/compiler-sfc] `modules` option can only be used with compileStyleAsync().'
+ '[@vue/compiler-sfc] `modules` option can only be used with compileStyleAsync().',
)
}
plugins.push(
...modulesOptions,
getJSON: (_cssFileName: string, json: Record<string, string>) => {
cssModules = json
- }
- })
+ },
+ }),
)
}
const postCSSOptions: ProcessOptions = {
...postcssOptions,
to: filename,
- from: filename
+ from: filename,
}
if (map) {
postCSSOptions.map = {
inline: false,
annotation: false,
- prev: map
+ prev: map,
}
}
let outMap: SourceMap | undefined
// stylus output include plain css. so need remove the repeat item
const dependencies = new Set(
- preProcessedSource ? preProcessedSource.dependencies : []
+ preProcessedSource ? preProcessedSource.dependencies : [],
)
// sass has filename self when provided filename option
dependencies.delete(filename)
errors,
modules: cssModules,
rawResult: result,
- dependencies: recordPlainCssDependencies(result.messages)
+ dependencies: recordPlainCssDependencies(result.messages),
}))
.catch(error => ({
code: '',
map: undefined,
errors: [...errors, error],
rawResult: undefined,
- dependencies
+ dependencies,
}))
}
map: outMap && outMap.toJSON(),
errors,
rawResult: result,
- dependencies
+ dependencies,
}
}
function preprocess(
options: SFCStyleCompileOptions,
- preprocessor: StylePreprocessor
+ preprocessor: StylePreprocessor,
): StylePreprocessorResults {
if ((__ESM_BROWSER__ || __GLOBAL__) && !options.preprocessCustomRequire) {
throw new Error(
`[@vue/compiler-sfc] Style preprocessing in the browser build must ` +
`provide the \`preprocessCustomRequire\` option to return the in-browser ` +
- `version of the preprocessor.`
+ `version of the preprocessor.`,
)
}
options.inMap || options.map,
{
filename: options.filename,
- ...options.preprocessOptions
+ ...options.preprocessOptions,
},
- options.preprocessCustomRequire
+ options.preprocessCustomRequire,
)
}
import {
- CompilerOptions,
- CodegenResult,
- CompilerError,
- NodeTransform,
- ParserOptions,
- RootNode,
+ type CodegenResult,
+ type CompilerError,
+ type CompilerOptions,
+ type ElementNode,
+ type NodeTransform,
NodeTypes,
- ElementNode,
- createRoot
+ type ParserOptions,
+ type RootNode,
+ createRoot,
} from '@vue/compiler-core'
import {
+ type RawSourceMap,
SourceMapConsumer,
SourceMapGenerator,
- RawSourceMap
} from 'source-map-js'
import {
- transformAssetUrl,
- AssetURLOptions,
+ type AssetURLOptions,
+ type AssetURLTagConfig,
createAssetUrlTransformWithOptions,
- AssetURLTagConfig,
- normalizeOptions
+ normalizeOptions,
+ transformAssetUrl,
} from './template/transformAssetUrl'
import {
+ createSrcsetTransformWithOptions,
transformSrcset,
- createSrcsetTransformWithOptions
} from './template/transformSrcset'
import { generateCodeFrame, isObject } from '@vue/shared'
import * as CompilerDOM from '@vue/compiler-dom'
render(
source: string,
options: any,
- cb: (err: Error | null, res: string) => void
+ cb: (err: Error | null, res: string) => void,
): void
}
function preprocess(
{ source, filename, preprocessOptions }: SFCTemplateCompileOptions,
- preprocessor: PreProcessor
+ preprocessor: PreProcessor,
): string {
// Consolidate exposes a callback based API, but the callback is in fact
// called synchronously for most templating engines. In our case, we have to
(_err, _res) => {
if (_err) err = _err
res = _res
- }
+ },
)
if (err) throw err
}
export function compileTemplate(
- options: SFCTemplateCompileOptions
+ options: SFCTemplateCompileOptions,
): SFCTemplateCompileResults {
const { preprocessLang, preprocessCustomRequire } = options
throw new Error(
`[@vue/compiler-sfc] Template preprocessing in the browser build must ` +
`provide the \`preprocessCustomRequire\` option to return the in-browser ` +
- `version of the preprocessor in the shape of { render(): string }.`
+ `version of the preprocessor in the shape of { render(): string }.`,
)
}
return doCompileTemplate({
...options,
source: preprocess(options, preprocessor),
- ast: undefined // invalidate AST if template goes through preprocessor
+ ast: undefined, // invalidate AST if template goes through preprocessor
})
} catch (e: any) {
return {
code: `export default function render() {}`,
source: options.source,
tips: [],
- errors: [e]
+ errors: [e],
}
}
} else if (preprocessLang) {
code: `export default function render() {}`,
source: options.source,
tips: [
- `Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
+ `Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`,
],
errors: [
- `Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
- ]
+ `Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`,
+ ],
}
} else {
return doCompileTemplate(options)
isProd = false,
compiler,
compilerOptions = {},
- transformAssetUrls
+ transformAssetUrls,
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
const errors: CompilerError[] = []
const warnings: CompilerError[] = []
const assetOptions = normalizeOptions(transformAssetUrls)
nodeTransforms = [
createAssetUrlTransformWithOptions(assetOptions),
- createSrcsetTransformWithOptions(assetOptions)
+ createSrcsetTransformWithOptions(assetOptions),
]
} else if (transformAssetUrls !== false) {
nodeTransforms = [transformAssetUrl, transformSrcset]
if (ssr && !ssrCssVars) {
warnOnce(
`compileTemplate is called with \`ssr: true\` but no ` +
- `corresponding \`cssVars\` option.\`.`
+ `corresponding \`cssVars\` option.\`.`,
)
}
if (!id) {
// the AST location info to be relative to the entire SFC.
const newAST = (ssr ? CompilerDOM : compiler).parse(inAST.source, {
parseMode: 'sfc',
- onError: e => errors.push(e)
+ onError: e => errors.push(e),
})
const template = newAST.children.find(
- node => node.type === NodeTypes.ELEMENT && node.tag === 'template'
+ node => node.type === NodeTypes.ELEMENT && node.tag === 'template',
) as ElementNode
inAST = createRoot(template.children, inAST.source)
}
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
onError: e => errors.push(e),
- onWarn: w => warnings.push(w)
+ onWarn: w => warnings.push(w),
})
// inMap should be the map produced by ./parse.ts which is a simple line-only
msg += `\n${generateCodeFrame(
inAST?.source || source,
w.loc.start.offset,
- w.loc.end.offset
+ w.loc.end.offset,
)}`
}
return msg
const origPosInOldMap = oldMapConsumer.originalPositionFor({
line: m.originalLine,
- column: m.originalColumn
+ column: m.originalColumn,
})
if (origPosInOldMap.source == null) {
mergedMapGenerator.addMapping({
generated: {
line: m.generatedLine,
- column: m.generatedColumn
+ column: m.generatedColumn,
},
original: {
line: origPosInOldMap.line, // map line
// use current column, since the oldMap produced by @vue/compiler-sfc
// does not
- column: m.originalColumn
+ column: m.originalColumn,
},
source: origPosInOldMap.source,
- name: origPosInOldMap.name
+ name: origPosInOldMap.name,
})
})
function patchErrors(
errors: CompilerError[],
source: string,
- inMap: RawSourceMap
+ inMap: RawSourceMap,
) {
const originalSource = inMap.sourcesContent![0]
const offset = originalSource.indexOf(source)
export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault'
export { resolveTypeElements, inferRuntimeType } from './script/resolveType'
-import { SFCParseResult, parseCache as _parseCache } from './parse'
+import { type SFCParseResult, parseCache as _parseCache } from './parse'
// #9521 export parseCache as a simple map to avoid exposing LRU types
export const parseCache = _parseCache as Map<string, SFCParseResult>
// error messages
import {
+ DOMErrorMessages,
errorMessages as coreErrorMessages,
- DOMErrorMessages
} from '@vue/compiler-dom'
export const errorMessages = {
...coreErrorMessages,
- ...DOMErrorMessages
+ ...DOMErrorMessages,
}
// Utilities
walkIdentifiers,
extractIdentifiers,
isInDestructureAssignment,
- isStaticProperty
+ isStaticProperty,
} from '@vue/compiler-core'
// Internals for type resolution
SFCBlock,
SFCTemplateBlock,
SFCScriptBlock,
- SFCStyleBlock
+ SFCStyleBlock,
} from './parse'
export type {
TemplateCompiler,
SFCTemplateCompileOptions,
- SFCTemplateCompileResults
+ SFCTemplateCompileResults,
} from './compileTemplate'
export type {
SFCStyleCompileOptions,
SFCAsyncStyleCompileOptions,
- SFCStyleCompileResults
+ SFCStyleCompileResults,
} from './compileStyle'
export type { SFCScriptCompileOptions } from './compileScript'
export type { ScriptCompileContext } from './script/context'
export type {
TypeResolveContext,
SimpleTypeResolveOptions,
- SimpleTypeResolveContext
+ SimpleTypeResolveContext,
} from './script/resolveType'
export type {
AssetURLOptions,
- AssetURLTagConfig
+ AssetURLTagConfig,
} from './template/transformAssetUrl'
export type {
CompilerOptions,
CompilerError,
- BindingMetadata
+ BindingMetadata,
} from '@vue/compiler-core'
/**
import {
+ type BindingMetadata,
+ type CompilerError,
+ type ElementNode,
NodeTypes,
- ElementNode,
- SourceLocation,
- CompilerError,
- BindingMetadata,
- RootNode,
- createRoot
+ type RootNode,
+ type SourceLocation,
+ createRoot,
} from '@vue/compiler-core'
import * as CompilerDOM from '@vue/compiler-dom'
-import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
-import { TemplateCompiler } from './compileTemplate'
+import { type RawSourceMap, SourceMapGenerator } from 'source-map-js'
+import type { TemplateCompiler } from './compileTemplate'
import { parseCssVars } from './style/cssVars'
import { createCache } from './cache'
-import { ImportBinding } from './compileScript'
+import type { ImportBinding } from './compileScript'
import { isImportUsed } from './script/importUsageCheck'
export const DEFAULT_FILENAME = 'anonymous.vue'
pad = false,
ignoreEmpty = true,
compiler = CompilerDOM,
- parseExpressions = true
- }: SFCParseOptions = {}
+ parseExpressions = true,
+ }: SFCParseOptions = {},
): SFCParseResult {
const sourceKey =
source + sourceMap + filename + sourceRoot + pad + compiler.parse
customBlocks: [],
cssVars: [],
slotted: false,
- shouldForceReload: prevImports => hmrShouldReload(prevImports, descriptor)
+ shouldForceReload: prevImports => hmrShouldReload(prevImports, descriptor),
}
const errors: (CompilerError | SyntaxError)[] = []
prefixIdentifiers: parseExpressions,
onError: e => {
errors.push(e)
- }
+ },
})
ast.children.forEach(node => {
if (node.type !== NodeTypes.ELEMENT) {
const templateBlock = (descriptor.template = createBlock(
node,
source,
- false
+ false,
) as SFCTemplateBlock)
if (!templateBlock.attrs.src) {
`<template functional> is no longer supported in Vue 3, since ` +
`functional components no longer have significant performance ` +
`difference from stateful ones. Just use a normal <template> ` +
- `instead.`
+ `instead.`,
) as CompilerError
err.loc = node.props.find(
- p => p.type === NodeTypes.ATTRIBUTE && p.name === 'functional'
+ p => p.type === NodeTypes.ATTRIBUTE && p.name === 'functional',
)!.loc
errors.push(err)
}
errors.push(
new SyntaxError(
`<style vars> has been replaced by a new proposal: ` +
- `https://github.com/vuejs/rfcs/pull/231`
- )
+ `https://github.com/vuejs/rfcs/pull/231`,
+ ),
)
}
descriptor.styles.push(styleBlock)
if (!descriptor.template && !descriptor.script && !descriptor.scriptSetup) {
errors.push(
new SyntaxError(
- `At least one <template> or <script> is required in a single file component.`
- )
+ `At least one <template> or <script> is required in a single file component.`,
+ ),
)
}
if (descriptor.scriptSetup) {
errors.push(
new SyntaxError(
`<script setup> cannot use the "src" attribute because ` +
- `its syntax will be ambiguous outside of the component.`
- )
+ `its syntax will be ambiguous outside of the component.`,
+ ),
)
descriptor.scriptSetup = null
}
errors.push(
new SyntaxError(
`<script> cannot use the "src" attribute when <script setup> is ` +
- `also present because they must be processed together.`
- )
+ `also present because they must be processed together.`,
+ ),
)
descriptor.script = null
}
(descriptor.template.lang === 'pug' || descriptor.template.lang === 'jade')
) {
;[descriptor.template.content, templateColumnOffset] = dedent(
- descriptor.template.content
+ descriptor.template.content,
)
}
block.content,
sourceRoot,
!pad || block.type === 'template' ? block.loc.start.line - 1 : 0,
- columnOffset
+ columnOffset,
)
}
}
// check if the SFC uses :slotted
const slottedRE = /(?:::v-|:)slotted\(/
descriptor.slotted = descriptor.styles.some(
- s => s.scoped && slottedRE.test(s.content)
+ s => s.scoped && slottedRE.test(s.content),
)
const result = {
descriptor,
- errors
+ errors,
}
parseCache.set(sourceKey, result)
return result
function createDuplicateBlockError(
node: ElementNode,
- isScriptSetup = false
+ isScriptSetup = false,
): CompilerError {
const err = new SyntaxError(
`Single file component can contain only one <${node.tag}${
isScriptSetup ? ` setup` : ``
- }> element`
+ }> element`,
) as CompilerError
err.loc = node.loc
return err
function createBlock(
node: ElementNode,
source: string,
- pad: SFCParseOptions['pad']
+ pad: SFCParseOptions['pad'],
): SFCBlock {
const type = node.tag
const loc = node.innerLoc!
type,
content: source.slice(loc.start.offset, loc.end.offset),
loc,
- attrs
+ attrs,
}
if (pad) {
block.content = padContent(source, block, pad) + block.content
generated: string,
sourceRoot: string,
lineOffset: number,
- columnOffset: number
+ columnOffset: number,
): RawSourceMap {
const map = new SourceMapGenerator({
file: filename.replace(/\\/g, '/'),
- sourceRoot: sourceRoot.replace(/\\/g, '/')
+ sourceRoot: sourceRoot.replace(/\\/g, '/'),
})
map.setSourceContent(filename, source)
map._sources.add(filename)
generatedLine,
generatedColumn: i,
source: filename,
- // @ts-ignore
- name: null
+ // @ts-expect-error
+ name: null,
})
}
}
function padContent(
content: string,
block: SFCBlock,
- pad: SFCParseOptions['pad']
+ pad: SFCParseOptions['pad'],
): string {
content = content.slice(0, block.loc.start.offset)
if (pad === 'space') {
*/
export function hmrShouldReload(
prevImports: Record<string, ImportBinding>,
- next: SFCDescriptor
+ next: SFCDescriptor,
): boolean {
if (
!next.scriptSetup ||
return line.slice(minIndent)
})
.join('\n'),
- minIndent
+ minIndent,
]
}
export function rewriteDefault(
input: string,
as: string,
- parserPlugins?: ParserPlugin[]
+ parserPlugins?: ParserPlugin[],
): string {
const ast = parse(input, {
sourceType: 'module',
- plugins: resolveParserPlugins('js', parserPlugins)
+ plugins: resolveParserPlugins('js', parserPlugins),
}).program.body
const s = new MagicString(input)
export function rewriteDefaultAST(
ast: Statement[],
s: MagicString,
- as: string
+ as: string,
): void {
if (!hasDefaultExport(ast)) {
s.append(`\nconst ${as} = {}`)
if (node.source) {
if (specifier.local.name === 'default') {
s.prepend(
- `import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`
+ `import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`,
)
const end = specifierEnd(s, specifier.local.end!, node.end!)
s.remove(specifier.start!, end)
s.prepend(
`import { ${s.slice(
specifier.local.start!,
- specifier.local.end!
- )} as __VUE_DEFAULT__ } from '${node.source.value}'\n`
+ specifier.local.end!,
+ )} as __VUE_DEFAULT__ } from '${node.source.value}'\n`,
)
const end = specifierEnd(s, specifier.exported.end!, node.end!)
s.remove(specifier.start!, end)
} else if (
stmt.type === 'ExportNamedDeclaration' &&
stmt.specifiers.some(
- spec => (spec.exported as Identifier).name === 'default'
+ spec => (spec.exported as Identifier).name === 'default',
)
) {
return true
-import {
+import type {
ArrayExpression,
Node,
ObjectExpression,
- Statement
+ Statement,
} from '@babel/types'
-import { BindingMetadata, BindingTypes } from '@vue/compiler-dom'
+import { type BindingMetadata, BindingTypes } from '@vue/compiler-dom'
import { resolveObjectKey } from './utils'
/**
// mark non-script-setup so we don't resolve components/directives from these
Object.defineProperty(bindings, '__isScriptSetup', {
enumerable: false,
- value: false
+ value: false,
})
for (const property of node.properties) {
if (
-import { CallExpression, Node, ObjectPattern, Program } from '@babel/types'
-import { SFCDescriptor } from '../parse'
+import type { CallExpression, Node, ObjectPattern, Program } from '@babel/types'
+import type { SFCDescriptor } from '../parse'
import { generateCodeFrame, isArray } from '@vue/shared'
-import { parse as babelParse, ParserPlugin } from '@babel/parser'
-import { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
-import { PropsDestructureBindings } from './defineProps'
-import { ModelDecl } from './defineModel'
-import { BindingMetadata } from '../../../compiler-core/src'
+import { type ParserPlugin, parse as babelParse } from '@babel/parser'
+import type { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
+import type { PropsDestructureBindings } from './defineProps'
+import type { ModelDecl } from './defineModel'
+import type { BindingMetadata } from '../../../compiler-core/src'
import MagicString from 'magic-string'
-import { TypeScope } from './resolveType'
+import type { TypeScope } from './resolveType'
export class ScriptCompileContext {
isJS: boolean
constructor(
public descriptor: SFCDescriptor,
- public options: Partial<SFCScriptCompileOptions>
+ public options: Partial<SFCScriptCompileOptions>,
) {
const { script, scriptSetup } = descriptor
const scriptLang = script && script.lang
// resolve parser plugins
const plugins: ParserPlugin[] = resolveParserPlugins(
(scriptLang || scriptSetupLang)!,
- options.babelParserPlugins
+ options.babelParserPlugins,
)
function parse(input: string, offset: number): Program {
try {
return babelParse(input, {
plugins,
- sourceType: 'module'
+ sourceType: 'module',
}).program
} catch (e: any) {
e.message = `[vue/compiler-sfc] ${e.message}\n\n${
}\n${generateCodeFrame(
descriptor.source,
e.pos + offset,
- e.pos + offset + 1
+ e.pos + offset + 1,
)}`
throw e
}
}\n${generateCodeFrame(
(scope || this.descriptor).source,
node.start! + offset,
- node.end! + offset
- )}`
+ node.end! + offset,
+ )}`,
)
}
}
export function resolveParserPlugins(
lang: string,
userPlugins?: ParserPlugin[],
- dts = false
+ dts = false,
) {
const plugins: ParserPlugin[] = []
if (
p =>
p === 'importAssertions' ||
p === 'importAttributes' ||
- (isArray(p) && p[0] === 'importAttributes')
+ (isArray(p) && p[0] === 'importAttributes'),
)
) {
plugins.push('importAttributes')
-import {
+import type {
ArrayPattern,
Identifier,
LVal,
Node,
ObjectPattern,
- RestElement
+ RestElement,
} from '@babel/types'
import { isCallOf } from './utils'
-import { ScriptCompileContext } from './context'
+import type { ScriptCompileContext } from './context'
import {
- TypeResolveContext,
+ type TypeResolveContext,
resolveTypeElements,
- resolveUnionType
+ resolveUnionType,
} from './resolveType'
export const DEFINE_EMITS = 'defineEmits'
export function processDefineEmits(
ctx: ScriptCompileContext,
node: Node,
- declId?: LVal
+ declId?: LVal,
): boolean {
if (!isCallOf(node, DEFINE_EMITS)) {
return false
ctx.error(
`${DEFINE_EMITS}() cannot accept both type and non-type arguments ` +
`at the same time. Use one or the other.`,
- node
+ node,
)
}
ctx.emitsTypeDecl = node.typeParameters.params[0]
.join(', ')}]`
emitsDecl = emitsDecl
? `/*#__PURE__*/${ctx.helper(
- 'mergeModels'
+ 'mergeModels',
)}(${emitsDecl}, ${modelEmitsDecl})`
: modelEmitsDecl
}
if (hasProperty) {
ctx.error(
`defineEmits() type cannot mixed call signature and property syntax.`,
- node
+ node,
)
}
for (const call of calls) {
function extractEventNames(
ctx: TypeResolveContext,
eventName: ArrayPattern | Identifier | ObjectPattern | RestElement,
- emits: Set<string>
+ emits: Set<string>,
) {
if (
eventName.type === 'Identifier' &&
-import { Node } from '@babel/types'
+import type { Node } from '@babel/types'
import { isCallOf } from './utils'
-import { ScriptCompileContext } from './context'
+import type { ScriptCompileContext } from './context'
export const DEFINE_EXPOSE = 'defineExpose'
export function processDefineExpose(
ctx: ScriptCompileContext,
- node: Node
+ node: Node,
): boolean {
if (isCallOf(node, DEFINE_EXPOSE)) {
if (ctx.hasDefineExposeCall) {
-import { LVal, Node, ObjectProperty, TSType } from '@babel/types'
-import { ScriptCompileContext } from './context'
+import type { LVal, Node, ObjectProperty, TSType } from '@babel/types'
+import type { ScriptCompileContext } from './context'
import { inferRuntimeType } from './resolveType'
import {
UNKNOWN_TYPE,
concatStrings,
isCallOf,
- toRuntimeTypeString
+ toRuntimeTypeString,
} from './utils'
import { BindingTypes, unwrapTSNode } from '@vue/compiler-dom'
export function processDefineModel(
ctx: ScriptCompileContext,
node: Node,
- declId?: LVal
+ declId?: LVal,
): boolean {
if (!isCallOf(node, DEFINE_MODEL)) {
return false
ctx.modelDecls[modelName] = {
type,
options: optionsString,
- identifier: declId && declId.type === 'Identifier' ? declId.name : undefined
+ identifier:
+ declId && declId.type === 'Identifier' ? declId.name : undefined,
}
// register binding type
ctx.bindingMetadata[modelName] = BindingTypes.PROPS
p =>
p.type === 'ObjectProperty' &&
((p.key.type === 'Identifier' && p.key.name === 'local') ||
- (p.key.type === 'StringLiteral' && p.key.value === 'local'))
+ (p.key.type === 'StringLiteral' && p.key.value === 'local')),
) as ObjectProperty
if (local) {
ctx.startOffset! + node.end!,
`${ctx.helper('useModel')}(__props, ${JSON.stringify(modelName)}${
runtimeOptions ? `, ${runtimeOptions}` : ``
- })`
+ })`,
)
return true
const codegenOptions = concatStrings([
runtimeType && `type: ${runtimeType}`,
- skipCheck && 'skipCheck: true'
+ skipCheck && 'skipCheck: true',
])
let decl: string
-import { Node } from '@babel/types'
+import type { Node } from '@babel/types'
import { unwrapTSNode } from '@vue/compiler-dom'
-import { ScriptCompileContext } from './context'
+import type { ScriptCompileContext } from './context'
import { isCallOf } from './utils'
import { DEFINE_PROPS } from './defineProps'
import { DEFINE_EMITS } from './defineEmits'
export function processDefineOptions(
ctx: ScriptCompileContext,
- node: Node
+ node: Node,
): boolean {
if (!isCallOf(node, DEFINE_OPTIONS)) {
return false
if (propsOption) {
ctx.error(
`${DEFINE_OPTIONS}() cannot be used to declare props. Use ${DEFINE_PROPS}() instead.`,
- propsOption
+ propsOption,
)
}
if (emitsOption) {
ctx.error(
`${DEFINE_OPTIONS}() cannot be used to declare emits. Use ${DEFINE_EMITS}() instead.`,
- emitsOption
+ emitsOption,
)
}
if (exposeOption) {
ctx.error(
`${DEFINE_OPTIONS}() cannot be used to declare expose. Use ${DEFINE_EXPOSE}() instead.`,
- exposeOption
+ exposeOption,
)
}
if (slotsOption) {
ctx.error(
`${DEFINE_OPTIONS}() cannot be used to declare slots. Use ${DEFINE_SLOTS}() instead.`,
- slotsOption
+ slotsOption,
)
}
-import {
- Node,
+import type {
+ Expression,
LVal,
- ObjectProperty,
- ObjectMethod,
+ Node,
ObjectExpression,
- Expression
+ ObjectMethod,
+ ObjectProperty,
} from '@babel/types'
import { BindingTypes, isFunctionType, unwrapTSNode } from '@vue/compiler-dom'
-import { ScriptCompileContext } from './context'
+import type { ScriptCompileContext } from './context'
import {
- TypeResolveContext,
+ type TypeResolveContext,
inferRuntimeType,
- resolveTypeElements
+ resolveTypeElements,
} from './resolveType'
import {
- resolveObjectKey,
UNKNOWN_TYPE,
concatStrings,
- isLiteralNode,
+ getEscapedPropName,
isCallOf,
+ isLiteralNode,
+ resolveObjectKey,
toRuntimeTypeString,
- getEscapedPropName
} from './utils'
import { genModelProps } from './defineModel'
import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings'
export function processDefineProps(
ctx: ScriptCompileContext,
node: Node,
- declId?: LVal
+ declId?: LVal,
) {
if (!isCallOf(node, DEFINE_PROPS)) {
return processWithDefaults(ctx, node, declId)
ctx.error(
`${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +
`at the same time. Use one or the other.`,
- node
+ node,
)
}
ctx.propsTypeDecl = node.typeParameters.params[0]
function processWithDefaults(
ctx: ScriptCompileContext,
node: Node,
- declId?: LVal
+ declId?: LVal,
): boolean {
if (!isCallOf(node, WITH_DEFAULTS)) {
return false
if (!processDefineProps(ctx, node.arguments[0], declId)) {
ctx.error(
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
- node.arguments[0] || node
+ node.arguments[0] || node,
)
}
ctx.error(
`${WITH_DEFAULTS} can only be used with type-based ` +
`${DEFINE_PROPS} declaration.`,
- node
+ node,
)
}
if (ctx.propsDestructureDecl) {
ctx.error(
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
- node.callee
+ node.callee,
)
}
ctx.propsRuntimeDefaults = node.arguments[1]
defaults.push(
`${finalKey}: ${d.valueString}${
d.needSkipFactory ? `, __skip_${finalKey}: true` : ``
- }`
+ }`,
)
}
if (defaults.length) {
propsDecls = `/*#__PURE__*/${ctx.helper(
- `mergeDefaults`
+ `mergeDefaults`,
)}(${propsDecls}, {\n ${defaults.join(',\n ')}\n})`
}
}
if (propsDecls && modelsDecls) {
return `/*#__PURE__*/${ctx.helper(
- 'mergeModels'
+ 'mergeModels',
)}(${propsDecls}, ${modelsDecls})`
} else {
return modelsDecls || propsDecls
}
export function extractRuntimeProps(
- ctx: TypeResolveContext
+ ctx: TypeResolveContext,
): string | undefined {
// this is only called if propsTypeDecl exists
const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!)
if (ctx.propsRuntimeDefaults && !hasStaticDefaults) {
propsDecls = `/*#__PURE__*/${ctx.helper(
- 'mergeDefaults'
+ 'mergeDefaults',
)}(${propsDecls}, ${ctx.getString(ctx.propsRuntimeDefaults)})`
}
function resolveRuntimePropsFromType(
ctx: TypeResolveContext,
- node: Node
+ node: Node,
): PropTypeData[] {
const props: PropTypeData[] = []
const elements = resolveTypeElements(ctx, node)
key,
required: !e.optional,
type: type || [`null`],
- skipCheck
+ skipCheck,
})
}
return props
function genRuntimePropFromType(
ctx: TypeResolveContext,
{ key, required, type, skipCheck }: PropTypeData,
- hasStaticDefaults: boolean
+ hasStaticDefaults: boolean,
): string {
let defaultString: string | undefined
const destructured = genDestructuredDefaultValue(ctx, key, type)
node => {
if (node.type === 'SpreadElement') return false
return resolveObjectKey(node.key, node.computed) === key
- }
+ },
) as ObjectProperty | ObjectMethod
if (prop) {
if (prop.type === 'ObjectProperty') {
`type: ${toRuntimeTypeString(type)}`,
`required: ${required}`,
skipCheck && 'skipCheck: true',
- defaultString
+ defaultString,
])} }`
} else if (
type.some(
el =>
el === 'Boolean' ||
- ((!hasStaticDefaults || defaultString) && el === 'Function')
+ ((!hasStaticDefaults || defaultString) && el === 'Function'),
)
) {
// #4783 for boolean, should keep the type
// in production
return `${finalKey}: { ${concatStrings([
`type: ${toRuntimeTypeString(type)}`,
- defaultString
+ defaultString,
])} }`
} else {
// #8989 for custom element, should keep the type
if (ctx.isCE) {
if (defaultString) {
return `${finalKey}: ${`{ ${defaultString}, type: ${toRuntimeTypeString(
- type
+ type,
)} }`}`
} else {
return `${finalKey}: {type: ${toRuntimeTypeString(type)}}`
ctx.propsRuntimeDefaults.properties.every(
node =>
node.type !== 'SpreadElement' &&
- (!node.computed || node.key.type.endsWith('Literal'))
+ (!node.computed || node.key.type.endsWith('Literal')),
)
)
}
function genDestructuredDefaultValue(
ctx: TypeResolveContext,
key: string,
- inferredType?: string[]
+ inferredType?: string[],
):
| {
valueString: string
if (valueType && !inferredType.includes(valueType)) {
ctx.error(
`Default value of prop "${key}" does not match declared type.`,
- unwrapped
+ unwrapped,
)
}
}
return {
valueString: needFactoryWrap ? `() => (${value})` : value,
- needSkipFactory
+ needSkipFactory,
}
}
}
-import {
- Node,
- Identifier,
+import type {
BlockStatement,
+ Expression,
+ Identifier,
+ Node,
+ ObjectPattern,
Program,
VariableDeclaration,
- ObjectPattern,
- Expression
} from '@babel/types'
import { walk } from 'estree-walker'
import {
isInDestructureAssignment,
isReferencedIdentifier,
isStaticProperty,
+ unwrapTSNode,
walkFunctionParams,
- unwrapTSNode
} from '@vue/compiler-dom'
import { genPropsAccessExp } from '@vue/shared'
import { isCallOf, resolveObjectKey } from './utils'
-import { ScriptCompileContext } from './context'
+import type { ScriptCompileContext } from './context'
import { DEFINE_PROPS } from './defineProps'
import { warnOnce } from '../warn'
export function processPropsDestructure(
ctx: ScriptCompileContext,
- declId: ObjectPattern
+ declId: ObjectPattern,
) {
if (!ctx.options.propsDestructure) {
return
`This project is using reactive props destructure, which is an experimental ` +
`feature. It may receive breaking changes or be removed in the future, so ` +
`use at your own risk.\n` +
- `To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/502.`
+ `To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/502.`,
)
ctx.propsDestructureDecl = declId
const registerBinding = (
key: string,
local: string,
- defaultValue?: Expression
+ defaultValue?: Expression,
) => {
ctx.propsDestructuredBindings[key] = { local, default: defaultValue }
if (local !== key) {
if (!propKey) {
ctx.error(
`${DEFINE_PROPS}() destructure cannot use computed key.`,
- prop.key
+ prop.key,
)
}
if (left.type !== 'Identifier') {
ctx.error(
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
- left
+ left,
)
}
registerBinding(propKey, left.name, right)
} else {
ctx.error(
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
- prop.value
+ prop.value,
)
}
} else {
export function transformDestructuredProps(
ctx: ScriptCompileContext,
- vueImportAliases: Record<string, string>
+ vueImportAliases: Record<string, string>,
) {
if (!ctx.options.propsDestructure) {
return
} else {
ctx.error(
'registerBinding called without active scope, something is wrong.',
- id
+ id,
)
}
}
// { prop } -> { prop: __props.prop }
ctx.s.appendLeft(
id.end! + ctx.startOffset!,
- `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`
+ `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`,
)
}
} else {
ctx.s.overwrite(
id.start! + ctx.startOffset!,
id.end! + ctx.startOffset!,
- genPropsAccessExp(propsLocalToPublicMap[id.name])
+ genPropsAccessExp(propsLocalToPublicMap[id.name]),
)
}
}
ctx.error(
`"${arg.name}" is a destructured prop and should not be passed directly to ${method}(). ` +
`Pass a getter () => ${arg.name} instead.`,
- arg
+ arg,
)
}
}
) {
popScope()
}
- }
+ },
})
}
-import { LVal, Node } from '@babel/types'
+import type { LVal, Node } from '@babel/types'
import { isCallOf } from './utils'
-import { ScriptCompileContext } from './context'
+import type { ScriptCompileContext } from './context'
export const DEFINE_SLOTS = 'defineSlots'
export function processDefineSlots(
ctx: ScriptCompileContext,
node: Node,
- declId?: LVal
+ declId?: LVal,
): boolean {
if (!isCallOf(node, DEFINE_SLOTS)) {
return false
ctx.s.overwrite(
ctx.startOffset! + node.start!,
ctx.startOffset! + node.end!,
- `${ctx.helper('useSlots')}()`
+ `${ctx.helper('useSlots')}()`,
)
}
-import { SFCDescriptor } from '../parse'
+import type { SFCDescriptor } from '../parse'
import {
+ type ExpressionNode,
NodeTypes,
- SimpleExpressionNode,
+ type SimpleExpressionNode,
+ type TemplateChildNode,
parserOptions,
walkIdentifiers,
- TemplateChildNode,
- ExpressionNode
} from '@vue/compiler-dom'
import { createCache } from '../cache'
import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
import { analyzeScriptBindings } from './analyzeScriptBindings'
-import { ScriptCompileContext } from './context'
+import type { ScriptCompileContext } from './context'
import MagicString from 'magic-string'
import { rewriteDefaultAST } from '../rewriteDefault'
import { genNormalScriptCssVarsCode } from '../style/cssVars'
export function processNormalScript(
ctx: ScriptCompileContext,
- scopeId: string
+ scopeId: string,
) {
const script = ctx.descriptor.script!
if (script.lang && !ctx.isJS && !ctx.isTS) {
bindings,
scopeId,
!!isProd,
- defaultVar
+ defaultVar,
)
}
if (!genDefaultAs) {
content,
map,
bindings,
- scriptAst: scriptAst.body
+ scriptAst: scriptAst.body,
}
} catch (e: any) {
// silently fallback if parse fails since user may be using custom
-import {
+import type {
Expression,
Identifier,
Node,
TSTypeLiteral,
TSTypeQuery,
TSTypeReference,
- TemplateLiteral
+ TemplateLiteral,
} from '@babel/types'
import {
UNKNOWN_TYPE,
createGetCanonicalFileName,
getId,
getImportedName,
+ joinPaths,
normalizePath,
- joinPaths
} from './utils'
-import { ScriptCompileContext, resolveParserPlugins } from './context'
-import { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
+import { type ScriptCompileContext, resolveParserPlugins } from './context'
+import type { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
import { capitalize, hasOwn } from '@vue/shared'
import { parse as babelParse } from '@babel/parser'
import { parse } from '../parse'
import { createCache } from '../cache'
import type TS from 'typescript'
-import { extname, dirname, join } from 'path'
+import { dirname, extname, join } from 'path'
import { minimatch as isMatch } from 'minimatch'
import * as process from 'process'
public offset: number = 0,
public imports: Record<string, Import> = Object.create(null),
public types: Record<string, ScopeTypeNode> = Object.create(null),
- public declares: Record<string, ScopeTypeNode> = Object.create(null)
+ public declares: Record<string, ScopeTypeNode> = Object.create(null),
) {}
isGenericScope = false
resolvedImportSources: Record<string, string> = Object.create(null)
ctx: TypeResolveContext,
node: Node & MaybeWithScope & { _resolvedElements?: ResolvedElements },
scope?: TypeScope,
- typeParameters?: Record<string, Node>
+ typeParameters?: Record<string, Node>,
): ResolvedElements {
const canCache = !typeParameters
if (canCache && node._resolvedElements) {
ctx,
node,
node._ownerScope || scope || ctxToScope(ctx),
- typeParameters
+ typeParameters,
)
return canCache ? (node._resolvedElements = resolved) : resolved
}
ctx: TypeResolveContext,
node: Node,
scope: TypeScope,
- typeParameters?: Record<string, Node>
+ typeParameters?: Record<string, Node>,
): ResolvedElements {
switch (node.type) {
case 'TSTypeLiteral':
ctx,
node.typeAnnotation,
scope,
- typeParameters
+ typeParameters,
)
case 'TSFunctionType': {
return { props: {}, calls: [node] }
case 'TSIntersectionType':
return mergeElements(
node.types.map(t => resolveTypeElements(ctx, t, scope, typeParameters)),
- node.type
+ node.type,
)
case 'TSMappedType':
return resolveMappedType(ctx, node, scope)
const types = resolveIndexType(ctx, node, scope)
return mergeElements(
types.map(t => resolveTypeElements(ctx, t, t._ownerScope)),
- 'TSUnionType'
+ 'TSUnionType',
)
}
case 'TSExpressionWithTypeArguments': // referenced by interface extends
ctx,
node.typeParameters.params[0],
scope,
- typeParameters
+ typeParameters,
),
- scope
+ scope,
)
}
const resolved = resolveTypeReference(ctx, node, scope)
ctx,
resolved,
resolved._ownerScope,
- typeParams
+ typeParams,
)
} else {
if (typeof typeName === 'string') {
ctx,
typeParameters[typeName],
scope,
- typeParameters
+ typeParameters,
)
}
if (
- // @ts-ignore
+ // @ts-expect-error
SupportedBuiltinsSet.has(typeName)
) {
return resolveBuiltin(
node,
typeName as any,
scope,
- typeParameters
+ typeParameters,
)
} else if (typeName === 'ReturnType' && node.typeParameters) {
// limited support, only reference types
const ret = resolveReturnType(
ctx,
node.typeParameters.params[0],
- scope
+ scope,
)
if (ret) {
return resolveTypeElements(ctx, ret, scope)
return ctx.error(
`Unresolvable type reference or unsupported built-in utility type`,
node,
- scope
+ scope,
)
}
}
) {
return resolveExtractPropTypes(
resolveTypeElements(ctx, node.typeParameters.params[0], scope),
- scope
+ scope,
)
}
const sourceScope = importSourceToScope(
ctx,
node.argument,
scope,
- node.argument.value
+ node.argument.value,
)
const resolved = resolveTypeReference(ctx, node, sourceScope)
if (resolved) {
ctx: TypeResolveContext,
elements: TSTypeElement[],
scope = ctxToScope(ctx),
- typeParameters?: Record<string, Node>
+ typeParameters?: Record<string, Node>,
): ResolvedElements {
const res: ResolvedElements = { props: {} }
for (const e of elements) {
ctx.error(
`Unsupported computed key in type referenced by a macro`,
e.key,
- scope
+ scope,
)
}
} else if (e.type === 'TSCallSignatureDeclaration') {
function mergeElements(
maps: ResolvedElements[],
- type: 'TSUnionType' | 'TSIntersectionType'
+ type: 'TSUnionType' | 'TSIntersectionType',
): ResolvedElements {
if (maps.length === 1) return maps[0]
const res: ResolvedElements = { props: {} }
baseProps[key].key,
{
type,
- // @ts-ignore
- types: [baseProps[key], props[key]]
+ // @ts-expect-error
+ types: [baseProps[key], props[key]],
},
baseProps[key]._ownerScope,
- baseProps[key].optional || props[key].optional
+ baseProps[key].optional || props[key].optional,
)
}
}
key: Expression,
typeAnnotation: TSType,
scope: TypeScope,
- optional: boolean
+ optional: boolean,
): TSPropertySignature & WithScope {
return {
type: 'TSPropertySignature',
optional,
typeAnnotation: {
type: 'TSTypeAnnotation',
- typeAnnotation
+ typeAnnotation,
},
- _ownerScope: scope
+ _ownerScope: scope,
}
}
ctx: TypeResolveContext,
node: TSInterfaceDeclaration & MaybeWithScope,
scope: TypeScope,
- typeParameters?: Record<string, Node>
+ typeParameters?: Record<string, Node>,
): ResolvedElements {
const base = typeElementsToMap(
ctx,
node.body.body,
node._ownerScope,
- typeParameters
+ typeParameters,
)
if (node.extends) {
for (const ext of node.extends) {
`interface Props extends /* @vue-ignore */ Base {}\n\n` +
`Note: both in 3.2 or with the ignore, the properties in the base ` +
`type are treated as fallthrough attrs at runtime.`,
- ext
+ ext,
)
}
}
function resolveMappedType(
ctx: TypeResolveContext,
node: TSMappedType,
- scope: TypeScope
+ scope: TypeScope,
): ResolvedElements {
const res: ResolvedElements = { props: {} }
const keys = resolveStringType(ctx, node.typeParameter.constraint!, scope)
res.props[key] = createProperty(
{
type: 'Identifier',
- name: key
+ name: key,
},
node.typeAnnotation!,
scope,
- !!node.optional
+ !!node.optional,
)
}
return res
function resolveIndexType(
ctx: TypeResolveContext,
node: TSIndexedAccessType,
- scope: TypeScope
+ scope: TypeScope,
): (TSType & MaybeWithScope)[] {
if (node.indexType.type === 'TSNumberKeyword') {
return resolveArrayElementType(ctx, node.objectType, scope)
function resolveArrayElementType(
ctx: TypeResolveContext,
node: Node,
- scope: TypeScope
+ scope: TypeScope,
): TSType[] {
// type[]
if (node.type === 'TSArrayType') {
// tuple
if (node.type === 'TSTupleType') {
return node.elementTypes.map(t =>
- t.type === 'TSNamedTupleMember' ? t.elementType : t
+ t.type === 'TSNamedTupleMember' ? t.elementType : t,
)
}
if (node.type === 'TSTypeReference') {
return ctx.error(
'Failed to resolve element type from target type',
node,
- scope
+ scope,
)
}
function resolveStringType(
ctx: TypeResolveContext,
node: Node,
- scope: TypeScope
+ scope: TypeScope,
): string[] {
switch (node.type) {
case 'StringLiteral':
ctx.error(
'Unsupported type when resolving index type',
node.typeName,
- scope
+ scope,
)
}
}
function resolveTemplateKeys(
ctx: TypeResolveContext,
node: TemplateLiteral,
- scope: TypeScope
+ scope: TypeScope,
): string[] {
if (!node.expressions.length) {
return [node.quasis[0].value.raw]
{
...node,
expressions: node.expressions.slice(1),
- quasis: q ? node.quasis.slice(1) : node.quasis
+ quasis: q ? node.quasis.slice(1) : node.quasis,
},
- scope
+ scope,
)
for (const r of resolved) {
'Required',
'Readonly',
'Pick',
- 'Omit'
+ 'Omit',
] as const)
type GetSetType<T> = T extends Set<infer V> ? V : never
node: TSTypeReference | TSExpressionWithTypeArguments,
name: GetSetType<typeof SupportedBuiltinsSet>,
scope: TypeScope,
- typeParameters?: Record<string, Node>
+ typeParameters?: Record<string, Node>,
): ResolvedElements {
const t = resolveTypeElements(
ctx,
node.typeParameters!.params[0],
scope,
- typeParameters
+ typeParameters,
)
switch (name) {
case 'Partial': {
const picked = resolveStringType(
ctx,
node.typeParameters!.params[1],
- scope
+ scope,
)
const res: ResolvedElements = { props: {}, calls: t.calls }
for (const key of picked) {
const omitted = resolveStringType(
ctx,
node.typeParameters!.params[1],
- scope
+ scope,
)
const res: ResolvedElements = { props: {}, calls: t.calls }
for (const key in t.props) {
},
scope?: TypeScope,
name?: string,
- onlyExported = false
+ onlyExported = false,
): ScopeTypeNode | undefined {
const canCache = !scope?.isGenericScope
if (canCache && node._resolvedReference) {
scope || ctxToScope(ctx),
name || getReferenceName(node),
node,
- onlyExported
+ onlyExported,
)
return canCache ? (node._resolvedReference = resolved) : resolved
}
scope: TypeScope,
name: string | string[],
node: ReferenceTypes,
- onlyExported: boolean
+ onlyExported: boolean,
): ScopeTypeNode | undefined {
if (typeof name === 'string') {
if (scope.imports[name]) {
childScope,
name.length > 2 ? name.slice(1) : name[name.length - 1],
node,
- !ns.declare
+ !ns.declare,
)
}
}
throw new Error('[vue/compiler-sfc] globalTypeFiles requires fs access.')
}
return ctx.options.globalTypeFiles.map(file =>
- fileToScope(ctx, normalizePath(file), true)
+ fileToScope(ctx, normalizePath(file), true),
)
}
}
) {
throw new Error(
'Failed to load TypeScript, which is required for resolving imported types. ' +
- 'Please make sure "typescript" is installed as a project dependency.'
+ 'Please make sure "typescript" is installed as a project dependency.',
)
} else {
throw new Error(
- 'Failed to load TypeScript for resolving imported types.'
+ 'Failed to load TypeScript for resolving imported types.',
)
}
}
file = file.replace(/\.ts$/, '')
}
return fs.readFile(file)
- }
+ },
})
}
ctx: TypeResolveContext,
node: ReferenceTypes,
name: string,
- scope: TypeScope
+ scope: TypeScope,
): ScopeTypeNode | undefined {
const { source, imported } = scope.imports[name]
const sourceScope = importSourceToScope(ctx, node, scope, source)
ctx: TypeResolveContext,
node: Node,
scope: TypeScope,
- source: string
+ source: string,
): TypeScope {
let fs: FS | undefined
try {
`No fs option provided to \`compileScript\` in non-Node environment. ` +
`File system access is required for resolving imported types.`,
node,
- scope
+ scope,
)
}
return ctx.error(
`Type import from non-relative sources is not supported in the browser build.`,
node,
- scope
+ scope,
)
}
if (!ts) {
`typescript is required as a peer dep for vue in order ` +
`to support resolving types from module imports.`,
node,
- scope
+ scope,
)
}
}
return ctx.error(
`Failed to resolve import source ${JSON.stringify(source)}.`,
node,
- scope
+ scope,
)
}
}
containingFile: string,
source: string,
ts: typeof TS,
- fs: FS
+ fs: FS,
): string | undefined {
if (!__NODE_JS__) return
for (const c of configs) {
const base = normalizePath(
(c.config.options.pathsBasePath as string) ||
- dirname(c.config.options.configFilePath as string)
+ dirname(c.config.options.configFilePath as string),
)
const included: string[] = c.config.raw?.include
const excluded: string[] = c.config.raw?.exclude
(matchedConfig.cache = ts.createModuleResolutionCache(
process.cwd(),
createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames),
- tsCompilerOptions
+ tsCompilerOptions,
))
} else {
tsCompilerOptions = {}
containingFile,
tsCompilerOptions,
fs,
- tsResolveCache
+ tsResolveCache,
)
if (res.resolvedModule) {
function loadTSConfig(
configPath: string,
ts: typeof TS,
- fs: FS
+ fs: FS,
): TS.ParsedCommandLine[] {
// The only case where `fs` is NOT `ts.sys` is during tests.
// parse config host requires an extra `readDirectory` method
? {
...fs,
useCaseSensitiveFileNames: true,
- readDirectory: () => []
+ readDirectory: () => [],
}
: ts.sys
const config = ts.parseJsonConfigFileContent(
parseConfigHost,
dirname(configPath),
undefined,
- configPath
+ configPath,
)
const res = [config]
if (config.projectReferences) {
export function fileToScope(
ctx: TypeResolveContext,
filename: string,
- asGlobal = false
+ asGlobal = false,
): TypeScope {
const cached = fileToScopeCache.get(filename)
if (cached) {
function parseFile(
filename: string,
content: string,
- parserPlugins?: SFCScriptCompileOptions['babelParserPlugins']
+ parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'],
): Statement[] {
const ext = extname(filename)
if (ext === '.ts' || ext === '.tsx') {
plugins: resolveParserPlugins(
ext.slice(1),
parserPlugins,
- filename.endsWith('.d.ts')
+ filename.endsWith('.d.ts'),
),
- sourceType: 'module'
+ sourceType: 'module',
}).program.body
} else if (ext === '.vue') {
const {
- descriptor: { script, scriptSetup }
+ descriptor: { script, scriptSetup },
} = parse(content)
if (!script && !scriptSetup) {
return []
const lang = script?.lang || scriptSetup?.lang
return babelParse(scriptContent, {
plugins: resolveParserPlugins(lang!, parserPlugins),
- sourceType: 'module'
+ sourceType: 'module',
}).program.body
}
return []
ctx.filename,
ctx.source,
'startOffset' in ctx ? ctx.startOffset! : 0,
- 'userImports' in ctx ? Object.create(ctx.userImports) : recordImports(body)
+ 'userImports' in ctx ? Object.create(ctx.userImports) : recordImports(body),
)
recordTypes(ctx, body, scope)
function moduleDeclToScope(
ctx: TypeResolveContext,
node: TSModuleDeclaration & { _resolvedChildScope?: TypeScope },
- parentScope: TypeScope
+ parentScope: TypeScope,
): TypeScope {
if (node._resolvedChildScope) {
return node._resolvedChildScope
parentScope.offset,
Object.create(parentScope.imports),
Object.create(parentScope.types),
- Object.create(parentScope.declares)
+ Object.create(parentScope.declares),
)
}
ctx: TypeResolveContext,
body: Statement[],
scope: TypeScope,
- asGlobal = false
+ asGlobal = false,
) {
const { types, declares, exportedTypes, exportedDeclares, imports } = scope
const isAmbient = asGlobal
// re-export, register an import + export as a type reference
imports[exported] = {
source: stmt.source.value,
- imported: local
+ imported: local,
}
exportedTypes[exported] = {
type: 'TSTypeReference',
typeName: {
type: 'Identifier',
- name: local
+ name: local,
},
- _ownerScope: scope
+ _ownerScope: scope,
}
} else if (types[local]) {
// exporting local defined type
ctx,
stmt.source,
scope,
- stmt.source.value
+ stmt.source.value,
)
Object.assign(scope.exportedTypes, sourceScope.exportedTypes)
} else if (stmt.type === 'ExportDefaultDeclaration' && stmt.declaration) {
stmt.declaration,
exportedTypes,
exportedDeclares,
- 'default'
+ 'default',
)
} else if (types[stmt.declaration.name]) {
exportedTypes['default'] = types[stmt.declaration.name]
node: Node,
types: Record<string, Node>,
declares: Record<string, Node>,
- overwriteId?: string
+ overwriteId?: string,
) {
switch (node.type) {
case 'TSInterfaceDeclaration':
type: 'ExportNamedDeclaration',
declaration: toBody,
exportKind: 'type',
- specifiers: []
+ specifiers: [],
})
}
} else if (fromBody.type === 'TSModuleDeclaration') {
type: 'ExportNamedDeclaration',
declaration: fromBody,
exportKind: 'type',
- specifiers: []
+ specifiers: [],
})
} else {
// both block
function attachNamespace(
to: Node & { _ns?: TSModuleDeclaration },
- ns: TSModuleDeclaration
+ ns: TSModuleDeclaration,
) {
if (!to._ns) {
to._ns = ns
for (const s of node.specifiers) {
imports[s.local.name] = {
imported: getImportedName(s),
- source: node.source.value
+ source: node.source.value,
}
}
}
export function inferRuntimeType(
ctx: TypeResolveContext,
node: Node & MaybeWithScope,
- scope = node._ownerScope || ctxToScope(ctx)
+ scope = node._ownerScope || ctxToScope(ctx),
): string[] {
try {
switch (node.type) {
return inferRuntimeType(
ctx,
node.typeAnnotation.typeAnnotation,
- scope
+ scope,
)
}
break
return inferRuntimeType(
ctx,
node.typeParameters.params[0],
- scope
+ scope,
).filter(t => t !== 'null')
}
break
return inferRuntimeType(
ctx,
node.typeParameters.params[1],
- scope
+ scope,
)
}
break
return inferRuntimeType(
ctx,
node.typeParameters.params[0],
- scope
+ scope,
)
}
break
return flattenTypes(ctx, node.types, scope)
case 'TSIntersectionType': {
return flattenTypes(ctx, node.types, scope).filter(
- t => t !== UNKNOWN_TYPE
+ t => t !== UNKNOWN_TYPE,
)
}
ctx,
node.argument,
scope,
- node.argument.value
+ node.argument.value,
)
const resolved = resolveTypeReference(ctx, node, sourceScope)
if (resolved) {
function flattenTypes(
ctx: TypeResolveContext,
types: TSType[],
- scope: TypeScope
+ scope: TypeScope,
): string[] {
if (types.length === 1) {
return inferRuntimeType(ctx, types[0], scope)
return [
...new Set(
([] as string[]).concat(
- ...types.map(t => inferRuntimeType(ctx, t, scope))
- )
- )
+ ...types.map(t => inferRuntimeType(ctx, t, scope)),
+ ),
+ ),
]
}
*/
function resolveExtractPropTypes(
{ props }: ResolvedElements,
- scope: TypeScope
+ scope: TypeScope,
): ResolvedElements {
const res: ResolvedElements = { props: {} }
for (const key in props) {
res.props[key] = reverseInferType(
raw.key,
raw.typeAnnotation!.typeAnnotation,
- scope
+ scope,
)
}
return res
node: TSType,
scope: TypeScope,
optional = true,
- checkObjectSyntax = true
+ checkObjectSyntax = true,
): TSPropertySignature & WithScope {
if (checkObjectSyntax && node.type === 'TSTypeLiteral') {
// check { type: xxx }
key,
ctorToType(node.typeName.name),
scope,
- optional
+ optional,
)
} else if (node.typeName.name === 'PropType' && node.typeParameters) {
// PropType<{}>
case 'Promise':
return {
type: 'TSTypeReference',
- typeName: { type: 'Identifier', name: ctor }
+ typeName: { type: 'Identifier', name: ctor },
}
}
// fallback to null
m.type === 'TSPropertySignature' &&
!m.computed &&
getId(m.key) === key &&
- m.typeAnnotation
+ m.typeAnnotation,
)
return prop && prop.typeAnnotation!.typeAnnotation
}
function resolveReturnType(
ctx: TypeResolveContext,
arg: Node,
- scope: TypeScope
+ scope: TypeScope,
) {
let resolved: Node | undefined = arg
if (
export function resolveUnionType(
ctx: TypeResolveContext,
node: Node & MaybeWithScope & { _resolvedElements?: ResolvedElements },
- scope?: TypeScope
+ scope?: TypeScope,
): Node[] {
if (node.type === 'TSTypeReference') {
const resolved = resolveTypeReference(ctx, node, scope)
-import { AwaitExpression } from '@babel/types'
-import { ScriptCompileContext } from './context'
+import type { AwaitExpression } from '@babel/types'
+import type { ScriptCompileContext } from './context'
/**
* Support context-persistence between top-level await expressions:
ctx: ScriptCompileContext,
node: AwaitExpression,
needSemi: boolean,
- isStatement: boolean
+ isStatement: boolean,
) {
const argumentStart =
node.argument.extra && node.argument.extra.parenthesized
const startOffset = ctx.startOffset!
const argumentStr = ctx.descriptor.source.slice(
argumentStart + startOffset,
- node.argument.end! + startOffset
+ node.argument.end! + startOffset,
)
const containsNestedAwait = /\bawait\b/.test(argumentStr)
node.start! + startOffset,
argumentStart + startOffset,
`${needSemi ? `;` : ``}(\n ([__temp,__restore] = ${ctx.helper(
- `withAsyncContext`
- )}(${containsNestedAwait ? `async ` : ``}() => `
+ `withAsyncContext`,
+ )}(${containsNestedAwait ? `async ` : ``}() => `,
)
ctx.s.appendLeft(
node.end! + startOffset,
`)),\n ${isStatement ? `` : `__temp = `}await __temp,\n __restore()${
isStatement ? `` : `,\n __temp`
- }\n)`
+ }\n)`,
)
}
-import {
+import type {
CallExpression,
Expression,
Identifier,
ImportNamespaceSpecifier,
ImportSpecifier,
Node,
- StringLiteral
+ StringLiteral,
} from '@babel/types'
import path from 'path'
export function isCallOf(
node: Node | null | undefined,
- test: string | ((id: string) => boolean) | null | undefined
+ test: string | ((id: string) => boolean) | null | undefined,
): node is CallExpression {
return !!(
node &&
}
export function getImportedName(
- specifier: ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier
+ specifier:
+ | ImportSpecifier
+ | ImportDefaultSpecifier
+ | ImportNamespaceSpecifier,
) {
if (specifier.type === 'ImportSpecifier')
return specifier.imported.type === 'Identifier'
export function getEscapedCssVarName(key: string, doubleEscape: boolean) {
return key.replace(cssVarNameEscapeSymbolsRE, s =>
- doubleEscape ? `\\\\${s}` : `\\${s}`
+ doubleEscape ? `\\\\${s}` : `\\${s}`,
)
}
import {
- processExpression,
- createTransformContext,
- createSimpleExpression,
- createRoot,
+ type BindingMetadata,
NodeTypes,
- SimpleExpressionNode,
- BindingMetadata
+ type SimpleExpressionNode,
+ createRoot,
+ createSimpleExpression,
+ createTransformContext,
+ processExpression,
} from '@vue/compiler-dom'
-import { SFCDescriptor } from '../parse'
+import type { SFCDescriptor } from '../parse'
import { getEscapedCssVarName } from '../script/utils'
-import { PluginCreator } from 'postcss'
+import type { PluginCreator } from 'postcss'
import hash from 'hash-sum'
export const CSS_VARS_HELPER = `useCssVars`
vars: string[],
id: string,
isProd: boolean,
- isSSR = false
+ isSSR = false,
): string {
return `{\n ${vars
.map(
key =>
- `"${isSSR ? `--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`
+ `"${isSSR ? `--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
)
.join(',\n ')}\n}`
}
id: string,
raw: string,
isProd: boolean,
- isSSR = false
+ isSSR = false,
): string {
if (isProd) {
return hash(id + raw)
enum LexerState {
inParens,
inSingleQuoteString,
- inDoubleQuoteString
+ inDoubleQuoteString,
}
function lexBinding(content: string, start: number): number | null {
}
decl.value = transformed + value.slice(lastIndex)
}
- }
+ },
}
}
cssVarsPlugin.postcss = true
vars: string[],
bindings: BindingMetadata,
id: string,
- isProd: boolean
+ isProd: boolean,
) {
const varsExp = genCssVarsFromList(vars, id, isProd)
const exp = createSimpleExpression(varsExp, false)
const context = createTransformContext(createRoot([]), {
prefixIdentifiers: true,
inline: true,
- bindingMetadata: bindings.__isScriptSetup === false ? undefined : bindings
+ bindingMetadata: bindings.__isScriptSetup === false ? undefined : bindings,
})
const transformed = processExpression(exp, context)
const transformedString =
bindings: BindingMetadata,
id: string,
isProd: boolean,
- defaultVar: string
+ defaultVar: string,
): string {
return (
`\nimport { ${CSS_VARS_HELPER} as _${CSS_VARS_HELPER} } from 'vue'\n` +
cssVars,
bindings,
id,
- isProd
+ isProd,
)}}\n` +
`const __setup__ = ${defaultVar}.setup\n` +
`${defaultVar}.setup = __setup__\n` +
-import { PluginCreator, Rule, AtRule } from 'postcss'
+import type { AtRule, PluginCreator, Rule } from 'postcss'
import selectorParser from 'postcss-selector-parser'
import { warn } from '../warn'
}
})
}
- }
+ },
}
}
id: string,
selector: selectorParser.Selector,
selectorRoot: selectorParser.Root,
- slotted = false
+ slotted = false,
) {
let node: selectorParser.Node | null = null
let shouldInject = true
n.spaces.before = n.spaces.after = ''
warn(
`the >>> and /deep/ combinators have been deprecated. ` +
- `Use :deep() instead.`
+ `Use :deep() instead.`,
)
return false
}
selector.insertAfter(
n,
selectorParser.combinator({
- value: ' '
- })
+ value: ' ',
+ }),
)
}
selector.removeChild(n)
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
warn(
`${value} usage as a combinator has been deprecated. ` +
- `Use :deep(<inner-selector>) instead of ${value} <inner-selector>.`
+ `Use :deep(<inner-selector>) instead of ${value} <inner-selector>.`,
)
const prev = selector.at(selector.index(n) - 1)
const { type, value } = node as selectorParser.Node
if (type === 'pseudo' && (value === ':is' || value === ':where')) {
;(node as selectorParser.Pseudo).nodes.forEach(value =>
- rewriteSelector(id, value, selectorRoot, slotted)
+ rewriteSelector(id, value, selectorRoot, slotted),
)
shouldInject = false
}
attribute: idToAdd,
value: idToAdd,
raws: {},
- quoteMark: `"`
- })
+ quoteMark: `"`,
+ }),
)
}
}
-import { PluginCreator } from 'postcss'
+import type { PluginCreator } from 'postcss'
const trimPlugin: PluginCreator<{}> = () => {
return {
if ('after' in raws && raws.after) raws.after = '\n'
}
})
- }
+ },
}
}
import merge from 'merge-source-map'
-import { RawSourceMap } from 'source-map-js'
-import { SFCStyleCompileOptions } from '../compileStyle'
+import type { RawSourceMap } from 'source-map-js'
+import type { SFCStyleCompileOptions } from '../compileStyle'
import { isFunction } from '@vue/shared'
export type StylePreprocessor = (
additionalData?: string | ((source: string, filename: string) => string)
filename: string
},
- customRequire: SFCStyleCompileOptions['preprocessCustomRequire']
+ customRequire: SFCStyleCompileOptions['preprocessCustomRequire'],
) => StylePreprocessorResults
export interface StylePreprocessorResults {
data: getSource(source, options.filename, options.additionalData),
file: options.filename,
outFile: options.filename,
- sourceMap: !!map
+ sourceMap: !!map,
}
try {
map,
result.map.toJSON
? result.map.toJSON()
- : JSON.parse(result.map.toString())
+ : JSON.parse(result.map.toString()),
),
errors: [],
- dependencies
+ dependencies,
}
}
map,
{
...options,
- indentedSyntax: true
+ indentedSyntax: true,
},
- load
+ load,
)
// .less
(err: Error | null, output: any) => {
error = err
result = output
- }
+ },
)
if (error) return { code: '', errors: [error], dependencies: [] }
code: result.css.toString(),
map: merge(map, result.map),
errors: [],
- dependencies: dependencies
+ dependencies: dependencies,
}
}
return {
code: result.css.toString(),
errors: [],
- dependencies: dependencies
+ dependencies: dependencies,
}
}
code: result,
map: merge(map, ref.sourcemap),
errors: [],
- dependencies
+ dependencies,
}
}
function getSource(
source: string,
filename: string,
- additionalData?: string | ((source: string, filename: string) => string)
+ additionalData?: string | ((source: string, filename: string) => string),
) {
if (!additionalData) return source
if (isFunction(additionalData)) {
sass,
scss,
styl,
- stylus: styl
+ stylus: styl,
}
-import { UrlWithStringQuery, parse as uriParse } from 'url'
+import { type UrlWithStringQuery, parse as uriParse } from 'url'
import { isString } from '@vue/shared'
export function isRelativeUrl(url: string): boolean {
import path from 'path'
import {
ConstantTypes,
- createSimpleExpression,
- ExpressionNode,
- NodeTransform,
+ type ExpressionNode,
+ type NodeTransform,
NodeTypes,
- SimpleExpressionNode,
- SourceLocation,
- TransformContext
+ type SimpleExpressionNode,
+ type SourceLocation,
+ type TransformContext,
+ createSimpleExpression,
} from '@vue/compiler-core'
import {
+ isDataUrl,
+ isExternalUrl,
isRelativeUrl,
parseUrl,
- isExternalUrl,
- isDataUrl
} from './templateUtils'
import { isArray } from '@vue/shared'
source: ['src'],
img: ['src'],
image: ['xlink:href', 'href'],
- use: ['xlink:href', 'href']
- }
+ use: ['xlink:href', 'href'],
+ },
}
export const normalizeOptions = (
- options: AssetURLOptions | AssetURLTagConfig
+ options: AssetURLOptions | AssetURLTagConfig,
): Required<AssetURLOptions> => {
if (Object.keys(options).some(key => isArray((options as any)[key]))) {
// legacy option format which directly passes in tags config
return {
...defaultAssetUrlOptions,
- tags: options as any
+ tags: options as any,
}
}
return {
...defaultAssetUrlOptions,
- ...options
+ ...options,
}
}
export const createAssetUrlTransformWithOptions = (
- options: Required<AssetURLOptions>
+ options: Required<AssetURLOptions>,
): NodeTransform => {
return (node, context) =>
(transformAssetUrl as Function)(node, context, options)
export const transformAssetUrl: NodeTransform = (
node,
context,
- options: AssetURLOptions = defaultAssetUrlOptions
+ options: AssetURLOptions = defaultAssetUrlOptions,
) => {
if (node.type === NodeTypes.ELEMENT) {
if (!node.props.length) {
arg: createSimpleExpression(attr.name, true, attr.loc),
exp,
modifiers: [],
- loc: attr.loc
+ loc: attr.loc,
}
})
}
path: string | null,
hash: string | null,
loc: SourceLocation,
- context: TransformContext
+ context: TransformContext,
): ExpressionNode {
if (path) {
let name: string
name,
false,
loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
// We need to ensure the path is not encoded (to %2F),
// so we decode it back in case it is encoded
context.imports.push({
exp,
- path: decodeURIComponent(path)
+ path: decodeURIComponent(path),
})
}
hashExp,
false,
loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
if (!context.hoistStatic) {
`_hoisted_${existingHoistIndex + 1}`,
false,
loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
}
return context.hoist(finalExp)
import path from 'path'
import {
ConstantTypes,
+ type ExpressionNode,
+ type NodeTransform,
+ NodeTypes,
+ type SimpleExpressionNode,
createCompoundExpression,
createSimpleExpression,
- ExpressionNode,
- NodeTransform,
- NodeTypes,
- SimpleExpressionNode
} from '@vue/compiler-core'
import {
+ isDataUrl,
+ isExternalUrl,
isRelativeUrl,
parseUrl,
- isExternalUrl,
- isDataUrl
} from './templateUtils'
-import { AssetURLOptions, defaultAssetUrlOptions } from './transformAssetUrl'
+import {
+ type AssetURLOptions,
+ defaultAssetUrlOptions,
+} from './transformAssetUrl'
const srcsetTags = ['img', 'source']
const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g
export const createSrcsetTransformWithOptions = (
- options: Required<AssetURLOptions>
+ options: Required<AssetURLOptions>,
): NodeTransform => {
return (node, context) =>
(transformSrcset as Function)(node, context, options)
export const transformSrcset: NodeTransform = (
node,
context,
- options: Required<AssetURLOptions> = defaultAssetUrlOptions
+ options: Required<AssetURLOptions> = defaultAssetUrlOptions,
) => {
if (node.type === NodeTypes.ELEMENT) {
if (srcsetTags.includes(node.tag) && node.props.length) {
let exp: SimpleExpressionNode
if (path) {
const existingImportsIndex = context.imports.findIndex(
- i => i.path === path
+ i => i.path === path,
)
if (existingImportsIndex > -1) {
exp = createSimpleExpression(
`_imports_${existingImportsIndex}`,
false,
attr.loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
} else {
exp = createSimpleExpression(
`_imports_${context.imports.length}`,
false,
attr.loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
context.imports.push({ exp, path })
}
`"${url}"`,
false,
attr.loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
compoundExpression.children.push(exp)
}
arg: createSimpleExpression('srcset', true, attr.loc),
exp,
modifiers: [],
- loc: attr.loc
+ loc: attr.loc,
}
}
})
export function warn(msg: string) {
console.warn(
- `\x1b[1m\x1b[33m[@vue/compiler-sfc]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`
+ `\x1b[1m\x1b[33m[@vue/compiler-sfc]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`,
)
}
compile(`<foo>
<template v-slot>foo</template>
<template v-slot:named>bar</template>
- </foo>`).code
+ </foo>`).code,
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require("vue")
const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
expect(
compile(`<foo>
<template v-slot:named v-if="ok">foo</template>
- </foo>`).code
+ </foo>`).code,
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode, createSlots: _createSlots } = require("vue")
const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
<span v-for="i in list"></span>
</div>
</template>
- </foo>`).code
+ </foo>`).code,
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = require("vue")
const { ssrRenderComponent: _ssrRenderComponent, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
test('should push marker string if is slot root', () => {
expect(
compile(`<foo><transition><div v-if="false"/></transition></foo>`)
- .code
+ .code,
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Transition: _Transition, createVNode: _createVNode } = require("vue")
const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
describe('ssr: element', () => {
test('basic elements', () => {
expect(getCompiledString(`<div></div>`)).toMatchInlineSnapshot(
- `"\`<div></div>\`"`
+ `"\`<div></div>\`"`,
)
expect(getCompiledString(`<div/>`)).toMatchInlineSnapshot(
- `"\`<div></div>\`"`
+ `"\`<div></div>\`"`,
)
})
test('nested elements', () => {
expect(
- getCompiledString(`<div><span></span><span></span></div>`)
+ getCompiledString(`<div><span></span><span></span></div>`),
).toMatchInlineSnapshot(`"\`<div><span></span><span></span></div>\`"`)
})
test('<textarea> with static value', () => {
expect(
- getCompiledString(`<textarea value="fo>o"/>`)
+ getCompiledString(`<textarea value="fo>o"/>`),
).toMatchInlineSnapshot(`"\`<textarea>fo>o</textarea>\`"`)
})
test('multiple _ssrInterpolate at parent and child import dependency once', () => {
expect(
- compile(`<div>{{ hello }}<textarea v-bind="a"></textarea></div>`).code
+ compile(`<div>{{ hello }}<textarea v-bind="a"></textarea></div>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
test('should pass tag to custom elements w/ dynamic v-bind', () => {
expect(
compile(`<my-foo v-bind="obj"></my-foo>`, {
- isCustomElement: () => true
- }).code
+ isCustomElement: () => true,
+ }).code,
).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require("vue")
const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
describe('attrs', () => {
test('static attrs', () => {
expect(
- getCompiledString(`<div id="foo" class="bar"></div>`)
+ getCompiledString(`<div id="foo" class="bar"></div>`),
).toMatchInlineSnapshot(`"\`<div id="foo" class="bar"></div>\`"`)
})
test('ignore static key/ref', () => {
expect(
- getCompiledString(`<div key="1" ref="el"></div>`)
+ getCompiledString(`<div key="1" ref="el"></div>`),
).toMatchInlineSnapshot(`"\`<div></div>\`"`)
})
test('ignore v-bind key/ref', () => {
expect(
- getCompiledString(`<div :key="1" :ref="el"></div>`)
+ getCompiledString(`<div :key="1" :ref="el"></div>`),
).toMatchInlineSnapshot(`"\`<div></div>\`"`)
})
// should merge style and :style
expect(
getCompiledString(
- `<div style="color:red;" :style="b" v-bind="obj"></div>`
- )
+ `<div style="color:red;" :style="b" v-bind="obj"></div>`,
+ ),
).toMatchInlineSnapshot(`
"\`<div\${
_ssrRenderAttrs(_mergeProps({
test('should ignore v-on', () => {
expect(
- getCompiledString(`<div id="foo" @click="bar"/>`)
+ getCompiledString(`<div id="foo" @click="bar"/>`),
).toMatchInlineSnapshot(`"\`<div id="foo"></div>\`"`)
expect(
- getCompiledString(`<div id="foo" v-on="bar"/>`)
+ getCompiledString(`<div id="foo" v-on="bar"/>`),
).toMatchInlineSnapshot(`"\`<div id="foo"></div>\`"`)
expect(getCompiledString(`<div v-bind="foo" v-on="bar"/>`))
.toMatchInlineSnapshot(`
test('custom dir with object v-bind + normal bindings', () => {
expect(
- getCompiledString(`<div v-bind="x" class="foo" v-xxx title="bar" />`)
+ getCompiledString(`<div v-bind="x" class="foo" v-xxx title="bar" />`),
).toMatchInlineSnapshot(`
"\`<div\${
_ssrRenderAttrs(_mergeProps(_ctx.x, {
test('basic', () => {
expect(
compile(`<div/>`, {
- ssrCssVars: `{ color }`
- }).code
+ ssrCssVars: `{ color }`,
+ }).code,
).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require("vue")
const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
test('fragment', () => {
expect(
compile(`<div/><div/>`, {
- ssrCssVars: `{ color }`
- }).code
+ ssrCssVars: `{ color }`,
+ }).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
test('passing on to components', () => {
expect(
compile(`<div/><foo/>`, {
- ssrCssVars: `{ color }`
- }).code
+ ssrCssVars: `{ color }`,
+ }).code,
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent } = require("vue")
const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
test('v-if branches', () => {
expect(
compile(`<div v-if="ok"/><template v-else><div/><div/></template>`, {
- ssrCssVars: `{ color }`
- }).code
+ ssrCssVars: `{ color }`,
+ }).code,
).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require("vue")
const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
</template>
</Suspense>`,
{
- ssrCssVars: `{ color }`
- }
- ).code
+ ssrCssVars: `{ color }`,
+ },
+ ).code,
).toMatchInlineSnapshot(`
"const { withCtx: _withCtx } = require("vue")
const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderSuspense: _ssrRenderSuspense } = require("vue/server-renderer")
const result = compile(`<div/>`, {
inline: true,
bindingMetadata: { dynamic: BindingTypes.SETUP_MAYBE_REF },
- ssrCssVars: '{ "--hash": (dynamic) }'
+ ssrCssVars: '{ "--hash": (dynamic) }',
})
expect(result.code).toMatchInlineSnapshot(`
`)
expect(
- compile(`<teleport :to="target" :disabled="foo"><div/></teleport>`).code
+ compile(`<teleport :to="target" :disabled="foo"><div/></teleport>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderTeleport: _ssrRenderTeleport } = require("vue/server-renderer")
expect(
compile(`<div><span>hello</span></div>`, {
scopeId,
- mode: 'module'
- }).code
+ mode: 'module',
+ }).code,
).toMatchInlineSnapshot(`
"import { ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer"
expect(
compile(`<foo>foo</foo>`, {
scopeId,
- mode: 'module'
- }).code
+ mode: 'module',
+ }).code,
).toMatchInlineSnapshot(`
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode } from "vue"
import { ssrRenderComponent as _ssrRenderComponent } from "vue/server-renderer"
expect(
compile(`<foo><span>hello</span></foo>`, {
scopeId,
- mode: 'module'
- }).code
+ mode: 'module',
+ }).code,
).toMatchInlineSnapshot(`
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from "vue"
import { ssrRenderComponent as _ssrRenderComponent } from "vue/server-renderer"
expect(
compile(`<foo><span>hello</span><bar><span/></bar></foo>`, {
scopeId,
- mode: 'module'
- }).code
+ mode: 'module',
+ }).code,
).toMatchInlineSnapshot(`
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from "vue"
import { ssrRenderComponent as _ssrRenderComponent } from "vue/server-renderer"
`<transition-group tag="div" class="red"><span>hello</span></transition-group>`,
{
scopeId,
- mode: 'module'
- }
- ).code
+ mode: 'module',
+ },
+ ).code,
).toMatchInlineSnapshot(`
"import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer"
`<transition-group :tag="someTag" class="red"><span>hello</span></transition-group>`,
{
scopeId,
- mode: 'module'
- }
- ).code
+ mode: 'module',
+ },
+ ).code,
).toMatchInlineSnapshot(`
"import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer"
import { compile } from '../src'
-import { ssrHelpers, SSR_RENDER_SLOT_INNER } from '../src/runtimeHelpers'
+import { SSR_RENDER_SLOT_INNER, ssrHelpers } from '../src/runtimeHelpers'
describe('ssr: <slot>', () => {
test('basic', () => {
test('with scopeId', async () => {
expect(
compile(`<slot/>`, {
- scopeId: 'hello'
- }).code
+ scopeId: 'hello',
+ }).code,
).toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot } = require("vue/server-renderer")
expect(
compile(`<slot/>`, {
scopeId: 'hello',
- slotted: false
- }).code
+ slotted: false,
+ }).code,
).toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot } = require("vue/server-renderer")
test('with forwarded scopeId', async () => {
expect(
compile(`<Comp><slot/></Comp>`, {
- scopeId: 'hello'
- }).code
+ scopeId: 'hello',
+ }).code,
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderSlot: _renderSlot } = require("vue")
const { ssrRenderSlot: _ssrRenderSlot, ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
<template #fallback>
loading...
</template>
- </suspense>`).code
+ </suspense>`).code,
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require("vue")
const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require("vue/server-renderer")
test('static text with template string special chars', () => {
expect(getCompiledString(`\`\${foo}\``)).toMatchInlineSnapshot(
- `"\`\\\`\\\${foo}\\\`\`"`
+ `"\`\\\`\\\${foo}\\\`\`"`,
)
})
// snapshot -> inline snapshot goes through two escapes
// so that makes a total of 3 * 2 * 2 = 12 back slashes
expect(getCompiledString(`\\$foo`)).toMatchInlineSnapshot(
- `"\`\\\\\\$foo\`"`
+ `"\`\\\\\\$foo\`"`,
)
})
test('comments', () => {
expect(getCompiledString(`<!--bar-->`)).toMatchInlineSnapshot(
- `"\`<!--bar-->\`"`
+ `"\`<!--bar-->\`"`,
)
})
test('static text escape', () => {
expect(getCompiledString(`<foo>`)).toMatchInlineSnapshot(
- `"\`<foo>\`"`
+ `"\`<foo>\`"`,
)
})
test('nested elements with static text', () => {
expect(
- getCompiledString(`<div><span>hello</span><span>bye</span></div>`)
+ getCompiledString(`<div><span>hello</span><span>bye</span></div>`),
).toMatchInlineSnapshot(
- `"\`<div><span>hello</span><span>bye</span></div>\`"`
+ `"\`<div><span>hello</span><span>bye</span></div>\`"`,
)
})
test('nested elements with interpolation', () => {
expect(
compile(`<div><span>{{ foo }} bar</span><span>baz {{ qux }}</span></div>`)
- .code
+ .code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
test('basic', () => {
expect(
compile(`<transition-group><div v-for="i in list"/></transition-group>`)
- .code
+ .code,
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
test('with static tag', () => {
expect(
compile(
- `<transition-group tag="ul"><div v-for="i in list"/></transition-group>`
- ).code
+ `<transition-group tag="ul"><div v-for="i in list"/></transition-group>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
test('with dynamic tag', () => {
expect(
compile(
- `<transition-group :tag="someTag"><div v-for="i in list"/></transition-group>`
- ).code
+ `<transition-group :tag="someTag"><div v-for="i in list"/></transition-group>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
<div v-for="i in 10"/>
<div v-for="i in 10"/>
<template v-if="ok"><div>ok</div></template>
- </transition-group>`
- ).code
+ </transition-group>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
expect(
compile(
`<transition-group tag="ul" class="red" id="ok">
- </transition-group>`
- ).code
+ </transition-group>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require("vue")
const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
compile(
`<div v-for="row, i in list">` +
`<div v-for="j in row">{{ i }},{{ j }}</div>` +
- `</div>`
- ).code
+ `</div>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
test('template v-for (single element)', () => {
expect(
compile(`<template v-for="i in list"><span>{{ i }}</span></template>`)
- .code
+ .code,
).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
test('template v-for (multi element)', () => {
expect(
compile(
- `<template v-for="i in list"><span>{{ i }}</span><span>{{ i + 1 }}</span></template>`
- ).code
+ `<template v-for="i in list"><span>{{ i }}</span><span>{{ i + 1 }}</span></template>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
test('render loop args should not be prefixed', () => {
const { code } = compile(
- `<div v-for="{ foo }, index in list">{{ foo + bar + index }}</div>`
+ `<div v-for="{ foo }, index in list">{{ foo + bar + index }}</div>`,
)
expect(code).toMatch(`_ctx.bar`)
expect(code).not.toMatch(`_ctx.foo`)
test('<template v-if> (multiple element)', () => {
expect(
- compile(`<template v-if="foo"><div>hi</div><div>ho</div></template>`).code
+ compile(`<template v-if="foo"><div>hi</div><div>ho</div></template>`)
+ .code,
).toMatchInlineSnapshot(`
"
return function ssrRender(_ctx, _push, _parent, _attrs) {
test('<template v-if> (with v-for inside)', () => {
expect(
- compile(`<template v-if="foo"><div v-for="i in list"/></template>`).code
+ compile(`<template v-if="foo"><div v-for="i in list"/></template>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
test('<template v-if> + normal v-else', () => {
expect(
compile(
- `<template v-if="foo"><div>hi</div><div>ho</div></template><div v-else/>`
- ).code
+ `<template v-if="foo"><div>hi</div><div>ho</div></template><div v-else/>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
test('<select v-model>', () => {
expect(
compileWithWrapper(
- `<select v-model="model"><option value="1"></option></select>`
- ).code
+ `<select v-model="model"><option value="1"></option></select>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
expect(
compileWithWrapper(
- `<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`
- ).code
+ `<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
expect(
compileWithWrapper(`<select multiple v-model="model"><slot/></select>`)
- .code
+ .code,
).toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
<optgroup label="foo">
<option value="bar">bar</option>
</optgroup>
- </select>`).code
+ </select>`).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
<optgroup label="foo">
<slot/>
</optgroup>
- </select>`).code
+ </select>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
test('<input type="radio">', () => {
expect(
- compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code
+ compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code,
).toMatchInlineSnapshot(`
"const { ssrLooseEqual: _ssrLooseEqual, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
expect(
compileWithWrapper(`<input type="checkbox" value="foo" v-model="bar">`)
- .code
+ .code,
).toMatchInlineSnapshot(`
"const { ssrLooseContain: _ssrLooseContain, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
expect(
compileWithWrapper(
- `<input type="checkbox" :true-value="foo" :false-value="bar" v-model="baz">`
- ).code
+ `<input type="checkbox" :true-value="foo" :false-value="bar" v-model="baz">`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrLooseEqual: _ssrLooseEqual, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
expect(
compileWithWrapper(
- `<input type="checkbox" true-value="foo" false-value="bar" v-model="baz">`
- ).code
+ `<input type="checkbox" true-value="foo" false-value="bar" v-model="baz">`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrLooseEqual: _ssrLooseEqual, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
`)
expect(
- compileWithWrapper(`<input :type="x" v-model="foo" value="bar">`).code
+ compileWithWrapper(`<input :type="x" v-model="foo" value="bar">`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
`)
expect(
- compileWithWrapper(`<input :type="x" v-model="foo" :value="bar">`).code
+ compileWithWrapper(`<input :type="x" v-model="foo" :value="bar">`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
expect(
compileWithWrapper(`<input id="x" v-bind="obj" v-model="foo" class="y">`)
- .code
+ .code,
).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require("vue")
const { ssrRenderAttrs: _ssrRenderAttrs, ssrGetDynamicModelProps: _ssrGetDynamicModelProps } = require("vue/server-renderer")
test('with dynamic style', () => {
expect(
- compileWithWrapper(`<div :style="{ color: 'red' }" v-show="foo"/>`).code
+ compileWithWrapper(`<div :style="{ color: 'red' }" v-show="foo"/>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
test('with static + dynamic style', () => {
expect(
compileWithWrapper(
- `<div style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`
- ).code
+ `<div style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
test('with v-bind', () => {
expect(
compileWithWrapper(
- `<div v-bind="baz" style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`
- ).code
+ `<div v-bind="baz" style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`,
+ ).code,
).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require("vue")
const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
// but also means this util can only be used for non-root cases.
const { code } = compile(`<div>${src}</div>`)
const match = code.match(
- /_push\(\`<div\${\s*_ssrRenderAttrs\(_attrs\)\s*}>([^]*)<\/div>\`\)/
+ /_push\(\`<div\${\s*_ssrRenderAttrs\(_attrs\)\s*}>([^]*)<\/div>\`\)/,
)
if (!match) {
import {
- SourceLocation,
- CompilerError,
+ type CompilerError,
+ DOMErrorCodes,
+ type SourceLocation,
createCompilerError,
- DOMErrorCodes
} from '@vue/compiler-dom'
export interface SSRCompilerError extends CompilerError {
export function createSSRCompilerError(
code: SSRErrorCodes,
- loc?: SourceLocation
+ loc?: SourceLocation,
) {
return createCompilerError(code, loc, SSRErrorMessages) as SSRCompilerError
}
export enum SSRErrorCodes {
X_SSR_UNSAFE_ATTR_NAME = 65 /* DOMErrorCodes.__EXTEND_POINT__ */,
X_SSR_NO_TELEPORT_TARGET,
- X_SSR_INVALID_AST_NODE
+ X_SSR_INVALID_AST_NODE,
}
if (__TEST__) {
throw new Error(
`SSRErrorCodes need to be updated to ${
DOMErrorCodes.__EXTEND_POINT__ + 1
- } to match extension point from core DOMErrorCodes.`
+ } to match extension point from core DOMErrorCodes.`,
)
}
}
export const SSRErrorMessages: { [code: number]: string } = {
[SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME]: `Unsafe attribute name for SSR.`,
[SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET]: `Missing the 'to' prop on teleport element.`,
- [SSRErrorCodes.X_SSR_INVALID_AST_NODE]: `Invalid AST node during SSR transform.`
+ [SSRErrorCodes.X_SSR_INVALID_AST_NODE]: `Invalid AST node during SSR transform.`,
}
import {
- CodegenResult,
+ type CodegenResult,
+ type CompilerOptions,
+ type RootNode,
baseParse,
- parserOptions,
- transform,
generate,
- CompilerOptions,
- transformExpression,
- trackVForSlotScopes,
- trackSlotScopes,
noopDirectiveTransform,
+ parserOptions,
+ trackSlotScopes,
+ trackVForSlotScopes,
+ transform,
transformBind,
- transformStyle,
+ transformExpression,
transformOn,
- RootNode
+ transformStyle,
} from '@vue/compiler-dom'
import { ssrCodegenTransform } from './ssrCodegenTransform'
import { ssrTransformElement } from './transforms/ssrTransformElement'
import {
+ rawOptionsMap,
ssrTransformComponent,
- rawOptionsMap
} from './transforms/ssrTransformComponent'
import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
import { ssrTransformIf } from './transforms/ssrVIf'
export function compile(
source: string | RootNode,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
): CodegenResult {
options = {
...options,
prefixIdentifiers: true,
// disable optimizations that are unnecessary for ssr
cacheHandlers: false,
- hoistStatic: false
+ hoistStatic: false,
}
const ast = typeof source === 'string' ? baseParse(source, options) : source
ssrTransformComponent,
trackSlotScopes,
transformStyle,
- ...(options.nodeTransforms || []) // user transforms
+ ...(options.nodeTransforms || []), // user transforms
],
directiveTransforms: {
// reusing core v-bind
cloak: noopDirectiveTransform,
once: noopDirectiveTransform,
memo: noopDirectiveTransform,
- ...(options.directiveTransforms || {}) // user transforms
- }
+ ...(options.directiveTransforms || {}), // user transforms
+ },
})
// traverse the template AST and convert into SSR codegen AST
[SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`,
[SSR_RENDER_TELEPORT]: `ssrRenderTeleport`,
[SSR_RENDER_SUSPENSE]: `ssrRenderSuspense`,
- [SSR_GET_DIRECTIVE_PROPS]: `ssrGetDirectiveProps`
+ [SSR_GET_DIRECTIVE_PROPS]: `ssrGetDirectiveProps`,
}
// Note: these are helpers imported from @vue/server-renderer
import {
- RootNode,
- BlockStatement,
- TemplateLiteral,
- createCallExpression,
- createTemplateLiteral,
- NodeTypes,
- TemplateChildNode,
+ type BlockStatement,
+ type CallExpression,
+ type CompilerOptions,
ElementTypes,
+ type IfStatement,
+ NodeTypes,
+ type RootNode,
+ type TemplateChildNode,
+ type TemplateLiteral,
createBlockStatement,
- CompilerOptions,
- IfStatement,
- CallExpression,
- isText,
- processExpression,
- createSimpleExpression,
+ createCallExpression,
createCompoundExpression,
+ createRoot,
+ createSimpleExpression,
+ createTemplateLiteral,
createTransformContext,
- createRoot
+ isText,
+ processExpression,
} from '@vue/compiler-dom'
-import { isString, escapeHtml } from '@vue/shared'
+import { escapeHtml, isString } from '@vue/shared'
import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers'
import { ssrProcessIf } from './transforms/ssrVIf'
import { ssrProcessFor } from './transforms/ssrVFor'
import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet'
import { ssrProcessComponent } from './transforms/ssrTransformComponent'
import { ssrProcessElement } from './transforms/ssrTransformElement'
-import { createSSRCompilerError, SSRErrorCodes } from './errors'
+import { SSRErrorCodes, createSSRCompilerError } from './errors'
// Because SSR codegen output is completely different from client-side output
// (e.g. multiple elements can be concatenated into a single template literal
const cssContext = createTransformContext(createRoot([]), options)
const varsExp = processExpression(
createSimpleExpression(options.ssrCssVars, false),
- cssContext
+ cssContext,
)
context.body.push(
- createCompoundExpression([`const _cssVars = { style: `, varsExp, `}`])
+ createCompoundExpression([`const _cssVars = { style: `, varsExp, `}`]),
)
Array.from(cssContext.helpers.keys()).forEach(helper => {
ast.helpers.add(helper)
ast.ssrHelpers = Array.from(
new Set([
...Array.from(ast.helpers).filter(h => h in ssrHelpers),
- ...context.helpers
- ])
+ ...context.helpers,
+ ]),
)
ast.helpers = new Set(Array.from(ast.helpers).filter(h => !(h in ssrHelpers)))
root: RootNode,
options: CompilerOptions,
helpers: Set<symbol> = new Set(),
- withSlotScopeId = false
+ withSlotScopeId = false,
) {
const body: BlockStatement['body'] = []
let currentString: TemplateLiteral | null = null
// close current string
currentString = null
body.push(statement)
- }
+ },
}
}
function createChildContext(
parent: SSRTransformContext,
- withSlotScopeId = parent.withSlotScopeId
+ withSlotScopeId = parent.withSlotScopeId,
): SSRTransformContext {
// ensure child inherits parent helpers
return createSSRTransformContext(
parent.root,
parent.options,
parent.helpers,
- withSlotScopeId
+ withSlotScopeId,
)
}
parent: Container,
context: SSRTransformContext,
asFragment = false,
- disableNestedFragments = false
+ disableNestedFragments = false,
) {
if (asFragment) {
context.pushStringPart(`<!--[-->`)
context.onError(
createSSRCompilerError(
SSRErrorCodes.X_SSR_INVALID_AST_NODE,
- (child as any).loc
- )
+ (child as any).loc,
+ ),
)
// make sure we exhaust all possible types
const exhaustiveCheck: never = child
break
case NodeTypes.INTERPOLATION:
context.pushStringPart(
- createCallExpression(context.helper(SSR_INTERPOLATE), [child.content])
+ createCallExpression(context.helper(SSR_INTERPOLATE), [
+ child.content,
+ ]),
)
break
case NodeTypes.IF:
context.onError(
createSSRCompilerError(
SSRErrorCodes.X_SSR_INVALID_AST_NODE,
- (child as any).loc
- )
+ (child as any).loc,
+ ),
)
// make sure we exhaust all possible types
const exhaustiveCheck: never = child
parent: Container,
parentContext: SSRTransformContext,
asFragment = false,
- withSlotScopeId = parentContext.withSlotScopeId
+ withSlotScopeId = parentContext.withSlotScopeId,
): BlockStatement {
const childContext = createChildContext(parentContext, withSlotScopeId)
processChildren(parent, childContext, asFragment)
import {
- NodeTransform,
- NodeTypes,
ElementTypes,
- locStub,
+ type NodeTransform,
+ NodeTypes,
+ type RootNode,
+ type TemplateChildNode,
createSimpleExpression,
- RootNode,
- TemplateChildNode,
- findDir
+ findDir,
+ locStub,
} from '@vue/compiler-dom'
export const ssrInjectCssVars: NodeTransform = (node, context) => {
arg: undefined,
exp: createSimpleExpression(`_cssVars`, false),
modifiers: [],
- loc: locStub
+ loc: locStub,
})
}
}
import {
- NodeTransform,
- NodeTypes,
ElementTypes,
- locStub,
+ type NodeTransform,
+ NodeTypes,
+ type ParentNode,
+ type RootNode,
+ type TemplateChildNode,
createSimpleExpression,
- RootNode,
- TemplateChildNode,
- ParentNode,
- findDir
+ findDir,
+ locStub,
} from '@vue/compiler-dom'
const filterChild = (node: ParentNode) =>
arg: undefined,
exp: createSimpleExpression(`_attrs`, false),
modifiers: [],
- loc: locStub
+ loc: locStub,
})
}
}
import {
- NodeTransform,
- NodeTypes,
+ CREATE_VNODE,
+ type CallExpression,
+ type CompilerOptions,
+ type ComponentNode,
+ DOMDirectiveTransforms,
+ DOMNodeTransforms,
+ type DirectiveNode,
ElementTypes,
- createCallExpression,
- resolveComponentType,
+ type ExpressionNode,
+ type FunctionExpression,
+ type JSChildNode,
+ Namespaces,
+ type NodeTransform,
+ NodeTypes,
+ RESOLVE_DYNAMIC_COMPONENT,
+ type ReturnStatement,
+ type RootNode,
+ SUSPENSE,
+ type SlotFnBuilder,
+ TELEPORT,
+ TRANSITION,
+ TRANSITION_GROUP,
+ type TemplateChildNode,
+ type TemplateNode,
+ type TransformContext,
+ type TransformOptions,
buildProps,
- ComponentNode,
- SlotFnBuilder,
- createFunctionExpression,
buildSlots,
- FunctionExpression,
- TemplateChildNode,
+ createCallExpression,
+ createFunctionExpression,
createIfStatement,
- createSimpleExpression,
- getBaseTransformPreset,
- DOMNodeTransforms,
- DOMDirectiveTransforms,
createReturnStatement,
- ReturnStatement,
- Namespaces,
- locStub,
- RootNode,
- TransformContext,
- CompilerOptions,
- TransformOptions,
createRoot,
+ createSimpleExpression,
createTransformContext,
- traverseNode,
- ExpressionNode,
- TemplateNode,
- SUSPENSE,
- TELEPORT,
- TRANSITION_GROUP,
- CREATE_VNODE,
- CallExpression,
- JSChildNode,
- RESOLVE_DYNAMIC_COMPONENT,
- TRANSITION,
+ getBaseTransformPreset,
+ locStub,
+ resolveComponentType,
stringifyExpression,
- DirectiveNode
+ traverseNode,
} from '@vue/compiler-dom'
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
import {
- SSRTransformContext,
+ type SSRTransformContext,
processChildren,
- processChildrenAsStatement
+ processChildrenAsStatement,
} from '../ssrCodegenTransform'
import { ssrProcessTeleport } from './ssrTransformTeleport'
import {
ssrProcessSuspense,
- ssrTransformSuspense
+ ssrTransformSuspense,
} from './ssrTransformSuspense'
import {
ssrProcessTransitionGroup,
- ssrTransformTransitionGroup
+ ssrTransformTransitionGroup,
} from './ssrTransformTransitionGroup'
-import { isSymbol, isObject, isArray, extend } from '@vue/shared'
+import { extend, isArray, isObject, isSymbol } from '@vue/shared'
import { buildSSRProps } from './ssrTransformElement'
import {
ssrProcessTransition,
- ssrTransformTransition
+ ssrTransformTransition,
} from './ssrTransformTransition'
// We need to construct the slot functions in the 1st pass to ensure proper
if (clonedNode.children.length) {
buildSlots(clonedNode, context, (props, vFor, children) => {
vnodeBranches.push(
- createVNodeSlotBranch(props, vFor, children, context)
+ createVNodeSlotBranch(props, vFor, children, context),
)
return createFunctionExpression(undefined)
})
context,
undefined,
true,
- isDynamicComponent
+ isDynamicComponent,
)
if (props || directives.length) {
propsExp = buildSSRProps(props, directives, context)
undefined, // no return, assign body later
true, // newline
true, // isSlot
- loc
+ loc,
)
wipEntries.push({
type: WIP_SLOT,
fn,
children,
// also collect the corresponding vnode branch built earlier
- vnodeBranch: vnodeBranches[wipEntries.length]
+ vnodeBranch: vnodeBranches[wipEntries.length],
})
return fn
}
createCallExpression(context.helper(CREATE_VNODE), [
component,
propsExp,
- slots
+ slots,
]),
- `_parent`
- ]
+ `_parent`,
+ ],
)
} else {
node.ssrCodegenNode = createCallExpression(
context.helper(SSR_RENDER_COMPONENT),
- [component, propsExp, slots, `_parent`]
+ [component, propsExp, slots, `_parent`],
)
}
}
export function ssrProcessComponent(
node: ComponentNode,
context: SSRTransformContext,
- parent: { children: TemplateChildNode[] }
+ parent: { children: TemplateChildNode[] },
) {
const component = componentTypeMap.get(node)!
if (!node.ssrCodegenNode) {
wipEntries[i],
context,
false,
- true /* withSlotScopeId */
+ true /* withSlotScopeId */,
),
- vnodeBranch
+ vnodeBranch,
)
}
if (typeof component === 'string') {
// static component
context.pushStatement(
- createCallExpression(`_push`, [node.ssrCodegenNode])
+ createCallExpression(`_push`, [node.ssrCodegenNode]),
)
} else {
// dynamic component (`resolveDynamicComponent` call)
const vnodeNodeTransforms = [...baseNodeTransforms, ...DOMNodeTransforms]
const vnodeDirectiveTransforms = {
...baseDirectiveTransforms,
- ...DOMDirectiveTransforms
+ ...DOMDirectiveTransforms,
}
function createVNodeSlotBranch(
slotProps: ExpressionNode | undefined,
vFor: DirectiveNode | undefined,
children: TemplateChildNode[],
- parentContext: TransformContext
+ parentContext: TransformContext,
): ReturnStatement {
// apply a sub-transform using vnode-based transforms.
const rawOptions = rawOptionsMap.get(parentContext.root)!
// overwrite with vnode-based transforms
nodeTransforms: [
...vnodeNodeTransforms,
- ...(rawOptions.nodeTransforms || [])
+ ...(rawOptions.nodeTransforms || []),
],
directiveTransforms: {
...vnodeDirectiveTransforms,
- ...(rawOptions.directiveTransforms || {})
- }
+ ...(rawOptions.directiveTransforms || {}),
+ },
}
// wrap the children with a wrapper template for proper children treatment.
exp: slotProps,
arg: undefined,
modifiers: [],
- loc: locStub
+ loc: locStub,
})
}
if (vFor) {
props: wrapperProps,
children,
loc: locStub,
- codegenNode: undefined
+ codegenNode: undefined,
}
subTransform(wrapperNode, subOptions, parentContext)
return createReturnStatement(children)
function subTransform(
node: TemplateChildNode,
options: TransformOptions,
- parentContext: TransformContext
+ parentContext: TransformContext,
) {
const childRoot = createRoot([node])
const childContext = createTransformContext(childRoot, options)
import {
- NodeTransform,
- NodeTypes,
+ type ArrayExpression,
+ type AttributeNode,
+ type CallExpression,
+ type DirectiveNode,
ElementTypes,
- TemplateLiteral,
- createTemplateLiteral,
- createInterpolation,
- createCallExpression,
- createConditionalExpression,
- createSimpleExpression,
- buildProps,
- DirectiveNode,
- PlainElementNode,
- createCompilerError,
ErrorCodes,
- CallExpression,
+ type ExpressionNode,
+ type InterpolationNode,
+ type JSChildNode,
+ MERGE_PROPS,
+ type NodeTransform,
+ NodeTypes,
+ type PlainElementNode,
+ type PropsExpression,
+ type TemplateLiteral,
+ type TextNode,
+ type TransformContext,
+ buildDirectiveArgs,
+ buildProps,
createArrayExpression,
- ExpressionNode,
- JSChildNode,
- ArrayExpression,
createAssignmentExpression,
- TextNode,
+ createCallExpression,
+ createCompilerError,
+ createConditionalExpression,
+ createInterpolation,
+ createSequenceExpression,
+ createSimpleExpression,
+ createTemplateLiteral,
hasDynamicKeyVBind,
- MERGE_PROPS,
isStaticArgOf,
- createSequenceExpression,
- InterpolationNode,
isStaticExp,
- AttributeNode,
- buildDirectiveArgs,
- TransformContext,
- PropsExpression
} from '@vue/compiler-dom'
import {
+ NO,
escapeHtml,
isBooleanAttr,
isBuiltInDirective,
isSSRSafeAttrName,
- NO,
- propsToAttrMap
+ propsToAttrMap,
} from '@vue/shared'
-import { createSSRCompilerError, SSRErrorCodes } from '../errors'
+import { SSRErrorCodes, createSSRCompilerError } from '../errors'
import {
+ SSR_GET_DIRECTIVE_PROPS,
+ SSR_GET_DYNAMIC_MODEL_PROPS,
+ SSR_INCLUDE_BOOLEAN_ATTR,
+ SSR_INTERPOLATE,
SSR_RENDER_ATTR,
+ SSR_RENDER_ATTRS,
SSR_RENDER_CLASS,
- SSR_RENDER_STYLE,
SSR_RENDER_DYNAMIC_ATTR,
- SSR_RENDER_ATTRS,
- SSR_INTERPOLATE,
- SSR_GET_DYNAMIC_MODEL_PROPS,
- SSR_INCLUDE_BOOLEAN_ATTR,
- SSR_GET_DIRECTIVE_PROPS
+ SSR_RENDER_STYLE,
} from '../runtimeHelpers'
-import { SSRTransformContext, processChildren } from '../ssrCodegenTransform'
+import {
+ type SSRTransformContext,
+ processChildren,
+} from '../ssrCodegenTransform'
// for directives with children overwrite (e.g. v-html & v-text), we need to
// store the raw children so that they can be added in the 2nd pass.
// so when they are present we need to bail out to full `renderAttrs`
const hasDynamicVBind = hasDynamicKeyVBind(node)
const hasCustomDir = node.props.some(
- p => p.type === NodeTypes.DIRECTIVE && !isBuiltInDirective(p.name)
+ p => p.type === NodeTypes.DIRECTIVE && !isBuiltInDirective(p.name),
)
const needMergeProps = hasDynamicVBind || hasCustomDir
if (needMergeProps) {
node.props,
false /* isComponent */,
false /* isDynamicComponent */,
- true /* ssr */
+ true /* ssr */,
)
if (props || directives.length) {
const mergedProps = buildSSRProps(props, directives, context)
const propsExp = createCallExpression(
context.helper(SSR_RENDER_ATTRS),
- [mergedProps]
+ [mergedProps],
)
if (node.tag === 'textarea') {
propsExp.arguments = [
createAssignmentExpression(
createSimpleExpression(tempId, false),
- mergedProps
- )
+ mergedProps,
+ ),
]
rawChildrenMap.set(
node,
createSimpleExpression(`${tempId}.value`, false),
createSimpleExpression(
existingText ? existingText.content : ``,
- true
+ true,
),
- false
- )
- ])
+ false,
+ ),
+ ]),
)
}
} else if (node.tag === 'input') {
context.helper(SSR_GET_DYNAMIC_MODEL_PROPS),
[
tempExp, // existing props
- vModel.exp! // model
- ]
- )
- ])
- ])
+ vModel.exp!, // model
+ ],
+ ),
+ ]),
+ ]),
]
}
}
node.children = [createInterpolation(prop.exp, prop.loc)]
} else if (prop.name === 'slot') {
context.onError(
- createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)
+ createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc),
)
} else if (isTextareaWithValue(node, prop) && prop.exp) {
if (!needMergeProps) {
const { props, ssrTagParts } = directiveTransform(
prop,
node,
- context
+ context,
)
if (ssrTagParts) {
openTag.push(...ssrTagParts)
` class="`,
(dynamicClassBinding = createCallExpression(
context.helper(SSR_RENDER_CLASS),
- [value]
+ [value],
)),
- `"`
+ `"`,
)
} else if (attrName === 'style') {
if (dynamicStyleBinding) {
` style="`,
(dynamicStyleBinding = createCallExpression(
context.helper(SSR_RENDER_STYLE),
- [value]
+ [value],
)),
- `"`
+ `"`,
)
}
} else {
createConditionalExpression(
createCallExpression(
context.helper(SSR_INCLUDE_BOOLEAN_ATTR),
- [value]
+ [value],
),
createSimpleExpression(' ' + attrName, true),
createSimpleExpression('', true),
- false /* no newline */
- )
+ false /* no newline */,
+ ),
)
} else if (isSSRSafeAttrName(attrName)) {
openTag.push(
createCallExpression(context.helper(SSR_RENDER_ATTR), [
key,
- value
- ])
+ value,
+ ]),
)
} else {
context.onError(
createSSRCompilerError(
SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME,
- key.loc
- )
+ key.loc,
+ ),
)
}
}
openTag.push(
createCallExpression(
context.helper(SSR_RENDER_DYNAMIC_ATTR),
- args
- )
+ args,
+ ),
)
}
}
}
openTag.push(
` ${prop.name}` +
- (prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
+ (prop.value ? `="${escapeHtml(prop.value.content)}"` : ``),
)
}
}
export function buildSSRProps(
props: PropsExpression | undefined,
directives: DirectiveNode[],
- context: TransformContext
+ context: TransformContext,
): JSChildNode {
let mergePropsArgs: JSChildNode[] = []
if (props) {
mergePropsArgs.push(
createCallExpression(context.helper(SSR_GET_DIRECTIVE_PROPS), [
`_ctx`,
- ...buildDirectiveArgs(dir, context).elements
- ] as JSChildNode[])
+ ...buildDirectiveArgs(dir, context).elements,
+ ] as JSChildNode[]),
)
}
}
function isTextareaWithValue(
node: PlainElementNode,
- prop: DirectiveNode
+ prop: DirectiveNode,
): boolean {
return !!(
node.tag === 'textarea' &&
function removeStaticBinding(
tag: TemplateLiteral['elements'],
- binding: string
+ binding: string,
) {
const regExp = new RegExp(`^ ${binding}=".+"$`)
function findVModel(node: PlainElementNode): DirectiveNode | undefined {
return node.props.find(
- p => p.type === NodeTypes.DIRECTIVE && p.name === 'model' && p.exp
+ p => p.type === NodeTypes.DIRECTIVE && p.name === 'model' && p.exp,
) as DirectiveNode | undefined
}
export function ssrProcessElement(
node: PlainElementNode,
- context: SSRTransformContext
+ context: SSRTransformContext,
) {
const isVoidTag = context.options.isVoidTag || NO
const elementsToAdd = node.ssrCodegenNode!.elements
import {
- NodeTransform,
- isSlotOutlet,
- processSlotOutlet,
+ ElementTypes,
+ type NodeTransform,
+ NodeTypes,
+ type SlotOutletNode,
+ TRANSITION,
createCallExpression,
- SlotOutletNode,
createFunctionExpression,
- NodeTypes,
- ElementTypes,
+ isSlotOutlet,
+ processSlotOutlet,
resolveComponentType,
- TRANSITION
} from '@vue/compiler-dom'
import { SSR_RENDER_SLOT, SSR_RENDER_SLOT_INNER } from '../runtimeHelpers'
import {
- SSRTransformContext,
- processChildrenAsStatement
+ type SSRTransformContext,
+ processChildrenAsStatement,
} from '../ssrCodegenTransform'
export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
// fallback content placeholder. will be replaced in the process phase
`null`,
`_push`,
- `_parent`
+ `_parent`,
]
// inject slot scope id if current template uses :slotted
export function ssrProcessSlotOutlet(
node: SlotOutletNode,
- context: SSRTransformContext
+ context: SSRTransformContext,
) {
const renderCall = node.ssrCodegenNode!
import {
- ComponentNode,
- TransformContext,
+ type ComponentNode,
+ type FunctionExpression,
+ type SlotsExpression,
+ type TemplateChildNode,
+ type TransformContext,
buildSlots,
- createFunctionExpression,
- FunctionExpression,
- TemplateChildNode,
createCallExpression,
- SlotsExpression
+ createFunctionExpression,
} from '@vue/compiler-dom'
import {
- SSRTransformContext,
- processChildrenAsStatement
+ type SSRTransformContext,
+ processChildrenAsStatement,
} from '../ssrCodegenTransform'
import { SSR_RENDER_SUSPENSE } from '../runtimeHelpers'
// phase 1
export function ssrTransformSuspense(
node: ComponentNode,
- context: TransformContext
+ context: TransformContext,
) {
return () => {
if (node.children.length) {
const wipEntry: WIPEntry = {
slotsExp: null!, // to be immediately set
- wipSlots: []
+ wipSlots: [],
}
wipMap.set(node, wipEntry)
wipEntry.slotsExp = buildSlots(
undefined, // no return, assign body later
true, // newline
false, // suspense slots are not treated as normal slots
- loc
+ loc,
)
wipEntry.wipSlots.push({
fn,
- children
+ children,
})
return fn
- }
+ },
).slots
}
}
// phase 2
export function ssrProcessSuspense(
node: ComponentNode,
- context: SSRTransformContext
+ context: SSRTransformContext,
) {
// complete wip slots with ssr code
const wipEntry = wipMap.get(node)
context.pushStatement(
createCallExpression(context.helper(SSR_RENDER_SUSPENSE), [
`_push`,
- slotsExp
- ])
+ slotsExp,
+ ]),
)
}
import {
- ComponentNode,
- findProp,
+ type ComponentNode,
+ type ExpressionNode,
NodeTypes,
- createSimpleExpression,
- createFunctionExpression,
createCallExpression,
- ExpressionNode
+ createFunctionExpression,
+ createSimpleExpression,
+ findProp,
} from '@vue/compiler-dom'
import {
- SSRTransformContext,
- processChildrenAsStatement
+ type SSRTransformContext,
+ processChildrenAsStatement,
} from '../ssrCodegenTransform'
-import { createSSRCompilerError, SSRErrorCodes } from '../errors'
+import { SSRErrorCodes, createSSRCompilerError } from '../errors'
import { SSR_RENDER_TELEPORT } from '../runtimeHelpers'
// Note: this is a 2nd-pass codegen transform.
export function ssrProcessTeleport(
node: ComponentNode,
- context: SSRTransformContext
+ context: SSRTransformContext,
) {
const targetProp = findProp(node, 'to')
if (!targetProp) {
context.onError(
- createSSRCompilerError(SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET, node.loc)
+ createSSRCompilerError(SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET, node.loc),
)
return
}
context.onError(
createSSRCompilerError(
SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET,
- targetProp.loc
- )
+ targetProp.loc,
+ ),
)
return
}
undefined, // Body is added later
true, // newline
false, // isSlot
- node.loc
+ node.loc,
)
contentRenderFn.body = processChildrenAsStatement(node, context)
context.pushStatement(
contentRenderFn,
target,
disabled,
- `_parent`
- ])
+ `_parent`,
+ ]),
)
}
import {
- ComponentNode,
- findProp,
+ type ComponentNode,
NodeTypes,
- TransformContext
+ type TransformContext,
+ findProp,
} from '@vue/compiler-dom'
-import { processChildren, SSRTransformContext } from '../ssrCodegenTransform'
+import {
+ type SSRTransformContext,
+ processChildren,
+} from '../ssrCodegenTransform'
const wipMap = new WeakMap<ComponentNode, Boolean>()
export function ssrTransformTransition(
node: ComponentNode,
- context: TransformContext
+ context: TransformContext,
) {
return () => {
const appear = findProp(node, 'appear', false, true)
export function ssrProcessTransition(
node: ComponentNode,
- context: SSRTransformContext
+ context: SSRTransformContext,
) {
// #5351: filter out comment children inside transition
node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
import {
- AttributeNode,
+ type AttributeNode,
+ type ComponentNode,
+ type DirectiveNode,
+ type JSChildNode,
+ NodeTypes,
+ type TransformContext,
buildProps,
- ComponentNode,
createCallExpression,
- DirectiveNode,
findProp,
- JSChildNode,
- NodeTypes,
- TransformContext
} from '@vue/compiler-dom'
import { SSR_RENDER_ATTRS } from '../runtimeHelpers'
-import { processChildren, SSRTransformContext } from '../ssrCodegenTransform'
+import {
+ type SSRTransformContext,
+ processChildren,
+} from '../ssrCodegenTransform'
import { buildSSRProps } from './ssrTransformElement'
const wipMap = new WeakMap<ComponentNode, WIPEntry>()
// phase 1: build props
export function ssrTransformTransitionGroup(
node: ComponentNode,
- context: TransformContext
+ context: TransformContext,
) {
return () => {
const tag = findProp(node, 'tag')
otherProps,
true /* isComponent */,
false /* isDynamicComponent */,
- true /* ssr (skip event listeners) */
+ true /* ssr (skip event listeners) */,
)
let propsExp = null
if (props || directives.length) {
propsExp = createCallExpression(context.helper(SSR_RENDER_ATTRS), [
- buildSSRProps(props, directives, context)
+ buildSSRProps(props, directives, context),
])
}
wipMap.set(node, {
tag,
propsExp,
- scopeId: context.scopeId || null
+ scopeId: context.scopeId || null,
})
}
}
// phase 2: process children
export function ssrProcessTransitionGroup(
node: ComponentNode,
- context: SSRTransformContext
+ context: SSRTransformContext,
) {
const entry = wipMap.get(node)
if (entry) {
* be patched using the same key map) so we need to account for that here
* by disabling nested fragment wrappers from being generated.
*/
- true
+ true,
)
context.pushStringPart(`</`)
context.pushStringPart(tag.exp!)
import {
- createStructuralDirectiveTransform,
- ForNode,
- processFor,
+ type ForNode,
+ NodeTypes,
createCallExpression,
- createFunctionExpression,
createForLoopParams,
- NodeTypes
+ createFunctionExpression,
+ createStructuralDirectiveTransform,
+ processFor,
} from '@vue/compiler-dom'
import {
- SSRTransformContext,
- processChildrenAsStatement
+ type SSRTransformContext,
+ processChildrenAsStatement,
} from '../ssrCodegenTransform'
import { SSR_RENDER_LIST } from '../runtimeHelpers'
// Plugin for the first transform pass, which simply constructs the AST node
export const ssrTransformFor = createStructuralDirectiveTransform(
'for',
- processFor
+ processFor,
)
// This is called during the 2nd transform pass to construct the SSR-specific
export function ssrProcessFor(
node: ForNode,
context: SSRTransformContext,
- disableNestedFragments = false
+ disableNestedFragments = false,
) {
const needFragmentWrapper =
!disableNestedFragments &&
(node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT)
const renderLoop = createFunctionExpression(
- createForLoopParams(node.parseResult)
+ createForLoopParams(node.parseResult),
)
renderLoop.body = processChildrenAsStatement(
node,
context,
- needFragmentWrapper
+ needFragmentWrapper,
)
// v-for always renders a fragment unless explicitly disabled
if (!disableNestedFragments) {
context.pushStatement(
createCallExpression(context.helper(SSR_RENDER_LIST), [
node.source,
- renderLoop
- ])
+ renderLoop,
+ ]),
)
if (!disableNestedFragments) {
context.pushStringPart(`<!--]-->`)
import {
- createStructuralDirectiveTransform,
- processIf,
- IfNode,
- createIfStatement,
+ type BlockStatement,
+ type IfBranchNode,
+ type IfNode,
+ NodeTypes,
createBlockStatement,
createCallExpression,
- IfBranchNode,
- BlockStatement,
- NodeTypes
+ createIfStatement,
+ createStructuralDirectiveTransform,
+ processIf,
} from '@vue/compiler-dom'
import {
- SSRTransformContext,
- processChildrenAsStatement
+ type SSRTransformContext,
+ processChildrenAsStatement,
} from '../ssrCodegenTransform'
// Plugin for the first transform pass, which simply constructs the AST node
export const ssrTransformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
- processIf
+ processIf,
)
// This is called during the 2nd transform pass to construct the SSR-specific
export function ssrProcessIf(
node: IfNode,
context: SSRTransformContext,
- disableNestedFragments = false
+ disableNestedFragments = false,
) {
const [rootBranch] = node.branches
const ifStatement = createIfStatement(
rootBranch.condition!,
- processIfBranch(rootBranch, context, disableNestedFragments)
+ processIfBranch(rootBranch, context, disableNestedFragments),
)
context.pushStatement(ifStatement)
const branchBlockStatement = processIfBranch(
branch,
context,
- disableNestedFragments
+ disableNestedFragments,
)
if (branch.condition) {
// else-if
currentIf = currentIf.alternate = createIfStatement(
branch.condition,
- branchBlockStatement
+ branchBlockStatement,
)
} else {
// else
if (!currentIf.alternate) {
currentIf.alternate = createBlockStatement([
- createCallExpression(`_push`, ['`<!---->`'])
+ createCallExpression(`_push`, ['`<!---->`']),
])
}
}
function processIfBranch(
branch: IfBranchNode,
context: SSRTransformContext,
- disableNestedFragments = false
+ disableNestedFragments = false,
): BlockStatement {
const { children } = branch
const needFragmentWrapper =
import {
- DirectiveTransform,
+ DOMErrorCodes,
+ type DirectiveTransform,
ElementTypes,
- transformModel,
- findProp,
+ type ExpressionNode,
NodeTypes,
- createDOMCompilerError,
- DOMErrorCodes,
- createObjectProperty,
- createSimpleExpression,
+ type PlainElementNode,
createCallExpression,
- PlainElementNode,
- ExpressionNode,
createConditionalExpression,
+ createDOMCompilerError,
createInterpolation,
- hasDynamicKeyVBind
+ createObjectProperty,
+ createSimpleExpression,
+ findProp,
+ hasDynamicKeyVBind,
+ transformModel,
} from '@vue/compiler-dom'
import {
- SSR_LOOSE_EQUAL,
+ SSR_INCLUDE_BOOLEAN_ATTR,
SSR_LOOSE_CONTAIN,
+ SSR_LOOSE_EQUAL,
SSR_RENDER_DYNAMIC_MODEL,
- SSR_INCLUDE_BOOLEAN_ATTR
} from '../runtimeHelpers'
-import { DirectiveTransformResult } from 'packages/compiler-core/src/transform'
+import type { DirectiveTransformResult } from 'packages/compiler-core/src/transform'
export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
const model = dir.exp!
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
- value.loc
- )
+ value.loc,
+ ),
)
}
}
createCallExpression(`Array.isArray`, [model]),
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
model,
- value
+ value,
]),
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
model,
- value
- ])
- )
+ value,
+ ]),
+ ),
]),
createSimpleExpression(' selected', true),
createSimpleExpression('', true),
- false /* no newline */
- )
+ false /* no newline */,
+ ),
)
}
} else if (plainNode.tag === 'optgroup') {
plainNode.children.forEach(option =>
- processOption(option as PlainElementNode)
+ processOption(option as PlainElementNode),
)
}
}
const res: DirectiveTransformResult = { props: [] }
const defaultProps = [
// default value binding for text type inputs
- createObjectProperty(`value`, model)
+ createObjectProperty(`value`, model),
]
if (node.tag === 'input') {
const type = findProp(node, 'type')
createCallExpression(context.helper(SSR_RENDER_DYNAMIC_MODEL), [
type.exp!,
model,
- value
- ])
+ value,
+ ]),
]
} else if (type.value) {
// static type
`checked`,
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
model,
- value
- ])
- )
+ value,
+ ]),
+ ),
]
break
case 'checkbox':
`checked`,
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
model,
- trueValue
- ])
- )
+ trueValue,
+ ]),
+ ),
]
} else {
res.props = [
createCallExpression(`Array.isArray`, [model]),
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
model,
- value
+ value,
]),
- model
- )
- )
+ model,
+ ),
+ ),
]
}
break
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
- dir.loc
- )
+ dir.loc,
+ ),
)
break
default:
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
- dir.loc
- )
+ dir.loc,
+ ),
)
}
import {
- DirectiveTransform,
DOMErrorCodes,
- createObjectProperty,
- createSimpleExpression,
+ type DirectiveTransform,
createConditionalExpression,
+ createDOMCompilerError,
createObjectExpression,
- createDOMCompilerError
+ createObjectProperty,
+ createSimpleExpression,
} from '@vue/compiler-dom'
export const ssrTransformShow: DirectiveTransform = (dir, node, context) => {
if (!dir.exp) {
context.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION)
+ createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION),
)
}
return {
createObjectExpression([
createObjectProperty(
`display`,
- createSimpleExpression(`none`, true)
- )
+ createSimpleExpression(`none`, true),
+ ),
]),
- false /* no newline */
- )
- )
- ]
+ false /* no newline */,
+ ),
+ ),
+ ],
}
}
const _CustomPropsNotErased = defineComponent({
props: {},
- setup() {}
+ setup() {},
})
// #8376
// @ts-expect-error not any
expectType<number>(binding.value)
- }
+ },
})
-import { createApp, App, Plugin, defineComponent } from 'vue'
+import { type App, type Plugin, createApp, defineComponent } from 'vue'
const app = createApp({})
// Plugin without types accept anything
const PluginWithoutType: Plugin = {
- install(app: App) {}
+ install(app: App) {},
}
app.use(PluginWithoutType)
options.option1
options.option2
options.option3
- }
+ },
}
for (const Plugin of [
PluginWithObjectOptions,
- PluginWithObjectOptions.install
+ PluginWithObjectOptions.install,
]) {
// @ts-expect-error: no params
app.use(Plugin)
}
const PluginNoOptions = {
- install(app: App) {}
+ install(app: App) {},
}
for (const Plugin of [PluginNoOptions, PluginNoOptions.install]) {
}
const PluginMultipleArgs = {
- install: (app: App, a: string, b: number) => {}
+ install: (app: App, a: string, b: number) => {},
}
for (const Plugin of [PluginMultipleArgs, PluginMultipleArgs.install]) {
const PluginOptionalOptions = {
install(
app: App,
- options: PluginOptions = { option2: 2, option3: true, option1: 'foo' }
+ options: PluginOptions = { option2: 2, option3: true, option1: 'foo' },
) {
options.option1
options.option2
options.option3
- }
+ },
}
for (const Plugin of [PluginOptionalOptions, PluginOptionalOptions.install]) {
defineComponent({
...aliases[key],
name: key,
- aliasName: aliases[key].name
- })
+ aliasName: aliases[key].name,
+ }),
)
import { CustomPropsNotErased } from '@vue/dts-built-test'
-import { expectType, describe } from './utils'
+import { describe, expectType } from './utils'
declare module 'vue' {
interface ComponentCustomProps {
import {
- createBlock,
- VNode,
- Teleport,
- Text,
- Static,
Comment,
Fragment,
+ Static,
Suspense,
- defineComponent
+ Teleport,
+ Text,
+ type VNode,
+ createBlock,
+ defineComponent,
} from 'vue'
import { expectType } from './utils'
import {
- Component,
+ type Component,
+ type ComponentPublicInstance,
+ type EmitsOptions,
+ type FunctionalComponent,
+ type PropType,
+ type Ref,
+ type SetupContext,
+ type ShallowUnwrapRef,
defineComponent,
- PropType,
ref,
- Ref,
- ShallowUnwrapRef,
- FunctionalComponent,
- ComponentPublicInstance,
toRefs,
- SetupContext,
- EmitsOptions
} from 'vue'
-import { describe, expectAssignable, expectType, IsAny } from './utils'
+import { type IsAny, describe, expectAssignable, expectType } from './utils'
declare function extractComponentOptions<
Props,
RawBindings,
Emits extends EmitsOptions | Record<string, any[]>,
- Slots extends Record<string, any>
+ Slots extends Record<string, any>,
>(
- obj: Component<Props, RawBindings, any, any, any, Emits, Slots>
+ obj: Component<Props, RawBindings, any, any, any, Emits, Slots>,
): {
props: Props
emits: Emits
// required should make property non-void
b: {
type: String,
- required: true
+ required: true,
},
e: Function,
// default value should infer type and make it non-void
bb: {
- default: 'hello'
+ default: 'hello',
},
bbb: {
// Note: default function value requires arrow syntax + explicit
// annotation
- default: (props: any) => (props.bb as string) || 'foo'
+ default: (props: any) => (props.bb as string) || 'foo',
},
// explicit type casting
cc: Array as PropType<string[]>,
// required + type casting
dd: {
type: Object as PropType<{ n: 1 }>,
- required: true
+ required: true,
},
// return type
ee: Function as PropType<() => string>,
// required + constructor type casting
ddd: {
type: Array as () => string[],
- required: true
+ required: true,
},
// required + object return
eee: {
type: Function as PropType<() => { a: string }>,
- required: true
+ required: true,
},
// required + arguments + object return
fff: {
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
- required: true
+ required: true,
},
hhh: {
type: Boolean,
- required: true
+ required: true,
},
// default + type casting
ggg: {
type: String as PropType<'foo' | 'bar'>,
- default: 'foo'
+ default: 'foo',
},
// default + function
ffff: {
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
- default: (_a: number, _b: string) => ({ a: true })
+ default: (_a: number, _b: string) => ({ a: true }),
},
validated: {
type: String,
// validator requires explicit annotation
- validator: (val: unknown) => val !== ''
+ validator: (val: unknown) => val !== '',
},
object: Object as PropType<object>,
- zzz: Object as PropType<any>
+ zzz: Object as PropType<any>,
},
setup(props) {
const refs = toRefs(props)
setupA: 1,
setupB: ref(1),
setupC: {
- a: ref(2)
+ a: ref(2),
},
setupD: undefined as Ref<number> | undefined,
- setupProps: props
+ setupProps: props,
}
- }
+ },
})
const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
// required should make property non-void
b: {
type: String,
- required: true
+ required: true,
},
e: Function,
// default value should infer type and make it non-void
bb: {
- default: 'hello'
+ default: 'hello',
},
bbb: {
// Note: default function value requires arrow syntax + explicit
// annotation
- default: (props: any) => (props.bb as string) || 'foo'
+ default: (props: any) => (props.bb as string) || 'foo',
},
// explicit type casting
cc: Array as PropType<string[]>,
// required + type casting
dd: {
type: Object as PropType<{ n: 1 }>,
- required: true
+ required: true,
},
// return type
ee: Function as PropType<() => string>,
// required + constructor type casting
ddd: {
type: Array as () => string[],
- required: true
+ required: true,
},
// required + object return
eee: {
type: Function as PropType<() => { a: string }>,
- required: true
+ required: true,
},
// required + arguments + object return
fff: {
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
- required: true
+ required: true,
},
hhh: {
type: Boolean,
- required: true
+ required: true,
},
// default + type casting
ggg: {
type: String as PropType<'foo' | 'bar'>,
- default: 'foo'
+ default: 'foo',
},
// default + function
ffff: {
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
- default: (_a: number, _b: string) => ({ a: true })
+ default: (_a: number, _b: string) => ({ a: true }),
},
validated: {
type: String,
// validator requires explicit annotation
- validator: (val: unknown) => val !== ''
+ validator: (val: unknown) => val !== '',
},
- object: Object as PropType<object>
+ object: Object as PropType<object>,
},
setup() {
return {
- setupA: 1
+ setupA: 1,
}
- }
+ },
} as const
const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
props: ['a', 'b'],
setup() {
return {
- c: 1
+ c: 1,
}
- }
+ },
})
const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
props: ['a', 'b'] as const,
setup() {
return {
- c: 1
+ c: 1,
}
- }
+ },
}
const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
const MyComponent = defineComponent({
setup() {
return {
- setupA: 1
+ setupA: 1,
}
- }
+ },
})
const { rawBindings, setup } = extractComponentOptions(MyComponent)
const MyComponent = {
setup() {
return {
- setupA: 1
+ setupA: 1,
}
- }
+ },
}
const { rawBindings, setup } = extractComponentOptions(MyComponent)
const MyComponent: FunctionalComponent<Props, Emits, Slots> = (
props,
- { emit, slots }
+ { emit, slots },
) => {
expectType<Props>(props)
expectType<{
describe('short emits', () => {
const {
- emit
+ emit,
}: SetupContext<{
a: [val: string]
b: [val: number]
import {
+ type ComponentInstance,
+ type ComponentPublicInstance,
+ type FunctionalComponent,
defineComponent,
- FunctionalComponent,
- ComponentPublicInstance,
- ComponentInstance,
- ref
+ ref,
} from 'vue'
-import { expectType, describe } from './utils'
+import { describe, expectType } from './utils'
describe('defineComponent', () => {
const CompSetup = defineComponent({
props: {
- test: String
+ test: String,
},
setup() {
return {
- a: 1
+ a: 1,
}
- }
+ },
})
const compSetup: ComponentInstance<typeof CompSetup> = {} as any
// Options
const CompOptions = defineComponent({
props: {
- test: String
+ test: String,
},
data() {
return {
- a: 1
+ a: 1,
}
},
computed: {
b() {
return 'test'
- }
+ },
},
methods: {
func(a: string) {
return true
- }
- }
+ },
+ },
})
const compOptions: ComponentInstance<typeof CompOptions> = {} as any
expectType<string | undefined>(compOptions.test)
const CompObjectSetup = {
props: {
- test: String
+ test: String,
},
setup() {
return {
- a: 1
+ a: 1,
}
- }
+ },
}
const compObjectSetup: ComponentInstance<typeof CompObjectSetup> = {} as any
expectType<string | undefined>(compObjectSetup.test)
const CompObjectData = {
props: {
- test: String
+ test: String,
},
data() {
return {
- a: 1
+ a: 1,
}
- }
+ },
}
const compObjectData: ComponentInstance<typeof CompObjectData> = {} as any
expectType<string | undefined>(compObjectData.test)
const CompObjectNoProps = {
data() {
return {
- a: 1
+ a: 1,
}
- }
+ },
}
const compObjectNoProps: ComponentInstance<typeof CompObjectNoProps> =
{} as any
{props.msg} {count.value}
</div>
)
- }
+ },
)
// defaults to known types since types are resolved on instantiation
bar: String,
baz: {
type: Number,
- required: true
- }
+ required: true,
+ },
},
data: () => ({ counter: 0 }),
this.state = 'not valid'
// @ts-expect-error
this.$.appContext.config.globalProperties.state = 'not valid'
- }
- }
+ },
+ },
})
expectType<JSX.Element>(<Custom baz={1} />)
import {
- Component,
- defineComponent,
- PropType,
- ref,
- reactive,
+ type Component,
+ type ComponentOptions,
+ type ComponentPublicInstance,
+ type PropType,
+ type SetupContext,
+ type Slots,
+ type SlotsType,
+ type VNode,
createApp,
- ComponentPublicInstance,
- ComponentOptions,
- SetupContext,
+ defineComponent,
h,
- SlotsType,
- Slots,
- VNode,
+ reactive,
+ ref,
withKeys,
- withModifiers
+ withModifiers,
} from 'vue'
-import { describe, expectType, IsUnion } from './utils'
+import { type IsUnion, describe, expectType } from './utils'
describe('with object props', () => {
interface ExpectedProps {
// required should make property non-void
b: {
type: String,
- required: true as true
+ required: true as true,
},
e: Function,
h: Boolean,
j: Function as PropType<undefined | (() => string | undefined)>,
// default value should infer type and make it non-void
bb: {
- default: 'hello'
+ default: 'hello',
},
bbb: {
// Note: default function value requires arrow syntax + explicit
// annotation
- default: (props: any) => (props.bb as string) || 'foo'
+ default: (props: any) => (props.bb as string) || 'foo',
},
bbbb: {
type: String,
- default: undefined
+ default: undefined,
},
bbbbb: {
type: String,
- default: () => undefined
+ default: () => undefined,
},
// explicit type casting
cc: Array as PropType<string[]>,
// required + type casting
dd: {
type: Object as PropType<{ n: 1 }>,
- required: true as true
+ required: true as true,
},
// return type
ee: Function as PropType<() => string>,
// required + constructor type casting
ddd: {
type: Array as () => string[],
- required: true as true
+ required: true as true,
},
// required + object return
eee: {
type: Function as PropType<() => { a: string }>,
- required: true as true
+ required: true as true,
},
// required + arguments + object return
fff: {
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
- required: true as true
+ required: true as true,
},
hhh: {
type: Boolean,
- required: true as true
+ required: true as true,
},
// default + type casting
ggg: {
type: String as PropType<'foo' | 'bar'>,
- default: 'foo'
+ default: 'foo',
},
// default + function
ffff: {
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
- default: (a: number, b: string) => ({ a: a > +b })
+ default: (a: number, b: string) => ({ a: a > +b }),
},
// union + function with different return types
iii: Function as PropType<(() => string) | (() => number)>,
type: Function as PropType<
((arg1: string) => string) | ((arg1: string, arg2: string) => string)
>,
- required: true as true
+ required: true as true,
},
kkk: null,
validated: {
type: String,
// validator requires explicit annotation
- validator: (val: unknown) => val !== ''
+ validator: (val: unknown) => val !== '',
},
date: Date,
l: [Date],
ll: [Date, Number],
- lll: [String, Number]
+ lll: [String, Number],
}
const MyComponent = defineComponent({
return {
c: ref(1),
d: {
- e: ref('hi')
+ e: ref('hi'),
},
f: reactive({
- g: ref('hello' as GT)
- })
+ g: ref('hello' as GT),
+ }),
}
},
provide() {
this.c = 2
return null
- }
+ },
})
expectType<Component>(MyComponent)
// should allow ref
ref={'foo'}
ref_for={true}
- />
+ />,
)
expectType<Component>(
fff={(a, b) => ({ a: a > +b })}
hhh={false}
jjj={() => ''}
- />
+ />,
)
// @ts-expect-error missing required props
default(): number {
// @ts-expect-error
return this.otherProp + 1
- }
+ },
},
otherProp: {
type: Number,
- required: true
- }
- }
+ required: true,
+ },
+ },
})
})
expectType<string>(props.msg)
expectType<string[]>(props.a)
return {
- b: 1
+ b: 1,
}
- }
+ },
})
expectType<JSX.Element>(<MyComponent msg="1" a={['1']} />)
expectType<any>(props.a)
expectType<any>(props.b)
return {
- c: 1
+ c: 1,
}
},
render() {
expectType<any>(this.a)
expectType<any>(this.b)
expectType<number>(this.c)
- }
+ },
})
expectType<JSX.Element>(<MyComponent a={[1, 2]} b="b" />)
// @ts-expect-error
props: { a: Number },
setup() {
return {
- b: 123
+ b: 123,
}
},
data() {
expectType<number | undefined>(this.a)
return {
c: this.a || 123,
- someRef: ref(0)
+ someRef: ref(0),
}
},
computed: {
expectType<number>(this.b)
expectType<number>(this.d)
expectType<number>(v)
- }
- }
+ },
+ },
},
watch: {
a() {
expectType<number>(this.b)
this.b + 1
- }
+ },
},
created() {
// props
},
returnSomething() {
return this.a
- }
+ },
},
render() {
// props
expectType<number>(this.e)
// method
expectType<() => number | undefined>(this.returnSomething)
- }
+ },
})
})
props: {
aP1: {
type: String,
- default: 'aP1'
+ default: 'aP1',
},
- aP2: Boolean
+ aP2: Boolean,
},
data() {
return {
- a: 1
+ a: 1,
}
- }
+ },
})
const MixinB = defineComponent({
props: ['bP1', 'bP2'],
data() {
return {
- b: 2
+ b: 2,
}
- }
+ },
})
const MixinC = defineComponent({
data() {
return {
- c: 3
+ c: 3,
}
- }
+ },
})
const MixinD = defineComponent({
mixins: [MixinA],
expectError<string>(this.dC2)
return {
- d: 4
+ d: 4,
}
},
setup(props) {
},
dC2() {
return this.aP1 + 'dC2'
- }
- }
+ },
+ },
})
const MyComponent = defineComponent({
mixins: [MixinA, MixinB, MixinC, MixinD],
// required should make property non-void
z: {
type: String,
- required: true
- }
+ required: true,
+ },
},
data(vm) {
this.d = 5
return null
- }
+ },
})
// Test TSX
expectType<JSX.Element>(
- <MyComponent aP1={'aP'} aP2 bP1={1} bP2={[1, 2]} z={'z'} />
+ <MyComponent aP1={'aP'} aP2 bP1={1} bP2={[1, 2]} z={'z'} />,
)
// missing required props
aP1: Boolean,
aP2: {
type: Number,
- default: 2
- }
+ default: 2,
+ },
},
data() {
return {
- a: 1
+ a: 1,
}
},
computed: {
c(): number {
return this.aP2 + this.a
- }
- }
+ },
+ },
})
const MyComponent = defineComponent({
extends: Base,
// required should make property non-void
z: {
type: String,
- required: true
- }
+ required: true,
+ },
},
render() {
const props = this.$props
this.a = 5
return null
- }
+ },
})
// Test TSX
props: {
mP1: {
type: String,
- default: 'mP1'
+ default: 'mP1',
},
mP2: Boolean,
mP3: {
type: Boolean,
- required: true
- }
+ required: true,
+ },
},
data() {
return {
- a: 1
+ a: 1,
}
- }
+ },
})
const Base = defineComponent({
emits: ['foo'],
p1: Boolean,
p2: {
type: Number,
- default: 2
+ default: 2,
},
p3: {
type: Boolean,
- required: true
- }
+ required: true,
+ },
},
data() {
return {
- b: 2
+ b: 2,
}
},
computed: {
c(): number {
return this.p2 + this.b
- }
- }
+ },
+ },
})
const MyComponent = defineComponent({
extends: Base,
// required should make property non-void
z: {
type: String,
- required: true
- }
+ required: true,
+ },
},
render() {
const props = this.$props
this.a = 5
return null
- }
+ },
})
// Test TSX
const CompWithD = defineComponent({
data() {
return { foo: 1 }
- }
+ },
})
const CompWithC = defineComponent({
computed: {
foo() {
return 1
- }
- }
+ },
+ },
})
const CompWithM = defineComponent({ methods: { foo() {} } })
const CompEmpty = defineComponent({})
mixins: [CompWithD, CompEmpty],
mounted() {
expectType<number>(this.foo)
- }
+ },
})
defineComponent({
mixins: [CompWithC, CompEmpty],
mounted() {
expectType<number>(this.foo)
- }
+ },
})
defineComponent({
mixins: [CompWithM, CompEmpty],
mounted() {
expectType<() => void>(this.foo)
- }
+ },
})
})
createApp(comp).mount('#hello')
const comp2 = defineComponent({
- props: { foo: String }
+ props: { foo: String },
})
createApp(comp2).mount('#hello')
const comp3 = defineComponent({
setup() {
return {
- a: 1
+ a: 1,
}
- }
+ },
})
createApp(comp3).mount('#hello')
})
describe('should accept components defined with defineComponent', () => {
const comp = defineComponent({})
defineComponent({
- components: { comp }
+ components: { comp },
})
})
constructor(_props: { foo: string }) {}
}
defineComponent({
- components: { Comp }
+ components: { Comp },
})
})
})
defineComponent({
emits: {
click: (n: number) => typeof n === 'number',
- input: (b: string) => b.length > 1
+ input: (b: string) => b.length > 1,
},
setup(props, { emit }) {
expectType<((n: number) => boolean) | undefined>(props.onClick)
// @ts-expect-error
this.$emit('input', 1)
})
- }
+ },
})
// with array emits
this.$emit('bar')
// @ts-expect-error
this.$emit('nope')
- }
+ },
})
// with tsx
const Component = defineComponent({
emits: {
- click: (n: number) => typeof n === 'number'
+ click: (n: number) => typeof n === 'number',
},
setup(props, { emit }) {
expectType<((n: number) => any) | undefined>(props.onClick)
emit('click')
// @ts-expect-error
emit('click', 'foo')
- }
+ },
})
defineComponent({
}}
/>
)
- }
+ },
})
// without emits
setup(props, { emit }) {
emit('test', 1)
emit('test')
- }
+ },
})
// emit should be valid when ComponentPublicInstance is used.
foo(): boolean {
// @ts-expect-error
return this.bar === 3
- }
- }
+ },
+ },
})
})
// with object inject
defineComponent({
props: {
- a: String
+ a: String,
},
inject: {
foo: 'foo',
- bar: 'bar'
+ bar: 'bar',
},
created() {
expectType<unknown>(this.foo)
expectType<unknown>(this.bar)
// @ts-expect-error
this.foobar = 1
- }
+ },
})
// with array inject
expectType<unknown>(this.bar)
// @ts-expect-error
this.foobar = 1
- }
+ },
})
// with no props
inject: {
foo: {
from: 'pfoo',
- default: 'foo'
+ default: 'foo',
},
bar: {
from: 'pbar',
- default: 'bar'
- }
+ default: 'bar',
+ },
},
created() {
expectType<unknown>(this.foo)
expectType<unknown>(this.bar)
// @ts-expect-error
this.foobar = 1
- }
+ },
})
// without inject
this.foo = 1
// @ts-expect-error
this.bar = 1
- }
+ },
})
})
describe('componentOptions setup should be `SetupContext`', () => {
expectType<ComponentOptions['setup']>(
- {} as (props: Record<string, any>, ctx: SetupContext) => any
+ {} as (props: Record<string, any>, ctx: SetupContext) => any,
)
})
props: {
baseA: {
type: Number,
- default: 1
- }
- }
+ default: 1,
+ },
+ },
})
const MixinA = defineComponent({
props: {
mA: {
type: String,
- default: ''
- }
- }
+ default: '',
+ },
+ },
})
const CompA = defineComponent({
extends: Base,
props: {
a: {
type: Boolean,
- default: false
+ default: false,
},
b: {
type: String,
- required: true
+ required: true,
},
- c: Number
- }
+ c: Number,
+ },
})
const compA = {} as InstanceType<typeof CompA>
return {
a: ref(1),
b: {
- c: ref('hi')
+ c: ref('hi'),
},
d: reactive({
- e: ref('hello' as GT)
- })
+ e: ref('hello' as GT),
+ }),
}
},
render() {
// setup context properties should be mutable
this.a = 2
- }
+ },
})
const vm = {} as InstanceType<typeof Comp>
expectType<[]>(attrs)
// @ts-expect-error should not be any
expectType<[]>(slots)
- }
+ },
})
expectType<Component>(component)
})
describe('should allow to assign props', () => {
const Child = defineComponent({
props: {
- bar: String
- }
+ bar: String,
+ },
})
const Parent = defineComponent({
props: {
...Child.props,
- foo: String
- }
+ foo: String,
+ },
})
const child = new Child()
props: {
onX: {
type: Function as PropType<(a: 1) => void>,
- required: true
- }
+ required: true,
+ },
},
setup(props) {
expectType<(a: 1) => void>(props.onX)
props.onX(1)
- }
+ },
})
defineComponent({
props: {
onX: {
type: Function as PropType<(a: 1) => void>,
- required: true
- }
+ required: true,
+ },
},
emits: {
- test: (a: 1) => true
+ test: (a: 1) => true,
},
setup(props) {
expectType<(a: 1) => void>(props.onX)
expectType<undefined | ((a: 1) => any)>(props.onTest)
- }
+ },
})
})
{props.msg} {count.value}
</div>
)
- }
+ },
)
expectType<JSX.Element>(<Comp msg="fse" list={['foo']} />)
expectType<JSX.Element>(
// @ts-expect-error missing prop
- <Comp msg={123} />
+ <Comp msg={123} />,
)
expectType<JSX.Element>(
// @ts-expect-error generics don't match
- <Comp msg="fse" list={[123]} />
+ <Comp msg="fse" list={[123]} />,
)
expectType<JSX.Element>(
// @ts-expect-error generics don't match
- <Comp msg={123} list={['123']} />
+ <Comp msg={123} list={['123']} />,
)
})
return () => {}
},
{
- emits: ['foo']
- }
+ emits: ['foo'],
+ },
)
expectType<JSX.Element>(<Foo msg="hi" onFoo={() => {}} />)
// @ts-expect-error
},
{
emits: {
- foo: (a: string) => true
- }
- }
+ foo: (a: string) => true,
+ },
+ },
)
})
return () => {}
},
{
- props: ['msg']
- }
+ props: ['msg'],
+ },
)
defineComponent(
return () => {}
},
{
- props: ['msg']
- }
+ props: ['msg'],
+ },
)
defineComponent(
},
{
props: {
- msg: String
- }
- }
+ msg: String,
+ },
+ },
)
// @ts-expect-error string prop names don't match
return () => {}
},
{
- props: ['bar']
- }
+ props: ['bar'],
+ },
)
defineComponent(
{
props: {
// @ts-expect-error prop type mismatch
- msg: Number
- }
- }
+ msg: Number,
+ },
+ },
)
// @ts-expect-error prop keys don't match
{
props: {
msg: String,
- bar: String
- }
- }
+ bar: String,
+ },
+ },
)
})
b: defineComponent({
data() {
return {}
- }
+ },
}),
c: defineComponent({
- props: ['a']
+ props: ['a'],
}),
d: defineComponent({
props: {
- a: Number
- }
- })
+ a: Number,
+ },
+ }),
}
describe('slots', () => {
}>,
setup(props, { slots }) {
expectType<(scope: { foo: string; bar: number }) => VNode[]>(
- slots.default
+ slots.default,
)
expectType<((scope: { data: string }) => VNode[]) | undefined>(
- slots.optional
+ slots.optional,
)
slots.default({ foo: 'foo', bar: 1 })
slots.optionalUndefinedScope?.('foo')
expectType<typeof slots | undefined>(new comp1().$slots)
- }
+ },
})
const comp2 = defineComponent({
// unknown slots
expectType<Slots>(slots)
expectType<((...args: any[]) => VNode[]) | undefined>(slots.default)
- }
+ },
})
expectType<Slots | undefined>(new comp2().$slots)
})
props: {
size: {
type: String as PropType<SizeType>,
- required: true
- }
+ required: true,
+ },
},
setup(props) {
expectType<SizeType>(props.size)
return {
- size: 1
+ size: 1,
}
- }
+ },
})
type CompInstance = InstanceType<typeof Comp>
;<input onKeydown={onKeydown} onClick={onClick} />
})
-import {
- DefineComponent,
+import type {
+ AllowedComponentProps,
+ ComponentCustomProps,
ComponentOptionsMixin,
+ DefineComponent,
EmitsOptions,
+ ExtractPropTypes,
VNodeProps,
- AllowedComponentProps,
- ComponentCustomProps,
- ExtractPropTypes
} from 'vue'
// code generated by tsc / vue-tsc, make sure this continues to work
import {
- defineCustomElement,
+ type VueElementConstructor,
defineComponent,
- type VueElementConstructor
+ defineCustomElement,
} from 'vue'
-import { expectType, describe, test } from './utils'
+import { describe, expectType, test } from './utils'
describe('inject', () => {
// with object inject
defineCustomElement({
props: {
- a: String
+ a: String,
},
inject: {
foo: 'foo',
- bar: 'bar'
+ bar: 'bar',
},
created() {
expectType<unknown>(this.foo)
expectType<unknown>(this.bar)
// @ts-expect-error
this.foobar = 1
- }
+ },
})
// with array inject
expectType<unknown>(this.bar)
// @ts-expect-error
this.foobar = 1
- }
+ },
})
// with no props
inject: {
foo: {
from: 'pbar',
- default: 'foo'
+ default: 'foo',
},
bar: {
from: 'pfoo',
- default: 'bar'
- }
+ default: 'bar',
+ },
},
created() {
expectType<unknown>(this.foo)
expectType<unknown>(this.bar)
// @ts-expect-error
this.foobar = 1
- }
+ },
})
// without inject
this.foo = 1
// @ts-expect-error
this.bar = 1
- }
+ },
})
})
test('with emits', () => {
const Comp1Vue = defineComponent({
props: {
- a: String
+ a: String,
},
emits: {
- click: () => true
- }
+ click: () => true,
+ },
})
const Comp = defineCustomElement(Comp1Vue)
expectType<VueElementConstructor>(Comp)
-import { ExtractPropTypes, ExtractPublicPropTypes } from 'vue'
-import { expectType, Prettify } from './utils'
+import type { ExtractPropTypes, ExtractPublicPropTypes } from 'vue'
+import { type Prettify, expectType } from './utils'
const propsOptions = {
foo: {
- default: 1
+ default: 1,
},
bar: {
type: String,
- required: true
+ required: true,
},
baz: Boolean,
- qux: Array
+ qux: Array,
} as const
// internal facing props
-import { h, Text, FunctionalComponent, Component, VNode } from 'vue'
+import {
+ type Component,
+ type FunctionalComponent,
+ Text,
+ type VNode,
+ h,
+} from 'vue'
import { expectType } from './utils'
// simple function signature
// assigning runtime options
Bar.props = {
- foo: Number
+ foo: Number,
}
// @ts-expect-error
Bar.props = { foo: String }
Bar.emits = {
- update: value => value > 1
+ update: value => value > 1,
}
// @ts-expect-error
Bar.emits = { baz: () => void 0 }
import {
- h,
- defineComponent,
- DefineComponent,
- ref,
+ type Component,
+ type DefineComponent,
Fragment,
- Teleport,
Suspense,
- Component,
- resolveComponent
+ Teleport,
+ defineComponent,
+ h,
+ ref,
+ resolveComponent,
} from 'vue'
import { describe, expectAssignable, expectType } from './utils'
h('div', {
onClick: e => {
expectType<MouseEvent>(e)
- }
+ },
})
h('input', {
onFocus(e) {
expectType<FocusEvent>(e)
- }
+ },
})
})
h(Suspense, 'foo')
h(Suspense, () => 'foo')
h(Suspense, null, {
- default: () => 'foo'
+ default: () => 'foo',
})
// @ts-expect-error
h(Suspense, { onResolve: 1 })
describe('h support w/ plain object component', () => {
const Foo = {
props: {
- foo: String
- }
+ foo: String,
+ },
}
h(Foo, { foo: 'ok' })
h(Foo, { foo: 'ok', class: 'extra' })
foo: String,
bar: {
type: Number,
- required: true
- }
- }
+ required: true,
+ },
+ },
})
h(Foo, { bar: 1 })
describe('describeComponent extends Component', () => {
// functional
expectAssignable<Component>(
- defineComponent((_props: { foo?: string; bar: number }) => () => {})
+ defineComponent((_props: { foo?: string; bar: number }) => () => {}),
)
// typed props
// prop arrays
expectAssignable<Component>(
defineComponent({
- props: ['a', 'b']
- })
+ props: ['a', 'b'],
+ }),
)
// prop object
foo: String,
bar: {
type: Number,
- required: true
- }
- }
- })
+ required: true,
+ },
+ },
+ }),
)
})
props: {
message: {
type: String,
- default: 'hello'
- }
- }
+ default: 'hello',
+ },
+ },
})
h(MyComponent, {})
describe('Boolean prop implicit false', () => {
const MyComponent = defineComponent({
props: {
- visible: Boolean
- }
+ visible: Boolean,
+ },
})
h(MyComponent, {})
props: {
visible: {
type: Boolean,
- required: true
- }
- }
+ required: true,
+ },
+ },
})
h(RequiredComponent, {
- visible: true
+ visible: true,
})
// @ts-expect-error
h(RequiredComponent, {})
describe('resolveComponent should work', () => {
h(resolveComponent('test'))
h(resolveComponent('test'), {
- message: '1'
+ message: '1',
})
})
const serializers = {
Paragraph: 'p',
Component: {} as Component,
- DefineComponent: {} as DefineComponent
+ DefineComponent: {} as DefineComponent,
}
const sampleComponent = serializers['' as keyof typeof serializers]
-import { provide, inject, ref, Ref, InjectionKey } from 'vue'
+import { type InjectionKey, type Ref, inject, provide, ref } from 'vue'
import { expectType } from './utils'
// non-symbol keys
-import { ref, readonly, shallowReadonly, Ref, reactive, markRaw } from 'vue'
+import {
+ type Ref,
+ markRaw,
+ reactive,
+ readonly,
+ ref,
+ shallowReadonly,
+} from 'vue'
import { describe, expectType } from './utils'
describe('should support DeepReadonly', () => {
}
const test = new Test<number>()
const plain = {
- ref: ref(1)
+ ref: ref(1),
}
const r = reactive({
class: {
raw: markRaw(test),
- reactive: test
+ reactive: test,
},
plain: {
raw: markRaw(plain),
- reactive: plain
- }
+ reactive: plain,
+ },
})
expectType<Test<number>>(r.class.raw)
import {
- Ref,
- ref,
- shallowRef,
+ type ComputedRef,
+ type MaybeRef,
+ type MaybeRefOrGetter,
+ type Ref,
+ type ShallowRef,
+ type ToRefs,
+ computed,
isRef,
- unref,
- reactive,
proxyRefs,
+ reactive,
+ readonly,
+ ref,
+ shallowReactive,
+ shallowRef,
toRef,
- toValue,
toRefs,
- ToRefs,
- shallowReactive,
- readonly,
- MaybeRef,
- MaybeRefOrGetter,
- ComputedRef,
- computed,
- ShallowRef
+ toValue,
+ unref,
} from 'vue'
-import { expectType, describe, IsUnion, IsAny } from './utils'
+import { type IsAny, type IsUnion, describe, expectType } from './utils'
function plainType(arg: number | Ref<number>) {
// ref coercing
// ref inner type should be unwrapped
const nestedRef = ref({
- foo: ref(1)
+ foo: ref(1),
})
expectType<{ foo: number }>(nestedRef.value)
// with symbol
expectType<Ref<IteratorFoo | null | undefined>>(
- ref<IteratorFoo | null | undefined>()
+ ref<IteratorFoo | null | undefined>(),
)
// should not unwrap ref inside arrays
expectType<HTMLElement>(unref(arg))
// ref inner type should be unwrapped
- // eslint-disable-next-line no-restricted-globals
const nestedRef = ref({ foo: ref(document.createElement('DIV')) })
expectType<Ref<{ foo: HTMLElement }>>(nestedRef)
expectType<{ foo: HTMLElement }>(nestedRef.value)
}
-// eslint-disable-next-line no-restricted-globals
const el = document.createElement('DIV')
bailType(el)
[Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(),
[Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() },
[Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() },
- [customSymbol]: { arr: [ref(1)] }
+ [customSymbol]: { arr: [ref(1)] },
}
const objRef = ref(obj)
expectType<WeakSet<Ref<boolean>>>(objRef.value[Symbol.split])
expectType<WeakMap<Ref<boolean>, string>>(objRef.value[Symbol.toPrimitive])
expectType<{ weakSet: WeakSet<Ref<boolean>> }>(
- objRef.value[Symbol.toStringTag]
+ objRef.value[Symbol.toStringTag],
)
expectType<{ weakMap: WeakMap<Ref<boolean>, string> }>(
- objRef.value[Symbol.unscopables]
+ objRef.value[Symbol.unscopables],
)
expectType<{ arr: Ref<number>[] }>(objRef.value[customSymbol])
}
const state = reactive({
foo: {
value: 1,
- label: 'bar'
- }
+ label: 'bar',
+ },
})
expectType<string>(state.foo.label)
{
// should return ShallowRef<T> | Ref<T>, not ShallowRef<T | Ref<T>>
expectType<ShallowRef<{ name: string }> | Ref<{ name: string }>>(
- shallowRef({} as MaybeRef<{ name: string }>)
+ shallowRef({} as MaybeRef<{ name: string }>),
)
expectType<ShallowRef<number> | Ref<string[]> | ShallowRef<string>>(
- shallowRef('' as Ref<string[]> | string | number)
+ shallowRef('' as Ref<string[]> | string | number),
)
}
// proxyRefs: should return `reactive` directly
const r1 = reactive({
- k: 'v'
+ k: 'v',
})
const p1 = proxyRefs(r1)
expectType<typeof r1>(p1)
c: computed(() => 1),
u: undefined,
obj: {
- k: ref('foo')
+ k: ref('foo'),
},
- union: Math.random() > 0 - 5 ? ref({ name: 'yo' }) : null
+ union: Math.random() > 0 - 5 ? ref({ name: 'yo' }) : null,
}
const p2 = proxyRefs(r2)
expectType<number>(p2.a)
} = {
a: 1,
b: ref(1),
- c: 1
+ c: 1,
}
// toRef
// Both should not do any unwrapping
const someReactive = shallowReactive({
a: {
- b: ref(42)
- }
+ b: ref(42),
+ },
})
const toRefResult = toRef(someReactive, 'a')
const data: ToRefs<AppData> = toRefs(
reactive({
- state: 'state1'
- })
+ state: 'state1',
+ }),
)
switch (data.state.value) {
const baz = reactive({
foo: shallowReactive({
a: {
- b: ref(42)
- }
- })
+ b: ref(42),
+ },
+ }),
})
const foo = toRef(baz, 'foo')
bar: {
baz: ref(123),
qux: reactive({
- z: ref(123)
- })
- }
- })
+ z: ref(123),
+ }),
+ },
+ }),
})
expectType<Ref<number>>(x.foo.bar.baz)
describe('ref in shallow ref', () => {
const x = shallowRef({
- a: ref(123)
+ a: ref(123),
})
expectType<Ref<number>>(x.value.a)
describe('reactive in shallow ref', () => {
const x = shallowRef({
a: reactive({
- b: ref(0)
- })
+ b: ref(0),
+ }),
})
expectType<number>(x.value.a.b)
a: MaybeRef<string>,
b: () => string,
c: MaybeRefOrGetter<string>,
- d: ComputedRef<string>
+ d: ComputedRef<string>,
) {
const r = toRef(a)
expectType<Ref<string>>(r)
r: toValue(r),
rb: toValue(rb),
rc: toValue(rc),
- rd: toValue(rd)
+ rd: toValue(rd),
}
}
'foo',
() => 'bar',
ref('baz'),
- computed(() => 'hi')
- )
+ computed(() => 'hi'),
+ ),
)
})
import {
- defineProps,
+ type Ref,
+ type Slots,
+ type VNode,
defineEmits,
+ defineModel,
+ defineProps,
+ defineSlots,
+ toRefs,
useAttrs,
useSlots,
withDefaults,
- Slots,
- defineSlots,
- VNode,
- Ref,
- defineModel,
- toRefs
} from 'vue'
import { describe, expectType } from './utils'
import { defineComponent } from 'vue'
fn: () => {},
genStr: () => '',
y: undefined,
- z: 'string'
- }
+ z: 'string',
+ },
)
res.number + 1
union1: 123,
union2: () => [123],
union3: () => ({ x: 123 }),
- union4: () => 123
- }
+ union4: () => 123,
+ },
)
})
generic2: () => ({ x: 123 }) as { x: T },
generic3: () => 'test' as TString,
- generic4: () => ({ a: 'test' }) as TA
- }
+ generic4: () => ({ a: 'test' }) as TA,
+ },
)
res.n + 1
defineProps<{
bool?: boolean
}>(),
- { bool: false }
+ { bool: false },
)
expectType<boolean>(res1.bool)
bool?: boolean
}>(),
{
- bool: undefined
- }
+ bool: undefined,
+ },
)
expectType<boolean | undefined>(res2.bool)
})
foo: String,
bar: {
type: Number,
- default: 1
+ default: 1,
},
baz: {
type: Array,
- required: true
- }
+ required: true,
+ },
})
expectType<{
foo?: string
describe('defineEmits w/ runtime declaration', () => {
const emit = defineEmits({
foo: () => {},
- bar: null
+ bar: null,
})
emit('foo')
emit('bar', 123)
for (const key of Object.keys(props.item) as (keyof T & string)[]) {
slots[`slot-${String(key)}`]?.({
- item: props.item
+ item: props.item,
})
}
slots.label?.({ item: props.item })
// @ts-expect-error
useModel(props, 'bar')
- }
+ },
})
defineComponent({
props: {
foo: String,
bar: { type: Number, required: true },
- baz: { type: Boolean }
+ baz: { type: Boolean },
},
setup(props) {
expectType<Ref<string | undefined>>(useModel(props, 'foo'))
expectType<Ref<number>>(useModel(props, 'bar'))
expectType<Ref<boolean>>(useModel(props, 'baz'))
- }
+ },
})
})
// @ts-expect-error non existing slot
slots['foo-asdas']?.({
- item: props.item
+ item: props.item,
})
for (const key in props.item) {
slots[`slot-${String(key)}`]?.({
- item: props.item
+ item: props.item,
})
slots[`slot-${String(key as keyof T)}`]?.({
- item: props.item
+ item: props.item,
})
}
for (const key of Object.keys(props.item) as (keyof T)[]) {
slots[`slot-${String(key)}`]?.({
- item: props.item
+ item: props.item,
})
}
slots.label?.({ item: props.item })
// TSX w/ defineComponent is tested in defineComponent.test-d.tsx
-import { KeepAlive, Suspense, Fragment, Teleport, VNode } from 'vue'
+import { Fragment, KeepAlive, Suspense, Teleport, type VNode } from 'vue'
import { expectType } from './utils'
expectType<VNode>(<div />)
// allow array styles and nested array styles
expectType<JSX.Element>(<div style={[{ color: 'red' }]} />)
expectType<JSX.Element>(
- <div style={[{ color: 'red' }, [{ fontSize: '1em' }]]} />
+ <div style={[{ color: 'red' }, [{ fontSize: '1em' }]]} />,
)
// allow undefined, string, object, array and nested array classes
expectType<JSX.Element>(<div class={{ foo: true, bar: false, baz: true }} />)
expectType<JSX.Element>(<div class={{}} />)
expectType<JSX.Element>(
- <div class={['foo', ['bar'], { baz: true }, [{ qux: true }]]} />
+ <div class={['foo', ['bar'], { baz: true }, [{ qux: true }]]} />,
)
expectType<JSX.Element>(
<div
{ qux: '' },
{ quux: null },
{ corge: undefined },
- { grault: NaN }
+ { grault: NaN },
]}
- />
+ />,
)
expectType<JSX.Element>(
<div
{ bar: 'not-empty' },
{ baz: 1 },
{ qux: {} },
- { quux: [] }
+ { quux: [] },
]}
- />
+ />,
)
// #7955
// infer correct event type
expectType<EventTarget | null>(e.target)
}}
- />
+ />,
)
// built-in types
expectType<JSX.Element>(<Suspense />)
expectType<JSX.Element>(<Suspense key="1" />)
expectType<JSX.Element>(
- <Suspense onResolve={() => {}} onFallback={() => {}} onPending={() => {}} />
+ <Suspense onResolve={() => {}} onFallback={() => {}} onPending={() => {}} />,
)
// @ts-expect-error
;<Suspense onResolve={123} />
<svg
xmlnsXlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
- />
+ />,
)
-import { ref, computed, watch, defineComponent, shallowRef } from 'vue'
+import { computed, defineComponent, ref, shallowRef, watch } from 'vue'
import { expectType } from './utils'
const source = ref('foo')
expectType<string>(value)
expectType<string | undefined>(oldValue)
},
- { immediate: true }
+ { immediate: true },
)
watch(
(values, oldValues) => {
expectType<[string, string, number]>(values)
expectType<[string | undefined, string | undefined, number | undefined]>(
- oldValues
+ oldValues,
)
},
- { immediate: true }
+ { immediate: true },
)
// const array
Readonly<[string | undefined, string | undefined, number | undefined]>
>(oldValues)
},
- { immediate: true }
+ { immediate: true },
)
// should provide correct ref.value inner type to callbacks
const nestedRefSource = ref({
- foo: ref(1)
+ foo: ref(1),
})
watch(nestedRefSource, (v, ov) => {
(v, ov) => {
expectType<number>(v)
expectType<number>(ov)
- }
+ },
)
- }
+ },
})
{
enter?: (node: T, parent: T | undefined) => any
leave?: (node: T, parent: T | undefined) => any
exit?: (node: T) => any
- } & ThisType<{ skip: () => void }>
+ } & ThisType<{ skip: () => void }>,
)
}
-import { reactive, effect, toRaw, isReactive } from '../../src'
+import { effect, isReactive, reactive, toRaw } from '../../src'
describe('reactivity/collections', () => {
function coverCollectionFn(collection: Map<any, any>, fnName: string) {
const map = reactive(new Map())
effect(() => {
dummy = 0
- // eslint-disable-next-line no-unused-vars
for (let [key, num] of map) {
key
dummy += num
effect(() => {
dummy = ''
dummy2 = 0
- // eslint-disable-next-line no-unused-vars
for (let [key, num] of map.entries()) {
dummy += key
dummy2 += num
const map = reactive(raw)
map.set(key, 2)
expect(
- `Reactive Map contains both the raw and reactive`
+ `Reactive Map contains both the raw and reactive`,
).toHaveBeenWarned()
})
-import { reactive, effect, isReactive, toRaw } from '../../src'
+import { effect, isReactive, reactive, toRaw } from '../../src'
describe('reactivity/collections', () => {
function coverCollectionFn(collection: Set<any>, fnName: string) {
const set = reactive(new Set<number>())
effect(() => {
dummy = 0
- // eslint-disable-next-line no-unused-vars
for (let [key, num] of set.entries()) {
key
dummy += num
const set = reactive(raw)
set.delete(key)
expect(
- `Reactive Set contains both the raw and reactive`
+ `Reactive Set contains both the raw and reactive`,
).toHaveBeenWarned()
})
-import { reactive, effect, toRaw, isReactive } from '../../src'
+import { effect, isReactive, reactive, toRaw } from '../../src'
describe('reactivity/collections', () => {
describe('WeakMap', () => {
-import { reactive, isReactive, effect, toRaw } from '../../src'
+import { effect, isReactive, reactive, toRaw } from '../../src'
describe('reactivity/collections', () => {
describe('WeakSet', () => {
sroMap.set(key, {} as any)
expect(
- `Set operation on key "[object Object]" failed: target is readonly.`
+ `Set operation on key "[object Object]" failed: target is readonly.`,
).toHaveBeenWarned()
})
sroMap.get(key)!.foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
})
values1[0].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
expect(isReadonly(values2[0][1])).toBe(false)
values2[0][1].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
val.foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
})
sroSet.add({} as any)
expect(
- `Add operation on key "[object Object]" failed: target is readonly.`
+ `Add operation on key "[object Object]" failed: target is readonly.`,
).toHaveBeenWarned()
})
})
sroSet.add({} as any)
expect(
- `Add operation on key "[object Object]" failed: target is readonly.`
+ `Add operation on key "[object Object]" failed: target is readonly.`,
).toHaveBeenWarned()
values[0].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
values1[0].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
expect(isReadonly(values2[0][1])).toBe(false)
values2[0][1].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
val.foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
})
-import { describe, bench } from 'vitest'
-import { ComputedRef, Ref, computed, ref } from '../src/index'
+import { bench, describe } from 'vitest'
+import { type ComputedRef, type Ref, computed, ref } from '../src/index'
describe('computed', () => {
bench('create computed', () => {
import {
+ type DebuggerEvent,
+ ITERATE_KEY,
+ TrackOpTypes,
+ TriggerOpTypes,
+ type WritableComputedRef,
computed,
- reactive,
effect,
- ref,
- WritableComputedRef,
isReadonly,
- DebuggerEvent,
+ reactive,
+ ref,
toRaw,
- TrackOpTypes,
- ITERATE_KEY,
- TriggerOpTypes
} from '../src'
describe('reactivity/computed', () => {
get: () => n.value + 1,
set: val => {
n.value = val - 1
- }
+ },
})
expect(plusOne.value).toBe(2)
get: () => n.value + 1,
set: val => {
n.value = val - 1
- }
+ },
})
let dummy
;(plusOne as WritableComputedRef<number>).value++ // Type cast to prevent TS from preventing the error
expect(
- 'Write operation failed: computed value is readonly'
+ 'Write operation failed: computed value is readonly',
).toHaveBeenWarnedLast()
})
},
set(v) {
a = v
- }
+ },
})
expect(isReadonly(z)).toBe(false)
expect(isReadonly(z.value.a)).toBe(false)
})
const obj = reactive({ foo: 1, bar: 2 })
const c = computed(() => (obj.foo, 'bar' in obj, Object.keys(obj)), {
- onTrack
+ onTrack,
})
expect(c.value).toEqual(['foo', 'bar'])
expect(onTrack).toHaveBeenCalledTimes(3)
effect: c.effect,
target: toRaw(obj),
type: TrackOpTypes.GET,
- key: 'foo'
+ key: 'foo',
},
{
effect: c.effect,
target: toRaw(obj),
type: TrackOpTypes.HAS,
- key: 'bar'
+ key: 'bar',
},
{
effect: c.effect,
target: toRaw(obj),
type: TrackOpTypes.ITERATE,
- key: ITERATE_KEY
- }
+ key: ITERATE_KEY,
+ },
])
})
type: TriggerOpTypes.SET,
key: 'foo',
oldValue: 1,
- newValue: 2
+ newValue: 2,
})
delete obj.foo
target: toRaw(obj),
type: TriggerOpTypes.DELETE,
key: 'foo',
- oldValue: 2
+ oldValue: 2,
})
})
const cSpy = vi.fn()
const a = ref<null | { v: number }>({
- v: 1
+ v: 1,
})
const b = computed(() => {
return a.value
import {
- reactive,
- effect,
- stop,
- toRaw,
+ type DebuggerEvent,
+ type ReactiveEffectRunner,
TrackOpTypes,
TriggerOpTypes,
- DebuggerEvent,
+ effect,
markRaw,
- shallowReactive,
+ reactive,
readonly,
- ReactiveEffectRunner
+ shallowReactive,
+ stop,
+ toRaw,
} from '../src/index'
import { pauseScheduling, resetScheduling } from '../src/effect'
import { ITERATE_KEY, getDepFromReactive } from '../src/reactiveEffect'
},
get prop() {
return hiddenValue
- }
+ },
})
Object.setPrototypeOf(obj, parent)
effect(() => (dummy = obj.prop))
a: 1,
get b() {
return this.a
- }
+ },
})
let dummy
a: 1,
b() {
return this.a
- }
+ },
})
let dummy
},
get prop() {
return hiddenValue
- }
+ },
})
Object.setPrototypeOf(obj, parent)
effect(() => (dummy = obj.prop))
() => {
dummy = obj.foo
},
- { scheduler }
+ { scheduler },
)
expect(scheduler).not.toHaveBeenCalled()
expect(dummy).toBe(1)
dummy = 'bar' in obj
dummy = Object.keys(obj)
},
- { onTrack }
+ { onTrack },
)
expect(dummy).toEqual(['foo', 'bar'])
expect(onTrack).toHaveBeenCalledTimes(3)
effect: runner.effect,
target: toRaw(obj),
type: TrackOpTypes.GET,
- key: 'foo'
+ key: 'foo',
},
{
effect: runner.effect,
target: toRaw(obj),
type: TrackOpTypes.HAS,
- key: 'bar'
+ key: 'bar',
},
{
effect: runner.effect,
target: toRaw(obj),
type: TrackOpTypes.ITERATE,
- key: ITERATE_KEY
- }
+ key: ITERATE_KEY,
+ },
])
})
() => {
dummy = obj.foo
},
- { onTrigger }
+ { onTrigger },
)
obj.foo!++
type: TriggerOpTypes.SET,
key: 'foo',
oldValue: 1,
- newValue: 2
+ newValue: 2,
})
delete obj.foo
target: toRaw(obj),
type: TriggerOpTypes.DELETE,
key: 'foo',
- oldValue: 2
+ oldValue: 2,
})
})
it('events: onStop', () => {
const onStop = vi.fn()
const runner = effect(() => {}, {
- onStop
+ onStop,
})
stop(runner)
it('markRaw', () => {
const obj = reactive({
foo: markRaw({
- prop: 0
- })
+ prop: 0,
+ }),
})
let dummy
effect(() => {
it('should not be triggered when the value and the old value both are NaN', () => {
const obj = reactive({
- foo: NaN
+ foo: NaN,
})
const fnSpy = vi.fn(() => obj.foo)
effect(fnSpy)
const obj = reactive<{ a: number; b: number; c: 'a' | 'b' }>({
a: 1,
b: 2,
- c: 'a'
+ c: 'a',
})
expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined()
effect(() => obj[obj.c])
import { nextTick, watch, watchEffect } from '@vue/runtime-core'
import {
- reactive,
- effect,
+ type ComputedRef,
EffectScope,
- onScopeDispose,
computed,
+ effect,
+ getCurrentScope,
+ onScopeDispose,
+ reactive,
ref,
- ComputedRef,
- getCurrentScope
} from '../src'
describe('reactivity/effect/scope', () => {
onScopeDispose(spy)
expect(
- '[Vue warn] onScopeDispose() is called when there is no active effect scope to be associated with.'
+ '[Vue warn] onScopeDispose() is called when there is no active effect scope to be associated with.',
).toHaveBeenWarned()
scope.stop()
import {
- ComputedRef,
+ type ComputedRef,
computed,
effect,
reactive,
shallowRef as ref,
- toRaw
+ toRaw,
} from '../src/index'
import { getDepFromReactive } from '../src/reactiveEffect'
-import { ref, isRef } from '../src/ref'
-import { reactive, isReactive, toRaw, markRaw } from '../src/reactive'
+import { isRef, ref } from '../src/ref'
+import { isReactive, markRaw, reactive, toRaw } from '../src/reactive'
import { computed } from '../src/computed'
import { effect } from '../src/effect'
test('nested reactives', () => {
const original = {
nested: {
- foo: 1
+ foo: 1,
},
- array: [{ bar: 2 }]
+ array: [{ bar: 2 }],
}
const observed = reactive(original)
expect(isReactive(observed.nested)).toBe(true)
// writable
const b = computed({
get: () => 1,
- set: () => {}
+ set: () => {},
})
const obj = reactive({ a, b })
// check type
const assertValue = (value: any) => {
reactive(value)
expect(
- `value cannot be made reactive: ${String(value)}`
+ `value cannot be made reactive: ${String(value)}`,
).toHaveBeenWarnedLast()
}
test('markRaw', () => {
const obj = reactive({
foo: { a: 1 },
- bar: markRaw({ b: 2 })
+ bar: markRaw({ b: 2 }),
})
expect(isReactive(obj.foo)).toBe(true)
expect(isReactive(obj.bar)).toBe(false)
foo: Object.preventExtensions({ a: 1 }),
// sealed or frozen objects are considered non-extensible as well
bar: Object.freeze({ a: 1 }),
- baz: Object.seal({ a: 1 })
+ baz: Object.seal({ a: 1 }),
})
expect(isReactive(obj.foo)).toBe(false)
expect(isReactive(obj.bar)).toBe(false)
test('should not observe objects with __v_skip', () => {
const original = {
foo: 1,
- __v_skip: true
+ __v_skip: true,
}
const observed = reactive(original)
expect(isReactive(observed)).toBe(false)
() => {
r[0]++
c.value
- }
+ },
)
}
-import { reactive, isReactive, toRaw } from '../src/reactive'
-import { ref, isRef } from '../src/ref'
+import { isReactive, reactive, toRaw } from '../src/reactive'
+import { isRef, ref } from '../src/ref'
import { effect } from '../src/effect'
describe('reactivity/reactive/Array', () => {
import { bench } from 'vitest'
-import { reactive, computed, ComputedRef } from '../src'
+import { type ComputedRef, computed, reactive } from '../src'
function createMap(obj: Record<string, any>) {
const map = new Map()
import { bench } from 'vitest'
-import { ComputedRef, computed, reactive } from '../src'
+import { type ComputedRef, computed, reactive } from '../src'
bench('create reactive obj', () => {
reactive({ a: 1 })
import {
- reactive,
- readonly,
- toRaw,
+ computed,
+ effect,
+ isProxy,
isReactive,
isReadonly,
markRaw,
- effect,
+ reactive,
+ readonly,
ref,
- isProxy,
- computed
+ toRaw,
} from '../src'
/**
const original = {
foo: 1,
bar: {
- baz: 2
+ baz: 2,
},
- [qux]: 3
+ [qux]: 3,
}
const wrapped: Writable<typeof original> = readonly(original)
wrapped.foo = 2
expect(wrapped.foo).toBe(1)
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).toHaveBeenWarnedLast()
wrapped.bar.baz = 3
expect(wrapped.bar.baz).toBe(2)
expect(
- `Set operation on key "baz" failed: target is readonly.`
+ `Set operation on key "baz" failed: target is readonly.`,
).toHaveBeenWarnedLast()
wrapped[qux] = 4
expect(wrapped[qux]).toBe(3)
expect(
- `Set operation on key "Symbol(qux)" failed: target is readonly.`
+ `Set operation on key "Symbol(qux)" failed: target is readonly.`,
).toHaveBeenWarnedLast()
// @ts-expect-error
delete wrapped.foo
expect(wrapped.foo).toBe(1)
expect(
- `Delete operation on key "foo" failed: target is readonly.`
+ `Delete operation on key "foo" failed: target is readonly.`,
).toHaveBeenWarnedLast()
// @ts-expect-error
delete wrapped.bar.baz
expect(wrapped.bar.baz).toBe(2)
expect(
- `Delete operation on key "baz" failed: target is readonly.`
+ `Delete operation on key "baz" failed: target is readonly.`,
).toHaveBeenWarnedLast()
// @ts-expect-error
delete wrapped[qux]
expect(wrapped[qux]).toBe(3)
expect(
- `Delete operation on key "Symbol(qux)" failed: target is readonly.`
+ `Delete operation on key "Symbol(qux)" failed: target is readonly.`,
).toHaveBeenWarnedLast()
})
wrapped[0] = 1
expect(wrapped[0]).not.toBe(1)
expect(
- `Set operation on key "0" failed: target is readonly.`
+ `Set operation on key "0" failed: target is readonly.`,
).toHaveBeenWarned()
wrapped[0].foo = 2
expect(wrapped[0].foo).toBe(1)
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).toHaveBeenWarned()
// should block length mutation
expect(wrapped.length).toBe(1)
expect(wrapped[0].foo).toBe(1)
expect(
- `Set operation on key "length" failed: target is readonly.`
+ `Set operation on key "length" failed: target is readonly.`,
).toHaveBeenWarned()
// mutation methods invoke set/length internally and thus are blocked as well
const key2 = {}
const original = new Collection([
[key1, {}],
- [key2, {}]
+ [key2, {}],
])
const wrapped = readonly(original)
expect(wrapped).not.toBe(original)
expect(dummy).toBeUndefined()
expect(map.has(key)).toBe(false)
expect(
- `Set operation on key "${key}" failed: target is readonly.`
+ `Set operation on key "${key}" failed: target is readonly.`,
).toHaveBeenWarned()
})
const key2 = {}
const original = new Map([
[key1, {}],
- [key2, {}]
+ [key2, {}],
])
const wrapped: any = readonly(original)
expect(wrapped.size).toBe(2)
const original = reactive(
new Map([
[key1, {}],
- [key2, {}]
- ])
+ [key2, {}],
+ ]),
)
const wrapped: any = readonly(original)
expect(wrapped.size).toBe(2)
const wrapped = readonly(new Collection())
expect(wrapped.clear()).toBeUndefined()
expect(
- `Clear operation failed: target is readonly.`
+ `Clear operation failed: target is readonly.`,
).toHaveBeenWarned()
})
}
expect(dummy).toBe(false)
expect(set.has(key)).toBe(false)
expect(
- `Add operation on key "${key}" failed: target is readonly.`
+ `Add operation on key "${key}" failed: target is readonly.`,
).toHaveBeenWarned()
})
const wrapped = readonly(new Collection())
expect(wrapped.clear()).toBeUndefined()
expect(
- `Clear operation failed: target is readonly.`
+ `Clear operation failed: target is readonly.`,
).toHaveBeenWarned()
})
}
test('markRaw', () => {
const obj = readonly({
foo: { a: 1 },
- bar: markRaw({ b: 2 })
+ bar: markRaw({ b: 2 }),
})
expect(isReadonly(obj.foo)).toBe(true)
expect(isReactive(obj.bar)).toBe(false)
n.value = 2
expect(n.value).toBe(1)
expect(
- `Set operation on key "value" failed: target is readonly.`
+ `Set operation on key "value" failed: target is readonly.`,
).toHaveBeenWarned()
})
expect(rC.value).toBe(true)
expect(
- 'Set operation on key "_dirty" failed: target is readonly.'
+ 'Set operation on key "_dirty" failed: target is readonly.',
).not.toHaveBeenWarned()
// @ts-expect-error - non-existent property
rC.randomProperty = true
expect(
- 'Set operation on key "randomProperty" failed: target is readonly.'
+ 'Set operation on key "randomProperty" failed: target is readonly.',
).toHaveBeenWarned()
})
-import { describe, bench } from 'vitest'
+import { bench, describe } from 'vitest'
import { ref } from '../src/index'
describe('ref', () => {
import {
- ref,
+ type Ref,
effect,
- reactive,
+ isReactive,
isRef,
+ reactive,
+ ref,
toRef,
toRefs,
- Ref,
- isReactive
} from '../src/index'
import { computed } from '@vue/runtime-dom'
-import { shallowRef, unref, customRef, triggerRef } from '../src/ref'
+import { customRef, shallowRef, triggerRef, unref } from '../src/ref'
import {
isReadonly,
isShallow,
readonly,
- shallowReactive
+ shallowReactive,
} from '../src/reactive'
describe('reactivity/ref', () => {
it('should make nested properties reactive', () => {
const a = ref({
- count: 1
+ count: 1,
})
let dummy
effect(() => {
const obj = reactive({
a,
b: {
- c: a
- }
+ c: a,
+ },
})
let dummy1: number
it('should unwrap nested values in types', () => {
const a = {
- b: ref(0)
+ b: ref(0),
}
const c = ref(a)
'1',
{ a: 1 },
() => 0,
- ref(0)
+ ref(0),
]
const tupleRef = ref(tuple)
[Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(),
[Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() },
[Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() },
- [customSymbol]: { arr: [ref(1)] }
+ [customSymbol]: { arr: [ref(1)] },
}
const objRef = ref(obj)
Symbol.toPrimitive,
Symbol.toStringTag,
Symbol.unscopables,
- customSymbol
+ customSymbol,
]
keys.forEach(key => {
test('toRef', () => {
const a = reactive({
- x: 1
+ x: 1,
})
const x = toRef(a, 'x')
expect(isRef(x)).toBe(true)
test('toRefs', () => {
const a = reactive({
x: 1,
- y: 2
+ y: 2,
})
const { x, y } = toRefs(a)
set(newValue: number) {
value = newValue
_trigger = trigger
- }
+ },
}))
expect(isRef(custom)).toBe(true)
isShallow,
reactive,
shallowReactive,
- shallowReadonly
+ shallowReadonly,
} from '../src/reactive'
import { effect } from '../src/effect'
-import { Ref, isRef, ref } from '../src/ref'
+import { type Ref, isRef, ref } from '../src/ref'
describe('shallowReactive', () => {
test('should not make non-reactive properties reactive', () => {
// vuejs/vue#12597
test('should not unwrap refs', () => {
const foo = shallowReactive({
- bar: ref(123)
+ bar: ref(123),
})
expect(isRef(foo.bar)).toBe(true)
expect(foo.bar.value).toBe(123)
test('should not mutate refs', () => {
const original = ref(123)
const foo = shallowReactive<{ bar: Ref<number> | number }>({
- bar: original
+ bar: original,
})
expect(foo.bar).toBe(original)
foo.bar = 234
a = Array.from(shallowSet)
},
{
- onTrack: onTrackFn
- }
+ onTrack: onTrackFn,
+ },
)
expect(a).toMatchObject([])
a = Array.from(shallowArray)
},
{
- onTrack: onTrackFn
- }
+ onTrack: onTrackFn,
+ },
)
expect(a).toMatchObject([])
props.n = 2
expect(props.n).toBe(1)
expect(
- `Set operation on key "n" failed: target is readonly.`
+ `Set operation on key "n" failed: target is readonly.`,
).toHaveBeenWarned()
})
props.n.foo = 2
expect(props.n.foo).toBe(2)
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
sroMap.set(key, {} as any)
expect(
- `Set operation on key "[object Object]" failed: target is readonly.`
+ `Set operation on key "[object Object]" failed: target is readonly.`,
).toHaveBeenWarned()
})
sroMap.get(key)!.foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
})
values1[0].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
expect(isReadonly(values2[0][1])).toBe(false)
values2[0][1].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
val.foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
})
sroSet.add({} as any)
expect(
- `Add operation on key "[object Object]" failed: target is readonly.`
+ `Add operation on key "[object Object]" failed: target is readonly.`,
).toHaveBeenWarned()
})
})
sroSet.add({} as any)
expect(
- `Add operation on key "[object Object]" failed: target is readonly.`
+ `Add operation on key "[object Object]" failed: target is readonly.`,
).toHaveBeenWarned()
values[0].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
values1[0].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
expect(isReadonly(values2[0][1])).toBe(false)
values2[0][1].foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
val.foo = 2
expect(
- `Set operation on key "foo" failed: target is readonly.`
+ `Set operation on key "foo" failed: target is readonly.`,
).not.toHaveBeenWarned()
})
})
import {
+ type Target,
+ isReadonly,
+ isShallow,
reactive,
+ reactiveMap,
readonly,
- toRaw,
- Target,
readonlyMap,
- reactiveMap,
shallowReactiveMap,
shallowReadonlyMap,
- isReadonly,
- isShallow
+ toRaw,
} from './reactive'
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
import {
+ pauseScheduling,
pauseTracking,
+ resetScheduling,
resetTracking,
- pauseScheduling,
- resetScheduling
} from './effect'
-import { track, trigger, ITERATE_KEY } from './reactiveEffect'
+import { ITERATE_KEY, track, trigger } from './reactiveEffect'
import {
- isObject,
- hasOwn,
- isSymbol,
hasChanged,
+ hasOwn,
isArray,
isIntegerKey,
- makeMap
+ isObject,
+ isSymbol,
+ makeMap,
} from '@vue/shared'
import { isRef } from './ref'
import { warn } from './warning'
// function
.filter(key => key !== 'arguments' && key !== 'caller')
.map(key => (Symbol as any)[key])
- .filter(isSymbol)
+ .filter(isSymbol),
)
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
- protected readonly _shallow = false
+ protected readonly _shallow = false,
) {}
get(target: Target, key: string | symbol, receiver: object) {
target: object,
key: string | symbol,
value: unknown,
- receiver: object
+ receiver: object,
): boolean {
let oldValue = (target as any)[key]
if (!this._shallow) {
track(
target,
TrackOpTypes.ITERATE,
- isArray(target) ? 'length' : ITERATE_KEY
+ isArray(target) ? 'length' : ITERATE_KEY,
)
return Reflect.ownKeys(target)
}
if (__DEV__) {
warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
- target
+ target,
)
}
return true
if (__DEV__) {
warn(
`Delete operation on key "${String(key)}" failed: target is readonly.`,
- target
+ target,
)
}
return true
/*#__PURE__*/ new ReadonlyReactiveHandler()
export const shallowReactiveHandlers = /*#__PURE__*/ new MutableReactiveHandler(
- true
+ true,
)
// Props handlers are special in the sense that it should not unwrap top-level
import { toRaw, toReactive, toReadonly } from './reactive'
import {
+ ITERATE_KEY,
+ MAP_KEY_ITERATE_KEY,
track,
trigger,
- ITERATE_KEY,
- MAP_KEY_ITERATE_KEY
} from './reactiveEffect'
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
-import { capitalize, hasOwn, hasChanged, toRawType, isMap } from '@vue/shared'
+import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared'
type CollectionTypes = IterableCollections | WeakCollections
target: MapTypes,
key: unknown,
isReadonly = false,
- isShallow = false
+ isShallow = false,
) {
// #1772: readonly(reactive(Map)) should return readonly + reactive version
// of the value
return function forEach(
this: IterableCollections,
callback: Function,
- thisArg?: unknown
+ thisArg?: unknown,
) {
const observed = this as any
const target = observed[ReactiveFlags.RAW]
function createIterableMethod(
method: string | symbol,
isReadonly: boolean,
- isShallow: boolean
+ isShallow: boolean,
) {
return function (
this: IterableCollections,
track(
rawTarget,
TrackOpTypes.ITERATE,
- isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
+ isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY,
)
// return a wrapped iterator which returns observed versions of the
// values emitted from the real iterator
? { value, done }
: {
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
- done
+ done,
}
},
// iterable protocol
[Symbol.iterator]() {
return this
- }
+ },
}
}
}
const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`,
- toRaw(this)
+ toRaw(this),
)
}
return type === TriggerOpTypes.DELETE
set,
delete: deleteEntry,
clear,
- forEach: createForEach(false, false)
+ forEach: createForEach(false, false),
}
const shallowInstrumentations: Record<string, Function | number> = {
set,
delete: deleteEntry,
clear,
- forEach: createForEach(false, true)
+ forEach: createForEach(false, true),
}
const readonlyInstrumentations: Record<string, Function | number> = {
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
- forEach: createForEach(true, false)
+ forEach: createForEach(true, false),
}
const shallowReadonlyInstrumentations: Record<string, Function | number> = {
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
- forEach: createForEach(true, true)
+ forEach: createForEach(true, true),
}
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
mutableInstrumentations[method as string] = createIterableMethod(
method,
false,
- false
+ false,
)
readonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
- false
+ false,
)
shallowInstrumentations[method as string] = createIterableMethod(
method,
false,
- true
+ true,
)
shallowReadonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
- true
+ true,
)
})
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
- shallowReadonlyInstrumentations
+ shallowReadonlyInstrumentations,
]
}
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
- shallowReadonlyInstrumentations
+ shallowReadonlyInstrumentations,
] = /* #__PURE__*/ createInstrumentations()
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
return (
target: CollectionTypes,
key: string | symbol,
- receiver: CollectionTypes
+ receiver: CollectionTypes,
) => {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
? instrumentations
: target,
key,
- receiver
+ receiver,
)
}
}
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: /*#__PURE__*/ createInstrumentationGetter(false, false)
+ get: /*#__PURE__*/ createInstrumentationGetter(false, false),
}
export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: /*#__PURE__*/ createInstrumentationGetter(false, true)
+ get: /*#__PURE__*/ createInstrumentationGetter(false, true),
}
export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: /*#__PURE__*/ createInstrumentationGetter(true, false)
+ get: /*#__PURE__*/ createInstrumentationGetter(true, false),
}
export const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> =
{
- get: /*#__PURE__*/ createInstrumentationGetter(true, true)
+ get: /*#__PURE__*/ createInstrumentationGetter(true, true),
}
function checkIdentityKeys(
target: CollectionTypes,
has: (key: unknown) => boolean,
- key: unknown
+ key: unknown,
) {
const rawKey = toRaw(key)
if (rawKey !== key && has.call(target, rawKey)) {
`versions of the same object${type === `Map` ? ` as keys` : ``}, ` +
`which can lead to inconsistencies. ` +
`Avoid differentiating between the raw and reactive versions ` +
- `of an object and only use the reactive version if possible.`
+ `of an object and only use the reactive version if possible.`,
)
}
}
-import { DebuggerOptions, ReactiveEffect } from './effect'
-import { Ref, trackRefValue, triggerRefValue } from './ref'
-import { hasChanged, isFunction, NOOP } from '@vue/shared'
+import { type DebuggerOptions, ReactiveEffect } from './effect'
+import { type Ref, trackRefValue, triggerRefValue } from './ref'
+import { NOOP, hasChanged, isFunction } from '@vue/shared'
import { toRaw } from './reactive'
-import { Dep } from './dep'
+import type { Dep } from './dep'
import { DirtyLevels, ReactiveFlags } from './constants'
declare const ComputedRefSymbol: unique symbol
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
- isSSR: boolean
+ isSSR: boolean,
) {
this.effect = new ReactiveEffect(
() => getter(this._value),
- () => triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty)
+ () => triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty),
)
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
*/
export function computed<T>(
getter: ComputedGetter<T>,
- debugOptions?: DebuggerOptions
+ debugOptions?: DebuggerOptions,
): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>,
- debugOptions?: DebuggerOptions
+ debugOptions?: DebuggerOptions,
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
- isSSR = false
+ isSSR = false,
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
export enum TrackOpTypes {
GET = 'get',
HAS = 'has',
- ITERATE = 'iterate'
+ ITERATE = 'iterate',
}
export enum TriggerOpTypes {
SET = 'set',
ADD = 'add',
DELETE = 'delete',
- CLEAR = 'clear'
+ CLEAR = 'clear',
}
export enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
- RAW = '__v_raw'
+ RAW = '__v_raw',
}
export enum DirtyLevels {
NotDirty = 0,
ComputedValueMaybeDirty = 1,
ComputedValueDirty = 2,
- Dirty = 3
+ Dirty = 3,
}
export const createDep = (
cleanup: () => void,
- computed?: ComputedRefImpl<any>
+ computed?: ComputedRefImpl<any>,
): Dep => {
const dep = new Map() as Dep
dep.cleanup = cleanup
import { NOOP, extend } from '@vue/shared'
import type { ComputedRefImpl } from './computed'
-import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants'
+import {
+ DirtyLevels,
+ type TrackOpTypes,
+ type TriggerOpTypes,
+} from './constants'
import type { Dep } from './dep'
-import { EffectScope, recordEffectScope } from './effectScope'
+import { type EffectScope, recordEffectScope } from './effectScope'
export type EffectScheduler = (...args: any[]) => any
public fn: () => T,
public trigger: () => void,
public scheduler?: EffectScheduler,
- scope?: EffectScope
+ scope?: EffectScope,
) {
recordEffectScope(this, scope)
}
*/
export function effect<T = any>(
fn: () => T,
- options?: ReactiveEffectOptions
+ options?: ReactiveEffectOptions,
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
fn = (fn as ReactiveEffectRunner).effect.fn
export function trackEffect(
effect: ReactiveEffect,
dep: Dep,
- debuggerEventExtraInfo?: DebuggerEventExtraInfo
+ debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
if (dep.get(effect) !== effect._trackId) {
dep.set(effect, effect._trackId)
export function triggerEffects(
dep: Dep,
dirtyLevel: DirtyLevels,
- debuggerEventExtraInfo?: DebuggerEventExtraInfo
+ debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
pauseScheduling()
for (const effect of dep.keys()) {
-import { ReactiveEffect } from './effect'
+import type { ReactiveEffect } from './effect'
import { warn } from './warning'
let activeEffectScope: EffectScope | undefined
if (!detached && activeEffectScope) {
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
- this
+ this,
) - 1
}
}
export function recordEffectScope(
effect: ReactiveEffect,
- scope: EffectScope | undefined = activeEffectScope
+ scope: EffectScope | undefined = activeEffectScope,
) {
if (scope && scope.active) {
scope.effects.push(effect)
} else if (__DEV__) {
warn(
`onScopeDispose() is called when there is no active effect scope` +
- ` to be associated with.`
+ ` to be associated with.`,
)
}
}
type ShallowRef,
type ShallowUnwrapRef,
type RefUnwrapBailTypes,
- type CustomRefFactory
+ type CustomRefFactory,
} from './ref'
export {
reactive,
type Raw,
type DeepReadonly,
type ShallowReactive,
- type UnwrapNestedRefs
+ type UnwrapNestedRefs,
} from './reactive'
export {
computed,
type WritableComputedRef,
type WritableComputedOptions,
type ComputedGetter,
- type ComputedSetter
+ type ComputedSetter,
} from './computed'
export { deferredComputed } from './deferredComputed'
export {
type EffectScheduler,
type DebuggerOptions,
type DebuggerEvent,
- type DebuggerEventExtraInfo
+ type DebuggerEventExtraInfo,
} from './effect'
export { trigger, track, ITERATE_KEY } from './reactiveEffect'
export {
effectScope,
EffectScope,
getCurrentScope,
- onScopeDispose
+ onScopeDispose,
} from './effectScope'
export { TrackOpTypes, TriggerOpTypes, ReactiveFlags } from './constants'
-import { isObject, toRawType, def } from '@vue/shared'
+import { def, isObject, toRawType } from '@vue/shared'
import {
mutableHandlers,
readonlyHandlers,
shallowReactiveHandlers,
- shallowReadonlyHandlers
+ shallowReadonlyHandlers,
} from './baseHandlers'
import {
mutableCollectionHandlers,
readonlyCollectionHandlers,
shallowCollectionHandlers,
- shallowReadonlyCollectionHandlers
+ shallowReadonlyCollectionHandlers,
} from './collectionHandlers'
-import type { UnwrapRefSimple, Ref, RawSymbol } from './ref'
+import type { RawSymbol, Ref, UnwrapRefSimple } from './ref'
import { ReactiveFlags } from './constants'
export interface Target {
enum TargetType {
INVALID = 0,
COMMON = 1,
- COLLECTION = 2
+ COLLECTION = 2,
}
function targetTypeMap(rawType: string) {
false,
mutableHandlers,
mutableCollectionHandlers,
- reactiveMap
+ reactiveMap,
)
}
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowreactive}
*/
export function shallowReactive<T extends object>(
- target: T
+ target: T,
): ShallowReactive<T> {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
- shallowReactiveMap
+ shallowReactiveMap,
)
}
* @see {@link https://vuejs.org/api/reactivity-core.html#readonly}
*/
export function readonly<T extends object>(
- target: T
+ target: T,
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers,
- readonlyMap
+ readonlyMap,
)
}
true,
shallowReadonlyHandlers,
shallowReadonlyCollectionHandlers,
- shallowReadonlyMap
+ shallowReadonlyMap,
)
}
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
- proxyMap: WeakMap<Target, any>
+ proxyMap: WeakMap<Target, any>,
) {
if (!isObject(target)) {
if (__DEV__) {
}
const proxy = new Proxy(
target,
- targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
+ targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
import { isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
-import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants'
-import { createDep, Dep } from './dep'
+import { DirtyLevels, type TrackOpTypes, TriggerOpTypes } from './constants'
+import { type Dep, createDep } from './dep'
import {
activeEffect,
pauseScheduling,
resetScheduling,
shouldTrack,
trackEffect,
- triggerEffects
+ triggerEffects,
} from './effect'
// The main WeakMap that stores {target -> key -> dep} connections.
? {
target,
type,
- key
+ key,
}
- : void 0
+ : void 0,
)
}
}
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
- oldTarget?: Map<unknown, unknown> | Set<unknown>
+ oldTarget?: Map<unknown, unknown> | Set<unknown>,
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
key,
newValue,
oldValue,
- oldTarget
+ oldTarget,
}
- : void 0
+ : void 0,
)
}
}
activeEffect,
shouldTrack,
trackEffect,
- triggerEffects
+ triggerEffects,
} from './effect'
import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants'
-import { isArray, hasChanged, IfAny, isFunction, isObject } from '@vue/shared'
+import {
+ type IfAny,
+ hasChanged,
+ isArray,
+ isFunction,
+ isObject,
+} from '@vue/shared'
import {
isProxy,
- toRaw,
isReactive,
- toReactive,
isReadonly,
- isShallow
+ isShallow,
+ toRaw,
+ toReactive,
} from './reactive'
import type { ShallowReactiveMarker } from './reactive'
-import { createDep, Dep } from './dep'
+import { type Dep, createDep } from './dep'
import { ComputedRefImpl } from './computed'
import { getDepFromReactive } from './reactiveEffect'
ref.dep ||
(ref.dep = createDep(
() => (ref.dep = undefined),
- ref instanceof ComputedRefImpl ? ref : undefined
+ ref instanceof ComputedRefImpl ? ref : undefined,
)),
__DEV__
? {
target: ref,
type: TrackOpTypes.GET,
- key: 'value'
+ key: 'value',
}
- : void 0
+ : void 0,
)
}
}
export function triggerRefValue(
ref: RefBase<any>,
dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
- newVal?: any
+ newVal?: any,
) {
ref = toRaw(ref)
const dep = ref.dep
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
- newValue: newVal
+ newValue: newVal,
}
- : void 0
+ : void 0,
)
}
}
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
*/
export function shallowRef<T>(
- value: T
+ value: T,
): Ref extends T
? T extends Ref
? IfAny<T, ShallowRef<T>, T>
constructor(
value: T,
- public readonly __v_isShallow: boolean
+ public readonly __v_isShallow: boolean,
) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
} else {
return Reflect.set(target, key, value, receiver)
}
- }
+ },
}
/**
* that contains refs.
*/
export function proxyRefs<T extends object>(
- objectWithRefs: T
+ objectWithRefs: T,
): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? objectWithRefs
export type CustomRefFactory<T> = (
track: () => void,
- trigger: () => void
+ trigger: () => void,
) => {
get: () => T
set: (value: T) => void
constructor(factory: CustomRefFactory<T>) {
const { get, set } = factory(
() => trackRefValue(this),
- () => triggerRefValue(this)
+ () => triggerRefValue(this),
)
this._get = get
this._set = set
constructor(
private readonly _object: T,
private readonly _key: K,
- private readonly _defaultValue?: T[K]
+ private readonly _defaultValue?: T[K],
) {}
get value() {
* @see {@link https://vuejs.org/api/reactivity-utilities.html#toref}
*/
export function toRef<T>(
- value: T
+ value: T,
): T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
: Ref<UnwrapRef<T>>
export function toRef<T extends object, K extends keyof T>(
object: T,
- key: K
+ key: K,
): ToRef<T[K]>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
- defaultValue: T[K]
+ defaultValue: T[K],
): ToRef<Exclude<T[K], undefined>>
export function toRef(
source: Record<string, any> | MaybeRef,
key?: string,
- defaultValue?: unknown
+ defaultValue?: unknown,
): Ref {
if (isRef(source)) {
return source
function propertyToRef(
source: Record<string, any>,
key: string,
- defaultValue?: unknown
+ defaultValue?: unknown,
) {
const val = source[key]
return isRef(val)
import {
+ type Component,
+ KeepAlive,
+ Suspense,
defineAsyncComponent,
h,
- Component,
- ref,
nextTick,
- Suspense,
- KeepAlive
+ ref,
} from '../src'
import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
import { onActivated } from '../src/components/KeepAlive'
() =>
new Promise(r => {
resolve = r as any
- })
+ }),
)
const toggle = ref(true)
const root = nodeOps.createElement('div')
createApp({
- render: () => (toggle.value ? h(Foo) : null)
+ render: () => (toggle.value ? h(Foo) : null),
}).mount(root)
expect(serializeInner(root)).toBe('<!---->')
resolve = r as any
}),
loadingComponent: () => 'loading',
- delay: 1 // defaults to 200
+ delay: 1, // defaults to 200
})
const toggle = ref(true)
const root = nodeOps.createElement('div')
createApp({
- render: () => (toggle.value ? h(Foo) : null)
+ render: () => (toggle.value ? h(Foo) : null),
}).mount(root)
// due to the delay, initial mount should be empty
resolve = r as any
}),
loadingComponent: () => 'loading',
- delay: 0
+ delay: 0,
})
const toggle = ref(true)
const root = nodeOps.createElement('div')
createApp({
- render: () => (toggle.value ? h(Foo) : null)
+ render: () => (toggle.value ? h(Foo) : null),
}).mount(root)
// with delay: 0, should show loading immediately
new Promise((_resolve, _reject) => {
resolve = _resolve as any
reject = _reject
- })
+ }),
)
const toggle = ref(true)
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => (toggle.value ? h(Foo) : null)
+ render: () => (toggle.value ? h(Foo) : null),
})
const handler = (app.config.errorHandler = vi.fn())
resolve = _resolve as any
reject = _reject
}),
- errorComponent: (props: { error: Error }) => props.error.message
+ errorComponent: (props: { error: Error }) => props.error.message,
})
const toggle = ref(true)
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => (toggle.value ? h(Foo) : null)
+ render: () => (toggle.value ? h(Foo) : null),
})
const handler = (app.config.errorHandler = vi.fn())
resolve = _resolve as any
reject = _reject
}),
- errorComponent: (props: { error: Error }) => props.error.message
+ errorComponent: (props: { error: Error }) => props.error.message,
})
const toggle = ref(true)
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => (toggle.value ? h(Foo) : null)
+ render: () => (toggle.value ? h(Foo) : null),
})
app.mount(root)
await timeout()
expect(serializeInner(root)).toBe('errored out')
expect(
- 'Unhandled error during execution of async component loader'
+ 'Unhandled error during execution of async component loader',
).toHaveBeenWarned()
toggle.value = false
}),
errorComponent: (props: { error: Error }) => props.error.message,
loadingComponent: () => 'loading',
- delay: 1
+ delay: 1,
})
const toggle = ref(true)
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => (toggle.value ? h(Foo) : null)
+ render: () => (toggle.value ? h(Foo) : null),
})
const handler = (app.config.errorHandler = vi.fn())
new Promise(_resolve => {
resolve = _resolve as any
}),
- timeout: 1
+ timeout: 1,
})
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => h(Foo)
+ render: () => h(Foo),
})
const handler = vi.fn()
await timeout(1)
expect(handler).toHaveBeenCalled()
expect(handler.mock.calls[0][0].message).toMatch(
- `Async component timed out after 1ms.`
+ `Async component timed out after 1ms.`,
)
expect(serializeInner(root)).toBe('<!---->')
resolve = _resolve as any
}),
timeout: 1,
- errorComponent: () => 'timed out'
+ errorComponent: () => 'timed out',
})
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => h(Foo)
+ render: () => h(Foo),
})
const handler = (app.config.errorHandler = vi.fn())
delay: 1,
timeout: 16,
errorComponent: () => 'timed out',
- loadingComponent: () => 'loading'
+ loadingComponent: () => 'loading',
})
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => h(Foo)
+ render: () => h(Foo),
})
const handler = (app.config.errorHandler = vi.fn())
app.mount(root)
}),
delay: 1,
timeout: 16,
- loadingComponent: () => 'loading'
+ loadingComponent: () => 'loading',
})
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => h(Foo)
+ render: () => h(Foo),
})
const handler = vi.fn()
app.config.errorHandler = handler
await timeout(16)
expect(handler).toHaveBeenCalled()
expect(handler.mock.calls[0][0].message).toMatch(
- `Async component timed out after 16ms.`
+ `Async component timed out after 16ms.`,
)
// should still display loading
expect(serializeInner(root)).toBe('loading')
() =>
new Promise(_resolve => {
resolve = _resolve as any
- })
+ }),
)
const root = nodeOps.createElement('div')
render: () =>
h(Suspense, null, {
default: () => h('div', [h(Foo), ' & ', h(Foo)]),
- fallback: () => 'loading'
- })
+ fallback: () => 'loading',
+ }),
})
app.mount(root)
new Promise(_resolve => {
resolve = _resolve as any
}),
- suspensible: false
+ suspensible: false,
})
const root = nodeOps.createElement('div')
render: () =>
h(Suspense, null, {
default: () => h('div', [h(Foo), ' & ', h(Foo)]),
- fallback: () => 'loading'
- })
+ fallback: () => 'loading',
+ }),
})
app.mount(root)
() =>
new Promise((_resolve, _reject) => {
reject = _reject
- })
+ }),
)
const root = nodeOps.createElement('div')
render: () =>
h(Suspense, null, {
default: () => h('div', [h(Foo), ' & ', h(Foo)]),
- fallback: () => 'loading'
- })
+ fallback: () => 'loading',
+ }),
})
const handler = (app.config.errorHandler = vi.fn())
} else {
fail()
}
- }
+ },
})
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => h(Foo)
+ render: () => h(Foo),
})
const handler = (app.config.errorHandler = vi.fn())
} else {
fail()
}
- }
+ },
})
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => h(Foo)
+ render: () => h(Foo),
})
const handler = (app.config.errorHandler = vi.fn())
} else {
fail()
}
- }
+ },
})
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => h(Foo)
+ render: () => h(Foo),
})
const handler = (app.config.errorHandler = vi.fn())
() =>
new Promise(r => {
resolve = r as any
- })
+ }),
)
const fooRef = ref<any>(null)
const toggle = ref(true)
const root = nodeOps.createElement('div')
createApp({
- render: () => (toggle.value ? h(Foo, { ref: fooRef }) : null)
+ render: () => (toggle.value ? h(Foo, { ref: fooRef }) : null),
}).mount(root)
expect(serializeInner(root)).toBe('<!---->')
resolve!({
data() {
return {
- id: 'foo'
+ id: 'foo',
}
},
- render: () => 'resolved'
+ render: () => 'resolved',
})
// first time resolve, wait for macro task since there are multiple
// microtasks / .then() calls
() =>
new Promise(r => {
resolve = r as any
- })
+ }),
)
const fooRef = ref<any>(null)
const root = nodeOps.createElement('div')
createApp({
render: () =>
- toggle.value ? [h(Foo, { ref: fooRef }), updater.value] : null
+ toggle.value ? [h(Foo, { ref: fooRef }), updater.value] : null,
}).mount(root)
expect(serializeInner(root)).toBe('<!---->0')
resolve!({
data() {
return {
- id: 'foo'
+ id: 'foo',
}
},
- render: () => 'resolved'
+ render: () => 'resolved',
})
await timeout()
() =>
new Promise(r => {
resolve = r as any
- })
+ }),
)
const updater = ref(0)
onVnodeBeforeUpdate: vi.fn(),
onVnodeUpdated: vi.fn(),
onVnodeBeforeUnmount: vi.fn(),
- onVnodeUnmounted: vi.fn()
+ onVnodeUnmounted: vi.fn(),
}
const toggle = ref(true)
const root = nodeOps.createElement('div')
createApp({
- render: () => (toggle.value ? [h(Foo, vnodeHooks), updater.value] : null)
+ render: () => (toggle.value ? [h(Foo, vnodeHooks), updater.value] : null),
}).mount(root)
expect(serializeInner(root)).toBe('<!---->0')
resolve!({
data() {
return {
- id: 'foo'
+ id: 'foo',
}
},
- render: () => 'resolved'
+ render: () => 'resolved',
})
await timeout()
() =>
new Promise(r => {
resolve = r as any
- })
+ }),
)
const Bar = defineAsyncComponent(() => Promise.resolve(() => 'Bar'))
const toggle = ref(true)
const root = nodeOps.createElement('div')
const app = createApp({
- render: () => h(KeepAlive, [toggle.value ? h(Foo) : h(Bar)])
+ render: () => h(KeepAlive, [toggle.value ? h(Foo) : h(Bar)]),
})
app.mount(root)
spy()
})
return () => 'Foo'
- }
+ },
})
await timeout()
import {
+ type Plugin,
createApp,
+ defineComponent,
+ getCurrentInstance,
h,
+ inject,
nodeOps,
- serializeInner,
provide,
- inject,
+ ref,
resolveComponent,
resolveDirective,
+ serializeInner,
withDirectives,
- Plugin,
- ref,
- getCurrentInstance,
- defineComponent
} from '@vue/runtime-test'
describe('api: createApp', () => {
const Comp = defineComponent({
props: {
count: {
- default: 0
- }
+ default: 0,
+ },
},
setup(props) {
return () => props.count
- }
+ },
})
const root1 = nodeOps.createElement('div')
//#5571 mount multiple apps to the same host element
createApp(Comp).mount(root1)
expect(
- `There is already an app instance mounted on the host container`
+ `There is already an app instance mounted on the host container`,
).toHaveBeenWarned()
// mount with props
const Comp = defineComponent({
props: {
count: {
- default: 0
- }
+ default: 0,
+ },
},
setup(props) {
return () => props.count
- }
+ },
})
const root = nodeOps.createElement('div')
// test override
provide('foo', 3)
return () => h(Child)
- }
+ },
}
const Child = {
inject('__proto__')
} catch (e: any) {}
return () => `${foo},${bar}`
- }
+ },
}
const app = createApp(Root)
setup() {
provide('foo', 'should not be seen')
return () => h('div')
- }
+ },
})
app.provide('foo', 1)
const Root = {
// local override
components: {
- BarBaz: () => 'barbaz-local!'
+ BarBaz: () => 'barbaz-local!',
},
setup() {
// resolve in setup
const BarBaz = resolveComponent('bar-baz') as any
return h('div', [h(FooBar), h(BarBaz)])
}
- }
+ },
}
const app = createApp(Root)
app.component('BarBaz', () => 'barbaz!')
expect(
- 'Component "BarBaz" has already been registered in target app.'
+ 'Component "BarBaz" has already been registered in target app.',
).toHaveBeenWarnedTimes(1)
const root = nodeOps.createElement('div')
const Root = {
// local override
directives: {
- BarBaz: { mounted: spy3 }
+ BarBaz: { mounted: spy3 },
},
setup() {
// resolve in setup
const BarBaz = resolveDirective('bar-baz')!
return withDirectives(h('div'), [[FooBar], [BarBaz]])
}
- }
+ },
}
const app = createApp(Root)
expect(app.directive('FooBar')).toBe(FooBar)
app.directive('BarBaz', {
- mounted: spy2
+ mounted: spy2,
})
app.directive('BarBaz', {
- mounted: spy2
+ mounted: spy2,
})
expect(
- 'Directive "BarBaz" has already been registered in target app.'
+ 'Directive "BarBaz" has already been registered in target app.',
).toHaveBeenWarnedTimes(1)
const root = nodeOps.createElement('div')
app.directive('bind', FooBar)
expect(
- `Do not use built-in directive ids as custom directive id: bind`
+ `Do not use built-in directive ids as custom directive id: bind`,
).toHaveBeenWarned()
})
const mixinA = {
data() {
return {
- a: 1
+ a: 1,
}
},
created(this: any) {
},
mounted() {
calls.push('mixinA mounted')
- }
+ },
}
const mixinB = {
name: 'mixinB',
data() {
return {
- b: 2
+ b: 2,
}
},
created(this: any) {
},
mounted() {
calls.push('mixinB mounted')
- }
+ },
}
const Comp = {
data() {
return {
- c: 3
+ c: 3,
}
},
created(this: any) {
},
render(this: any) {
return `${this.a}${this.b}${this.c}`
- }
+ },
}
const app = createApp(Comp)
app.mixin(mixinA)
app.mixin(mixinB)
expect(
- 'Mixin has already been applied to target app'
+ 'Mixin has already been applied to target app',
).toHaveBeenWarnedTimes(2)
expect(
- 'Mixin has already been applied to target app: mixinB'
+ 'Mixin has already been applied to target app: mixinB',
).toHaveBeenWarnedTimes(1)
const root = nodeOps.createElement('div')
'comp created',
'mixinA mounted',
'mixinB mounted',
- 'comp mounted'
+ 'comp mounted',
])
})
test('use', () => {
const PluginA: Plugin = app => app.provide('foo', 1)
const PluginB: Plugin = {
- install: (app, arg1, arg2) => app.provide('bar', arg1 + arg2)
+ install: (app, arg1, arg2) => app.provide('bar', arg1 + arg2),
}
class PluginC {
someProperty = {}
const foo = inject('foo')
const bar = inject('bar')
return () => `${foo},${bar}`
- }
+ },
}
const app = createApp(Root)
app.use(PluginA)
expect(
- `Plugin has already been applied to target app`
+ `Plugin has already been applied to target app`,
).toHaveBeenWarnedTimes(1)
app.use(PluginD)
expect(
`A plugin must either be a function or an object with an "install" ` +
- `function.`
+ `function.`,
).toHaveBeenWarnedTimes(1)
})
setup() {
const count = ref(0)
return {
- count
+ count,
}
},
render() {
throw error
- }
+ },
}
const app = createApp(Root)
name: 'Hello',
setup() {
ctx = getCurrentInstance()
- }
+ },
}
const app = createApp(Root)
name: 'div',
render() {
return null
- }
+ },
}
const app = createApp(Root)
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
- writable: false
+ writable: false,
})
app.mount(nodeOps.createElement('div'))
expect(
- `Do not use built-in or reserved HTML elements as component id: div`
+ `Do not use built-in or reserved HTML elements as component id: div`,
).toHaveBeenWarned()
})
test('Component.components', () => {
const Root = {
components: {
- div: () => 'div'
+ div: () => 'div',
},
render() {
return null
- }
+ },
}
const app = createApp(Root)
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
- writable: false
+ writable: false,
})
app.mount(nodeOps.createElement('div'))
expect(
- `Do not use built-in or reserved HTML elements as component id: div`
+ `Do not use built-in or reserved HTML elements as component id: div`,
).toHaveBeenWarned()
})
test('Component.directives', () => {
const Root = {
directives: {
- bind: () => {}
+ bind: () => {},
},
render() {
return null
- }
+ },
}
const app = createApp(Root)
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
- writable: false
+ writable: false,
})
app.mount(nodeOps.createElement('div'))
expect(
- `Do not use built-in directive ids as custom directive id: bind`
+ `Do not use built-in directive ids as custom directive id: bind`,
).toHaveBeenWarned()
})
test('register using app.component', () => {
const app = createApp({
- render() {}
+ render() {},
})
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
- writable: false
+ writable: false,
})
app.component('div', () => 'div')
app.mount(nodeOps.createElement('div'))
expect(
- `Do not use built-in or reserved HTML elements as component id: div`
+ `Do not use built-in or reserved HTML elements as component id: div`,
).toHaveBeenWarned()
})
})
foo: 'local',
beforeCreate() {
merged = this.$options.foo
- }
+ },
})
const app = createApp(App)
app.mixin({
- foo: 'global'
+ foo: 'global',
})
app.config.optionMergeStrategies.foo = (a, b) => (a ? `${a},` : ``) + b
const app = createApp({
render() {
return this.foo
- }
+ },
})
app.config.globalProperties.foo = 'hello'
const root = nodeOps.createElement('div')
const Comp = defineComponent({
setup() {
return {
- _: ref(0) // return property "_" should not overwrite "ctx._"
+ _: ref(0), // return property "_" should not overwrite "ctx._"
}
},
render() {
return h('input', {
- ref: 'input'
+ ref: 'input',
})
- }
+ },
})
const root1 = nodeOps.createElement('div')
createApp(Comp).mount(root1)
expect(
- `setup() return property "_" should not start with "$" or "_" which are reserved prefixes for Vue internals.`
+ `setup() return property "_" should not start with "$" or "_" which are reserved prefixes for Vue internals.`,
).toHaveBeenWarned()
})
setup() {
return {
_: ref(0), // return property "_" should not overwrite "ctx._"
- __isScriptSetup: true // mock __isScriptSetup = true
+ __isScriptSetup: true, // mock __isScriptSetup = true
}
},
render() {
return h('input', {
- ref: 'input'
+ ref: 'input',
})
- }
+ },
})
const root1 = nodeOps.createElement('div')
app.$refs.input
expect(
- `TypeError: Cannot read property '__isScriptSetup' of undefined`
+ `TypeError: Cannot read property '__isScriptSetup' of undefined`,
).not.toHaveBeenWarned()
})
setup(_, { expose }) {
expose({
foo: 1,
- bar: ref(2)
+ bar: ref(2),
})
return {
bar: ref(3),
- baz: ref(4)
+ baz: ref(4),
}
- }
+ },
})
const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
render() {},
data() {
return {
- foo: 1
+ foo: 1,
}
},
setup() {
return {
bar: ref(2),
- baz: ref(3)
+ baz: ref(3),
}
},
- expose: ['foo', 'bar']
+ expose: ['foo', 'bar'],
})
const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expose: ['foo'],
data() {
return {
- foo: 1
+ foo: 1,
}
},
setup(_, { expose }) {
expose({
- bar: ref(2)
+ bar: ref(2),
})
return {
bar: ref(3),
- baz: ref(4)
+ baz: ref(4),
}
- }
+ },
})
const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expose: [],
data() {
return {
- foo: 1
+ foo: 1,
}
- }
+ },
})
const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expose: [],
setup(_, { expose }) {
expose({
- foo: 1
+ foo: 1,
})
- }
+ },
})
const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect((this.$parent! as any).bar).toBe(undefined)
expect((this.$root! as any).foo).toBe(1)
expect((this.$root! as any).bar).toBe(undefined)
- }
+ },
})
const Parent = defineComponent({
expose: [],
setup(_, { expose }) {
expose({
- foo: 1
+ foo: 1,
})
return {
- bar: 2
+ bar: 2,
}
},
render() {
return h(Child)
- }
+ },
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
const Component = defineComponent({
setup(_, { expose }) {
expose({
- foo: 1
+ foo: 1,
})
return {
- bar: 2
+ bar: 2,
}
},
render() {
return h('div')
- }
+ },
})
const root = nodeOps.createElement('div')
const vm = createApp(Component).mount(root) as any
const GrandChild = defineComponent({
render() {
return h('div')
- }
+ },
})
const grandChildRef = ref()
},
setup(_, { expose }) {
expose({
- foo: 42
+ foo: 42,
})
return () => h(GrandChild, { ref: grandChildRef })
- }
+ },
})
const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
setup(_, { expose }) {
expose(ref(1))
return () => null
- }
+ },
})
render(h(Comp), nodeOps.createElement('div'))
expect(
- 'expose() should be passed a plain object, received ref'
+ 'expose() should be passed a plain object, received ref',
).toHaveBeenWarned()
})
setup(_, { expose }) {
expose(['focus'])
return () => null
- }
+ },
})
render(h(Comp), nodeOps.createElement('div'))
expect(
- 'expose() should be passed a plain object, received array'
+ 'expose() should be passed a plain object, received array',
).toHaveBeenWarned()
})
setup(_, { expose }) {
expose(() => null)
return () => null
- }
+ },
})
render(h(Comp), nodeOps.createElement('div'))
expect(
- 'expose() should be passed a plain object, received function'
+ 'expose() should be passed a plain object, received function',
).toHaveBeenWarned()
})
})
import {
+ type InjectionKey,
+ type Ref,
+ defineComponent,
h,
- provide,
+ hasInjectionContext,
inject,
- InjectionKey,
- ref,
nextTick,
- Ref,
- readonly,
+ provide,
reactive,
- defineComponent,
- hasInjectionContext
+ readonly,
+ ref,
} from '../src/index'
-import { render, nodeOps, serialize, createApp } from '@vue/runtime-test'
+import { createApp, nodeOps, render, serialize } from '@vue/runtime-test'
// reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject
describe('api: provide/inject', () => {
setup() {
provide('foo', 1)
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
setup() {
const foo = inject('foo')
return () => foo
- }
+ },
}
const root = nodeOps.createElement('div')
setup() {
provide(key, 1)
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
setup() {
const foo = inject(key) || 1
return () => foo + 1
- }
+ },
}
const root = nodeOps.createElement('div')
setup() {
provide('foo', 'foo')
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
// default value should be used if value is not provided
const bar = inject('bar', 'bar')
return () => foo + bar
- }
+ },
}
const root = nodeOps.createElement('div')
const Provider = {
setup() {
return () => h(Consumer)
- }
+ },
}
const Consumer = defineComponent({
from: 'foo',
default() {
return this!.$options.name
- }
- }
+ },
+ },
},
render() {
- // @ts-ignore
return this.foo
- }
+ },
})
const root = nodeOps.createElement('div')
provide('foo', 'foo')
provide('bar', 'bar')
return () => h(ProviderTwo)
- }
+ },
}
const ProviderTwo = {
provide('foo', 'fooOverride')
provide('baz', 'baz')
return () => h(Consumer)
- }
+ },
}
const Consumer = {
const bar = inject('bar')
const baz = inject('baz')
return () => [foo, bar, baz].join(',')
- }
+ },
}
const root = nodeOps.createElement('div')
setup() {
provide('count', count)
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
setup() {
const count = inject<Ref<number>>('count')!
return () => count.value
- }
+ },
}
const root = nodeOps.createElement('div')
setup() {
provide('count', readonly(count))
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
// should not work
count.value++
return () => count.value
- }
+ },
}
const root = nodeOps.createElement('div')
expect(serialize(root)).toBe(`<div>1</div>`)
expect(
- `Set operation on key "value" failed: target is readonly`
+ `Set operation on key "value" failed: target is readonly`,
).toHaveBeenWarned()
// source mutation should still work
setup() {
provide('state', rootState)
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
setup() {
const state = inject<typeof rootState>('state')!
return () => state.count
- }
+ },
}
const root = nodeOps.createElement('div')
setup() {
provide('state', readonly(rootState))
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
// should not work
state.count++
return () => state.count
- }
+ },
}
const root = nodeOps.createElement('div')
expect(serialize(root)).toBe(`<div>1</div>`)
expect(
- `Set operation on key "count" failed: target is readonly`
+ `Set operation on key "count" failed: target is readonly`,
).toHaveBeenWarned()
rootState.count++
const Provider = {
setup() {
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
const foo = inject('foo')
expect(foo).toBeUndefined()
return () => foo
- }
+ },
}
const root = nodeOps.createElement('div')
const Provider = {
setup() {
return () => h(Middle)
- }
+ },
}
const Middle = {
- render: () => h(Consumer)
+ render: () => h(Consumer),
}
const Consumer = {
setup() {
const foo = inject('foo', undefined)
return () => foo
- }
+ },
}
const root = nodeOps.createElement('div')
provide('foo', 'foo')
const injection = inject('foo', null)
return () => injection
- }
+ },
}
const root = nodeOps.createElement('div')
setup() {
expect(hasInjectionContext()).toBe(true)
return () => null
- }
+ },
}
const root = nodeOps.createElement('div')
import {
- onBeforeMount,
+ TrackOpTypes,
h,
- nodeOps,
- render,
- serializeInner,
- onMounted,
- ref,
- onBeforeUpdate,
nextTick,
- onUpdated,
+ nodeOps,
+ onBeforeMount,
onBeforeUnmount,
- onUnmounted,
+ onBeforeUpdate,
+ onMounted,
onRenderTracked,
+ onRenderTriggered,
+ onUnmounted,
+ onUpdated,
reactive,
- TrackOpTypes,
- onRenderTriggered
+ ref,
+ render,
+ serializeInner,
} from '@vue/runtime-test'
-import { ITERATE_KEY, DebuggerEvent, TriggerOpTypes } from '@vue/reactivity'
+import {
+ type DebuggerEvent,
+ ITERATE_KEY,
+ TriggerOpTypes,
+} from '@vue/reactivity'
// reference: https://vue-composition-api-rfc.netlify.com/api.html#lifecycle-hooks
setup() {
onBeforeMount(fn)
return () => h('div')
- }
+ },
}
render(h(Comp), root)
expect(fn).toHaveBeenCalledTimes(1)
setup() {
onMounted(fn)
return () => h('div')
- }
+ },
}
render(h(Comp), root)
expect(fn).toHaveBeenCalledTimes(1)
setup() {
onBeforeUpdate(fn)
return () => h('div', count.value)
- }
+ },
}
render(h(Comp), root)
renderSpy()
return h('div', count.value)
}
- }
+ },
}
render(h(Comp), root)
expect(renderSpy).toHaveBeenCalledTimes(1)
setup() {
onUpdated(fn)
return () => h('div', count.value)
- }
+ },
}
render(h(Comp), root)
const Comp = {
setup() {
return () => (toggle.value ? h(Child) : null)
- }
+ },
}
const Child = {
setup() {
onBeforeUnmount(fn)
return () => h('div')
- }
+ },
}
render(h(Comp), root)
const Comp = {
setup() {
return () => (toggle.value ? h(Child) : null)
- }
+ },
}
const Child = {
setup() {
onUnmounted(fn)
return () => h('div')
- }
+ },
}
render(h(Comp), root)
const Comp = {
setup() {
return () => (toggle.value ? h(Child) : null)
- }
+ },
}
const Child = {
onBeforeUnmount(fn)
})
return () => h('div')
- }
+ },
}
render(h(Comp), root)
onBeforeUnmount(() => calls.push('root onBeforeUnmount'))
onUnmounted(() => calls.push('root onUnmounted'))
return () => h(Mid, { count: count.value })
- }
+ },
}
const Mid = {
onBeforeUnmount(() => calls.push('mid onBeforeUnmount'))
onUnmounted(() => calls.push('mid onUnmounted'))
return () => h(Child, { count: props.count })
- }
+ },
}
const Child = {
onBeforeUnmount(() => calls.push('child onBeforeUnmount'))
onUnmounted(() => calls.push('child onUnmounted'))
return () => h('div', props.count)
- }
+ },
}
// mount
'child onBeforeMount',
'child onMounted',
'mid onMounted',
- 'root onMounted'
+ 'root onMounted',
])
calls.length = 0
'child onBeforeUpdate',
'child onUpdated',
'mid onUpdated',
- 'root onUpdated'
+ 'root onUpdated',
])
calls.length = 0
'child onBeforeUnmount',
'child onUnmounted',
'mid onUnmounted',
- 'root onUnmounted'
+ 'root onUnmounted',
])
})
onRenderTracked(onTrack)
return () =>
h('div', [obj.foo, 'bar' in obj, Object.keys(obj).join('')])
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
{
target: obj,
type: TrackOpTypes.GET,
- key: 'foo'
+ key: 'foo',
},
{
target: obj,
type: TrackOpTypes.HAS,
- key: 'bar'
+ key: 'bar',
},
{
target: obj,
type: TrackOpTypes.ITERATE,
- key: ITERATE_KEY
- }
+ key: ITERATE_KEY,
+ },
])
})
onRenderTriggered(onTrigger)
return () =>
h('div', [obj.foo, 'bar' in obj, Object.keys(obj).join('')])
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
type: TriggerOpTypes.SET,
key: 'foo',
oldValue: 1,
- newValue: 2
+ newValue: 2,
})
delete obj.bar
expect(events[1]).toMatchObject({
type: TriggerOpTypes.DELETE,
key: 'bar',
- oldValue: 2
+ oldValue: 2,
})
;(obj as any).baz = 3
await nextTick()
expect(events[2]).toMatchObject({
type: TriggerOpTypes.ADD,
key: 'baz',
- newValue: 3
+ newValue: 3,
})
})
const Comp = {
setup() {
return () => (toggle.value ? [h(Child), h(Child)] : null)
- }
+ },
}
const Child = {
setup() {
onMounted(fn)
onBeforeUnmount(fn)
return () => h('div')
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
/**
* @vitest-environment jsdom
*/
-import { type Mock } from 'vitest'
+import type { Mock } from 'vitest'
import {
+ type TestElement,
+ computed,
+ createApp,
+ defineComponent,
h,
+ nextTick,
nodeOps,
+ ref,
render,
+ renderToString,
serializeInner,
triggerEvent,
- TestElement,
- nextTick,
- renderToString,
- ref,
- defineComponent,
- createApp,
- computed
} from '@vue/runtime-test'
import { render as domRender } from 'vue'
const Comp = defineComponent({
data() {
return {
- foo: 1
+ foo: 1,
}
},
render() {
{
onClick: () => {
this.foo++
- }
+ },
},
- this.foo
+ this.foo,
)
- }
+ },
})
const root = nodeOps.createElement('div')
render(h(Comp), root)
const Comp = defineComponent({
data() {
return {
- foo: 1
+ foo: 1,
}
},
computed: {
bar(): number {
return this.foo + 1
},
- baz: (vm: any): number => vm.bar + 1
+ baz: (vm: any): number => vm.bar + 1,
},
render() {
return h(
{
onClick: () => {
this.foo++
- }
+ },
},
- this.bar + this.baz
+ this.bar + this.baz,
)
- }
+ },
})
const root = nodeOps.createElement('div')
render(h(Comp), root)
// #3300 method on ctx should be overwritable
this.incBy = this.incBy.bind(this, 2)
return {
- foo: 1
+ foo: 1,
}
},
methods: {
},
incBy(n = 0) {
this.foo += n
- }
+ },
},
render() {
return h(
'div',
{
onClick: this.inc,
- onFoo: this.incBy
+ onFoo: this.incBy,
},
- this.foo
+ this.foo,
)
- }
+ },
})
const root = nodeOps.createElement('div')
render(h(Comp), root)
methods: {
foo() {
return 'foo'
- }
+ },
},
render() {
return this.foo()
- }
+ },
})
app.config.globalProperties.foo = () => 'bar'
foo: 1,
bar: 2,
baz: {
- qux: 3
+ qux: 3,
},
qux: 4,
dot: {
- path: 5
- }
+ path: 5,
+ },
}
},
watch: {
bar: spyB,
baz: {
handler: spyC,
- deep: true
+ deep: true,
},
qux: {
- handler: 'onQuxChange'
+ handler: 'onQuxChange',
},
- 'dot.path': spyE
+ 'dot.path': spyE,
},
methods: {
onFooChange: spyA,
- onQuxChange: spyD
+ onQuxChange: spyD,
},
render() {
ctx = this
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
foo: 1,
bar: 2,
baz: {
- qux: 3
- }
+ qux: 3,
+ },
}
},
watch: {
baz: [
{
handler: spyC,
- deep: true
- }
- ]
+ deep: true,
+ },
+ ],
},
methods: {
- onFooChange: spyA
+ onFooChange: spyA,
},
render() {
ctx = this
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
const mixinA = {
data() {
return {
- fromMixinA: ''
+ fromMixinA: '',
}
},
watch: {
obj: {
handler(this: any, to: any) {
this.fromMixinA = to
- }
- }
- }
+ },
+ },
+ },
}
const mixinB = {
data() {
return {
- fromMixinB: ''
+ fromMixinB: '',
}
},
watch: {
- obj: 'setMixinB'
+ obj: 'setMixinB',
},
methods: {
setMixinB(this: any, to: any) {
this.fromMixinB = to
- }
- }
+ },
+ },
}
let vm: any
mixins: [mixinA, mixinB],
data: () => ({
obj: 'foo',
- fromComp: ''
+ fromComp: '',
}),
watch: {
obj(this: any, to: any) {
this.fromComp = to
- }
+ },
},
mounted() {
vm = this
- }
+ },
}
const root = nodeOps.createElement('div')
const Root = defineComponent({
data() {
return {
- a: 1
+ a: 1,
}
},
provide() {
return {
a: this.a,
- [symbolKey]: 2
+ [symbolKey]: 2,
}
},
render() {
h(ChildG),
h(ChildH),
h(ChildI),
- h(ChildJ)
+ h(ChildJ),
]
- }
+ },
})
const defineChild = (injectOptions: any, injectedKey = 'b') =>
inject: injectOptions,
render() {
return this[injectedKey]
- }
+ },
}) as any
const ChildA = defineChild(['a'], 'a')
const ChildB = defineChild({ b: 'a' })
const ChildC = defineChild({
b: {
- from: 'a'
- }
+ from: 'a',
+ },
})
const ChildD = defineChild(
{
a: {
- default: () => 0
- }
+ default: () => 0,
+ },
},
- 'a'
+ 'a',
)
const ChildE = defineChild({
b: {
from: 'c',
- default: 2
- }
+ default: 2,
+ },
})
const ChildF = defineChild({
b: {
from: 'c',
- default: () => 3
- }
+ default: () => 3,
+ },
})
const ChildG = defineChild({
b: {
- default: 4
- }
+ default: 4,
+ },
})
const ChildH = defineChild({
b: {
- default: () => 5
- }
+ default: () => 5,
+ },
})
const ChildI = defineChild({
- b: symbolKey
+ b: symbolKey,
})
const ChildJ = defineChild({
b: {
- from: symbolKey
- }
+ from: symbolKey,
+ },
})
expect(renderToString(h(Root))).toBe(`1111234522`)
})
provide() {
return {
n,
- np
+ np,
}
},
- render: () => h(Child)
+ render: () => h(Child),
})
const Child = defineComponent({
inject: ['n', 'np'],
render(this: any) {
return this.n + this.np
- }
+ },
})
const app = createApp(Parent)
const root = nodeOps.createElement('div')
const Base = defineComponent({
data() {
return {
- a: 1
+ a: 1,
}
},
provide() {
return {
- a: this.a
+ a: this.a,
}
- }
+ },
})
const Child = {
inject: ['a'],
render() {
return (this as any).a
- }
+ },
}
const Root = defineComponent({
extends: Base,
render() {
return h(Child)
- }
+ },
})
expect(renderToString(h(Root))).toBe(`1`)
})
},
render() {
return h(Mid, { count: count.value })
- }
+ },
}
const Mid = {
},
render(this: any) {
return h(Child, { count: this.$props.count })
- }
+ },
}
const Child = {
},
render(this: any) {
return h('div', this.$props.count)
- }
+ },
}
// mount
'child onBeforeMount',
'child onMounted',
'mid onMounted',
- 'root onMounted'
+ 'root onMounted',
])
calls.length = 0
'child onBeforeUpdate',
'child onUpdated',
'mid onUpdated',
- 'root onUpdated'
+ 'root onUpdated',
])
calls.length = 0
'child onBeforeUnmount',
'child onUnmounted',
'mid onUnmounted',
- 'root onUnmounted'
+ 'root onUnmounted',
])
})
const mixinA = defineComponent({
data() {
return {
- a: 1
+ a: 1,
}
},
created(this: any) {
},
mounted() {
calls.push('mixinA mounted')
- }
+ },
})
const mixinB = defineComponent({
props: {
bP: {
- type: String
- }
+ type: String,
+ },
},
data() {
return {
- b: 2
+ b: 2,
}
},
created(this: any) {
},
mounted() {
calls.push('mixinB mounted')
- }
+ },
})
const mixinC = defineComponent({
props: ['cP1', 'cP2'],
data() {
return {
- c: 3
+ c: 3,
}
},
created() {
},
mounted() {
calls.push('mixinC mounted')
- }
+ },
})
const Comp = defineComponent({
props: {
- aaa: String
+ aaa: String,
},
mixins: [mixinA, mixinB, mixinC],
data() {
return {
c: 4,
- z: 4
+ z: 4,
}
},
created() {
},
render() {
return `${this.a}${this.b}${this.c}`
- }
+ },
})
expect(renderToString(h(Comp))).toBe(`124`)
expect(calls).toEqual([
'mixinA mounted',
'mixinB mounted',
'mixinC mounted',
- 'comp mounted'
+ 'comp mounted',
])
})
const Comp = {
mixins: [
{
- render: () => 'from mixin'
- }
- ]
+ render: () => 'from mixin',
+ },
+ ],
}
expect(renderToString(h(Comp))).toBe('from mixin')
})
},
created() {
calls.push('mixinA created')
- }
+ },
}
const extendA = {
},
created() {
calls.push('extendA created')
- }
+ },
}
const Comp = {
},
created() {
calls.push('self created')
- }
+ },
}
expect(renderToString(h(Comp))).toBe(`123`)
'self beforeCreate',
'mixinA created',
'extendA created',
- 'self created'
+ 'self created',
])
})
test('unlikely mixin usage', () => {
const MixinA = {
- data() {}
+ data() {},
}
const MixinB = {
- data() {}
+ data() {},
}
defineComponent({
// @ts-expect-error edge case after #7963, unlikely to happen in practice
// since the user will want to type the mixins themselves.
mixins: [defineComponent(MixinA), defineComponent(MixinB)],
// @ts-expect-error
- data() {}
+ data() {},
})
})
},
created() {
calls.push('extendA created')
- }
+ },
}
const mixinA = {
},
created() {
calls.push('mixinA created')
- }
+ },
}
const Comp = {
},
created() {
calls.push('self created')
- }
+ },
}
expect(renderToString(h(Comp))).toBe(`123`)
'self beforeCreate',
'extendA created',
'mixinA created',
- 'self created'
+ 'self created',
])
})
data() {
return {
a: 1,
- b: 1
+ b: 1,
}
},
methods: {
- sayA() {}
+ sayA() {},
},
mounted(this: any) {
expect(this.a).toBe(1)
expect(this.b).toBe(2)
calls.push('base')
- }
+ },
})
const Comp = defineComponent({
extends: Base,
data() {
return {
- b: 2
+ b: 2,
}
},
mounted() {
},
render() {
return `${this.a}${this.b}`
- }
+ },
})
expect(renderToString(h(Comp))).toBe(`12`)
data() {
return {
a: 1,
- x: 'base'
+ x: 'base',
}
},
methods: {
- sayA() {}
+ sayA() {},
},
mounted(this: any) {
expect(this.a).toBe(1)
expect(this.b).toBeTruthy()
expect(this.c).toBe(2)
calls.push('base')
- }
+ },
})
const Mixin = defineComponent({
data() {
return {
b: true,
- x: 'mixin'
+ x: 'mixin',
}
},
mounted(this: any) {
expect(this.b).toBeTruthy()
expect(this.c).toBe(2)
calls.push('mixin')
- }
+ },
})
const Comp = defineComponent({
extends: Base,
mixins: [Mixin],
data() {
return {
- c: 2
+ c: 2,
}
},
mounted() {
},
render() {
return `${this.a}${this.b}${this.c}${this.x}`
- }
+ },
})
expect(renderToString(h(Comp))).toBe(`1true2mixin`)
},
created() {
calls.push('createdA')
- }
+ },
}
const BaseB = {
extends: BaseA,
},
created() {
calls.push('createdB')
- }
+ },
}
const MixinA = {
},
created() {
calls.push('createdC')
- }
+ },
}
const MixinB = {
mixins: [MixinA],
},
created() {
calls.push('createdD')
- }
+ },
}
const Comp = {
created() {
calls.push('selfCreated')
},
- render() {}
+ render() {},
}
renderToString(h(Comp))
'createdB',
'createdC',
'createdD',
- 'selfCreated'
+ 'selfCreated',
])
})
test('flatten merged options', async () => {
const MixinBase = {
- msg1: 'base'
+ msg1: 'base',
}
const ExtendsBase = {
- msg2: 'base'
+ msg2: 'base',
}
const Mixin = {
- mixins: [MixinBase]
+ mixins: [MixinBase],
}
const Extends = {
- extends: ExtendsBase
+ extends: ExtendsBase,
}
const Comp = defineComponent({
extends: defineComponent(Extends),
mixins: [defineComponent(Mixin)],
render() {
return `${this.$options.msg1},${this.$options.msg2}`
- }
+ },
})
expect(renderToString(h(Comp))).toBe('base,base')
test('extends template', () => {
const Comp = {
extends: {
- template: `<h1>Foo</h1>`
- }
+ template: `<h1>Foo</h1>`,
+ },
}
const root = document.createElement('div') as any
test('options defined in component have higher priority', async () => {
const Mixin = {
- msg1: 'base'
+ msg1: 'base',
}
const Extends = {
- msg2: 'base'
+ msg2: 'base',
}
const Comp = defineComponent({
msg1: 'local',
mixins: [defineComponent(Mixin)],
render() {
return `${this.$options.msg1},${this.$options.msg2}`
- }
+ },
})
expect(renderToString(h(Comp))).toBe('local,local')
const Comp = defineComponent({
setup() {
return {
- count: ref(0)
+ count: ref(0),
}
},
data() {
return {
- plusOne: (this as any).count + 1
+ plusOne: (this as any).count + 1,
}
},
computed: {
plusTwo(): number {
return this.count + 2
- }
+ },
},
methods: {
inc() {
this.count++
- }
+ },
},
render() {
return h(
'div',
{
- onClick: this.inc
+ onClick: this.inc,
},
- `${this.count},${this.plusOne},${this.plusTwo}`
+ `${this.count},${this.plusOne},${this.plusTwo}`,
)
- }
+ },
})
const root = nodeOps.createElement('div')
render(h(Comp), root)
const mixin1 = {
data() {
return {
- mixin1Data: 'mixin1'
+ mixin1Data: 'mixin1',
}
},
- methods: {}
+ methods: {},
}
const watchSpy = vi.fn()
const mixin2 = {
watch: {
- mixin3Data: watchSpy
- }
+ mixin3Data: watchSpy,
+ },
}
const mixin3 = {
data() {
return {
- mixin3Data: 'mixin3'
+ mixin3Data: 'mixin3',
}
},
- methods: {}
+ methods: {},
}
let vm: any
render() {},
created() {
vm = this
- }
+ },
}
const root = nodeOps.createElement('div')
test('injection from closest ancestor', () => {
const Root = defineComponent({
provide: {
- a: 'root'
+ a: 'root',
},
render() {
return [h(Mid), ' ', h(MidWithProvide), ' ', h(MidWithMixinProvide)]
- }
+ },
})
const Mid = {
render() {
return h(Child)
- }
+ },
} as any
const MidWithProvide = {
provide: {
- a: 'midWithProvide'
+ a: 'midWithProvide',
},
render() {
return h(Child)
- }
+ },
} as any
const mixin = {
provide: {
- a: 'midWithMixinProvide'
- }
+ a: 'midWithMixinProvide',
+ },
}
const MidWithMixinProvide = {
mixins: [mixin],
render() {
return h(Child)
- }
+ },
} as any
const Child = {
inject: ['a'],
render() {
return this.a
- }
+ },
} as any
expect(renderToString(h(Root))).toBe(
- 'root midWithProvide midWithMixinProvide'
+ 'root midWithProvide midWithMixinProvide',
)
})
const mixin = {
data() {
return { foo: 1, bar: 2 }
- }
+ },
}
createApp({
mixins: [mixin],
data() {
return {
foo: 3,
- baz: 4
+ baz: 4,
}
},
created() {
expect(this.$options.data()).toEqual({
foo: 3,
bar: 2,
- baz: 4
+ baz: 4,
})
},
- render: () => null
+ render: () => null,
}).mount(nodeOps.createElement('div'))
})
test('this.$options.inject', () => {
const mixin = {
- inject: ['a']
+ inject: ['a'],
}
const app = createApp({
mixins: [mixin],
expect(this.b).toBe(2)
expect(this.c).toBe(3)
},
- render: () => null
+ render: () => null,
})
app.provide('a', 1)
test('this.$options.provide', () => {
const mixin = {
provide: {
- a: 1
- }
+ a: 1,
+ },
}
createApp({
mixins: [mixin],
provide() {
return {
- b: 2
+ b: 2,
}
},
created() {
expect(this.$options.provide).toBeInstanceOf(Function)
expect(this.$options.provide()).toEqual({ a: 1, b: 2 })
},
- render: () => null
+ render: () => null,
}).mount(nodeOps.createElement('div'))
})
const mixin = {
mounted() {},
beforeUnmount() {},
- unmounted() {}
+ unmounted() {},
}
createApp({
mixins: [mixin],
expect(this.$options.unmounted).toBeInstanceOf(Array)
expect(this.$options.unmounted.length).toBe(2)
},
- render: () => null
+ render: () => null,
}).mount(nodeOps.createElement('div'))
})
test('this.$options[asset-name]', () => {
const mixin = {
components: {
- a: {}
+ a: {},
},
directives: {
- d1: {}
- }
+ d1: {},
+ },
}
createApp({
mixins: [mixin],
components: {
- b: {}
+ b: {},
},
directives: {
- d2: {}
+ d2: {},
},
created() {
expect('a' in this.$options.components).toBe(true)
expect('d1' in this.$options.directives).toBe(true)
expect('d2' in this.$options.directives).toBe(true)
},
- render: () => null
+ render: () => null,
}).mount(nodeOps.createElement('div'))
})
test('this.$options.methods', () => {
const mixin = {
methods: {
- fn1() {}
- }
+ fn1() {},
+ },
}
createApp({
mixins: [mixin],
methods: {
- fn2() {}
+ fn2() {},
},
created() {
expect(this.$options.methods.fn1).toBeInstanceOf(Function)
expect(this.$options.methods.fn2).toBeInstanceOf(Function)
},
- render: () => null
+ render: () => null,
}).mount(nodeOps.createElement('div'))
})
test('this.$options.computed', () => {
const mixin = {
computed: {
- c1() {}
- }
+ c1() {},
+ },
}
createApp({
mixins: [mixin],
computed: {
- c2() {}
+ c2() {},
},
created() {
expect(this.$options.computed.c1).toBeInstanceOf(Function)
expect(this.$options.computed.c2).toBeInstanceOf(Function)
},
- render: () => null
+ render: () => null,
}).mount(nodeOps.createElement('div'))
})
this.$options.computed = {}
}
this.$options.computed.value = () => count.value
- }
+ },
}
const root = nodeOps.createElement('div')
createApp({
mixins: [mixin],
render(this: any) {
return this.value
- }
+ },
}).mount(root)
expect(serializeInner(root)).toBe('0')
watch: {
foo: 'notExistingMethod',
foo2: {
- handler: 'notExistingMethod2'
- }
+ handler: 'notExistingMethod2',
+ },
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- 'Invalid watch handler specified by key "notExistingMethod"'
+ 'Invalid watch handler specified by key "notExistingMethod"',
).toHaveBeenWarned()
expect(
- 'Invalid watch handler specified by key "notExistingMethod2"'
+ 'Invalid watch handler specified by key "notExistingMethod2"',
).toHaveBeenWarned()
})
test('Invalid watch option', () => {
const Comp = {
watch: { foo: true },
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
const Comp = {
computed: {
foo: {
- set() {}
- }
+ set() {},
+ },
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
const Comp = {
computed: {
foo: {
- get() {}
- }
+ get() {},
+ },
},
mounted() {
instance = this
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
instance.foo = 1
expect(
- 'Write operation failed: computed property "foo" is readonly'
+ 'Write operation failed: computed property "foo" is readonly',
).toHaveBeenWarned()
})
const Comp = {
data() {
return {
- a: 1
+ a: 1,
}
},
provide() {
return {
- a: this.a
+ a: this.a,
}
},
render() {
return [h(ChildA)]
- }
+ },
} as any
const ChildA = {
props: { a: Number },
inject: ['a'],
render() {
return this.a
- }
+ },
} as any
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Inject property "a" is already defined in Props.`
+ `Inject property "a" is already defined in Props.`,
).toHaveBeenWarned()
})
test('methods property is not a function', () => {
const Comp = {
methods: {
- foo: 1
+ foo: 1,
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
`Method "foo" has type "number" in the component definition. ` +
- `Did you reference the function correctly?`
+ `Did you reference the function correctly?`,
).toHaveBeenWarned()
})
test('methods property is already declared in props', () => {
const Comp = {
props: {
- foo: Number
+ foo: Number,
},
methods: {
- foo() {}
+ foo() {},
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Methods property "foo" is already defined in Props.`
+ `Methods property "foo" is already defined in Props.`,
).toHaveBeenWarned()
})
const Comp = {
data() {
return {
- a: 1
+ a: 1,
}
},
provide() {
return {
- a: this.a
+ a: this.a,
}
},
render() {
return [h(ChildA)]
- }
+ },
} as any
const ChildA = {
methods: {
- a: () => null
+ a: () => null,
},
inject: ['a'],
render() {
return this.a
- }
+ },
} as any
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Methods property "a" is already defined in Inject.`
+ `Methods property "a" is already defined in Inject.`,
).toHaveBeenWarned()
})
const Comp = {
props: { foo: Number },
data: () => ({
- foo: 1
+ foo: 1,
}),
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Data property "foo" is already defined in Props.`
+ `Data property "foo" is already defined in Props.`,
).toHaveBeenWarned()
})
const Comp = {
data() {
return {
- a: 1
+ a: 1,
}
},
provide() {
return {
- a: this.a
+ a: this.a,
}
},
render() {
return [h(ChildA)]
- }
+ },
} as any
const ChildA = {
data() {
return {
- a: 1
+ a: 1,
}
},
inject: ['a'],
render() {
return this.a
- }
+ },
} as any
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Data property "a" is already defined in Inject.`
+ `Data property "a" is already defined in Inject.`,
).toHaveBeenWarned()
})
test('data property is already declared in methods', () => {
const Comp = {
data: () => ({
- foo: 1
+ foo: 1,
}),
methods: {
- foo() {}
+ foo() {},
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Data property "foo" is already defined in Methods.`
+ `Data property "foo" is already defined in Methods.`,
).toHaveBeenWarned()
})
const Comp = {
props: { foo: Number },
computed: {
- foo() {}
+ foo() {},
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Computed property "foo" is already defined in Props.`
+ `Computed property "foo" is already defined in Props.`,
).toHaveBeenWarned()
})
const Comp = {
data() {
return {
- a: 1
+ a: 1,
}
},
provide() {
return {
- a: this.a
+ a: this.a,
}
},
render() {
return [h(ChildA)]
- }
+ },
} as any
const ChildA = {
computed: {
a: {
get() {},
- set() {}
- }
+ set() {},
+ },
},
inject: ['a'],
render() {
return this.a
- }
+ },
} as any
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Computed property "a" is already defined in Inject.`
+ `Computed property "a" is already defined in Inject.`,
).toHaveBeenWarned()
})
test('computed property is already declared in methods', () => {
const Comp = {
computed: {
- foo() {}
+ foo() {},
},
methods: {
- foo() {}
+ foo() {},
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Computed property "foo" is already defined in Methods.`
+ `Computed property "foo" is already defined in Methods.`,
).toHaveBeenWarned()
})
test('computed property is already declared in data', () => {
const Comp = {
data: () => ({
- foo: 1
+ foo: 1,
}),
computed: {
- foo() {}
+ foo() {},
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(
- `Computed property "foo" is already defined in Data.`
+ `Computed property "foo" is already defined in Data.`,
).toHaveBeenWarned()
})
})
-import { ref, reactive } from '@vue/reactivity'
+import { reactive, ref } from '@vue/reactivity'
import {
- renderToString,
+ type TestElement,
+ defineComponent,
h,
+ nextTick,
nodeOps,
render,
+ renderToString,
serializeInner,
- nextTick,
- watchEffect,
- defineComponent,
triggerEvent,
- TestElement
+ watchEffect,
} from '@vue/runtime-test'
// reference: https://vue-composition-api-rfc.netlify.com/api.html#setup
// object exposed as-is
object: reactive({ msg: 'bar' }),
// primitive value exposed as-is
- value: 'baz'
+ value: 'baz',
}
},
render() {
return `${this.ref} ${this.object.msg} ${this.value}`
- }
+ },
})
expect(renderToString(h(Comp))).toMatch(`foo bar baz`)
})
return () => {
return h('div', 'hello')
}
- }
+ },
}
expect(renderToString(h(Comp))).toMatch(`hello`)
})
let dummy
const Parent = {
- render: () => h(Child, { count: count.value })
+ render: () => h(Child, { count: count.value }),
}
const Child = defineComponent({
dummy = props.count
})
return () => h('div', props.count)
- }
+ },
})
const root = nodeOps.createElement('div')
const toggle = ref(true)
const Parent = {
- render: () => h(Child, toggle.value ? { id: 'foo' } : { class: 'baz' })
+ render: () => h(Child, toggle.value ? { id: 'foo' } : { class: 'baz' }),
}
const Child = {
inheritAttrs: false,
setup(props: any, { attrs }: any) {
return () => h('div', attrs)
- }
+ },
}
const root = nodeOps.createElement('div')
const toggle = ref(true)
const Parent = {
- render: () => h(Child, toggle.value ? { id: 'foo' } : { class: 'baz' })
+ render: () => h(Child, toggle.value ? { id: 'foo' } : { class: 'baz' }),
}
const Wrapper = {
render(this: any) {
return this.$slots.default()
- }
+ },
}
const Child = {
return () => {
const vnode = h(Wrapper, null, {
default: () => [h('div', attrs)],
- _: 1 // mark stable slots
+ _: 1, // mark stable slots
})
vnode.dynamicChildren = [] // force optimized mode
return vnode
}
- }
+ },
}
const root = nodeOps.createElement('div')
render: () =>
h(Child, null, {
foo: () => id.value,
- bar: () => 'bar'
- })
+ bar: () => 'bar',
+ }),
}
const Child = {
setup(props: any, { slots }: any) {
return () => h('div', [...slots.foo(), ...slots.bar()])
- }
+ },
}
const root = nodeOps.createElement('div')
onInc: (newVal: number) => {
spy()
count.value = newVal
- }
- })
+ },
+ }),
}
const Child = defineComponent({
props: {
count: {
type: Number,
- default: 1
- }
+ default: 1,
+ },
},
setup(props, { emit }) {
return () =>
h(
'div',
{
- onClick: () => emit('inc', props.count + 1)
+ onClick: () => emit('inc', props.count + 1),
},
- props.count
+ props.count,
)
- }
+ },
})
const root = nodeOps.createElement('div')
import {
- ComponentInternalInstance,
+ type ComponentInternalInstance,
+ type ComputedRef,
+ Fragment,
+ type Ref,
+ type SetupContext,
+ Suspense,
+ computed,
createApp,
+ createBlock,
+ createElementBlock,
+ createElementVNode,
+ createVNode,
defineComponent,
getCurrentInstance,
h,
+ nextTick,
nodeOps,
onMounted,
+ openBlock,
+ ref,
render,
serializeInner,
- SetupContext,
- Suspense,
- computed,
- ComputedRef,
shallowReactive,
- nextTick,
- ref,
- Ref,
watch,
- openBlock,
- createVNode,
- createElementVNode,
- createBlock,
- createElementBlock,
- Fragment
} from '@vue/runtime-test'
import {
+ createPropsRestProxy,
defineEmits,
- defineProps,
defineExpose,
- withDefaults,
+ defineProps,
+ mergeDefaults,
+ mergeModels,
useAttrs,
+ useModel,
useSlots,
- mergeDefaults,
withAsyncContext,
- createPropsRestProxy,
- mergeModels,
- useModel
+ withDefaults,
} from '../src/apiSetupHelpers'
describe('SFC <script setup> helpers', () => {
slots = useSlots()
attrs = useAttrs()
return () => {}
- }
+ },
}
const passedAttrs = { id: 'foo' }
const passedSlots = {
default: () => {},
- x: () => {}
+ x: () => {},
}
render(h(Comp, passedAttrs, passedSlots), nodeOps.createElement('div'))
expect(typeof slots!.default).toBe('function')
attrs = useAttrs()
ctx = _ctx
return () => {}
- }
+ },
})
render(h(Comp), nodeOps.createElement('div'))
expect(slots).toBe(ctx!.slots)
{
foo: null,
bar: { type: String, required: false },
- baz: String
+ baz: String,
},
{
foo: 1,
bar: 'baz',
- baz: 'qux'
- }
+ baz: 'qux',
+ },
)
expect(merged).toMatchObject({
foo: { default: 1 },
bar: { type: String, required: false, default: 'baz' },
- baz: { type: String, default: 'qux' }
+ baz: { type: String, default: 'qux' },
})
})
const merged = mergeDefaults(['foo', 'bar', 'baz'], {
foo: 1,
bar: 'baz',
- baz: 'qux'
+ baz: 'qux',
})
expect(merged).toMatchObject({
foo: { default: 1 },
bar: { default: 'baz' },
- baz: { default: 'qux' }
+ baz: { default: 'qux' },
})
})
const fn = () => {}
const merged = mergeDefaults(['foo', 'bar', 'baz'], {
foo: fn,
- __skip_foo: true
+ __skip_foo: true,
})
expect(merged).toMatchObject({
- foo: { default: fn, skipFactory: true }
+ foo: { default: fn, skipFactory: true },
})
})
test('should warn missing', () => {
mergeDefaults({}, { foo: 1 })
expect(
- `props default key "foo" has no corresponding declaration`
+ `props default key "foo" has no corresponding declaration`,
).toHaveBeenWarned()
})
})
expect(mergeModels(['foo', 'bar'], ['baz'])).toMatchObject([
'foo',
'bar',
- 'baz'
+ 'baz',
])
})
test('object syntax', () => {
expect(
- mergeModels({ foo: null, bar: { required: true } }, ['baz'])
+ mergeModels({ foo: null, bar: { required: true } }, ['baz']),
).toMatchObject({
foo: null,
bar: { required: true },
- baz: {}
+ baz: {},
})
expect(
- mergeModels(['baz'], { foo: null, bar: { required: true } })
+ mergeModels(['baz'], { foo: null, bar: { required: true } }),
).toMatchObject({
foo: null,
bar: { required: true },
- baz: {}
+ baz: {},
})
})
expect(
mergeModels(
{ foo: null, bar: { required: true } },
- { bar: {}, baz: {} }
- )
+ { bar: {}, baz: {} },
+ ),
).toMatchObject({
foo: null,
bar: {},
- baz: {}
+ baz: {},
})
})
})
compRender()
return foo.value
}
- }
+ },
})
const msg = ref('')
createApp(() =>
h(Comp, {
modelValue: msg.value,
- 'onUpdate:modelValue': setValue
- })
+ 'onUpdate:modelValue': setValue,
+ }),
).mount(root)
expect(foo.value).toBe('')
compRender()
return foo.value
}
- }
+ },
})
const root = nodeOps.createElement('div')
compRender()
return count.value
}
- }
+ },
})
const root = nodeOps.createElement('div')
compRender()
return childCount.value
}
- }
+ },
})
const Parent = defineComponent({
count: count.value,
'onUpdate:count': val => {
count.value = val
- }
+ },
})
- }
+ },
})
const root = nodeOps.createElement('div')
compRender()
return childCount.value
}
- }
+ },
})
const toggle = ref(true)
count: count.value,
'onUpdate:count': val => {
count.value = val
- }
+ },
})
: h(Comp)
- }
+ },
})
const root = nodeOps.createElement('div')
const Comp = {
render(this: any) {
return this.$slots.default()
- }
+ },
}
const childRender = vi.fn()
slotRender()
return createElementVNode('div', null, foo.value)
},
- _: 1 /* STABLE */
- })
+ _: 1 /* STABLE */,
+ }),
])
)
}
- }
+ },
})
const msg = ref('')
Child,
{
modelValue: msg.value,
- 'onUpdate:modelValue': setValue
+ 'onUpdate:modelValue': setValue,
},
null,
8 /* PROPS */,
- ['modelValue']
+ ['modelValue'],
)
)
- }
+ },
}).mount(root)
expect(foo.value).toBe('')
const original = shallowReactive({
foo: 1,
bar: 2,
- baz: 3
+ baz: 3,
})
const rest = createPropsRestProxy(original, ['foo', 'bar'])
expect('foo' in rest).toBe(false)
() =>
new Promise(r => {
resolve = r
- })
+ }),
)),
(__temp = await __temp),
__restore(),
onMounted(spy)
afterInstance = getCurrentInstance()
return () => msg
- }
+ },
})
const root = nodeOps.createElement('div')
render(
h(() => h(Suspense, () => h(Comp))),
- root
+ root,
)
expect(spy).not.toHaveBeenCalled()
() =>
new Promise((_, rj) => {
reject = rj
- })
+ }),
)
__temp = await __temp
__restore()
onMounted(spy)
afterInstance = getCurrentInstance()
return () => ''
- }
+ },
})
const root = nodeOps.createElement('div')
render(
h(() => h(Suspense, () => h(Comp))),
- root
+ root,
)
expect(spy).not.toHaveBeenCalled()
resolve()
return ''
}
- }
+ },
})
const root = nodeOps.createElement('div')
render(
h(() => h(Suspense, () => h(Comp))),
- root
+ root,
)
await ready
__temp = await __temp
__restore()
},
- render() {}
+ render() {},
})
const app = createApp(() => h(Suspense, () => h(Comp)))
test('race conditions', async () => {
const uids = {
one: { before: NaN, after: NaN },
- two: { before: NaN, after: NaN }
+ two: { before: NaN, after: NaN },
}
const Comp = defineComponent({
uids[props.name].after = getCurrentInstance()!.uid
return () => ''
- }
+ },
})
const app = createApp(() =>
h(Suspense, () =>
- h('div', [h(Comp, { name: 'one' }), h(Comp, { name: 'two' })])
- )
+ h('div', [h(Comp, { name: 'one' }), h(Comp, { name: 'two' })]),
+ ),
)
const root = nodeOps.createElement('div')
app.mount(root)
// register the lifecycle after an await statement
onMounted(resolve)
return () => ''
- }
+ },
})
const app = createApp(() => h(Suspense, () => h(Comp)))
'update ref to trigger watchEffect (scheduled but not executed)',
() => {
v.value = i++
- }
+ },
)
}
import {
- watch,
- watchEffect,
- reactive,
+ type ComponentInternalInstance,
+ type ComponentPublicInstance,
computed,
- nextTick,
- ref,
defineComponent,
getCurrentInstance,
- ComponentInternalInstance,
- ComponentPublicInstance
+ nextTick,
+ reactive,
+ ref,
+ watch,
+ watchEffect,
} from '../src/index'
import {
- render,
+ type TestElement,
+ createApp,
+ h,
nodeOps,
+ onMounted,
+ render,
serializeInner,
- TestElement,
- h,
- createApp,
watchPostEffect,
watchSyncEffect,
- onMounted
} from '@vue/runtime-test'
import {
+ type DebuggerEvent,
ITERATE_KEY,
- DebuggerEvent,
+ type Ref,
TrackOpTypes,
TriggerOpTypes,
- triggerRef,
- shallowRef,
- Ref,
effectScope,
- toRef
+ shallowRef,
+ toRef,
+ triggerRef,
} from '@vue/reactivity'
// reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
if (prevCount) {
prevCount + 1
}
- }
+ },
)
state.count++
await nextTick()
dummy = [c, prevCount]
},
{
- deep: true
- }
+ deep: true,
+ },
)
count.value++
await nextTick()
it('directly watching reactive object (with automatic deep: true)', async () => {
const src = reactive({
- count: 0
+ count: 0,
})
let dummy
watch(src, ({ count }) => {
await nextTick()
expect(dummy).toMatchObject([
[2, 2, 3],
- [1, 1, 2]
+ [1, 1, 2],
])
})
expect([newA, newB]).toMatchObject([undefined, undefined])
expect([oldA, oldB]).toMatchObject([undefined, undefined])
},
- { immediate: true }
+ { immediate: true },
)
await nextTick()
expect(called).toBe(true)
await nextTick()
expect(dummy).toMatchObject([
[2, true],
- [1, false]
+ [1, false],
])
})
() => state.count,
count => {
dummy = count
- }
+ },
)
state.count++
assertion(count.value, count2.value)
})
return () => count.value
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
() => {
assertion(count.value)
},
- { flush: 'post' }
+ { flush: 'post' },
)
return () => count.value
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
assertion(count.value)
})
return () => count.value
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
assertion(count.value)
},
{
- flush: 'sync'
- }
+ flush: 'sync',
+ },
)
return () => count.value
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
assertion(count.value)
})
return () => count.value
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
setup() {
watch(toggle, cb, { flush: 'post' })
},
- render() {}
+ render() {},
}
const App = {
render() {
return toggle.value ? h(Comp) : null
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
expect(cb).not.toHaveBeenCalled()
setup() {
watch(toggle, cb, { flush: 'pre' })
},
- render() {}
+ render() {},
}
const App = {
render() {
return toggle.value ? h(Comp) : null
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
expect(cb).not.toHaveBeenCalled()
props: ['visible'],
render() {
return visible.value ? h(Comp) : null
- }
+ },
})
const Comp = {
setup() {
watch(visible, cb, { flush: 'pre' })
},
- render() {}
+ render() {},
}
const App = {
render() {
return h(Parent, {
- visible: visible.value
+ visible: visible.value,
})
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
expect(cb).not.toHaveBeenCalled()
val => {
calls.push('watcher child')
},
- { flush: 'pre' }
+ { flush: 'pre' },
)
return () => {
b.value
calls.push('render child')
}
- }
+ },
}
const Parent = {
val => {
calls.push('watcher parent')
},
- { flush: 'pre' }
+ { flush: 'pre' },
)
return () => {
b.value
calls.push('render parent')
return h(Comp)
}
- }
+ },
}
const App = {
render() {
return h(Parent, {
- a: b.value
+ a: b.value,
})
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
'watcher parent',
'render parent',
'watcher child',
- 'render child'
+ 'render child',
])
})
calls.push('watcher 1')
c.value++
},
- { flush: 'pre' }
+ { flush: 'pre' },
)
// #1777 chained pre-watcher
() => {
calls.push('watcher 2')
},
- { flush: 'pre' }
+ { flush: 'pre' },
)
return () => {
c.value
calls.push('render')
}
- }
+ },
}
const App = {
render() {
return h(Comp, { a: a.value, b: b.value })
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
() => {
calls.push('watch ' + count.value)
},
- { flush: 'pre' }
+ { flush: 'pre' },
)
onMounted(() => {
calls.push('mounted')
count.value++
count.value++
count.value++
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
expect(calls).toMatchObject(['watch 3', 'mounted'])
() => {
dom = domRef.value
},
- { flush: 'post' }
+ { flush: 'post' },
)
return () => {
return toggle.value ? h('p', { ref: domRef }) : null
}
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
it('deep', async () => {
const state = reactive({
nested: {
- count: ref(0)
+ count: ref(0),
},
array: [1, 2, 3],
map: new Map([
['a', 1],
- ['b', 2]
+ ['b', 2],
]),
- set: new Set([1, 2, 3])
+ set: new Set([1, 2, 3]),
})
let dummy
state.nested.count,
state.array[0],
state.map.get('a'),
- state.set.has(1)
+ state.set.has(1),
]
},
- { deep: true }
+ { deep: true },
)
state.nested.count++
state => {
dummy = [state[0].value, state[1].value]
},
- { deep: true }
+ { deep: true },
)
count.value++
dummy = count.value
},
// @ts-expect-error
- { immediate: false }
+ { immediate: false },
)
expect(dummy).toBe(0)
expect(`"immediate" option is only respected`).toHaveBeenWarned()
return arr
},
// @ts-expect-error
- { deep: true }
+ { deep: true },
)
expect(spy).toHaveBeenCalledTimes(1)
;(arr.value[1] as Array<number>)[0] = 3
() => {
dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
},
- { onTrack }
+ { onTrack },
)
await nextTick()
expect(dummy).toEqual([1, true, ['foo', 'bar']])
{
target: obj,
type: TrackOpTypes.GET,
- key: 'foo'
+ key: 'foo',
},
{
target: obj,
type: TrackOpTypes.HAS,
- key: 'bar'
+ key: 'bar',
},
{
target: obj,
type: TrackOpTypes.ITERATE,
- key: ITERATE_KEY
- }
+ key: ITERATE_KEY,
+ },
])
})
() => {
dummy = obj.foo
},
- { onTrigger }
+ { onTrigger },
)
await nextTick()
expect(dummy).toBe(1)
type: TriggerOpTypes.SET,
key: 'foo',
oldValue: 1,
- newValue: 2
+ newValue: 2,
})
delete obj.foo
expect(events[1]).toMatchObject({
type: TriggerOpTypes.DELETE,
key: 'foo',
- oldValue: 2
+ oldValue: 2,
})
})
++calls
},
{
- flush: 'sync'
- }
+ flush: 'sync',
+ },
)
expect(calls).toBe(0)
mounted() {
instance = getCurrentInstance()
},
- unmounted() {}
+ unmounted() {},
})
const Comp = defineComponent({
render() {
return this.show
? h(Child, {
- ref: vm => void (this.comp = vm as ComponentPublicInstance)
+ ref: vm => void (this.comp = vm as ComponentPublicInstance),
})
: null
},
// the effect for this `$watch` should nonetheless be registered with Child
this.comp!.$watch(
() => this.show,
- () => void 0
+ () => void 0,
)
- }
+ },
})
render(h(Comp), nodeOps.createElement('div'))
created(this: any) {
instance = this
this.$watch(source, function () {})
- }
+ },
})
const root = nodeOps.createElement('div')
render() {},
setup() {
watch(source, () => {})
- }
+ },
})
const root = nodeOps.createElement('div')
watch: {
a() {
b.value
- }
+ },
},
render() {
return h('div', this.a)
- }
+ },
})
const Parent = defineComponent({
render() {
return h(Child, { a: a.value })
- }
+ },
})
const root = nodeOps.createElement('div')
data() {
return {
a: {
- b: 1
- }
+ b: 1,
+ },
}
},
watch: {
- 'a.b': spy
+ 'a.b': spy,
},
created(this: any) {
this.$watch('a.b', spy)
},
mounted(this: any) {
this.a.b++
- }
+ },
})
const root = nodeOps.createElement('div')
effectScope(true).run(() => {
watch(
() => 1,
- () => {}
+ () => {},
)
})
return () => ''
- }
+ },
}
const root = nodeOps.createElement('div')
createApp(Comp).mount(root)
watch(trigger, () => countW++)
})
return () => ''
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
{ name: 'only trigger once watch' },
{
deep: true,
- name: 'only trigger once watch with deep'
+ name: 'only trigger once watch with deep',
},
{
flush: 'sync',
- name: 'only trigger once watch with flush: sync'
+ name: 'only trigger once watch with flush: sync',
},
{
flush: 'pre',
- name: 'only trigger once watch with flush: pre'
+ name: 'only trigger once watch with flush: pre',
},
{
immediate: true,
- name: 'only trigger once watch with immediate'
- }
+ name: 'only trigger once watch with immediate',
+ },
] as const
test.each(options)('$name', async option => {
const count = ref(0)
// ./rendererAttrsFallthrough.spec.ts.
import {
- render,
+ type ComponentPublicInstance,
defineComponent,
h,
+ nextTick,
nodeOps,
+ render,
toHandlers,
- nextTick,
- ComponentPublicInstance
} from '@vue/runtime-test'
import { isEmitListener } from '../src/componentEmits'
this.$emit('foo')
this.$emit('bar')
this.$emit('!baz')
- }
+ },
})
const onfoo = vi.fn()
render() {},
created() {
this.$emit('test-event')
- }
+ },
})
const fooSpy = vi.fn()
const Comp = () =>
h(Foo, {
- onTestEvent: fooSpy
+ onTestEvent: fooSpy,
})
render(h(Comp), nodeOps.createElement('div'))
render() {},
created() {
this.$emit('test-event')
- }
+ },
})
const fooSpy = vi.fn()
const Comp = () =>
h(Foo, {
- 'onTest-event': fooSpy
+ 'onTest-event': fooSpy,
})
render(h(Comp), nodeOps.createElement('div'))
created() {
this.$emit('test-event')
this.$emit('testEvent')
- }
+ },
})
const fooSpy = vi.fn()
Foo,
toHandlers({
'test-event': fooSpy,
- testEvent: barSpy
- })
+ testEvent: barSpy,
+ }),
)
render(h(Comp), nodeOps.createElement('div'))
created() {
this.$emit('update:fooProp')
this.$emit('update:barProp')
- }
+ },
})
const fooSpy = vi.fn()
const Comp = () =>
h(Foo, {
'onUpdate:fooProp': fooSpy,
- 'onUpdate:bar-prop': barSpy
+ 'onUpdate:bar-prop': barSpy,
})
render(h(Comp), nodeOps.createElement('div'))
setup(_, { emit }) {
emit('foo', 1)
return () => h('div')
- }
+ },
})
const fn1 = vi.fn()
setup() {
return () =>
h(Child, {
- onFoo: [fn1, fn2]
+ onFoo: [fn1, fn2],
})
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
created() {
// @ts-expect-error
this.$emit('bar')
- }
+ },
})
render(h(Foo), nodeOps.createElement('div'))
expect(
- `Component emitted event "bar" but it is neither declared`
+ `Component emitted event "bar" but it is neither declared`,
).toHaveBeenWarned()
})
test('warning for undeclared event (object)', () => {
const Foo = defineComponent({
emits: {
- foo: null
+ foo: null,
},
render() {},
created() {
// @ts-expect-error
this.$emit('bar')
- }
+ },
})
render(h(Foo), nodeOps.createElement('div'))
expect(
- `Component emitted event "bar" but it is neither declared`
+ `Component emitted event "bar" but it is neither declared`,
).toHaveBeenWarned()
})
created() {
// @ts-expect-error
this.$emit('foo')
- }
+ },
})
render(h(Foo), nodeOps.createElement('div'))
expect(
- `Component emitted event "foo" but it is neither declared`
+ `Component emitted event "foo" but it is neither declared`,
).not.toHaveBeenWarned()
})
test('validator warning', () => {
const Foo = defineComponent({
emits: {
- foo: (arg: number) => arg > 0
+ foo: (arg: number) => arg > 0,
},
render() {},
created() {
this.$emit('foo', -1)
- }
+ },
})
render(h(Foo), nodeOps.createElement('div'))
expect(`event validation failed for event "foo"`).toHaveBeenWarned()
test('merging from mixins', () => {
const mixin = {
emits: {
- foo: (arg: number) => arg > 0
- }
+ foo: (arg: number) => arg > 0,
+ },
}
const Foo = defineComponent({
mixins: [mixin],
render() {},
created() {
this.$emit('foo', -1)
- }
+ },
})
render(h(Foo), nodeOps.createElement('div'))
expect(`event validation failed for event "foo"`).toHaveBeenWarned()
render() {},
created() {
this.$emit('foo')
- }
+ },
})
render(h(Foo), nodeOps.createElement('div'))
expect(
- `Component emitted event "foo" but it is neither declared`
+ `Component emitted event "foo" but it is neither declared`,
).not.toHaveBeenWarned()
})
render() {},
emits: {
foo: null,
- bar: null
+ bar: null,
},
created() {
this.$emit('foo')
this.$emit('foo')
this.$emit('bar')
this.$emit('bar')
- }
+ },
})
const fn = vi.fn()
const barFn = vi.fn()
render(
h(Foo, {
onFooOnce: fn,
- onBarOnce: barFn
+ onBarOnce: barFn,
}),
- nodeOps.createElement('div')
+ nodeOps.createElement('div'),
)
expect(fn).toHaveBeenCalledTimes(1)
expect(barFn).toHaveBeenCalledTimes(1)
const Foo = defineComponent({
render() {},
emits: {
- foo: null
+ foo: null,
},
created() {
this.$emit('foo')
this.$emit('foo')
- }
+ },
})
const onFoo = vi.fn()
const onFooOnce = vi.fn()
render(
h(Foo, {
onFoo,
- onFooOnce
+ onFooOnce,
}),
- nodeOps.createElement('div')
+ nodeOps.createElement('div'),
)
expect(onFoo).toHaveBeenCalledTimes(2)
expect(onFooOnce).toHaveBeenCalledTimes(1)
created() {
this.$emit('update:modelValue', '1')
this.$emit('update:foo', '2')
- }
+ },
})
const fn1 = vi.fn()
foo: null,
fooModifiers: { number: true },
- 'onUpdate:foo': fn2
+ 'onUpdate:foo': fn2,
})
render(h(Comp), nodeOps.createElement('div'))
created() {
this.$emit('update:modelValue', ' one ')
this.$emit('update:foo', ' two ')
- }
+ },
})
const fn1 = vi.fn()
foo: null,
fooModifiers: { trim: true },
- 'onUpdate:foo': fn2
+ 'onUpdate:foo': fn2,
})
render(h(Comp), nodeOps.createElement('div'))
created() {
this.$emit('update:modelValue', ' +01.2 ')
this.$emit('update:foo', ' 1 ')
- }
+ },
})
const fn1 = vi.fn()
foo: null,
fooModifiers: { trim: true, number: true },
- 'onUpdate:foo': fn2
+ 'onUpdate:foo': fn2,
})
render(h(Comp), nodeOps.createElement('div'))
render() {},
created() {
this.$emit('update:modelValue', ' foo ', { bar: ' bar ' })
- }
+ },
})
const fn = vi.fn()
h(Foo, {
modelValue: null,
modelModifiers: { trim: true },
- 'onUpdate:modelValue': fn
+ 'onUpdate:modelValue': fn,
})
render(h(Comp), nodeOps.createElement('div'))
click: null,
'test-event': null,
fooBar: null,
- FooBaz: null
+ FooBaz: null,
}
expect(isEmitListener(options, 'onClick')).toBe(true)
expect(isEmitListener(options, 'onclick')).toBe(false)
},
render() {
return h('div')
- }
+ },
})
const Comp = () =>
h(Foo, {
- onClosing: fn
+ onClosing: fn,
})
const el = nodeOps.createElement('div')
test('merge string array emits', async () => {
const ComponentA = defineComponent({
- emits: ['one', 'two']
+ emits: ['one', 'two'],
})
const ComponentB = defineComponent({
- emits: ['three']
+ emits: ['three'],
})
const renderFn = vi.fn(function (this: ComponentPublicInstance) {
expect(this.$options.emits).toEqual(['one', 'two', 'three'])
})
const ComponentC = defineComponent({
render: renderFn,
- mixins: [ComponentA, ComponentB]
+ mixins: [ComponentA, ComponentB],
})
const el = nodeOps.createElement('div')
expect(renderFn).toHaveBeenCalledTimes(0)
const ComponentA = defineComponent({
emits: {
one: null,
- two: twoFn
- }
+ two: twoFn,
+ },
})
const ComponentB = defineComponent({
- emits: ['three']
+ emits: ['three'],
})
const renderFn = vi.fn(function (this: ComponentPublicInstance) {
expect(this.$options.emits).toEqual({
one: null,
two: twoFn,
- three: null
+ three: null,
})
expect(this.$options.emits.two).toBe(twoFn)
return h('div')
})
const ComponentC = defineComponent({
render: renderFn,
- mixins: [ComponentA, ComponentB]
+ mixins: [ComponentA, ComponentB],
})
const el = nodeOps.createElement('div')
expect(renderFn).toHaveBeenCalledTimes(0)
*/
import {
- ComponentInternalInstance,
+ type ComponentInternalInstance,
+ type FunctionalComponent,
+ type SetupContext,
+ createApp,
+ defineComponent,
getCurrentInstance,
- render,
h,
+ inject,
nodeOps,
- FunctionalComponent,
- defineComponent,
+ provide,
ref,
+ render,
serializeInner,
- createApp,
- provide,
- inject,
- watch,
toRefs,
- SetupContext
+ watch,
} from '@vue/runtime-test'
import { render as domRender, nextTick } from 'vue'
props = this.$props
attrs = this.$attrs
proxy = this
- }
+ },
})
const root = nodeOps.createElement('div')
props = _props
attrs = _attrs
}
- }
+ },
})
const root = nodeOps.createElement('div')
foo: Boolean,
bar: Boolean,
baz: Boolean,
- qux: Boolean
+ qux: Boolean,
},
render() {
proxy = this
- }
+ },
}
render(
h(Comp, {
// absent should cast to false
bar: '', // empty string should cast to true
baz: 'baz', // same string should cast to true
- qux: 'ok' // other values should be left in-tact (but raise warning)
+ qux: 'ok', // other values should be left in-tact (but raise warning)
}),
- nodeOps.createElement('div')
+ nodeOps.createElement('div'),
)
expect(proxy.foo).toBe(false)
const Comp = {
props: {
foo: {
- default: 1
+ default: 1,
},
bar: {
- default: defaultFn
+ default: defaultFn,
},
baz: {
type: Function,
- default: defaultBaz
- }
+ default: defaultBaz,
+ },
},
render() {
proxy = this
- }
+ },
}
const root = nodeOps.createElement('div')
const Child = defineComponent({
props: {
test: {
- default: () => inject('test', 'default')
- }
+ default: () => inject('test', 'default'),
+ },
},
setup(props) {
return () => {
return h('div', props.test)
}
- }
+ },
})
const Comp = {
setup() {
provide('test', 'injected')
return () => h(Child)
- }
+ },
}
const root = nodeOps.createElement('div')
test('optimized props updates', async () => {
const Child = defineComponent({
props: ['foo'],
- template: `<div>{{ foo }}</div>`
+ template: `<div>{{ foo }}</div>`,
})
const foo = ref(1)
setup() {
return {
foo,
- id
+ id,
}
},
components: { Child },
- template: `<Child :foo="foo" :id="id"/>`
+ template: `<Child :foo="foo" :id="id"/>`,
})
// Note this one is using the main Vue render so it can compile template
props: {
foo: {
type: Number,
- validator: (value, props) => mockFn(value, props)
+ validator: (value, props) => mockFn(value, props),
},
bar: {
- type: Number
- }
+ type: Number,
+ },
},
- template: `<div />`
+ template: `<div />`,
})
// Note this one is using the main Vue render so it can compile template
props: {
foo: {
type: Number,
- validator: (value, props) => !!(props.bar = 1)
+ validator: (value, props) => !!(props.bar = 1),
},
bar: {
type: Number,
- validator: value => mockFn(value)
- }
+ validator: value => mockFn(value),
+ },
},
- template: `<div />`
+ template: `<div />`,
})
// Note this one is using the main Vue render so it can compile template
const root = document.createElement('div')
domRender(h(Comp, { foo: 1, bar: 2 }), root)
expect(
- `Set operation on key "bar" failed: target is readonly.`
+ `Set operation on key "bar" failed: target is readonly.`,
).toHaveBeenWarnedLast()
expect(mockFn).toHaveBeenCalledWith(2)
})
instance = getCurrentInstance()!
setupProps = props
return () => null
- }
+ },
}
render(h(Comp, { foo: 1 }), nodeOps.createElement('div'))
expect(setupProps.foo).toBe(1)
props: {
bool: { type: Boolean, required: true },
str: { type: String, required: true },
- num: { type: Number, required: true }
+ num: { type: Number, required: true },
},
setup() {
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
expect(`Missing required prop: "bool"`).toHaveBeenWarned()
cls: { type: MyClass },
fn: { type: Function },
skipCheck: { type: [Boolean, Function], skipCheck: true },
- empty: { type: [] }
+ empty: { type: [] },
},
setup() {
return () => null
- }
+ },
}
render(
h(Comp, {
cls: {},
fn: true,
skipCheck: 'foo',
- empty: [1, 2, 3]
+ empty: [1, 2, 3],
}),
- nodeOps.createElement('div')
+ nodeOps.createElement('div'),
)
expect(
- `Invalid prop: type check failed for prop "bool". Expected Boolean, got String`
+ `Invalid prop: type check failed for prop "bool". Expected Boolean, got String`,
).toHaveBeenWarned()
expect(
- `Invalid prop: type check failed for prop "str". Expected String with value "100", got Number with value 100.`
+ `Invalid prop: type check failed for prop "str". Expected String with value "100", got Number with value 100.`,
).toHaveBeenWarned()
expect(
- `Invalid prop: type check failed for prop "num". Expected Number with value 100, got String with value "100".`
+ `Invalid prop: type check failed for prop "num". Expected Number with value 100, got String with value "100".`,
).toHaveBeenWarned()
expect(
- `Invalid prop: type check failed for prop "arr". Expected Array, got Object`
+ `Invalid prop: type check failed for prop "arr". Expected Array, got Object`,
).toHaveBeenWarned()
expect(
- `Invalid prop: type check failed for prop "obj". Expected Object, got String with value "false"`
+ `Invalid prop: type check failed for prop "obj". Expected Object, got String with value "false"`,
).toHaveBeenWarned()
expect(
- `Invalid prop: type check failed for prop "fn". Expected Function, got Boolean with value true.`
+ `Invalid prop: type check failed for prop "fn". Expected Function, got Boolean with value true.`,
).toHaveBeenWarned()
expect(
- `Invalid prop: type check failed for prop "cls". Expected MyClass, got Object`
+ `Invalid prop: type check failed for prop "cls". Expected MyClass, got Object`,
).toHaveBeenWarned()
expect(
- `Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".`
+ `Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".`,
).not.toHaveBeenWarned()
expect(
- `Prop type [] for prop "empty" won't match anything. Did you mean to use type Array instead?`
+ `Prop type [] for prop "empty" won't match anything. Did you mean to use type Array instead?`,
).toHaveBeenWarned()
})
test('should not warn required props using kebab-case', async () => {
const Comp = {
props: {
- fooBar: { type: String, required: true }
+ fooBar: { type: String, required: true },
},
setup() {
return () => null
- }
+ },
}
render(
h(Comp, {
- 'foo-bar': 'hello'
+ 'foo-bar': 'hello',
}),
- nodeOps.createElement('div')
+ nodeOps.createElement('div'),
)
expect(`Missing required prop: "fooBar"`).not.toHaveBeenWarned()
})
let renderProxy: any
const E = {
- props: ['base']
+ props: ['base'],
}
const M1 = {
- props: ['m1']
+ props: ['m1'],
}
const M2 = {
- props: { m2: null }
+ props: { m2: null },
}
const Comp = {
props: ['self'],
render(this: any) {
renderProxy = this
return h('div', [this.self, this.base, this.m1, this.m2])
- }
+ },
}
const root = nodeOps.createElement('div')
self: 'from self, ',
base: 'from base, ',
m1: 'from mixin 1, ',
- m2: 'from mixin 2'
+ m2: 'from mixin 2',
}
render(h(Comp, props), root)
expect(serializeInner(root)).toMatch(
- `from self, from base, from mixin 1, from mixin 2`
+ `from self, from base, from mixin 1, from mixin 2`,
)
expect(setupProps).toMatchObject(props)
expect(renderProxy.$props).toMatchObject(props)
let renderProxy: any
const M1 = {
- props: ['m1']
+ props: ['m1'],
}
const M2 = {
- props: { m2: null }
+ props: { m2: null },
}
const Comp = {
props: ['self'],
render(this: any) {
renderProxy = this
return h('div', [this.self, this.m1, this.m2])
- }
+ },
}
const props = {
self: 'from self, ',
m1: 'from mixin 1, ',
- m2: 'from mixin 2'
+ m2: 'from mixin 2',
}
const app = createApp(Comp, props)
app.mixin(M1)
app.mount(root)
expect(serializeInner(root)).toMatch(
- `from self, from mixin 1, from mixin 2`
+ `from self, from mixin 1, from mixin 2`,
)
expect(setupProps).toMatchObject(props)
expect(renderProxy.$props).toMatchObject(props)
test('props type support BigInt', () => {
const Comp = {
props: {
- foo: BigInt
+ foo: BigInt,
},
render(this: any) {
return h('div', [this.foo])
- }
+ },
}
const root = nodeOps.createElement('div')
render(
h(Comp, {
- foo: BigInt(BigInt(100000111)) + BigInt(2000000000) * BigInt(30000000)
+ foo: BigInt(BigInt(100000111)) + BigInt(2000000000) * BigInt(30000000),
}),
- root
+ root,
)
expect(serializeInner(root)).toMatch('<div>60000000100000111</div>')
props: {
foo: {
type: Object,
- default: () => ({ val: 1 })
+ default: () => ({ val: 1 }),
},
- bar: Number
+ bar: Number,
},
setup(props: any) {
watch(
() => props.foo,
() => {
count++
- }
+ },
)
return () => h('h1', [props.foo.val, props.bar])
- }
+ },
}
const foo = ref()
const bar = ref(0)
const app = createApp({
- render: () => h(Comp, { foo: foo.value, bar: bar.value })
+ render: () => h(Comp, { foo: foo.value, bar: bar.value }),
})
const root = nodeOps.createElement('div')
const Comp = {
render() {},
props: {
- foo: String
+ foo: String,
},
setup(props: any) {
initialKeys = Object.keys(props)
const { foo } = toRefs(props)
watch(foo, changeSpy)
- }
+ },
}
const Parent = () => (passFoo.value ? h(Comp, { foo: 'ok' }) : h(Comp))
childProps.value && childProps.value.foo
return slots.default!()
}
- }
+ },
}
const Child = {
props: {
foo: {
type: Boolean,
- required: false
- }
+ required: false,
+ },
},
setup(props: { foo: boolean }) {
const register = inject('register') as any
register(props)
return () => 'foo'
- }
+ },
}
const App = {
setup() {
return () => h(Parent, () => h(Child as any, { foo: '' }, () => null))
- }
+ },
}
const root = nodeOps.createElement('div')
test('support null in required + multiple-type declarations', () => {
const Comp = {
props: {
- foo: { type: [Function, null], required: true }
+ foo: { type: [Function, null], required: true },
},
- render() {}
+ render() {},
}
const root = nodeOps.createElement('div')
expect(() => {
const Comp = {
render(this: any) {
return JSON.stringify(this.$attrs) + Object.keys(this.$attrs)
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp, attrs), root)
expect(serializeInner(root)).toBe(
- JSON.stringify(attrs) + Object.keys(attrs)
+ JSON.stringify(attrs) + Object.keys(attrs),
)
render(h(Comp, (attrs = { foo: 'bar' })), root)
expect(serializeInner(root)).toBe(
- JSON.stringify(attrs) + Object.keys(attrs)
+ JSON.stringify(attrs) + Object.keys(attrs),
)
})
test('should not mutate original props long-form definition object', () => {
const props = {
msg: {
- type: String
- }
+ type: String,
+ },
}
const Comp = defineComponent({
props,
- render() {}
+ render() {},
})
const root = nodeOps.createElement('div')
import {
- h,
- render,
+ createApp,
+ defineComponent,
getCurrentInstance,
+ h,
nodeOps,
- createApp,
+ render,
shallowReadonly,
- defineComponent
} from '@vue/runtime-test'
-import { ComponentInternalInstance, ComponentOptions } from '../src/component'
+import type {
+ ComponentInternalInstance,
+ ComponentOptions,
+} from '../src/component'
describe('component: proxy', () => {
test('data', () => {
const Comp = {
data() {
return {
- foo: 1
+ foo: 1,
}
},
mounted() {
},
render() {
return null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.foo).toBe(1)
const Comp = {
setup() {
return {
- foo: 1
+ foo: 1,
}
},
mounted() {
},
render() {
return null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.foo).toBe(1)
},
mounted() {
instanceProxy = this
- }
+ },
}
render(h(Comp, { count: 1 }), nodeOps.createElement('div'))
expect('count' in instanceProxy).toBe(false)
mounted() {
instance = getCurrentInstance()!
instanceProxy = this
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.$data).toBe(instance!.data)
expect(instanceProxy.$slots).toBe(shallowReadonly(instance!.slots))
expect(instanceProxy.$refs).toBe(shallowReadonly(instance!.refs))
expect(instanceProxy.$parent).toBe(
- instance!.parent && instance!.parent.proxy
+ instance!.parent && instance!.parent.proxy,
)
expect(instanceProxy.$root).toBe(instance!.root.proxy)
expect(instanceProxy.$emit).toBe(instance!.emit)
mounted() {
instance = getCurrentInstance()!
instanceProxy = this
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
instanceProxy.foo = 1
mounted() {
instance = getCurrentInstance()!
instanceProxy = this
- }
+ },
}
const app = createApp(Comp)
const Comp = {
render() {},
props: {
- msg: String
+ msg: String,
},
data() {
return {
- foo: 0
+ foo: 0,
}
},
setup() {
return {
- bar: 1
+ bar: 1,
}
},
mounted() {
instanceProxy = this
- }
+ },
}
const app = createApp(Comp, { msg: 'hello' })
'msg',
'bar',
'foo',
- 'baz'
+ 'baz',
])
})
render() {},
setup() {
return {
- isDisplayed: true
+ isDisplayed: true,
}
},
mounted() {
instanceProxy = this
- }
+ },
}
const app = createApp(Comp)
Object.defineProperty(instanceProxy, 'isDisplayed', {
get() {
return false
- }
+ },
})
expect(instanceProxy.isDisplayed).toBe(false)
Object.defineProperty(instanceProxy, 'isDisplayed', {
get() {
return true
- }
+ },
})
expect(instanceProxy.isDisplayed).toBe(true)
return {
toggle() {
return 'a'
- }
+ },
}
},
mounted() {
instanceProxy = this
- }
+ },
}
const app = createApp(Comp)
get() {
getCalledTimes++
return () => 'b'
- }
+ },
})
// getter should not be evaluated on initial definition
render() {},
setup() {
return {
- toggle: 'a'
+ toggle: 'a',
}
},
mounted() {
instanceProxy = this
- }
+ },
}
const app = createApp(Comp)
expect(v1).toEqual('a')
Object.defineProperty(instanceProxy, 'toggle', {
- value: 'b'
+ value: 'b',
})
const v2 = instanceProxy.toggle
expect(v2).toEqual('b')
// expect null to be a settable value
Object.defineProperty(instanceProxy, 'toggle', {
- value: null
+ value: null,
})
const v3 = instanceProxy.toggle
expect(v3).toBeNull()
computed: {
greet() {
return 'Hi ' + (this as any).name
- }
+ },
},
render() {},
setup() {
return {
- fromSetup: true
+ fromSetup: true,
}
},
mounted() {
instanceProxy = this
- }
+ },
}
const app = createApp(Comp, {
- fromProp: true
+ fromProp: true,
})
app.mount(nodeOps.createElement('div'))
Object.defineProperty(instanceProxy, 'name', {
get() {
return 'getter.name'
- }
+ },
})
// computed is same still cached
Object.defineProperty(instanceProxy, 'greet', {
get() {
return 'Hi greet.getter.computed'
- }
+ },
})
expect(instanceProxy.greet).toEqual('Hi greet.getter.computed')
Object.defineProperty(instanceProxy, 'fromSetup', {
get() {
return false
- }
+ },
})
expect(instanceProxy.fromSetup).toBe(false)
Object.defineProperty(instanceProxy, 'fromProp', {
get() {
return false
- }
+ },
})
expect(instanceProxy.fromProp).toBe(false)
})
props: ['test'],
render(this: any) {
return this.test
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
expect(
- `was accessed during render but is not defined`
+ `was accessed during render but is not defined`,
).not.toHaveBeenWarned()
})
return '1'
}
return '2'
- }
+ },
}
const app = createApp(Comp)
expect(
`Property ${JSON.stringify(
- Symbol.unscopables
- )} was accessed during render ` + `but is not defined on instance.`
+ Symbol.unscopables,
+ )} was accessed during render ` + `but is not defined on instance.`,
).toHaveBeenWarned()
})
setup() {
return {
__isScriptSetup: true,
- foo: 1
+ foo: 1,
}
},
mounted() {
try {
this.foo = 123
} catch (e) {}
- }
+ },
})
render(h(Comp), nodeOps.createElement('div'))
expect(`Cannot mutate <script setup> binding "foo"`).toHaveBeenWarned()
import {
- ref,
- render,
+ getCurrentInstance,
h,
- nodeOps,
nextTick,
- getCurrentInstance
+ nodeOps,
+ ref,
+ render,
} from '@vue/runtime-test'
import { normalizeVNode } from '../src/vnode'
import { createSlots } from '../src/helpers/createSlots'
render() {
instance = getCurrentInstance()
return h('div')
- }
+ },
}
render(h(Comp, null, slots), nodeOps.createElement('div'))
_inner: '_inner',
foo: null,
header: 'header',
- footer: ['f1', 'f2']
+ footer: ['f1', 'f2'],
})
expect(
- '[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.'
+ '[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
).toHaveBeenWarned()
expect(
- '[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.'
+ '[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
).toHaveBeenWarned()
expect(slots).not.toHaveProperty('_inner')
expect(slots.header()).toMatchObject([normalizeVNode('header')])
expect(slots.footer()).toMatchObject([
normalizeVNode('f1'),
- normalizeVNode('f2')
+ normalizeVNode('f2'),
])
})
render() {
proxy = getCurrentInstance()
return h('div')
- }
+ },
}
render(
h(Comp, null, {
- header: () => 'header'
+ header: () => 'header',
}),
- nodeOps.createElement('div')
+ nodeOps.createElement('div'),
)
expect(proxy.slots.header()).toMatchObject([normalizeVNode('header')])
const { slots } = renderWithSlots([h('span')])
expect(
- '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.'
+ '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
).toHaveBeenWarned()
expect(slots.default()).toMatchObject([normalizeVNode(h('span'))])
flag1.value
? {
name: 'one',
- fn: () => [h('span')]
+ fn: () => [h('span')],
}
: {
name: 'two',
- fn: () => [h('div')]
- }
- ])
- )
+ fn: () => [h('div')],
+ },
+ ]),
+ ),
]
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
const oldSlots = {
header: 'header',
- footer: undefined
+ footer: undefined,
}
const newSlots = {
header: undefined,
- footer: 'footer'
+ footer: 'footer',
}
const Comp = {
setup() {
return () => [
- h(Child, { n: flag1.value }, flag1.value ? oldSlots : newSlots)
+ h(Child, { n: flag1.value }, flag1.value ? oldSlots : newSlots),
]
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
await nextTick()
expect(
- '[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.'
+ '[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
).toHaveBeenWarned()
expect(
- '[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.'
+ '[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
).toHaveBeenWarned()
expect(instance.slots).not.toHaveProperty('header')
const Comp = {
setup() {
return () => [
- h(Child, { n: flag1.value }, flag1.value ? ['header'] : ['footer'])
+ h(Child, { n: flag1.value }, flag1.value ? ['header'] : ['footer']),
]
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
await nextTick()
expect(
- '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.'
+ '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
).toHaveBeenWarned()
expect(instance.slots.default()).toMatchObject([normalizeVNode('footer')])
{ n: flag2.value },
{
foo: () => 'foo',
- $stable: true
- }
- )
+ $stable: true,
+ },
+ ),
]
- }
+ },
}
render(h(App), nodeOps.createElement('div'))
import {
- nodeOps,
- render,
- h,
BaseTransition,
- BaseTransitionProps,
- ref,
+ type BaseTransitionProps,
+ KeepAlive,
+ type TestElement,
+ type VNodeProps,
+ h,
nextTick,
- serializeInner,
+ nodeOps,
+ ref,
+ render,
serialize,
- VNodeProps,
- KeepAlive,
- TestElement
+ serializeInner,
} from '@vue/runtime-test'
function mount(
props: BaseTransitionProps,
slot: () => any,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const root = nodeOps.createElement('div')
const show = ref(true)
return withKeepAlive ? h(KeepAlive, null, slot()) : slot()
})
: null
- }
+ },
}
render(h(App), root)
doneLeave: Record<string, () => void>
} = {
doneEnter: {},
- doneLeave: {}
+ doneLeave: {},
}
const props: BaseTransitionProps = {
onBeforeEnter: vi.fn(el => {
}),
onAfterAppear: vi.fn(),
onAppearCancelled: vi.fn(),
- ...extra
+ ...extra,
}
return {
props,
- cbs
+ cbs,
}
}
function assertCalls(
props: BaseTransitionProps,
- calls: Record<string, number>
+ calls: Record<string, number>,
) {
Object.keys(calls).forEach(key => {
expect(props[key as keyof BaseTransitionProps]).toHaveBeenCalledTimes(
- calls[key]
+ calls[key],
)
})
}
trueBranch: () => h('div'),
falseBranch: () => h('span'),
trueSerialized: `<div></div>`,
- falseSerialized: `<span></span>`
+ falseSerialized: `<span></span>`,
})
}
trueBranch: () => h(CompA, { msg: 'foo' }),
falseBranch: () => h(CompB, { msg: 'bar' }),
trueSerialized: `<div>foo</div>`,
- falseSerialized: `<span>bar</span>`
+ falseSerialized: `<span>bar</span>`,
})
}
setup() {
const count = ref(0)
return () => h('div', count.value)
- }
+ },
}
const falseComp = {
setup() {
const count = ref(0)
return () => h('span', count.value)
- }
+ },
}
return tester(
{
trueBranch: () => h(trueComp),
falseBranch: () => h(falseComp),
trueSerialized: `<div>0</div>`,
- falseSerialized: `<span>0</span>`
+ falseSerialized: `<span>0</span>`,
},
- true /* withKeepAlive: true */
+ true /* withKeepAlive: true */,
)
}
describe('BaseTransition', () => {
test('appear: true w/ appear hooks', () => {
const { props, cbs } = mockProps({
- appear: true
+ appear: true,
})
mount(props, () => h('div'))
expect(props.onBeforeAppear).toHaveBeenCalledTimes(1)
onBeforeAppear: undefined,
onAppear: undefined,
onAfterAppear: undefined,
- onAppearCancelled: undefined
+ onAppearCancelled: undefined,
})
mount(props, () => h('div'))
expect(props.onBeforeEnter).toHaveBeenCalledTimes(1)
})
}
}
- }
+ },
}
return { state, toggle, hooks }
}
trueBranch,
trueSerialized,
falseBranch,
- falseSerialized
+ falseSerialized,
}: ToggleOptions,
- mode?: BaseTransitionProps['mode']
+ mode?: BaseTransitionProps['mode'],
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode })
const { root } = mount(props, () =>
- toggle.value ? trueBranch() : falseBranch()
+ toggle.value ? trueBranch() : falseBranch(),
)
// without appear: true, enter hooks should not be called on mount
onBeforeLeave: 1,
onLeave: 1,
onAfterLeave: 1,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
trueBranch: () => h('div'),
trueSerialized: `<div></div>`,
falseBranch: () => null,
- falseSerialized: `<!---->`
+ falseSerialized: `<!---->`,
})
})
trueBranch: () => h(Comp, { msg: 'hello' }),
trueSerialized: `<div>hello</div>`,
falseBranch: () => null,
- falseSerialized: `<!---->`
+ falseSerialized: `<!---->`,
})
})
trueBranch: () => h('div'),
trueSerialized: `<div></div>`,
falseBranch: () => null,
- falseSerialized: `<!---->`
+ falseSerialized: `<!---->`,
},
- 'in-out'
+ 'in-out',
)
})
})
trueBranch,
trueSerialized,
falseBranch = () => null,
- falseSerialized = `<!---->`
+ falseSerialized = `<!---->`,
}: ToggleOptions) {
const toggle = ref(false)
const { props, cbs } = mockProps()
const { root } = mount(props, () =>
- toggle.value ? trueBranch() : falseBranch()
+ toggle.value ? trueBranch() : falseBranch(),
)
// start enter
onBeforeLeave: 1,
onLeave: 1,
onAfterLeave: 1,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
trueBranch: () => h('div'),
trueSerialized: `<div></div>`,
falseBranch: () => null,
- falseSerialized: `<!---->`
+ falseSerialized: `<!---->`,
})
})
trueBranch: () => h(Comp, { msg: 'hello' }),
trueSerialized: `<div>hello</div>`,
falseBranch: () => null,
- falseSerialized: `<!---->`
+ falseSerialized: `<!---->`,
})
})
})
trueBranch,
falseBranch,
trueSerialized,
- falseSerialized
+ falseSerialized,
}: ToggleOptions,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const toggle = ref(true)
const { props, cbs } = mockProps({}, withKeepAlive)
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
- withKeepAlive
+ withKeepAlive,
)
// without appear: true, enter hooks should not be called on mount
onLeave: 2,
onAfterLeave: 2,
onEnterCancelled: 0,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
trueBranch,
falseBranch,
trueSerialized,
- falseSerialized
+ falseSerialized,
}: ToggleOptions,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const toggle = ref(true)
const { props, cbs } = mockProps({}, withKeepAlive)
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
- withKeepAlive
+ withKeepAlive,
)
// start toggle
onBeforeLeave: 3,
onLeave: 3,
onAfterLeave: withKeepAlive ? 1 : 3,
- onLeaveCancelled: withKeepAlive ? 2 : 0
+ onLeaveCancelled: withKeepAlive ? 2 : 0,
})
}
trueBranch,
falseBranch,
trueSerialized,
- falseSerialized
+ falseSerialized,
}: ToggleOptions,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
- withKeepAlive
+ withKeepAlive,
)
// trigger toggle
onBeforeLeave: 2,
onLeave: 2,
onAfterLeave: 2,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
trueBranch,
falseBranch,
trueSerialized,
- falseSerialized
+ falseSerialized,
}: ToggleOptions,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
const { root, unmount } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
- withKeepAlive
+ withKeepAlive,
)
// trigger toggle
onBeforeLeave: 1,
onLeave: 1,
onAfterLeave: 1,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
describe('mode: "out-in" toggle before finish', () => {
async function testOutInBeforeFinish(
{ trueBranch, falseBranch, trueSerialized }: ToggleOptions,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
- withKeepAlive
+ withKeepAlive,
)
// trigger toggle
onBeforeLeave: 1,
onLeave: 1,
onAfterLeave: 1,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
trueBranch,
falseBranch,
trueSerialized,
- falseSerialized
+ falseSerialized,
}: ToggleOptions,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
- withKeepAlive
+ withKeepAlive,
)
// double quick toggle
onBeforeLeave: 1,
onLeave: 1,
onAfterLeave: 1,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
trueBranch,
falseBranch,
trueSerialized,
- falseSerialized
+ falseSerialized,
}: ToggleOptions,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'in-out' }, withKeepAlive)
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
- withKeepAlive
+ withKeepAlive,
)
toggle.value = false
onBeforeLeave: 2,
onLeave: 2,
onAfterLeave: 2,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
trueBranch,
falseBranch,
trueSerialized,
- falseSerialized
+ falseSerialized,
}: ToggleOptions,
- withKeepAlive = false
+ withKeepAlive = false,
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'in-out' }, withKeepAlive)
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
- withKeepAlive
+ withKeepAlive,
)
toggle.value = false
onBeforeLeave: 1,
onLeave: 1,
onAfterLeave: 1,
- onLeaveCancelled: 0
+ onLeaveCancelled: 0,
})
}
import {
- h,
- TestElement,
- nodeOps,
- render,
- ref,
+ type Component,
+ type ComponentOptions,
+ type ComponentPublicInstance,
KeepAlive,
- serializeInner,
- nextTick,
- ComponentOptions,
- markRaw,
- inject,
- defineComponent,
- ComponentPublicInstance,
- Ref,
+ type Ref,
+ type TestElement,
cloneVNode,
- provide,
- defineAsyncComponent,
- Component,
createApp,
+ defineAsyncComponent,
+ defineComponent,
+ h,
+ inject,
+ markRaw,
+ nextTick,
+ nodeOps,
onActivated,
- onUnmounted,
+ onDeactivated,
onMounted,
+ onUnmounted,
+ provide,
reactive,
+ ref,
+ render,
+ serializeInner,
shallowRef,
- onDeactivated
} from '@vue/runtime-test'
-import { KeepAliveProps } from '../../src/components/KeepAlive'
+import type { KeepAliveProps } from '../../src/components/KeepAlive'
const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
mounted: vi.fn(),
activated: vi.fn(),
deactivated: vi.fn(),
- unmounted: vi.fn()
+ unmounted: vi.fn(),
}
two = {
name: 'two',
mounted: vi.fn(),
activated: vi.fn(),
deactivated: vi.fn(),
- unmounted: vi.fn()
+ unmounted: vi.fn(),
}
views = {
one,
- two
+ two,
}
})
component.mounted.mock.calls.length,
component.activated.mock.calls.length,
component.deactivated.mock.calls.length,
- component.unmounted.mock.calls.length
+ component.unmounted.mock.calls.length,
]).toEqual(callCounts)
}
const App = {
render() {
return h(KeepAlive, null, {
- default: () => h(views[viewRef.value], { ref: instanceRef })
+ default: () => h(views[viewRef.value], { ref: instanceRef }),
})
- }
+ },
}
render(h(App), root)
expect(serializeInner(root)).toBe(`<div>one</div>`)
const App = {
render() {
return toggle.value ? h(KeepAlive, () => h(views[viewRef.value])) : null
- }
+ },
}
render(h(App), root)
const App = {
render() {
return toggle.value ? h(KeepAlive, () => h(views[viewRef.value])) : null
- }
+ },
}
render(h(App), root)
const App = {
render() {
return h(KeepAlive, () => (toggle.value ? h(one) : null))
- }
+ },
}
render(h(App), root)
render(this: any) {
return h('div', this.msg)
},
- activated: vi.fn()
+ activated: vi.fn(),
}
const one = {
name: 'one',
data: () => ({ msg: 'one' }),
render(this: any) {
return h(two)
- }
+ },
}
const toggle = ref(true)
const App = {
render() {
return h(KeepAlive, () => (toggle.value ? h(one) : null))
- }
+ },
}
render(h(App), root)
const App = {
render() {
return h(KeepAlive, () => (toggle1.value ? h(one) : null))
- }
+ },
}
render(h(App), root)
return outerRef.value
? h(KeepAlive, props, () => h(views[viewRef.value]))
: null
- }
+ },
}
render(h(App), root)
spyCC.mock.calls.length,
spyCA.mock.calls.length,
spyCDA.mock.calls.length,
- spyCUM.mock.calls.length
+ spyCUM.mock.calls.length,
]).toEqual(calls)
}
created: spyAC,
activated: spyAA,
deactivated: spyADA,
- unmounted: spyAUM
+ unmounted: spyAUM,
},
b: {
render: () => `two`,
created: spyBC,
activated: spyBA,
deactivated: spyBDA,
- unmounted: spyBUM
+ unmounted: spyBUM,
},
c: {
render: () => `three`,
created: spyCC,
activated: spyCA,
deactivated: spyCDA,
- unmounted: spyCUM
- }
+ unmounted: spyCUM,
+ },
}
const App = {
return h(KeepAlive, { max: 2 }, () => {
return h(views[viewRef.value])
})
- }
+ },
}
render(h(App), root)
assertCount([1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
return h(
KeepAlive,
{
- include: includeRef.value
+ include: includeRef.value,
},
- () => h(views[viewRef.value])
+ () => h(views[viewRef.value]),
)
- }
+ },
}
render(h(App), root)
return { viewRef, includeRef }
return h(
KeepAlive,
{
- exclude: excludeRef.value
+ exclude: excludeRef.value,
},
- () => h(views[viewRef.value])
+ () => h(views[viewRef.value]),
)
- }
+ },
}
render(h(App), root)
return { viewRef, excludeRef }
const one = {
name: 'one',
created: vi.fn(),
- render: () => 'one'
+ render: () => 'one',
}
const two = {
// anonymous
created: vi.fn(),
- render: () => 'two'
+ render: () => 'two',
}
const views: any = { one, two }
return h(
KeepAlive,
{
- include: include ? 'one' : undefined
+ include: include ? 'one' : undefined,
},
- () => h(views[viewRef.value])
+ () => h(views[viewRef.value]),
)
- }
+ },
}
render(h(App), root)
test('should not destroy active instance when pruning cache', async () => {
const Foo = {
render: () => 'foo',
- unmounted: vi.fn()
+ unmounted: vi.fn(),
}
const includeRef = ref(['foo'])
const App = {
return h(
KeepAlive,
{
- include: includeRef.value
+ include: includeRef.value,
},
- () => h(Foo)
+ () => h(Foo),
)
- }
+ },
}
render(h(App), root)
// condition: a render where a previous component is reused
setup() {
return () =>
h(KeepAlive, () => (toggle.value ? h(Foo, { n: n.value }) : null))
- }
+ },
}
render(h(App), root)
name: 'Foo',
render() {
return h('Foo')
- }
+ },
})
const Bar = markRaw({
name: 'Bar',
render() {
return h('Bar')
- }
+ },
})
const spyMounted = vi.fn()
},
onVnodeUnmounted() {
spyUnmounted()
- }
+ },
}
return () => {
const child: any = slots.default!({
- Component: Component!.value
+ Component: Component!.value,
})[0]
const innerChild = child.children[0]
child.children[0] = cloneVNode(innerChild, componentProps)
return child
}
- }
+ },
})
let toggle: () => void = () => {}
}
return {
component,
- toggle
+ toggle,
}
},
render() {
return h(RouterView, null, {
- default: ({ Component }: any) => h(KeepAlive, null, [h(Component)])
+ default: ({ Component }: any) => h(KeepAlive, null, [h(Component)]),
})
- }
+ },
})
render(h(App), root)
__scopeId: 'foo',
render: () => {
return h(KeepAlive, null, {
- default: () => h(views[viewRef.value], { ref: instanceRef })
+ default: () => h(views[viewRef.value], { ref: instanceRef }),
})
- }
+ },
}
render(h(App), root)
expect(serializeInner(root)).toBe(`<div foo>one</div>`)
() =>
new Promise(r => {
resolve = r as any
- })
+ }),
)
const toggle = ref(true)
const App = {
render: () => {
return h(KeepAlive, { include: 'Foo' }, () =>
- toggle.value ? h(AsyncComp, { ref: instanceRef }) : null
+ toggle.value ? h(AsyncComp, { ref: instanceRef }) : null,
)
- }
+ },
}
render(h(App), root)
data: () => ({ count: 0 }),
render() {
return h('p', this.count)
- }
+ },
})
await timeout()
const app = createApp({
setup() {
return () => h(KeepAlive, null, () => h(Child))
- }
+ },
})
const Child = {
throw err
})
},
- render() {}
+ render() {},
}
app.config.errorHandler = handler
onActivated(activatedA)
onDeactivated(deactivatedA)
return () => 'A'
- }
+ },
}
const B = {
name: 'B',
onMounted(mountedB)
onUnmounted(unmountedB)
return () => 'B'
- }
+ },
}
const include = reactive<string[]>([])
h(
KeepAlive,
{
- include
+ include,
},
- h(current.value)
- )
+ h(current.value),
+ ),
]
}
- }
+ },
})
app.mount(root)
*/
import {
+ type ComponentOptions,
+ Fragment,
+ KeepAlive,
+ Suspense,
+ type SuspenseProps,
h,
+ nextTick,
+ nodeOps,
+ onErrorCaptured,
+ onMounted,
+ onUnmounted,
ref,
- Suspense,
- ComponentOptions,
render,
- nodeOps,
+ resolveDynamicComponent,
serializeInner,
- nextTick,
- onMounted,
+ shallowRef,
watch,
watchEffect,
- onUnmounted,
- onErrorCaptured,
- shallowRef,
- SuspenseProps,
- resolveDynamicComponent,
- Fragment,
- KeepAlive
} from '@vue/runtime-test'
import { createApp, defineComponent } from 'vue'
-import { type RawSlots } from 'packages/runtime-core/src/componentSlots'
+import type { RawSlots } from 'packages/runtime-core/src/componentSlots'
describe('Suspense', () => {
const deps: Promise<any>[] = []
// a simple async factory for testing purposes only.
function defineAsyncComponent<T extends ComponentOptions>(
comp: T,
- delay: number = 0
+ delay: number = 0,
) {
return {
setup(props: any, { slots }: any) {
// an extra tick to avoid race conditions
deps.push(p.then(() => Promise.resolve()))
return p
- }
+ },
}
}
const Async = defineAsyncComponent({
render() {
return h('div', 'async')
- }
+ },
})
const Comp = {
return () =>
h(Suspense, null, {
default: h(Async),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
const Async = defineAsyncComponent({
render() {
return h('div', 'async')
- }
+ },
})
const onFallback = vi.fn()
onResolve,
onPending,
// force displaying the fallback right away
- timeout: 0
+ timeout: 0,
},
{
default: () => (show.value ? h(Async) : null),
- fallback: h('div', 'fallback')
- }
+ fallback: h('div', 'fallback'),
+ },
)
- }
+ },
}
const root = nodeOps.createElement('div')
calls.push('outer mounted')
})
return () => h(AsyncInner)
- }
+ },
})
const AsyncInner = defineAsyncComponent(
calls.push('inner mounted')
})
return () => h('div', 'inner')
- }
+ },
},
- 10
+ 10,
)
const Comp = {
return () =>
h(Suspense, null, {
default: h(AsyncOuter),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
const Async = defineAsyncComponent({
render() {
return h('div', 'async')
- }
+ },
})
const onResolve = vi.fn()
h(
Suspense,
{
- onResolve
+ onResolve,
},
{
default: h(Async),
- fallback: h('div', 'fallback')
- }
+ fallback: h('div', 'fallback'),
+ },
)
- }
+ },
}
const root = nodeOps.createElement('div')
() => {
calls.push('watch effect')
},
- { flush: 'post' }
+ { flush: 'post' },
)
const count = ref(0)
() => {
calls.push('watch callback')
},
- { flush: 'post' }
+ { flush: 'post' },
)
count.value++ // trigger the watcher now
await p
return () => h('div', 'async')
- }
+ },
}
const Comp = {
return () =>
h(Suspense, null, {
default: toggle.value ? h(Async) : null,
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
`watch effect`,
`watch callback`,
`mounted`,
- 'unmounted'
+ 'unmounted',
])
})
await p
return () => h('div', 'async')
- }
+ },
}
const Fallback = {
calls.push('unmounted')
})
return () => h('div', 'fallback')
- }
+ },
}
const Comp = {
return () =>
h(Suspense, null, {
default: toggle.value ? h(Async) : null,
- fallback: h(Fallback)
+ fallback: h(Fallback),
})
- }
+ },
}
const root = nodeOps.createElement('div')
props: { msg: String },
setup(props: any) {
return () => h('div', props.msg)
- }
+ },
})
const msg = ref('foo')
return () =>
h(Suspense, null, {
default: h(Async, { msg: msg.value }),
- fallback: h('div', `fallback ${msg.value}`)
+ fallback: h('div', `fallback ${msg.value}`),
})
- }
+ },
}
const root = nodeOps.createElement('div')
() => {
calls.push('watch effect')
},
- { flush: 'post' }
+ { flush: 'post' },
)
const count = ref(0)
() => {
calls.push('watch callback')
},
- { flush: 'post' }
+ { flush: 'post' },
)
count.value++ // trigger the watcher now
await p
return () => h('div', 'async')
- }
+ },
}
const Comp = {
return () =>
h(Suspense, null, {
default: toggle.value ? h(Async) : null,
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
setup() {
onUnmounted(unmounted)
return () => h('div', 'async')
- }
+ },
})
const Comp = {
toggle.value
? h(Suspense, null, {
default: h(Async),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
: null
- }
+ },
}
const root = nodeOps.createElement('div')
onMounted(mounted)
onUnmounted(unmounted)
return () => h('div', 'async')
- }
+ },
})
const Comp = {
toggle.value
? h(Suspense, null, {
default: h(Async),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
: null
- }
+ },
}
const root = nodeOps.createElement('div')
const p = new Promise(r => setTimeout(r, 1))
await p
return () => h('div', 'async')
- }
+ },
}
const Comp: ComponentOptions<{ data: string }> = {
props: ['data'],
setup(props) {
return () => h(Async, { 'data-test': props.data })
- }
+ },
}
const Root = {
return () =>
h(Suspense, null, {
default: h(Comp, { data: data.value }),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
calls.push('outer mounted')
})
return () => h('div', 'async outer')
- }
+ },
},
- 1
+ 1,
)
const AsyncInner = defineAsyncComponent(
calls.push('inner mounted')
})
return () => h('div', 'async inner')
- }
+ },
},
- 10
+ 10,
)
const Inner = {
return () =>
h(Suspense, null, {
default: h(AsyncInner),
- fallback: h('div', 'fallback inner')
+ fallback: h('div', 'fallback inner'),
})
- }
+ },
}
const Comp = {
return () =>
h(Suspense, null, {
default: h('div', [h(AsyncOuter), h(Inner)]),
- fallback: h('div', 'fallback outer')
+ fallback: h('div', 'fallback outer'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
await deps[0]
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>async outer</div><div>fallback inner</div></div>`
+ `<div><div>async outer</div><div>fallback inner</div></div>`,
)
expect(calls).toEqual([`outer mounted`])
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>async outer</div><div>async inner</div></div>`
+ `<div><div>async outer</div><div>async inner</div></div>`,
)
expect(calls).toEqual([`outer mounted`, `inner mounted`])
})
calls.push('outer mounted')
})
return () => h('div', 'async outer')
- }
+ },
},
- 10
+ 10,
)
const AsyncInner = defineAsyncComponent(
calls.push('inner mounted')
})
return () => h('div', 'async inner')
- }
+ },
},
- 1
+ 1,
)
const Inner = {
return () =>
h(Suspense, null, {
default: h(AsyncInner),
- fallback: h('div', 'fallback inner')
+ fallback: h('div', 'fallback inner'),
})
- }
+ },
}
const Comp = {
return () =>
h(Suspense, null, {
default: h('div', [h(AsyncOuter), h(Inner)]),
- fallback: h('div', 'fallback outer')
+ fallback: h('div', 'fallback outer'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>async outer</div><div>async inner</div></div>`
+ `<div><div>async outer</div><div>async inner</div></div>`,
)
expect(calls).toEqual([`inner mounted`, `outer mounted`])
})
const Async = {
async setup() {
throw new Error('oops')
- }
+ },
}
const Comp = {
? h('div', errorMessage.value)
: h(Suspense, null, {
default: h(Async),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
const Async = {
async setup() {
throw new Error('oops')
- }
+ },
}
const Comp = {
return false
})
return { errorMessage }
- }
+ },
}
const root = nodeOps.createElement('div')
return () =>
h(Suspense, null, {
default: h(AsyncInsideNestedSuspense, { msg: props.msg }),
- fallback: h('div', 'nested fallback')
+ fallback: h('div', 'nested fallback'),
})
- }
+ },
})
const AsyncInsideNestedSuspense = defineAsyncComponent(
calls.push(2)
})
return () => h('div', props.msg)
- }
+ },
},
- 20
+ 20,
)
const AsyncChildParent = defineAsyncComponent({
calls.push(1)
})
return () => h(NestedAsyncChild, { msg: props.msg })
- }
+ },
})
const NestedAsyncChild = defineAsyncComponent(
calls.push(3)
})
return () => h('div', props.msg)
- }
+ },
},
- 10
+ 10,
)
const MiddleComponent = {
setup() {
return () =>
h(AsyncChildWithSuspense, {
- msg: msg.value
+ msg: msg.value,
})
- }
+ },
}
const Comp = {
default: h('div', [
h(MiddleComponent),
h(AsyncChildParent, {
- msg: 'root async'
- })
+ msg: 'root async',
+ }),
]),
- fallback: h('div', 'root fallback')
+ fallback: h('div', 'root fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
await deps[3]
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>nested fallback</div><div>root async</div></div>`
+ `<div><div>nested fallback</div><div>root async</div></div>`,
)
expect(calls).toEqual([0, 1, 3])
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>nested changed</div><div>root async</div></div>`
+ `<div><div>nested changed</div><div>root async</div></div>`,
)
expect(calls).toEqual([0, 1, 3, 2])
msg.value = 'nested changed again'
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>nested changed again</div><div>root async</div></div>`
+ `<div><div>nested changed again</div><div>root async</div></div>`,
)
})
calls.push('foo unmounted')
})
return () => h('div', ['foo', h(FooNested)])
- }
+ },
})
const FooNested = defineAsyncComponent(
calls.push('foo nested unmounted')
})
return () => h('div', 'foo nested')
- }
+ },
},
- 10
+ 10,
)
const Bar = defineAsyncComponent(
calls.push('bar unmounted')
})
return () => h('div', 'bar')
- }
+ },
},
- 10
+ 10,
)
const Comp = {
return () =>
h(Suspense, null, {
default: toggle.value ? h(Foo) : h(Bar),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
`foo nested mounted`,
`bar mounted`,
`foo nested unmounted`,
- `foo unmounted`
+ `foo unmounted`,
]
expect(calls).toEqual(tempCalls)
expect(serializeInner(root)).toBe(`<div>bar</div>`)
...tempCalls,
`foo mounted`,
`foo nested mounted`,
- `bar unmounted`
+ `bar unmounted`,
])
expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
})
calls.push(`${name} unmounted`)
})
return () => h('div', [name])
- }
+ },
},
- delay
+ delay,
)
const One = makeComp('one')
return () =>
h(Suspense, null, {
default: h(view.value),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
calls.push(`${name} unmounted`)
})
return () => h('div', [name])
- }
+ },
},
- delay
+ delay,
)
const One = makeComp('one')
return () =>
h(Suspense, null, {
default: h(view.value),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
const root = nodeOps.createElement('div')
calls.push(`${name} unmounted`)
})
return () => h('div', [name])
- }
+ },
},
- delay
+ delay,
)
const One = makeComp('one')
h(
Suspense,
{
- timeout: 10
+ timeout: 10,
},
{
default: h(view.value),
- fallback: h('div', 'fallback')
- }
+ fallback: h('div', 'fallback'),
+ },
)
- }
+ },
}
const root = nodeOps.createElement('div')
{
setup() {
return () => h('div', [name])
- }
+ },
},
- delay
+ delay,
)
const One = makeComp('one')
h(
Suspense,
{
- timeout: 10
+ timeout: 10,
},
{
default: h(view.value),
- fallback: h('div', 'fallback')
- }
+ fallback: h('div', 'fallback'),
+ },
),
- h('div', 'three')
+ h('div', 'three'),
])
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(
- `<div><div>fallback</div><div>three</div></div>`
+ `<div><div>fallback</div><div>three</div></div>`,
)
await deps[0]
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>one</div><div>three</div></div>`
+ `<div><div>one</div><div>three</div></div>`,
)
view.value = Two
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>one</div><div>three</div></div>`
+ `<div><div>one</div><div>three</div></div>`,
)
await new Promise(r => setTimeout(r, 10))
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>fallback</div><div>three</div></div>`
+ `<div><div>fallback</div><div>three</div></div>`,
)
await deps[1]
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>two</div><div>three</div></div>`
+ `<div><div>two</div><div>three</div></div>`,
)
})
<Suspense>
<div><span>{{ n }}</span></div>
</Suspense>
- `
+ `,
}
const root = document.createElement('div')
createApp(Comp).mount(root)
const Child = {
async setup() {
return () => h('div', 'child')
- }
+ },
}
const Parent = () => h('div', ['parent', toggle.value ? h(Child) : null])
const Comp = {
setup() {
return () => h(Suspense, () => h(Parent))
- }
+ },
}
const root = nodeOps.createElement('div')
name: 'Child',
async setup() {
return () => h('div', 'child')
- }
+ },
}
const Parent = {
setup() {
return () => h('div', [h(Child)])
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(
- `A component with async setup() must be nested in a <Suspense>`
+ `A component with async setup() must be nested in a <Suspense>`,
).toHaveBeenWarned()
})
calls.push('innerA mounted')
})
return () => h('div', 'innerA')
- }
+ },
},
- 10
+ 10,
)
const InnerB = defineAsyncComponent(
calls.push('innerB mounted')
})
return () => h('div', 'innerB')
- }
+ },
},
- 10
+ 10,
)
const OuterA = defineAsyncComponent(
})
return () =>
h(Fragment, null, [h('div', 'outerA'), slots.default?.()])
- }
+ },
},
- 5
+ 5,
)
const OuterB = defineAsyncComponent(
})
return () =>
h(Fragment, null, [h('div', 'outerB'), slots.default?.()])
- }
+ },
},
- 5
+ 5,
)
const outerToggle = ref(false)
Suspense,
{ suspensible: true },
{
- default: h(innerToggle.value ? InnerB : InnerA)
- }
- )
- })
+ default: h(innerToggle.value ? InnerB : InnerA),
+ },
+ ),
+ }),
],
- fallback: h('div', 'fallback outer')
+ fallback: h('div', 'fallback outer'),
})
- }
+ },
}
expected = `<div>fallback outer</div>`
'outerA created',
'innerA created',
'outerA mounted',
- 'innerA mounted'
+ 'innerA mounted',
])
// toggle outer component
calls.push('innerA mounted')
})
return () => h('div', 'innerA')
- }
+ },
})
const InnerB = defineComponent({
calls.push('innerB mounted')
})
return () => h('div', 'innerB')
- }
+ },
})
const OuterA = defineComponent({
calls.push('outerA mounted')
})
return () => h(Fragment, null, [h('div', 'outerA'), slots.default?.()])
- }
+ },
})
const OuterB = defineComponent({
calls.push('outerB mounted')
})
return () => h(Fragment, null, [h('div', 'outerB'), slots.default?.()])
- }
+ },
})
const outerToggle = ref(false)
Suspense,
{ suspensible: true },
{
- default: h(innerToggle.value ? InnerB : InnerA)
- }
- )
- })
+ default: h(innerToggle.value ? InnerB : InnerA),
+ },
+ ),
+ }),
],
- fallback: h('div', 'fallback outer')
+ fallback: h('div', 'fallback outer'),
})
- }
+ },
})
expected = `<div>outerA</div><div>innerA</div>`
'outerA created',
'innerA created',
'innerA mounted',
- 'outerA mounted'
+ 'outerA mounted',
])
// toggle outer component
const Async = defineAsyncComponent({
render() {
return h('div', 'async')
- }
+ },
})
const Sync = {
render() {
return h('div', 'sync')
- }
+ },
}
const components = [Async, Sync]
const viewRef = ref(0)
default: () => {
return h(Suspense, null, {
default: h(components[viewRef.value]),
- fallback: h('div', 'Loading-dynamic-components')
+ fallback: h('div', 'Loading-dynamic-components'),
})
- }
+ },
})
- }
+ },
}
render(h(App), root)
expect(serializeInner(root)).toBe(`<div>Loading-dynamic-components</div>`)
const Async = defineAsyncComponent({
render() {
return h('div', 'async')
- }
+ },
})
const Comp = {
render() {
return h(Async, { key: key.value })
- }
+ },
}
const root = nodeOps.createElement('div')
const App = {
render() {
return h(Suspense, null, {
- default: h(Comp, { data: data.value })
+ default: h(Comp, { data: data.value }),
})
- }
+ },
}
render(h(App), root)
expect(serializeInner(root)).toBe(`<!---->`)
function baseCheckWarn(
shouldWarn: boolean,
children: RawSlots,
- props: SuspenseProps | null = null
+ props: SuspenseProps | null = null,
) {
const Comp = {
setup() {
return () => h(Suspense, props, children)
- }
+ },
}
const root = nodeOps.createElement('div')
expect(`<Suspense> slots expect a single root node.`).toHaveBeenWarned()
} else {
expect(
- `<Suspense> slots expect a single root node.`
+ `<Suspense> slots expect a single root node.`,
).not.toHaveBeenWarned()
}
}
test('does not warn on single child', async () => {
checkNoWarn({
default: h('div'),
- fallback: h('div')
+ fallback: h('div'),
})
})
test('does not warn on null', async () => {
checkNoWarn({
default: null,
- fallback: null
+ fallback: null,
})
})
test('does not warn on <component :is="null" />', async () => {
checkNoWarn({
default: () => [resolveDynamicComponent(null)],
- fallback: () => null
+ fallback: () => null,
})
})
test('does not warn on empty array', async () => {
checkNoWarn({
default: [],
- fallback: () => []
+ fallback: () => [],
})
})
test('warns on multiple children in default', async () => {
checkWarn({
- default: [h('div'), h('div')]
+ default: [h('div'), h('div')],
})
})
test('warns on multiple children in fallback', async () => {
checkWarn({
default: h('div'),
- fallback: [h('div'), h('div')]
+ fallback: [h('div'), h('div')],
})
})
})
*/
import {
- nodeOps,
- serializeInner,
- render,
- h,
Teleport,
Text,
- ref,
- nextTick,
- markRaw,
+ createApp,
defineComponent,
+ h,
+ markRaw,
+ nextTick,
+ nodeOps,
+ ref,
+ render,
+ serializeInner,
withDirectives,
- createApp
} from '@vue/runtime-test'
-import { createVNode, Fragment } from '../../src/vnode'
+import { Fragment, createVNode } from '../../src/vnode'
import { compile, render as domRender } from 'vue'
describe('renderer: teleport', () => {
render(
h(() => [
h(Teleport, { to: target }, h('div', 'teleported')),
- h('div', 'root')
+ h('div', 'root'),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
})
setup() {
return {
svg,
- circle
+ circle,
}
},
template: `
<svg ref="svg"></svg>
<teleport :to="svg" v-if="svg">
<circle ref="circle"></circle>
- </teleport>`
+ </teleport>`,
})
domRender(h(Comp), root)
await nextTick()
expect(root.innerHTML).toMatchInlineSnapshot(
- `"<svg><circle></circle></svg><!--teleport start--><!--teleport end-->"`
+ `"<svg><circle></circle></svg><!--teleport start--><!--teleport end-->"`,
)
expect(svg.value.namespaceURI).toBe('http://www.w3.org/2000/svg')
render(
h(() => [
h(Teleport, { to: target.value }, h('div', 'teleported')),
- h('div', 'root')
+ h('div', 'root'),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(targetA)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
expect(serializeInner(targetB)).toMatchInlineSnapshot(`""`)
await nextTick()
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(targetA)).toMatchInlineSnapshot(`""`)
expect(serializeInner(targetB)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
})
render(
h(() => h(Teleport, { to: target }, children.value)),
- root
+ root,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
children.value = []
function testUnmount(props: any) {
render(
h(() => [h(Teleport, props, h('div', 'teleported')), h('div', 'root')]),
- root
+ root,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- props.disabled ? `""` : `"<div>teleported</div>"`
+ props.disabled ? `""` : `"<div>teleported</div>"`,
)
render(null, root)
const Comp = {
render() {
return [h('p'), h('p')]
- }
+ },
}
render(
h(() => [h(Teleport, { to: target }, h(Comp)), h('div', 'root')]),
- root
+ root,
)
expect(serializeInner(target)).toMatchInlineSnapshot(`"<p></p><p></p>"`)
return [h('p'), h('p')]
},
beforeUnmount: vi.fn(),
- unmounted: vi.fn()
+ unmounted: vi.fn(),
}
render(
h(() => [h(Teleport, { to: null, disabled: true }, h(CompWithHook))]),
- root
+ root,
)
expect(CompWithHook.beforeUnmount).toBeCalledTimes(0)
expect(CompWithHook.unmounted).toBeCalledTimes(0)
render(
h('div', [
h(Teleport, { to: target }, h('div', 'one')),
- h(Teleport, { to: target }, 'two')
+ h(Teleport, { to: target }, 'two'),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>"`
+ `"<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(`"<div>one</div>two"`)
render(
h('div', [
h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
- h(Teleport, { to: target }, 'three')
+ h(Teleport, { to: target }, 'three'),
]),
- root
+ root,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>one</div><div>two</div>three"`
+ `"<div>one</div><div>two</div>three"`,
)
// toggling
render(h('div', [null, h(Teleport, { to: target }, 'three')]), root)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><!----><!--teleport start--><!--teleport end--></div>"`
+ `"<div><!----><!--teleport start--><!--teleport end--></div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(`"three"`)
render(
h('div', [
h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
- h(Teleport, { to: target }, 'three')
+ h(Teleport, { to: target }, 'three'),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>"`
+ `"<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>"`,
)
// should append
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"three<div>one</div><div>two</div>"`
+ `"three<div>one</div><div>two</div>"`,
)
// toggle the other teleport
render(
h('div', [
h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
- null
+ null,
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--><!----></div>"`
+ `"<div><!--teleport start--><!--teleport end--><!----></div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>one</div><div>two</div>"`
+ `"<div>one</div><div>two</div>"`,
)
})
h(
Teleport,
{ to: target.value, disabled: disabled.value },
- h('div', 'teleported')
- )
+ h('div', 'teleported'),
+ ),
])
- }
+ },
}
render(h(App), root)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div></div><!--teleport start--><div>teleported</div><!--teleport end-->"`
+ `"<div></div><!--teleport start--><div>teleported</div><!--teleport end-->"`,
)
disabled.value = false
await nextTick()
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><div>teleported</div></div><!--teleport start--><!--teleport end-->"`
+ `"<div><div>teleported</div></div><!--teleport start--><!--teleport end-->"`,
)
})
const renderWithDisabled = (disabled: boolean) => {
return h(Fragment, [
h(Teleport, { to: target, disabled }, h('div', 'teleported')),
- h('div', 'root')
+ h('div', 'root'),
])
}
render(renderWithDisabled(false), root)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
render(renderWithDisabled(true), root)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toBe(``)
// toggle back
render(renderWithDisabled(false), root)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
})
render(
h(Fragment, [
h(Teleport, { to: target }, h('div', 'teleported')),
- h('div', 'root')
+ h('div', 'root'),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
render(
h(Fragment, [
h('div', 'root'),
- h(Teleport, { to: target }, h('div', 'teleported'))
+ h(Teleport, { to: target }, h('div', 'teleported')),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div>root</div><!--teleport start--><!--teleport end-->"`
+ `"<div>root</div><!--teleport start--><!--teleport end-->"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
render(
h(Fragment, [
h(Teleport, { to: target }, h('div', 'teleported')),
- h('div', 'root')
+ h('div', 'root'),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
})
render(
h(Fragment, [
h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
- h('div', 'root')
+ h('div', 'root'),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toBe('')
render(
h(Fragment, [
h('div', 'root'),
- h(Teleport, { to: target, disabled: true }, h('div', 'teleported'))
+ h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div>root</div><!--teleport start--><div>teleported</div><!--teleport end-->"`
+ `"<div>root</div><!--teleport start--><div>teleported</div><!--teleport end-->"`,
)
expect(serializeInner(target)).toBe('')
render(
h(Fragment, [
h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
- h('div', 'root')
+ h('div', 'root'),
]),
- root
+ root,
)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toBe('')
})
setup() {
return {
target: markRaw(target),
- disabled
+ disabled,
}
},
render: compile(`
<div>teleported</div><span>{{ disabled }}</span><span v-if="disabled"/>
</teleport>
<div>root</div>
- `)
+ `),
}
render(h(App), root)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div><span>false</span><!--v-if-->"`
+ `"<div>teleported</div><span>false</span><!--v-if-->"`,
)
disabled.value = true
await nextTick()
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><div>teleported</div><span>true</span><span></span><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><div>teleported</div><span>true</span><span></span><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toBe(``)
disabled.value = false
await nextTick()
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div><span>false</span><!--v-if-->"`
+ `"<div>teleported</div><span>false</span><!--v-if-->"`,
)
})
const toggle = ref(true)
const dir = {
mounted: vi.fn(),
- unmounted: vi.fn()
+ unmounted: vi.fn(),
}
const app = createApp({
return () => {
return toggle.value
? h(Teleport, { to: target }, [
- withDirectives(h('div', ['foo']), [[dir]])
+ withDirectives(h('div', ['foo']), [[dir]]),
])
: null
}
- }
+ },
})
app.mount(root)
expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end-->"`
+ `"<!--teleport start--><!--teleport end-->"`,
)
expect(serializeInner(target)).toMatchInlineSnapshot(`"<div>foo</div>"`)
expect(dir.mounted).toHaveBeenCalledTimes(1)
h(
Teleport,
{ to: target.value, disabled: disabled.value },
- h('div', 'teleported')
- )
+ h('div', 'teleported'),
+ ),
])
- }
+ },
}
render(h(App), root)
disabled.value = false
await nextTick()
expect(serializeInner(target1)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
expect(serializeInner(target3)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`
+ `"<div>teleported</div>"`,
)
})
})
import {
+ type DirectiveBinding,
+ type DirectiveHook,
+ type VNode,
+ defineComponent,
h,
- withDirectives,
+ nextTick,
+ nodeOps,
ref,
render,
- nodeOps,
- DirectiveHook,
- VNode,
- DirectiveBinding,
- nextTick,
- defineComponent
+ withDirectives,
} from '@vue/runtime-test'
-import { currentInstance, ComponentInternalInstance } from '../src/component'
+import {
+ type ComponentInternalInstance,
+ currentInstance,
+} from '../src/component'
describe('directives', () => {
it('should work', async () => {
beforeUpdate,
updated,
beforeUnmount,
- unmounted
+ unmounted,
}
let _instance: ComponentInternalInstance | null = null
// argument
'foo',
// modifiers
- { ok: true }
- ]
+ { ok: true },
+ ],
])
return _vnode
- }
+ },
}
const root = nodeOps.createElement('div')
// argument
'foo',
// modifiers
- { ok: true }
- ]
+ { ok: true },
+ ],
])
return _vnode
- }
+ },
}
const root = nodeOps.createElement('div')
beforeUpdate,
updated,
beforeUnmount,
- unmounted
+ unmounted,
}
let _instance: ComponentInternalInstance | null = null
// argument
'foo',
// modifiers
- { ok: true }
- ]
+ { ok: true },
+ ],
])
- }
+ },
}
const root = nodeOps.createElement('div')
// #2298
it('directive merging on component root', () => {
const d1 = {
- mounted: vi.fn()
+ mounted: vi.fn(),
}
const d2 = {
- mounted: vi.fn()
+ mounted: vi.fn(),
}
const Comp = {
render() {
return withDirectives(h('div'), [[d2]])
- }
+ },
}
const App = {
name: 'App',
render() {
return h('div', [withDirectives(h(Comp), [[d1]])])
- }
+ },
}
const root = nodeOps.createElement('div')
return withDirectives(h('p', text.value), [
[
{
- beforeUpdate
- }
- ]
+ beforeUpdate,
+ },
+ ],
])
- }
+ },
}
const root = nodeOps.createElement('div')
const App = defineComponent({
setup(_, { expose }) {
expose({
- msg: 'Test'
+ msg: 'Test',
})
return () =>
{
mounted(el, { instance }) {
res = (instance as any).msg as string
- }
- }
- ]
+ },
+ },
+ ],
])
- }
+ },
})
const root = nodeOps.createElement('div')
render(h(App), root)
test('should not throw with unknown directive', async () => {
const d1 = {
- mounted: vi.fn()
+ mounted: vi.fn(),
}
const App = {
name: 'App',
render() {
// simulates the code generated on an unknown directive
return withDirectives(h('div'), [[undefined], [d1]])
- }
+ },
}
const root = nodeOps.createElement('div')
import {
- onMounted,
- onErrorCaptured,
- render,
+ createApp,
+ defineComponent,
h,
+ nextTick,
nodeOps,
- watch,
+ onErrorCaptured,
+ onMounted,
ref,
- nextTick,
- defineComponent,
+ render,
+ watch,
watchEffect,
- createApp
} from '@vue/runtime-test'
describe('error handling', () => {
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
fn(err, info, 'child')
})
return () => h(GrandChild)
- }
+ },
}
const GrandChild = {
throw err
})
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
return false
})
return () => h(GrandChild)
- }
+ },
}
const GrandChild = {
throw err
})
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
throw err
})
},
- render() {}
+ render() {},
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
throw err2
})
return () => h(GrandChild)
- }
+ },
}
const GrandChild = {
throw err
})
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
setup() {
throw err
},
- render() {}
+ render() {},
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => [h(Child1), h(Child2)]
- }
+ },
}
const Child1 = {
created() {
throw err
},
- render() {}
+ render() {},
}
const Child2 = {
beforeCreate() {
throw err
},
- render() {}
+ render() {},
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
return () => {
throw err
}
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const Child = defineComponent(() => () => h('div', { ref }))
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
throw err
})
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
() => {
throw err
},
- () => {}
+ () => {},
)
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const count = ref(0)
() => count.value,
() => {
throw err
- }
+ },
)
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
return false
})
return () => h(Child)
- }
+ },
}
const Child = {
})
})
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
h(Child, {
onFoo: () => {
throw err
- }
+ },
})
- }
+ },
}
const Child = {
setup(props: any, { emit }: any) {
emit('foo')
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
h(Child, {
async onFoo() {
throw err
- }
+ },
})
- }
+ },
}
const Child = {
setup(props: any, { emit }: any) {
emit('foo')
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
h(Child, {
onFoo: [
createAsyncHandler(Promise.reject(err)),
- createAsyncHandler(Promise.resolve(1))
- ]
+ createAsyncHandler(Promise.resolve(1)),
+ ],
})
- }
+ },
}
const Child = {
setup(props: any, { emit }: any) {
emit('foo')
return () => null
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
fn(err, info)
})
return () => h(Child)
- }
+ },
}
const Child = {
setup() {
throw err
},
- render() {}
+ render() {},
}
let caughtError
}
expect(fn).toHaveBeenCalledWith(err, 'setup function')
expect(
- `Unhandled error during execution of setup function`
+ `Unhandled error during execution of setup function`,
).toHaveBeenWarned()
expect(caughtError).toBe(err)
() => {
throw error1
},
- { immediate: true }
+ { immediate: true },
)
watch(
count,
async () => {
throw error2
},
- { immediate: true }
+ { immediate: true },
)
watchEffect(() => {
throw error3
throw error4
})
},
- render() {}
+ render() {},
})
app.config.errorHandler = handler
import { h } from '../src/h'
import { createVNode } from '../src/vnode'
-import { RawSlots } from '../src/componentSlots'
+import type { RawSlots } from '../src/componentSlots'
// Since h is a thin layer on top of createVNode, we are only testing its
// own logic here. Details of vnode creation is tested in vnode.spec.ts.
test('type + props', () => {
expect(h('div', { id: 'foo' })).toMatchObject(
- createVNode('div', { id: 'foo' })
+ createVNode('div', { id: 'foo' }),
)
})
expect(h('div', {}, slots)).toMatchObject(createVNode('div', {}, slots))
const Component = { template: '<br />' }
expect(h(Component, {}, slots)).toMatchObject(
- createVNode(Component, {}, slots)
+ createVNode(Component, {}, slots),
)
// default slot
const slot = () => {}
expect(h(Component, {}, slot)).toMatchObject(
- createVNode(Component, {}, slot)
+ createVNode(Component, {}, slot),
)
// single vnode
const vnode = h('div')
const slot = () => {}
expect(
h(Component, null, {
- foo: slot
- })
+ foo: slot,
+ }),
).toMatchObject(
createVNode(Component, null, {
- foo: slot
- })
+ foo: slot,
+ }),
)
})
// note this signature is not supported in types; it's purely for usage with
// compiled code.
test('support variadic children', () => {
- // @ts-ignore
+ // @ts-expect-error
const vnode = h('div', null, h('span'), h('span'))
expect(vnode.children).toMatchObject([
{
- type: 'span'
+ type: 'span',
},
{
- type: 'span'
- }
+ type: 'span',
+ },
])
})
})
-import { Slot } from '../../src/componentSlots'
+import type { Slot } from '../../src/componentSlots'
import { createSlots } from '../../src/helpers/createSlots'
describe('createSlot', () => {
const actual = createSlots(record, dynamicSlot)
const ret = actual.descriptor()
- // @ts-ignore
+ // @ts-expect-error
expect(ret.key).toBe('1')
})
it('should add all slots to the record', () => {
const dynamicSlot = [
{ name: 'descriptor', fn: slot },
- { name: 'descriptor2', fn: slot }
+ { name: 'descriptor2', fn: slot },
]
const actual = createSlots(record, dynamicSlot)
it('should add slot to the record when given slot is an array', () => {
const dynamicSlot = [
{ name: 'descriptor', fn: slot },
- [{ name: 'descriptor2', fn: slot }]
+ [{ name: 'descriptor2', fn: slot }],
]
const actual = createSlots(record, dynamicSlot)
{ name: 'descriptor', fn: slot },
[
{ name: 'descriptor2', fn: slot },
- { name: 'descriptor3', fn: slot }
- ]
+ { name: 'descriptor3', fn: slot },
+ ],
]
const actual = createSlots(record, dynamicSlot)
expect(actual).toEqual({
descriptor: slot,
descriptor2: slot,
- descriptor3: slot
+ descriptor3: slot,
})
})
})
describe('renderList', () => {
it('should render items in an array', () => {
expect(
- renderList(['1', '2', '3'], (item, index) => `node ${index}: ${item}`)
+ renderList(['1', '2', '3'], (item, index) => `node ${index}: ${item}`),
).toEqual(['node 0: 1', 'node 1: 2', 'node 2: 3'])
})
it('should render characters of a string', () => {
expect(
- renderList('123', (item, index) => `node ${index}: ${item}`)
+ renderList('123', (item, index) => `node ${index}: ${item}`),
).toEqual(['node 0: 1', 'node 1: 2', 'node 2: 3'])
})
expect(renderList(3, (item, index) => `node ${index}: ${item}`)).toEqual([
'node 0: 1',
'node 1: 2',
- 'node 2: 3'
+ 'node 2: 3',
])
})
renderList(3.1, () => {})
} catch (e) {}
expect(
- `The v-for range expect an integer value but got 3.1.`
+ `The v-for range expect an integer value but got 3.1.`,
).toHaveBeenWarned()
})
expect(
renderList(
{ a: 1, b: 2, c: 3 },
- (item, key, index) => `node ${index}/${key}: ${item}`
- )
+ (item, key, index) => `node ${index}/${key}: ${item}`,
+ ),
).toEqual(['node 0/a: 1', 'node 1/b: 2', 'node 2/c: 3'])
})
}
expect(
- renderList(iterable(), (item, index) => `node ${index}: ${item}`)
+ renderList(iterable(), (item, index) => `node ${index}: ${item}`),
).toEqual(['node 0: 1', 'node 1: 2', 'node 2: 3'])
})
it('should return empty array when source is undefined', () => {
expect(
- renderList(undefined, (item, index) => `node ${index}: ${item}`)
+ renderList(undefined, (item, index) => `node ${index}: ${item}`),
).toEqual([])
})
})
import { renderSlot } from '../../src/helpers/renderSlot'
import {
- h,
- withCtx,
- createVNode,
- openBlock,
- createBlock,
Fragment,
+ type Slot,
+ createBlock,
createCommentVNode,
- Slot
+ createVNode,
+ h,
+ openBlock,
+ withCtx,
} from '../../src'
import { PatchFlags } from '@vue/shared'
import { setCurrentRenderingInstance } from '../../src/componentRenderContext'
let child
const vnode = renderSlot(
{ default: () => [(child = h('child'))] },
- 'default'
+ 'default',
)
expect(vnode.children).toEqual([child])
})
return [createVNode('div', null, 'foo', PatchFlags.TEXT)]
},
// mock instance
- { type: {}, appContext: {} } as any
+ { type: {}, appContext: {} } as any,
) as Slot
// manual invocation should not track
{ default: () => [createCommentVNode('foo')] },
'default',
undefined,
- () => [(fallback = h('fallback'))]
+ () => [(fallback = h('fallback'))],
)
expect(vnode.children).toEqual([fallback])
expect(vnode.patchFlag).toBe(PatchFlags.BAIL)
{ default: () => [renderSlot({}, 'foo')] },
'default',
undefined,
- () => [(fallback = h('fallback'))]
+ () => [(fallback = h('fallback'))],
)
expect(vnode.children).toEqual([fallback])
expect(vnode.patchFlag).toBe(PatchFlags.BAIL)
import {
+ Comment,
+ type Component,
+ type Directive,
+ type VNode,
createApp,
+ createVNode,
+ h,
nodeOps,
resolveComponent,
resolveDirective,
- Component,
- Directive,
resolveDynamicComponent,
- h,
serializeInner,
- createVNode,
- Comment,
- VNode
} from '@vue/runtime-test'
describe('resolveAssets', () => {
const Root = {
components: {
- FooBar: FooBar
+ FooBar: FooBar,
},
directives: {
- BarBaz: BarBaz
+ BarBaz: BarBaz,
},
setup() {
return () => {
component4 = resolveComponent('foo-bar')!
directive4 = resolveDirective('bar-baz')!
}
- }
+ },
}
const app = createApp(Root)
name: 'Root',
components: {
Foo,
- Root: Foo
+ Root: Foo,
},
setup() {
return () => {
component2 = resolveComponent('Foo', true)
component3 = resolveComponent('Bar', true)
}
- }
+ },
}
const app = createApp(Root)
test('used outside render() or setup()', () => {
resolveComponent('foo')
expect(
- 'resolveComponent can only be used in render() or setup().'
+ 'resolveComponent can only be used in render() or setup().',
).toHaveBeenWarned()
resolveDirective('foo')
expect(
- 'resolveDirective can only be used in render() or setup().'
+ 'resolveDirective can only be used in render() or setup().',
).toHaveBeenWarned()
})
resolveComponent('foo')
resolveDirective('bar')
return () => null
- }
+ },
}
const app = createApp(Root)
const dynamicComponents = {
foo: () => 'foo',
bar: () => 'bar',
- baz: { render: () => 'baz' }
+ baz: { render: () => 'baz' },
}
let foo, bar, baz // dynamic components
let dynamicVNode: VNode
const Child = {
render(this: any) {
return this.$slots.default()
- }
+ },
}
const Root = {
baz = resolveDynamicComponent(dynamicComponents.baz) // <component :is="baz"/>, object
})
}
- }
+ },
}
const app = createApp(Root)
setup() {
return () => {
return createVNode(resolveDynamicComponent('div') as string, null, {
- default: () => 'hello'
+ default: () => 'hello',
})
}
- }
+ },
}
const app = createApp(Root)
const Base = {
components: {
- FooBar: FooBar
- }
+ FooBar: FooBar,
+ },
}
const Mixin = {
directives: {
- BarBaz: BarBaz
- }
+ BarBaz: BarBaz,
+ },
}
const Root = {
component4 = resolveComponent('foo-bar')!
directive4 = resolveDirective('bar-baz')!
}
- }
+ },
}
const app = createApp(Root)
toHandlers(undefined as any)
expect(
- 'v-on with no argument expects an object value.'
+ 'v-on with no argument expects an object value.',
).toHaveBeenWarnedTimes(2)
})
expect(toHandlers({ input, change })).toStrictEqual({
onInput: input,
- onChange: change
+ onChange: change,
})
})
})
// since v-memo really is a compiler + runtime combo feature, we are performing
// more of an integration test here.
-import { ComponentOptions, createApp, nextTick } from 'vue'
+import { type ComponentOptions, createApp, nextTick } from 'vue'
describe('v-memo', () => {
function mount(options: ComponentOptions): [HTMLElement, any] {
test('on with external array', async () => {
const [el, vm] = mount({
template: `<div v-memo="arr">{{ arr[0] }} {{ arr[1] }} {{arr[2] ?? '_' }} ({{c}})</div>{{c}}`,
- data: () => ({ arr: [0, 0], c: 0 })
+ data: () => ({ arr: [0, 0], c: 0 }),
})
expect(el.innerHTML).toBe(`<div>0 0 _ (0)</div>0`)
test('on normal element', async () => {
const [el, vm] = mount({
template: `<div v-memo="[x]">{{ x }} {{ y }}</div>`,
- data: () => ({ x: 0, y: 0 })
+ data: () => ({ x: 0, y: 0 }),
})
expect(el.innerHTML).toBe(`<div>0 0</div>`)
components: {
Comp: {
props: ['x', 'y'],
- template: `<div>{{x}} {{y}}</div>`
- }
- }
+ template: `<div>{{x}} {{y}}</div>`,
+ },
+ },
})
expect(el.innerHTML).toBe(`<div>0 0</div>`)
const [el, vm] = mount({
template: `<div v-if="ok" v-memo="[x]">{{ x }} {{ y }}</div>
<div v-else v-memo="[y]">{{ y }} {{ x }}</div>`,
- data: () => ({ ok: true, x: 0, y: 0 })
+ data: () => ({ ok: true, x: 0, y: 0 }),
})
expect(el.innerHTML).toBe(`<div>0 0</div>`)
data: () => ({
list: [{ x: 1 }, { x: 2 }, { x: 3 }],
y: 1,
- z: 'z'
- })
+ z: 'z',
+ }),
})
expect(el.innerHTML).toBe(
- `<div>1 yes z</div><div>2 no z</div><div>3 no z</div>`
+ `<div>1 yes z</div><div>2 no z</div><div>3 no z</div>`,
)
vm.y = 2
await nextTick()
expect(el.innerHTML).toBe(
- `<div>1 no z</div><div>2 yes z</div><div>3 no z</div>`
+ `<div>1 no z</div><div>2 yes z</div><div>3 no z</div>`,
)
vm.list[0].x = 4
await nextTick()
expect(el.innerHTML).toBe(
- `<div>4 no z</div><div>2 yes z</div><div>3 no z</div>`
+ `<div>4 no z</div><div>2 yes z</div><div>3 no z</div>`,
)
vm.list[0].x = 5
vm.y = 5
await nextTick()
expect(el.innerHTML).toBe(
- `<div>5 yes z</div><div>2 no z</div><div>3 no z</div>`
+ `<div>5 yes z</div><div>2 no z</div><div>3 no z</div>`,
)
vm.z = 'zz'
await nextTick()
// should not update
expect(el.innerHTML).toBe(
- `<div>5 yes z</div><div>2 no z</div><div>3 no z</div>`
+ `<div>5 yes z</div><div>2 no z</div><div>3 no z</div>`,
)
})
{{count}}
</div>`,
data: () => ({
- count: 0
- })
+ count: 0,
+ }),
})
expect(el.innerHTML).toBe(`<div>0</div><div>0</div><div>0</div>`)
test('v-memo dependency is NaN should be equal', async () => {
const [el, vm] = mount({
template: `<div v-memo="[x]">{{ y }}</div>`,
- data: () => ({ x: NaN, y: 0 })
+ data: () => ({ x: NaN, y: 0 }),
})
expect(el.innerHTML).toBe(`<div>0</div>`)
-import { HMRRuntime } from '../src/hmr'
+import type { HMRRuntime } from '../src/hmr'
import '../src/hmr'
-import { ComponentOptions, InternalRenderFunction } from '../src/component'
+import type { ComponentOptions, InternalRenderFunction } from '../src/component'
import {
- render,
- nodeOps,
+ type TestElement,
h,
+ nextTick,
+ nodeOps,
+ render,
serializeInner,
triggerEvent,
- TestElement,
- nextTick
} from '@vue/runtime-test'
import * as runtimeTest from '@vue/runtime-test'
-import { registerRuntimeCompiler, createApp } from '@vue/runtime-test'
+import { createApp, registerRuntimeCompiler } from '@vue/runtime-test'
import { baseCompile } from '@vue/compiler-core'
declare var __VUE_HMR_RUNTIME__: HMRRuntime
function compileToFunction(template: string) {
const { code } = baseCompile(template, { hoistStatic: true, hmr: true })
const render = new Function('Vue', code)(
- runtimeTest
+ runtimeTest,
) as InternalRenderFunction
render._rc = true // isRuntimeCompiled
return render
const Child: ComponentOptions = {
__hmrId: childId,
- render: compileToFunction(`<div><slot/></div>`)
+ render: compileToFunction(`<div><slot/></div>`),
}
createRecord(childId, Child)
},
components: { Child },
render: compileToFunction(
- `<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`
- )
+ `<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`,
+ ),
}
createRecord(parentId, Parent)
rerender(
parentId,
compileToFunction(
- `<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`
- )
+ `<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`,
+ ),
)
expect(serializeInner(root)).toBe(`<div>1!<div>1</div></div>`)
rerender(
parentId,
compileToFunction(
- `<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>`
- )
+ `<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>`,
+ ),
)
expect(serializeInner(root)).toBe(`<div>1!<div>1!</div></div>`)
compileToFunction(
`<div @click="count++">{{ count }}<span>{{ count }}</span>
<Child>{{ count }}!</Child>
- </div>`
- )
+ </div>`,
+ ),
)
expect(serializeInner(root)).toBe(`<div>1<span>1</span><div>1!</div></div>`)
compileToFunction(
`<div @click="count++">
<Child><span>{{ count }}</span></Child>
- </div>`
- )
+ </div>`,
+ ),
)
expect(serializeInner(root)).toBe(`<div><div><span>1</span></div></div>`)
})
return { count: 0 }
},
unmounted: unmountSpy,
- render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
+ render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
}
createRecord(childId, Child)
const Parent: ComponentOptions = {
- render: () => h(Child)
+ render: () => h(Child),
}
render(h(Parent), root)
return { count: 1 }
},
mounted: mountSpy,
- render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
+ render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
})
await nextTick()
expect(serializeInner(root)).toBe(`<div>1</div>`)
return { count: 0 }
},
unmounted: unmountSpy,
- render: compileToFunction(`<div>{{ count }}</div>`)
+ render: compileToFunction(`<div>{{ count }}</div>`),
}
createRecord(childId, Child)
},
render: compileToFunction(
`<button @click="toggle = !toggle" />
- <KeepAlive><Child v-if="toggle" /></KeepAlive>`
- )
+ <KeepAlive><Child v-if="toggle" /></KeepAlive>`,
+ ),
}
render(h(Parent), root)
unmounted: unmountSpy,
activated: activeSpy,
deactivated: deactiveSpy,
- render: compileToFunction(`<div>{{ count }}</div>`)
+ render: compileToFunction(`<div>{{ count }}</div>`),
})
await nextTick()
expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
return { count: 0 }
},
unmounted: unmountSpy,
- render: compileToFunction(`<div>{{ count }}</div>`)
+ render: compileToFunction(`<div>{{ count }}</div>`),
}
createRecord(childId, Child)
`<button @click="toggle = !toggle" />
<BaseTransition>
<KeepAlive><Child v-if="toggle" /></KeepAlive>
- </BaseTransition>`
- )
+ </BaseTransition>`,
+ ),
}
render(h(Parent), root)
unmounted: unmountSpy,
activated: activeSpy,
deactivated: deactiveSpy,
- render: compileToFunction(`<div>{{ count }}</div>`)
+ render: compileToFunction(`<div>{{ count }}</div>`),
})
await nextTick()
expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
return { count: 0 }
},
unmounted: unmountSpy,
- render: compileToFunction(`<div>{{ count }}</div>`)
+ render: compileToFunction(`<div>{{ count }}</div>`),
}
createRecord(childId, Child)
return { toggle: true }
},
methods: {
- // @ts-ignore
+ // @ts-expect-error
onLeave(_, done) {
setTimeout(done, 0)
- }
+ },
},
render: compileToFunction(
`<button @click="toggle = !toggle" />
<BaseTransition mode="out-in" @leave="onLeave">
<KeepAlive><Child v-if="toggle" /></KeepAlive>
- </BaseTransition>`
- )
+ </BaseTransition>`,
+ ),
}
render(h(Parent), root)
unmounted: unmountSpy,
activated: activeSpy,
deactivated: deactiveSpy,
- render: compileToFunction(`<div>{{ count }}</div>`)
+ render: compileToFunction(`<div>{{ count }}</div>`),
})
await nextTick()
await new Promise(r => setTimeout(r, 0))
return { count: 0 }
},
unmounted: unmountSpy,
- render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
+ render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
}
}
createRecord(childId, Child)
const Parent: ComponentOptions = {
- render: () => h(Child)
+ render: () => h(Child),
}
render(h(Parent), root)
return { count: 1 }
},
mounted: mountSpy,
- render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
+ render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
}
}
data() {
return { count: 0 }
},
- render: compileToFunction(template)
+ render: compileToFunction(template),
}
createRecord(id, Comp)
render(h(Comp), root)
expect(serializeInner(root)).toBe(
- `<div><div>0</div><button>++</button></div>`
+ `<div><div>0</div><button>++</button></div>`,
)
// 1. click to trigger update
triggerEvent((root as any).children[0].children[1], 'click')
await nextTick()
expect(serializeInner(root)).toBe(
- `<div><div>1</div><button>++</button></div>`
+ `<div><div>1</div><button>++</button></div>`,
)
// 2. trigger HMR
rerender(
id,
- compileToFunction(template.replace(`<button`, `<button class="foo"`))
+ compileToFunction(template.replace(`<button`, `<button class="foo"`)),
)
expect(serializeInner(root)).toBe(
- `<div><div>1</div><button class="foo">++</button></div>`
+ `<div><div>1</div><button class="foo">++</button></div>`,
)
})
const Child: ComponentOptions = {
__hmrId: childId,
props: {
- msg: String
+ msg: String,
},
- render: compileToFunction(`<div>{{ msg }}</div>`)
+ render: compileToFunction(`<div>{{ msg }}</div>`),
}
createRecord(childId, Child)
const Parent: ComponentOptions = {
__hmrId: parentId,
components: { Child },
- render: compileToFunction(`<Child msg="foo" />`)
+ render: compileToFunction(`<Child msg="foo" />`),
}
createRecord(parentId, Parent)
const Child: ComponentOptions = {
__hmrId: childId,
- render: compileToFunction(`<div>child</div>`)
+ render: compileToFunction(`<div>child</div>`),
}
createRecord(childId, Child)
const Parent: ComponentOptions = {
__hmrId: parentId,
components: { Child },
- render: compileToFunction(`<Child class="test" />`)
+ render: compileToFunction(`<Child class="test" />`),
}
createRecord(parentId, Parent)
const Child: ComponentOptions = {
__hmrId: childId,
- render: compileToFunction(`<div>child</div>`)
+ render: compileToFunction(`<div>child</div>`),
}
createRecord(childId, Child)
for (let i = 0; i < numberOfParents; i++) {
const parentId = `${parent}${i}`
const parentComp: ComponentOptions = {
- __hmrId: parentId
+ __hmrId: parentId,
}
components.push(parentComp)
if (i === 0) {
parentComp.render = compileToFunction(`<Child />`)
parentComp.components = {
- Child
+ Child,
}
} else {
parentComp.render = compileToFunction(`<Parent />`)
parentComp.components = {
- Parent: components[i - 1]
+ Parent: components[i - 1],
}
}
return {
// style is used to ensure that the div tag will be tracked by Teleport
style: {},
- target
+ target,
}
},
render: compileToFunction(`
<slot/>
</div>
</teleport>
- `)
+ `),
}
const Parent: ComponentOptions = {
<div>1</div>
</template>
</Child>
- `)
+ `),
}
createRecord(parentId, Parent)
render(h(Parent), root)
expect(serializeInner(root)).toBe(
- `<!--teleport start--><!--teleport end-->`
+ `<!--teleport start--><!--teleport end-->`,
)
expect(serializeInner(target)).toBe(`<div style={}><div>1</div></div>`)
<div>2</div>
</template>
</Child>
- `)
+ `),
)
expect(serializeInner(root)).toBe(
- `<!--teleport start--><!--teleport end-->`
+ `<!--teleport start--><!--teleport end-->`,
)
expect(serializeInner(target)).toBe(
- `<div style={}><div>1</div><div>2</div></div>`
+ `<div style={}><div>1</div><div>2</div></div>`,
)
})
created: createSpy1,
render() {
return h('div')
- }
+ },
}
createRecord(childId, Child)
const Parent: ComponentOptions = {
- render: () => h(Child)
+ render: () => h(Child),
}
const app = createApp(Parent)
created: createSpy2,
render() {
return h('div')
- }
+ },
})
await nextTick()
expect(createSpy1).toHaveBeenCalledTimes(1)
const id = 'no-active-instance-rerender'
const Foo: ComponentOptions = {
__hmrId: id,
- render: () => 'foo'
+ render: () => 'foo',
}
createRecord(id, Foo)
const id = 'no-active-instance-reload'
const Foo: ComponentOptions = {
__hmrId: id,
- render: () => 'foo'
+ render: () => 'foo',
}
createRecord(id, Foo)
reload(id, {
__hmrId: id,
- render: () => 'bar'
+ render: () => 'bar',
})
const root = nodeOps.createElement('div')
computed: {
slotContent() {
return this.$slots.default?.()
- }
+ },
},
- render: compileToFunction(`<component :is="() => slotContent" />`)
+ render: compileToFunction(`<component :is="() => slotContent" />`),
}
createRecord(childId, Child)
const Parent: ComponentOptions = {
__hmrId: parentId,
components: { Child },
- render: compileToFunction(`<Child>1</Child>`)
+ render: compileToFunction(`<Child>1</Child>`),
}
createRecord(parentId, Parent)
<div>1</div>
</div>
<p>2</p>
- <p>3</p>`
- )
+ <p>3</p>`,
+ ),
}
createRecord(appId, App)
render(h(App), root)
expect(serializeInner(root)).toBe(
- `<div><div>1</div></div><div><div>1</div></div><p>2</p><p>3</p>`
+ `<div><div>1</div></div><div><div>1</div></div><p>2</p><p>3</p>`,
)
// move the <p>3</p> into the <div>1</div>
`<div v-for="item of 2">
<div>1<p>3</p></div>
</div>
- <p>2</p>`
- )
+ <p>2</p>`,
+ ),
)
expect(serializeInner(root)).toBe(
- `<div><div>1<p>3</p></div></div><div><div>1<p>3</p></div></div><p>2</p>`
+ `<div><div>1<p>3</p></div></div><div><div>1<p>3</p></div></div><p>2</p>`,
)
})
})
*/
import {
- createSSRApp,
- h,
- ref,
- nextTick,
- VNode,
+ Suspense,
Teleport,
+ Transition,
+ type VNode,
+ createCommentVNode,
+ createSSRApp,
createStaticVNode,
- Suspense,
- onMounted,
- defineAsyncComponent,
- defineComponent,
createTextVNode,
createVNode,
- withDirectives,
- vModelCheckbox,
+ defineAsyncComponent,
+ defineComponent,
+ h,
+ nextTick,
+ onMounted,
+ ref,
renderSlot,
- Transition,
- createCommentVNode,
- vShow
+ vModelCheckbox,
+ vShow,
+ withDirectives,
} from '@vue/runtime-dom'
-import { renderToString, SSRContext } from '@vue/server-renderer'
+import { type SSRContext, renderToString } from '@vue/server-renderer'
import { PatchFlags } from '@vue/shared'
import { vShowOldKey } from '../../runtime-dom/src/directives/vShow'
const container = document.createElement('div')
container.innerHTML = html
const app = createSSRApp({
- render
+ render,
})
return {
vnode: app.mount(container).$.subTree as VNode<Node, Element> & {
el: Element
},
- container
+ container,
}
}
test('empty text', async () => {
const { container } = mountWithHydration('<div></div>', () =>
- h('div', createTextVNode(''))
+ h('div', createTextVNode('')),
)
expect(container.textContent).toBe('')
expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
test('static', () => {
const html = '<div><span>hello</span></div>'
const { vnode, container } = mountWithHydration(html, () =>
- createStaticVNode('', 1)
+ createStaticVNode('', 1),
)
expect(vnode.el).toBe(container.firstChild)
expect(vnode.el.outerHTML).toBe(html)
test('static (with text node as starting node)', () => {
const html = ` A <span>foo</span> B`
const { vnode, container } = mountWithHydration(html, () =>
- createStaticVNode(` A <span>foo</span> B`, 3)
+ createStaticVNode(` A <span>foo</span> B`, 3),
)
expect(vnode.el).toBe(container.firstChild)
expect(vnode.anchor).toBe(container.lastChild)
test('static with content adoption', () => {
const html = ` A <span>foo</span> B`
const { vnode, container } = mountWithHydration(html, () =>
- createStaticVNode(``, 3)
+ createStaticVNode(``, 3),
)
expect(vnode.el).toBe(container.firstChild)
expect(vnode.anchor).toBe(container.lastChild)
const msg = ref('foo')
const { vnode, container } = mountWithHydration(
'<div class="foo">foo</div>',
- () => h('div', { class: msg.value }, msg.value)
+ () => h('div', { class: msg.value }, msg.value),
)
expect(vnode.el).toBe(container.firstChild)
expect(container.firstChild!.textContent).toBe('foo')
() =>
h('div', [
h('span', msg.value),
- h('span', { class: msg.value, onClick: fn })
- ])
+ h('span', { class: msg.value, onClick: fn }),
+ ]),
)
expect(vnode.el).toBe(container.firstChild)
expect((vnode.children as VNode[])[0].el).toBe(
- container.firstChild!.childNodes[0]
+ container.firstChild!.childNodes[0],
)
expect((vnode.children as VNode[])[1].el).toBe(
- container.firstChild!.childNodes[1]
+ container.firstChild!.childNodes[1],
)
// event handler
test('element with ref', () => {
const el = ref()
const { vnode, container } = mountWithHydration('<div></div>', () =>
- h('div', { ref: el })
+ h('div', { ref: el }),
)
expect(vnode.el).toBe(container.firstChild)
expect(el.value).toBe(vnode.el)
'<div><!--[--><span>foo</span><!--[--><span class="foo"></span><!--]--><!--]--></div>',
() =>
h('div', [
- [h('span', msg.value), [h('span', { class: msg.value, onClick: fn })]]
- ])
+ [
+ h('span', msg.value),
+ [h('span', { class: msg.value, onClick: fn })],
+ ],
+ ]),
)
expect(vnode.el).toBe(container.firstChild)
expect(vnode.el.innerHTML).toBe(
- `<!--[--><span>foo</span><!--[--><span class="foo"></span><!--]--><!--]-->`
+ `<!--[--><span>foo</span><!--[--><span class="foo"></span><!--]--><!--]-->`,
)
// start fragment 1
msg.value = 'bar'
await nextTick()
expect(vnode.el.innerHTML).toBe(
- `<!--[--><span>bar</span><!--[--><span class="bar"></span><!--]--><!--]-->`
+ `<!--[--><span>bar</span><!--[--><span class="bar"></span><!--]--><!--]-->`,
)
})
() =>
h(Teleport, { to: '#teleport' }, [
h('span', msg.value),
- h('span', { class: msg.value, onClick: fn })
- ])
+ h('span', { class: msg.value, onClick: fn }),
+ ]),
)
expect(vnode.el).toBe(container.firstChild)
expect(vnode.target).toBe(teleportContainer)
expect((vnode.children as VNode[])[0].el).toBe(
- teleportContainer.childNodes[0]
+ teleportContainer.childNodes[0],
)
expect((vnode.children as VNode[])[1].el).toBe(
- teleportContainer.childNodes[1]
+ teleportContainer.childNodes[1],
)
expect(vnode.targetAnchor).toBe(teleportContainer.childNodes[2])
msg.value = 'bar'
await nextTick()
expect(teleportContainer.innerHTML).toBe(
- `<span>bar</span><span class="bar"></span><!--teleport anchor-->`
+ `<span>bar</span><span class="bar"></span><!--teleport anchor-->`,
)
})
const Comp = () => [
h(Teleport, { to: '#teleport2' }, [
h('span', msg.value),
- h('span', { class: msg.value, onClick: fn1 })
+ h('span', { class: msg.value, onClick: fn1 }),
]),
h(Teleport, { to: '#teleport2' }, [
h('span', msg.value + '2'),
- h('span', { class: msg.value + '2', onClick: fn2 })
- ])
+ h('span', { class: msg.value + '2', onClick: fn2 }),
+ ]),
]
const teleportContainer = document.createElement('div')
const ctx: SSRContext = {}
const mainHtml = await renderToString(h(Comp), ctx)
expect(mainHtml).toMatchInlineSnapshot(
- `"<!--[--><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--><!--]-->"`
+ `"<!--[--><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--><!--]-->"`,
)
const teleportHtml = ctx.teleports!['#teleport2']
expect(teleportHtml).toMatchInlineSnapshot(
- `"<span>foo</span><span class="foo"></span><!--teleport anchor--><span>foo2</span><span class="foo2"></span><!--teleport anchor-->"`
+ `"<span>foo</span><span class="foo"></span><!--teleport anchor--><span>foo2</span><span class="foo2"></span><!--teleport anchor-->"`,
)
teleportContainer.innerHTML = teleportHtml
expect(teleportVnode1.target).toBe(teleportContainer)
expect((teleportVnode1 as any).children[0].el).toBe(
- teleportContainer.childNodes[0]
+ teleportContainer.childNodes[0],
)
expect(teleportVnode1.targetAnchor).toBe(teleportContainer.childNodes[2])
expect(teleportVnode2.target).toBe(teleportContainer)
expect((teleportVnode2 as any).children[0].el).toBe(
- teleportContainer.childNodes[3]
+ teleportContainer.childNodes[3],
)
expect(teleportVnode2.targetAnchor).toBe(teleportContainer.childNodes[5])
msg.value = 'bar'
await nextTick()
expect(teleportContainer.innerHTML).toMatchInlineSnapshot(
- `"<span>bar</span><span class="bar"></span><!--teleport anchor--><span>bar2</span><span class="bar2"></span><!--teleport anchor-->"`
+ `"<span>bar</span><span class="bar"></span><!--teleport anchor--><span>bar2</span><span class="bar2"></span><!--teleport anchor-->"`,
)
})
h('div', 'foo'),
h(Teleport, { to: '#teleport3', disabled: true }, [
h('span', msg.value),
- h('span', { class: msg.value, onClick: fn1 })
+ h('span', { class: msg.value, onClick: fn1 }),
]),
- h('div', { class: msg.value + '2', onClick: fn2 }, 'bar')
+ h('div', { class: msg.value + '2', onClick: fn2 }, 'bar'),
]
const teleportContainer = document.createElement('div')
const ctx: SSRContext = {}
const mainHtml = await renderToString(h(Comp), ctx)
expect(mainHtml).toMatchInlineSnapshot(
- `"<!--[--><div>foo</div><!--teleport start--><span>foo</span><span class="foo"></span><!--teleport end--><div class="foo2">bar</div><!--]-->"`
+ `"<!--[--><div>foo</div><!--teleport start--><span>foo</span><span class="foo"></span><!--teleport end--><div class="foo2">bar</div><!--]-->"`,
)
const teleportHtml = ctx.teleports!['#teleport3']
const teleportVnode = children[1]
expect(teleportVnode.el).toBe(container.childNodes[2])
expect((teleportVnode.children as VNode[])[0].el).toBe(
- container.childNodes[3]
+ container.childNodes[3],
)
expect((teleportVnode.children as VNode[])[1].el).toBe(
- container.childNodes[4]
+ container.childNodes[4],
)
expect(teleportVnode.anchor).toBe(container.childNodes[5])
expect(children[2].el).toBe(container.childNodes[6])
msg.value = 'bar'
await nextTick()
expect(container.innerHTML).toMatchInlineSnapshot(
- `"<!--[--><div>foo</div><!--teleport start--><span>bar</span><span class="bar"></span><!--teleport end--><div class="bar2">bar</div><!--]-->"`
+ `"<!--[--><div>foo</div><!--teleport start--><span>bar</span><span class="bar"></span><!--teleport end--><div class="bar2">bar</div><!--]-->"`,
)
})
h('div', 'Parent fragment'),
h(() =>
h(Teleport, { to: 'body', disabled: true }, [
- h('div', 'Teleport content')
- ])
- )
- ]
+ h('div', 'Teleport content'),
+ ]),
+ ),
+ ],
)
expect(document.body.innerHTML).toBe('')
expect(container.innerHTML).toBe(
- '<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->'
+ '<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->',
)
expect(
- `Hydration completed but contains mismatches.`
+ `Hydration completed but contains mismatches.`,
).not.toHaveBeenWarned()
})
const wrapper = {
render() {
return h(Teleport, { to: '#teleport4' }, ['hello'])
- }
+ },
}
const { vnode, container } = mountWithHydration(
'<div><!--teleport start--><!--teleport end--><div></div></div>',
- () => h('div', [h(wrapper), h('div')])
+ () => h('div', [h(wrapper), h('div')]),
)
expect(vnode.el).toBe(container.firstChild)
// component el
'<!--teleport start--><!--teleport end-->',
() =>
h(Teleport, { to: '#teleport5' }, [
- h('div', [h(Teleport, { to: '#teleport5' }, [h('div', 'child')])])
- ])
+ h('div', [h(Teleport, { to: '#teleport5' }, [h('div', 'child')])]),
+ ]),
)
expect(vnode.el).toBe(container.firstChild)
expect(childTeleportVNode.targetAnchor).toBe(teleportContainer.lastChild)
expect(childTeleportVNode.children[0].el).toBe(
- teleportContainer.lastChild?.previousSibling
+ teleportContainer.lastChild?.previousSibling,
)
})
count: 0,
text: 'hello',
style: {
- color: 'red'
- }
+ color: 'red',
+ },
}
},
mounted() {
<span class="text">{{ text }}</span>
<input v-model="text">
</div>
- `
+ `,
}
const App = {
<span>hello</span>
</div>`,
components: {
- Child
+ Child,
},
methods: {
- log
- }
+ log,
+ },
}
const container = document.createElement('div')
template: `
<div>
<button class="parent-click" @click="throwError">click me</button>
- </div>`
+ </div>`,
}
const container = document.createElement('div')
template: `
<div>
<input class="parent-click" @blur="throwError"/>
- </div>`
+ </div>`,
}
const container = document.createElement('div')
{
onClick: () => {
count.value++
- }
+ },
},
- count.value
+ count.value,
)
- }
+ },
}
const { vnode, container } = mountWithHydration('<span>0</span>', () =>
- h(Suspense, () => h(AsyncChild))
+ h(Suspense, () => h(AsyncChild)),
)
expect(vnode.el).toBe(container.firstChild)
// wait for hydration to finish
{
onClick: () => {
count.value++
- }
+ },
},
- count.value
+ count.value,
)
- }
+ },
})
const done = vi.fn()
</div>
</Suspense>`,
components: {
- AsyncChild
+ AsyncChild,
},
methods: {
- done
- }
+ done,
+ },
}
const container = document.createElement('div')
// server render
container.innerHTML = await renderToString(h(App))
expect(container.innerHTML).toMatchInlineSnapshot(
- `"<div><span>1</span><span>2</span></div>"`
+ `"<div><span>1</span><span>2</span></div>"`,
)
// reset asyncDeps from ssr
asyncDeps.length = 0
// should flush buffered effects
expect(mountedCalls).toMatchObject([1, 2])
expect(container.innerHTML).toMatch(
- `<div><span>1</span><span>2</span></div>`
+ `<div><span>1</span><span>2</span></div>`,
)
const span1 = container.querySelector('span')!
triggerEvent('click', span1)
await nextTick()
expect(container.innerHTML).toMatch(
- `<div><span>2</span><span>2</span></div>`
+ `<div><span>2</span><span>2</span></div>`,
)
const span2 = span1.nextSibling as Element
triggerEvent('click', span2)
await nextTick()
expect(container.innerHTML).toMatch(
- `<div><span>2</span><span>3</span></div>`
+ `<div><span>2</span><span>3</span></div>`,
)
})
h(
'button',
{
- onClick: spy
+ onClick: spy,
},
- 'hello!'
+ 'hello!',
)
let serverResolve: any
() =>
new Promise(r => {
serverResolve = r
- })
+ }),
)
const App = {
render() {
return ['hello', h(AsyncComp), 'world']
- }
+ },
}
// server render
serverResolve(Comp)
const html = await htmlPromise
expect(html).toMatchInlineSnapshot(
- `"<!--[-->hello<button>hello!</button>world<!--]-->"`
+ `"<!--[-->hello<button>hello!</button>world<!--]-->"`,
)
// hydration
() =>
new Promise(r => {
clientResolve = r
- })
+ }),
)
const container = document.createElement('div')
const Comp = {
render() {
return h('h1', 'Async component')
- }
+ },
}
let serverResolve: any
let AsyncComp = defineAsyncComponent(
() =>
new Promise(r => {
serverResolve = r
- })
+ }),
)
const toggle = ref(true)
return () => {
return [toggle.value ? 'hello' : 'world', h(AsyncComp)]
}
- }
+ },
}
// server render
serverResolve(Comp)
const html = await htmlPromise
expect(html).toMatchInlineSnapshot(
- `"<!--[-->hello<h1>Async component</h1><!--]-->"`
+ `"<!--[-->hello<h1>Async component</h1><!--]-->"`,
)
// hydration
() =>
new Promise(r => {
clientResolve = r
- })
+ }),
)
const container = document.createElement('div')
// should be hydrated now
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
expect(container.innerHTML).toMatchInlineSnapshot(
- `"<!--[-->world<h1>Async component</h1><!--]-->"`
+ `"<!--[-->world<h1>Async component</h1><!--]-->"`,
)
})
async setup() {
await new Promise<void>(r => setTimeout(r, 10))
return () => h('h1', 'Async component')
- }
+ },
}
const AsyncWrapper = {
render() {
return h(AsyncComp)
- }
+ },
}
const SiblingComp = {
setup() {
toggle.value = false
return () => h('span')
- }
+ },
}
const App = {
default: () => [
h('main', {}, [
h(AsyncWrapper, {
- prop: toggle.value ? 'hello' : 'world'
+ prop: toggle.value ? 'hello' : 'world',
}),
- h(SiblingComp)
- ])
- ]
- }
+ h(SiblingComp),
+ ]),
+ ],
+ },
)
- }
+ },
}
// server render
const html = await renderToString(h(App))
expect(html).toMatchInlineSnapshot(
- `"<main><h1 prop="hello">Async component</h1><span></span></main>"`
+ `"<main><h1 prop="hello">Async component</h1><span></span></main>"`,
)
expect(toggle.value).toBe(false)
// should be hydrated now
expect(container.innerHTML).toMatchInlineSnapshot(
- `"<main><h1 prop="world">Async component</h1><span></span></main>"`
+ `"<main><h1 prop="world">Async component</h1><span></span></main>"`,
)
})
async setup() {
await new Promise<void>(r => setTimeout(r, 10))
return () => h('h1', 'Async component')
- }
+ },
}
const AsyncWrapper = { render: () => h(AsyncComp) }
setup() {
toggle.value = false
return () => h('span')
- }
+ },
}
const App = {
default: () => [
h('main', {}, [
h(AsyncWrapperWrapper, {
- prop: toggle.value ? 'hello' : 'world'
+ prop: toggle.value ? 'hello' : 'world',
}),
- h(SiblingComp)
- ])
- ]
- }
+ h(SiblingComp),
+ ]),
+ ],
+ },
)
- }
+ },
}
// server render
const html = await renderToString(h(App))
expect(html).toMatchInlineSnapshot(
- `"<main><h1 prop="hello">Async component</h1><span></span></main>"`
+ `"<main><h1 prop="hello">Async component</h1><span></span></main>"`,
)
expect(toggle.value).toBe(false)
// should be hydrated now
expect(container.innerHTML).toMatchInlineSnapshot(
- `"<main><h1 prop="world">Async component</h1><span></span></main>"`
+ `"<main><h1 prop="world">Async component</h1><span></span></main>"`,
)
})
() =>
new Promise(r => {
resolve = r
- })
+ }),
)
const show = ref(true)
createSSRApp({
render() {
return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')])
- }
+ },
}).mount(root)
show.value = false
() =>
new Promise(r => {
resolve = r
- })
+ }),
)
const show = ref(true)
createSSRApp({
render() {
return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')])
- }
+ },
}).mount(root)
show.value = false
test('elements with camel-case in svg ', () => {
const { vnode, container } = mountWithHydration(
'<animateTransform></animateTransform>',
- () => h('animateTransform')
+ () => h('animateTransform'),
)
expect(vnode.el).toBe(container.firstChild)
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
const svgContainer = document.createElement('svg')
svgContainer.innerHTML = '<g></g>'
const app = createSSRApp({
- render: () => h('g')
+ render: () => h('g'),
})
expect(
app.mount(svgContainer).$.subTree as VNode<Node, Element> & {
el: Element
}
- ).el instanceof SVGElement
+ ).el instanceof SVGElement,
)
})
() =>
h('input', {
type: 'checkbox',
- '.indeterminate': true
- })
+ '.indeterminate': true,
+ }),
)
expect((container.firstChild! as any).indeterminate).toBe(true)
})
{ type: 'checkbox', 'true-value': true },
null,
PatchFlags.PROPS,
- ['true-value']
+ ['true-value'],
),
- [[vModelCheckbox, true]]
- )
+ [[vModelCheckbox, true]],
+ ),
)
expect((container.firstChild as any)._trueValue).toBe(true)
})
'input',
{ type: 'checkbox', indeterminate: '' },
null,
- PatchFlags.HOISTED
- )
+ PatchFlags.HOISTED,
+ ),
)
expect((container.firstChild as any).indeterminate).toBe(true)
})
() =>
h('select', [
// hoisted because bound value is a constant...
- createVNode('option', { value: true }, null, -1 /* HOISTED */)
- ])
+ createVNode('option', { value: true }, null, -1 /* HOISTED */),
+ ]),
)
expect((container.firstChild!.firstChild as any)._value).toBe(true)
})
const Comp = {
render(this: any) {
return renderSlot(this.$slots, 'default', {}, () => [
- createTextVNode('')
+ createTextVNode(''),
])
- }
+ },
}
const { container, vnode } = mountWithHydration('<!--[--><!--]-->', () =>
- h(Comp)
+ h(Comp),
)
expect(container.childNodes.length).toBe(3)
const text = container.childNodes[1]
return () =>
h('button', {
- onClick: () => count.value++
+ onClick: () => count.value++,
})
- }
+ },
})
const app = createSSRApp(App)
// #6637
test('stringified root fragment', () => {
mountWithHydration(`<!--[--><div></div><!--]-->`, () =>
- createStaticVNode(`<div></div>`, 1)
+ createStaticVNode(`<div></div>`, 1),
)
expect(`mismatch`).not.toHaveBeenWarned()
})
Transition,
{ appear: true },
{
- default: () => h('div', 'foo')
- }
- )
+ default: () => h('div', 'foo'),
+ },
+ ),
)
expect(container.firstChild).toMatchInlineSnapshot(`
<div
Transition,
{ appear: true },
{
- default: () => (show ? h('div', 'foo') : createCommentVNode(''))
- }
- )
+ default: () => (show ? h('div', 'foo') : createCommentVNode('')),
+ },
+ ),
)
expect(container.firstChild).toMatchInlineSnapshot('<!---->')
expect(vnode.el).toBe(container.firstChild)
{ appear: true },
{
default: () =>
- withDirectives(createVNode('div', null, 'foo'), [[vShow, show]])
- }
- )
+ withDirectives(createVNode('div', null, 'foo'), [[vShow, show]]),
+ },
+ ),
)
expect(container.firstChild).toMatchInlineSnapshot(`
<div
createSSRApp({
data() {
return {
- count: 0
+ count: 0,
}
},
template: `
<Transition appear>
<button @click="count++">{{count}}</button>
</Transition>
- `
+ `,
}).mount(container)
expect(container.firstChild).toMatchInlineSnapshot(`
test('element text content', () => {
const { container } = mountWithHydration(`<div>foo</div>`, () =>
- h('div', 'bar')
+ h('div', 'bar'),
)
expect(container.innerHTML).toBe('<div>bar</div>')
expect(`Hydration text content mismatch`).toHaveBeenWarned()
test('not enough children', () => {
const { container } = mountWithHydration(`<div></div>`, () =>
- h('div', [h('span', 'foo'), h('span', 'bar')])
+ h('div', [h('span', 'foo'), h('span', 'bar')]),
)
expect(container.innerHTML).toBe(
- '<div><span>foo</span><span>bar</span></div>'
+ '<div><span>foo</span><span>bar</span></div>',
)
expect(`Hydration children mismatch`).toHaveBeenWarned()
})
test('too many children', () => {
const { container } = mountWithHydration(
`<div><span>foo</span><span>bar</span></div>`,
- () => h('div', [h('span', 'foo')])
+ () => h('div', [h('span', 'foo')]),
)
expect(container.innerHTML).toBe('<div><span>foo</span></div>')
expect(`Hydration children mismatch`).toHaveBeenWarned()
test('complete mismatch', () => {
const { container } = mountWithHydration(
`<div><span>foo</span><span>bar</span></div>`,
- () => h('div', [h('div', 'foo'), h('p', 'bar')])
+ () => h('div', [h('div', 'foo'), h('p', 'bar')]),
)
expect(container.innerHTML).toBe('<div><div>foo</div><p>bar</p></div>')
expect(`Hydration node mismatch`).toHaveBeenWarnedTimes(2)
test('fragment mismatch removal', () => {
const { container } = mountWithHydration(
`<div><!--[--><div>foo</div><div>bar</div><!--]--></div>`,
- () => h('div', [h('span', 'replaced')])
+ () => h('div', [h('span', 'replaced')]),
)
expect(container.innerHTML).toBe('<div><span>replaced</span></div>')
expect(`Hydration node mismatch`).toHaveBeenWarned()
test('fragment not enough children', () => {
const { container } = mountWithHydration(
`<div><!--[--><div>foo</div><!--]--><div>baz</div></div>`,
- () => h('div', [[h('div', 'foo'), h('div', 'bar')], h('div', 'baz')])
+ () => h('div', [[h('div', 'foo'), h('div', 'bar')], h('div', 'baz')]),
)
expect(container.innerHTML).toBe(
- '<div><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>'
+ '<div><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>',
)
expect(`Hydration node mismatch`).toHaveBeenWarned()
})
test('fragment too many children', () => {
const { container } = mountWithHydration(
`<div><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>`,
- () => h('div', [[h('div', 'foo')], h('div', 'baz')])
+ () => h('div', [[h('div', 'foo')], h('div', 'baz')]),
)
expect(container.innerHTML).toBe(
- '<div><!--[--><div>foo</div><!--]--><div>baz</div></div>'
+ '<div><!--[--><div>foo</div><!--]--><div>baz</div></div>',
)
// fragment ends early and attempts to hydrate the extra <div>bar</div>
// as 2nd fragment child.
document.body.appendChild(teleportContainer)
mountWithHydration('<!--teleport start--><!--teleport end-->', () =>
- h(Teleport, { to: '#teleport' }, [h('span', 'value')])
+ h(Teleport, { to: '#teleport' }, [h('span', 'value')]),
)
expect(teleportContainer.innerHTML).toBe(`<span>value</span>`)
expect(`Hydration children mismatch`).toHaveBeenWarned()
test('comment mismatch (element)', () => {
const { container } = mountWithHydration(`<div><span></span></div>`, () =>
- h('div', [createCommentVNode('hi')])
+ h('div', [createCommentVNode('hi')]),
)
expect(container.innerHTML).toBe('<div><!--hi--></div>')
expect(`Hydration node mismatch`).toHaveBeenWarned()
test('comment mismatch (text)', () => {
const { container } = mountWithHydration(`<div>foobar</div>`, () =>
- h('div', [createCommentVNode('hi')])
+ h('div', [createCommentVNode('hi')]),
)
expect(container.innerHTML).toBe('<div><!--hi--></div>')
expect(`Hydration node mismatch`).toHaveBeenWarned()
test('class mismatch', () => {
mountWithHydration(`<div class="foo bar"></div>`, () =>
- h('div', { class: ['foo', 'bar'] })
+ h('div', { class: ['foo', 'bar'] }),
)
mountWithHydration(`<div class="foo bar"></div>`, () =>
- h('div', { class: { foo: true, bar: true } })
+ h('div', { class: { foo: true, bar: true } }),
)
mountWithHydration(`<div class="foo bar"></div>`, () =>
- h('div', { class: 'foo bar' })
+ h('div', { class: 'foo bar' }),
)
// SVG classes
mountWithHydration(`<svg class="foo bar"></svg>`, () =>
- h('svg', { class: 'foo bar' })
+ h('svg', { class: 'foo bar' }),
)
// class with different order
mountWithHydration(`<div class="foo bar"></div>`, () =>
- h('div', { class: 'bar foo' })
+ h('div', { class: 'bar foo' }),
)
expect(`Hydration class mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<div class="foo bar"></div>`, () =>
- h('div', { class: 'foo' })
+ h('div', { class: 'foo' }),
)
expect(`Hydration class mismatch`).toHaveBeenWarned()
})
test('style mismatch', () => {
mountWithHydration(`<div style="color:red;"></div>`, () =>
- h('div', { style: { color: 'red' } })
+ h('div', { style: { color: 'red' } }),
)
mountWithHydration(`<div style="color:red;"></div>`, () =>
- h('div', { style: `color:red;` })
+ h('div', { style: `color:red;` }),
)
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<div style="color:red;"></div>`, () =>
- h('div', { style: { color: 'green' } })
+ h('div', { style: { color: 'green' } }),
)
expect(`Hydration style mismatch`).toHaveBeenWarned()
})
test('attr mismatch', () => {
mountWithHydration(`<div id="foo"></div>`, () => h('div', { id: 'foo' }))
mountWithHydration(`<div spellcheck></div>`, () =>
- h('div', { spellcheck: '' })
+ h('div', { spellcheck: '' }),
)
mountWithHydration(`<div></div>`, () => h('div', { id: undefined }))
// boolean
mountWithHydration(`<select multiple></div>`, () =>
- h('select', { multiple: true })
+ h('select', { multiple: true }),
)
mountWithHydration(`<select multiple></div>`, () =>
- h('select', { multiple: 'multiple' })
+ h('select', { multiple: 'multiple' }),
)
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
-import { render, h, nodeOps, reactive, isReactive } from '@vue/runtime-test'
+import { h, isReactive, nodeOps, reactive, render } from '@vue/runtime-test'
describe('misc', () => {
test('component public instance should not be observable', () => {
render() {},
mounted() {
instance = this
- }
+ },
}
render(h(Comp), nodeOps.createElement('div'))
expect(instance).toBeDefined()
// using DOM renderer because this case is mostly DOM-specific
import {
+ Fragment,
+ type FunctionalComponent,
+ createBlock,
+ createCommentVNode,
+ defineComponent,
h,
- render,
- nextTick,
mergeProps,
- ref,
+ nextTick,
onUpdated,
- defineComponent,
openBlock,
- createBlock,
- FunctionalComponent,
- createCommentVNode,
- Fragment,
- withModifiers
+ ref,
+ render,
+ withModifiers,
} from '@vue/runtime-dom'
import { PatchFlags } from '@vue/shared'
class: 'c' + count.value,
style: { color: count.value ? 'red' : 'green' },
onClick: inc,
- 'data-id': count.value + 1
+ 'data-id': count.value + 1,
})
- }
+ },
}
const Child = {
'div',
{
class: 'c2',
- style: { fontWeight: 'bold' }
+ style: { fontWeight: 'bold' },
},
- props.foo
+ props.foo,
)
- }
+ },
}
const root = document.createElement('div')
id: 'test',
class: 'c' + count.value,
style: { color: count.value ? 'red' : 'green' },
- onClick: inc
+ onClick: inc,
})
const Child = (props: any) => {
'div',
{
class: 'c2',
- style: { fontWeight: 'bold' }
+ style: { fontWeight: 'bold' },
},
- props.foo
+ props.foo,
)
}
id: 'test',
class: 'c' + count.value,
style: { color: count.value ? 'red' : 'green' },
- onClick: inc
+ onClick: inc,
})
const Child = (props: { foo: number }) => {
'div',
{
class: 'c2',
- style: { fontWeight: 'bold' }
+ style: { fontWeight: 'bold' },
},
- props.foo
+ props.foo,
)
}
Child.props = ['foo']
id: 'test',
class: 'c' + count.value,
style: { color: count.value ? 'red' : 'green' },
- onClick: inc
+ onClick: inc,
})
- }
+ },
}
const Child = {
// this will result in merging the same attrs, but should be deduped by
// `mergeProps`.
return () => h(GrandChild, props)
- }
+ },
}
const GrandChild = defineComponent({
props: {
id: String,
- foo: Number
+ foo: Number,
},
setup(props) {
onUpdated(grandChildUpdated)
{
id: props.id,
class: 'c2',
- style: { fontWeight: 'bold' }
+ style: { fontWeight: 'bold' },
},
- props.foo
+ props.foo,
)
- }
+ },
})
const root = document.createElement('div')
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent' })
- }
+ },
}
const Child = defineComponent({
inheritAttrs: false,
render() {
return h('div', this.foo)
- }
+ },
})
const root = document.createElement('div')
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent' })
- }
+ },
}
const mixin = {
- inheritAttrs: false
+ inheritAttrs: false,
}
const Child = defineComponent({
props: ['foo'],
render() {
return h('div', this.foo)
- }
+ },
})
const root = document.createElement('div')
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent' })
- }
+ },
}
const Child = defineComponent({
'div',
mergeProps(
{
- class: 'child'
+ class: 'child',
},
- this.$attrs
+ this.$attrs,
),
- this.foo
+ this.foo,
)
- }
+ },
})
const root = document.createElement('div')
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent', onBar: () => {} })
- }
+ },
}
const Child = defineComponent({
props: ['foo'],
render() {
return [h('div'), h('div')]
- }
+ },
})
const root = document.createElement('div')
const Parent = {
render() {
return h(Child, { onClick: inc })
- }
+ },
}
const Child = defineComponent({
'div',
mergeProps(
{
- onClick: withModifiers(() => {}, ['prevent', 'stop'])
+ onClick: withModifiers(() => {}, ['prevent', 'stop']),
},
- this.$attrs
- )
+ this.$attrs,
+ ),
)
- }
+ },
})
const root = document.createElement('div')
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent', onBar: () => {} })
- }
+ },
}
const Child = defineComponent({
props: ['foo'],
render() {
return [h('div'), h('div', this.$attrs)]
- }
+ },
})
const root = document.createElement('div')
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent', onBar: () => {} })
- }
+ },
}
const Child = defineComponent({
props: ['foo'],
setup(_props, { attrs }) {
return () => [h('div'), h('div', attrs)]
- }
+ },
})
const root = document.createElement('div')
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent', onBar: () => {} })
- }
+ },
}
const Child: FunctionalComponent = (_, { attrs }) => [
h('div'),
- h('div', attrs)
+ h('div', attrs),
]
Child.props = ['foo']
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent', onBar: () => {} })
- }
+ },
}
const Child = (props: any) => [h('div'), h('div', { class: props.class })]
const Parent = {
render() {
return h(Child, { foo: 1, class: 'parent', onBar: () => {} })
- }
+ },
}
const Child: FunctionalComponent = () => [h('div'), h('div')]
const Parent = {
render() {
return h(Child, { 'aria-hidden': aria.value, class: cls.value })
- }
+ },
}
const Child = {
props: [],
render() {
return openBlock(), createBlock('div')
- }
+ },
}
const root = document.createElement('div')
{
onClick: () => {
this.$emit('click', 'custom')
- }
+ },
},
- 'hello'
+ 'hello',
)
- }
+ },
})
const onClick = vi.fn()
const App = {
render() {
return h(Child, {
- onClick
+ onClick,
})
- }
+ },
}
const root = document.createElement('div')
return h('button', {
onClick: () => {
emit('click', 'custom')
- }
+ },
})
}
Child.emits = ['click']
const App = {
render() {
return h(Child, {
- onClick
+ onClick,
})
- }
+ },
}
const root = document.createElement('div')
const Hello = {
setup() {
return () => h(Child, { class: 'foo', onClick: click })
- }
+ },
}
const Child = {
[
createCommentVNode('hello'),
h('button'),
- createCommentVNode('world')
+ createCommentVNode('world'),
],
- PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT
+ PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
)
)
- }
+ },
}
const root = document.createElement('div')
render(h(Hello), root)
expect(root.innerHTML).toBe(
- `<!--hello--><button class="foo"></button><!--world-->`
+ `<!--hello--><button class="foo"></button><!--world-->`,
)
const button = root.children[0] as HTMLElement
button.dispatchEvent(new CustomEvent('click'))
modelValue: textFoo,
'onUpdate:modelValue': (val: string) => {
textFoo = val
- }
+ },
})
- }
+ },
})
const Child = defineComponent({
'onUpdate:modelValue': (val: string) => {
textBar = val
emit('update:modelValue', 'from Child')
- }
+ },
})
- }
+ },
})
const GrandChild = defineComponent({
onClick() {
click()
emit('update:modelValue', 'from GrandChild')
- }
+ },
})
- }
+ },
})
const root = document.createElement('div')
// reference: https://github.com/vuejs/vue/blob/dev/test/unit/modules/vdom/patch/children.spec.js
import {
+ type TestElement,
+ TestNodeTypes,
h,
- render,
nodeOps,
- TestNodeTypes,
- TestElement,
+ render,
serialize,
- serializeInner
+ serializeInner,
} from '@vue/runtime-test'
function toSpan(content: any) {
if (typeof content === 'string') {
'2',
'3',
'4',
- '5'
+ '5',
])
})
'2',
'3',
'4',
- '5'
+ '5',
])
})
'2',
'3',
'4',
- '5'
+ '5',
])
})
'2',
'3',
'4',
- '5'
+ '5',
])
})
'2',
'3',
'4',
- '5'
+ '5',
])
render(h('div'), root)
'1',
'2',
'4',
- '5'
+ '5',
])
})
'2',
'3',
'1',
- '4'
+ '4',
])
})
'1',
'4',
'2',
- '3'
+ '3',
])
})
'4',
'2',
'3',
- '1'
+ '1',
])
})
'1',
'2',
'3',
- '6'
+ '6',
])
})
'4',
'3',
'2',
- '1'
+ '1',
])
})
'2',
'1',
'5',
- '0'
+ '0',
])
})
render(
h(
'span',
- arr.map(n => spanNumWithOpacity(n, '1'))
+ arr.map(n => spanNumWithOpacity(n, '1')),
),
- root
+ root,
)
elm = root.children[0] as TestElement
for (let i = 0; i < elms; ++i) {
expect(serializeInner(elm.children[i] as TestElement)).toBe(
- i.toString()
+ i.toString(),
)
opacities[i] = Math.random().toFixed(5).toString()
}
render(
h(
'span',
- arr.map(n => spanNumWithOpacity(shufArr[n], opacities[n]))
+ arr.map(n => spanNumWithOpacity(shufArr[n], opacities[n])),
),
- root
+ root,
)
elm = root.children[0] as TestElement
for (let i = 0; i < elms; ++i) {
expect(serializeInner(elm.children[i] as TestElement)).toBe(
- shufArr[i].toString()
+ shufArr[i].toString(),
)
expect(elm.children[i]).toMatchObject({
props: {
style: {
- opacity: opacities[i]
- }
- }
+ opacity: opacities[i],
+ },
+ },
})
}
}
h('div', { key: 1 }, 'one'),
h('div', { key: 2 }, 'two'),
h('div', { key: 3 }, 'three'),
- h('div', { key: 4 }, 'four')
+ h('div', { key: 4 }, 'four'),
]),
- root
+ root,
)
elm = root.children[0] as TestElement
expect((elm.children as TestElement[]).map(c => c.tag)).toEqual([
'div',
'div',
'div',
- 'div'
+ 'div',
])
expect((elm.children as TestElement[]).map(inner)).toEqual([
'one',
'two',
'three',
- 'four'
+ 'four',
])
render(
h('div', { key: 4 }, 'four'),
h('span', { key: 3 }, 'three'),
h('span', { key: 2 }, 'two'),
- h('div', { key: 1 }, 'one')
+ h('div', { key: 1 }, 'one'),
]),
- root
+ root,
)
expect((elm.children as TestElement[]).map(c => c.tag)).toEqual([
'div',
'span',
'span',
- 'div'
+ 'div',
])
expect((elm.children as TestElement[]).map(inner)).toEqual([
'four',
'three',
'two',
- 'one'
+ 'one',
])
})
elm = root.children[0] as TestElement
expect(elm.children[0]).toMatchObject({
props: {
- class: 'hi'
- }
+ class: 'hi',
+ },
})
render(h('div', [h('div', 'four')]), root)
props: {
// in the DOM renderer this will be ''
// but the test renderer simply sets whatever value it receives.
- class: null
- }
+ class: null,
+ },
})
expect(serialize(elm.children[0])).toBe(`<div>four</div>`)
})
'1',
'a',
'b',
- 'c'
+ 'c',
])
elm = renderChildren(['d', 'a', 'b', 'c', 1, 'e'])
'b',
'c',
'1',
- 'e'
+ 'e',
])
})
elm = renderChildren(['hello', 'world'])
expect((elm.children as TestElement[]).map(inner)).toEqual([
'hello',
- 'world'
+ 'world',
])
})
elm = root.children[0] as TestElement
expect(elm.children[0]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'text'
+ text: 'text',
})
render(h('div', ['text', h('span', ['hello'])]), root)
elm = root.children[0] as TestElement
expect(elm.children[0]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'text'
+ text: 'text',
})
})
elm = root.children[0] as TestElement
expect(elm.children[0]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'text'
+ text: 'text',
})
render(h('div', ['text2', h('span', ['hello'])]), root)
elm = root.children[0] as TestElement
expect(elm.children[0]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'text2'
+ text: 'text2',
})
})
render(h('div', [h('span', ['hello']), h('span', ['world'])]), root)
expect((elm.children as TestElement[]).map(inner)).toEqual([
'hello',
- 'world'
+ 'world',
])
})
render(h('div', [h('div', ['hello']), h('span', ['world'])]), root)
expect((elm.children as TestElement[]).map(c => c.tag)).toEqual([
'div',
- 'span'
+ 'span',
])
expect((elm.children as TestElement[]).map(inner)).toEqual([
'hello',
- 'world'
+ 'world',
])
})
test('remove elements with updating children without keys', () => {
render(
h('div', [h('span', ['one']), h('span', ['two']), h('span', ['three'])]),
- root
+ root,
)
elm = root.children[0] as TestElement
expect((elm.children as TestElement[]).map(inner)).toEqual([
'one',
'two',
- 'three'
+ 'three',
])
render(h('div', [h('span', ['one']), h('span', ['three'])]), root)
elm = root.children[0] as TestElement
expect((elm.children as TestElement[]).map(c => serialize(c))).toEqual([
'one',
- '<span>two</span>'
+ '<span>two</span>',
])
render(h('div', [h('div', ['three'])]), root)
test('reorder elements', () => {
render(
h('div', [h('span', ['one']), h('div', ['two']), h('b', ['three'])]),
- root
+ root,
)
elm = root.children[0] as TestElement
expect((elm.children as TestElement[]).map(inner)).toEqual([
'one',
'two',
- 'three'
+ 'three',
])
render(
h('div', [h('b', ['three']), h('div', ['two']), h('span', ['one'])]),
- root
+ root,
)
elm = root.children[0] as TestElement
expect((elm.children as TestElement[]).map(inner)).toEqual([
'three',
'two',
- 'one'
+ 'one',
])
})
import {
- ref,
+ type Ref,
+ type SetupContext,
+ type VNode,
h,
- render,
- nodeOps,
- serializeInner,
+ inject,
nextTick,
- VNode,
+ nodeOps,
provide,
- inject,
- Ref,
+ ref,
+ render,
+ serializeInner,
watch,
- SetupContext
} from '@vue/runtime-test'
describe('renderer: component', () => {
render: () => {
// let Parent first rerender
return (parentVnode = h(Child))
- }
+ },
}
const Child = {
return value.value
? (childVnode1 = h('div'))
: (childVnode2 = h('span'))
- }
+ },
}
const root = nodeOps.createElement('div')
const Comp = {
render: () => {
return h('div')
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp, { id: 'foo', class: 'bar' }), root)
const Comp = {
render: () => {
return h('div', 'test')
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp, { id: 'foo', class: 'bar' }), root)
const Comp1 = {
render: () => {
return h('div', 'foo')
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(Comp1), root)
const Comp2 = {
render: () => {
return h('span', 'foo')
- }
+ },
}
render(h(Comp2), root)
expect(serializeInner(root)).toBe('<span>foo</span>')
const Comp1 = {
emits: ['foo'],
updated: vi.fn(),
- render: () => null
+ render: () => null,
}
const root = nodeOps.createElement('div')
render(
h(Comp1, {
- onFoo: () => {}
+ onFoo: () => {},
}),
- root
+ root,
)
render(
h(Comp1, {
- onFoo: () => {}
+ onFoo: () => {},
}),
- root
+ root,
)
expect(Comp1.updated).not.toHaveBeenCalled()
})
return () => {
return [h('div', n.value), h(Child)]
}
- }
+ },
}
const Child = {
return () => {
return h('div', n.value)
}
- }
+ },
}
const root = nodeOps.createElement('div')
let instance: any
const Comp = {
props: {
- testProp: String
+ testProp: String,
},
data() {
return {
- testData: undefined
+ testData: undefined,
}
},
watch: {
testProp() {
- // @ts-ignore
+ // @ts-expect-error
propWatchSpy(this.$el)
},
testData() {
- // @ts-ignore
+ // @ts-expect-error
dataWatchSpy(this.$el)
- }
+ },
},
created() {
render() {
return h('div')
- }
+ },
}
const root = nodeOps.createElement('div')
h('div', inner.value),
h(Child, {
value: outer.value,
- onUpdate: (val: number) => (inner.value = val)
- })
+ onUpdate: (val: number) => (inner.value = val),
+ }),
]
}
- }
+ },
}
const Child = {
setup(props: any, { emit }: SetupContext) {
watch(
() => props.value,
- (val: number) => emit('update', val)
+ (val: number) => emit('update', val),
)
return () => {
return h('div', props.value)
}
- }
+ },
}
const root = nodeOps.createElement('div')
props: ['foo'],
data() {
return {
- count: 0
+ count: 0,
}
},
watch: {
immediate: true,
handler() {
;(this as any).count
- }
- }
+ },
+ },
},
created() {
childInstance = this as any
},
render() {
return h('h1', (this as any).count)
- }
+ },
}
const App = {
setup() {
return () => h(Child)
},
- updated: vi.fn()
+ updated: vi.fn(),
}
const root = nodeOps.createElement('div')
},
render() {
return h('h1', (this as any).foo)
- }
+ },
}
const root = nodeOps.createElement('div')
render() {
const c = [h(Comp)]
return [c, c, c]
- }
+ },
}
const ids: number[] = []
render: () => h('h1'),
beforeUnmount() {
ids.push((this as any).$.uid)
- }
+ },
}
const root = nodeOps.createElement('div')
const App = {
render() {
return h(Comp, { text: text.value })
- }
+ },
}
const Comp = {
render(this: any) {
spy()
return h('h1', this.text)
- }
+ },
}
const root = nodeOps.createElement('div')
const App = {
setup() {
return {
- __isScriptSetup: true
+ __isScriptSetup: true,
}
},
render(this: any) {
return this.$attrs.id
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(
- `Property '$attrs' was accessed via 'this'. Avoid using 'this' in templates.`
+ `Property '$attrs' was accessed via 'this'. Avoid using 'this' in templates.`,
).toHaveBeenWarned()
})
})
import {
+ type TestElement,
h,
- render,
+ serializeInner as inner,
nodeOps,
- TestElement,
- serializeInner as inner
+ render,
} from '@vue/runtime-test'
describe('renderer: element', () => {
import {
- h,
- createVNode,
- render,
- nodeOps,
- TestNodeTypes,
- TestElement,
Fragment,
- resetOps,
- dumpOps,
NodeOpTypes,
- serializeInner,
- createTextVNode,
+ type TestElement,
+ TestNodeTypes,
createBlock,
+ createCommentVNode,
+ createTextVNode,
+ createVNode,
+ dumpOps,
+ h,
+ nodeOps,
openBlock,
- createCommentVNode
+ render,
+ resetOps,
+ serializeInner,
} from '@vue/runtime-test'
import { PatchFlags } from '@vue/shared'
import { renderList } from '../src/helpers/renderList'
const App = {
render() {
return [h('div', 'one'), 'two']
- }
+ },
}
const root = nodeOps.createElement('div')
expect(root.children.length).toBe(4)
expect(root.children[0]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: ''
+ text: '',
})
expect(root.children[1]).toMatchObject({
type: TestNodeTypes.ELEMENT,
- tag: 'div'
+ tag: 'div',
})
expect((root.children[1] as TestElement).children[0]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'one'
+ text: 'one',
})
expect(root.children[2]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'two'
+ text: 'two',
})
expect(root.children[3]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: ''
+ text: '',
})
})
const root = nodeOps.createElement('div')
render(
h(Fragment, [h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')]),
- root
+ root,
)
expect(serializeInner(root)).toBe(`<div>one</div><div>two</div>`)
resetOps()
render(
h(Fragment, [h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')]),
- root
+ root,
)
expect(serializeInner(root)).toBe(`<div>two</div><div>one</div>`)
const ops = dumpOps()
// should be moving nodes instead of re-creating or patching them
expect(ops).toMatchObject([
{
- type: NodeOpTypes.INSERT
- }
+ type: NodeOpTypes.INSERT,
+ },
])
})
// should be patching nodes instead of moving or re-creating them
expect(ops).toMatchObject([
{
- type: NodeOpTypes.SET_ELEMENT_TEXT
+ type: NodeOpTypes.SET_ELEMENT_TEXT,
},
{
- type: NodeOpTypes.SET_ELEMENT_TEXT
- }
+ type: NodeOpTypes.SET_ELEMENT_TEXT,
+ },
])
})
null,
[
createVNode('div', null, 'one', PatchFlags.TEXT),
- createTextVNode('two')
+ createTextVNode('two'),
],
- PatchFlags.UNKEYED_FRAGMENT
+ PatchFlags.UNKEYED_FRAGMENT,
),
- root
+ root,
)
expect(serializeInner(root)).toBe(`<div>one</div>two`)
[
createVNode('div', null, 'foo', PatchFlags.TEXT),
createTextVNode('bar'),
- createTextVNode('baz')
+ createTextVNode('baz'),
],
- PatchFlags.UNKEYED_FRAGMENT
+ PatchFlags.UNKEYED_FRAGMENT,
),
- root
+ root,
)
expect(serializeInner(root)).toBe(`<div>foo</div>barbaz`)
null,
[
createTextVNode('baz'),
- createVNode('div', null, 'foo', PatchFlags.TEXT)
+ createVNode('div', null, 'foo', PatchFlags.TEXT),
],
- PatchFlags.UNKEYED_FRAGMENT
+ PatchFlags.UNKEYED_FRAGMENT,
),
- root
+ root,
)
expect(serializeInner(root)).toBe(`baz<div>foo</div>`)
})
Fragment,
null,
[h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')],
- PatchFlags.KEYED_FRAGMENT
+ PatchFlags.KEYED_FRAGMENT,
),
- root
+ root,
)
expect(serializeInner(root)).toBe(`<div>one</div><div>two</div>`)
Fragment,
null,
[h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')],
- PatchFlags.KEYED_FRAGMENT
+ PatchFlags.KEYED_FRAGMENT,
),
- root
+ root,
)
expect(serializeInner(root)).toBe(`<div>two</div><div>one</div>`)
const ops = dumpOps()
// should be moving nodes instead of re-creating or patching them
expect(ops).toMatchObject([
{
- type: NodeOpTypes.INSERT
- }
+ type: NodeOpTypes.INSERT,
+ },
])
})
h('div', { key: 1 }, 'outer'),
h(Fragment, { key: 2 }, [
h('div', { key: 1 }, 'one'),
- h('div', { key: 2 }, 'two')
- ])
+ h('div', { key: 2 }, 'two'),
+ ]),
]),
- root
+ root,
)
expect(serializeInner(root)).toBe(
- `<div><div>outer</div><div>one</div><div>two</div></div>`
+ `<div><div>outer</div><div>one</div><div>two</div></div>`,
)
resetOps()
h('div', [
h(Fragment, { key: 2 }, [
h('div', { key: 2 }, 'two'),
- h('div', { key: 1 }, 'one')
+ h('div', { key: 1 }, 'one'),
]),
- h('div', { key: 1 }, 'outer')
+ h('div', { key: 1 }, 'outer'),
]),
- root
+ root,
)
expect(serializeInner(root)).toBe(
- `<div><div>two</div><div>one</div><div>outer</div></div>`
+ `<div><div>two</div><div>one</div><div>outer</div></div>`,
)
const ops = dumpOps()
// should be moving nodes instead of re-creating them
{ type: NodeOpTypes.INSERT, targetNode: { type: 'text', text: '' } },
{ type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
{ type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
- { type: NodeOpTypes.INSERT, targetNode: { type: 'text', text: '' } }
+ { type: NodeOpTypes.INSERT, targetNode: { type: 'text', text: '' } },
])
})
h('div', { key: 1 }, 'outer'),
h(Fragment, { key: 2 }, [
h('div', { key: 1 }, 'one'),
- h('div', { key: 2 }, 'two')
- ])
+ h('div', { key: 2 }, 'two'),
+ ]),
]),
- root
+ root,
)
expect(serializeInner(root)).toBe(
- `<div>outer</div><div>one</div><div>two</div>`
+ `<div>outer</div><div>one</div><div>two</div>`,
)
resetOps()
h(Fragment, [
h(Fragment, { key: 2 }, [
h('div', { key: 2 }, 'two'),
- h('div', { key: 1 }, 'one')
+ h('div', { key: 1 }, 'one'),
]),
- h('div', { key: 1 }, 'outer')
+ h('div', { key: 1 }, 'outer'),
]),
- root
+ root,
)
expect(serializeInner(root)).toBe(
- `<div>two</div><div>one</div><div>outer</div>`
+ `<div>two</div><div>one</div><div>outer</div>`,
)
const ops = dumpOps()
// should be moving nodes instead of re-creating them
{ type: NodeOpTypes.INSERT, targetNode: { type: 'text', text: '' } },
{ type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
{ type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
- { type: NodeOpTypes.INSERT, targetNode: { type: 'text', text: '' } }
+ { type: NodeOpTypes.INSERT, targetNode: { type: 'text', text: '' } },
])
// should properly remove nested fragments
[
createCommentVNode('comment'),
hoisted,
- createVNode('div', null, item, PatchFlags.TEXT)
+ createVNode('div', null, item, PatchFlags.TEXT),
],
- PatchFlags.STABLE_FRAGMENT
+ PatchFlags.STABLE_FRAGMENT,
)
)
}),
- PatchFlags.KEYED_FRAGMENT
+ PatchFlags.KEYED_FRAGMENT,
)
)
}
render(renderFn(['one', 'two']), root)
expect(serializeInner(root)).toBe(
- `<!--comment--><span></span><div>one</div><!--comment--><span></span><div>two</div>`
+ `<!--comment--><span></span><div>one</div><!--comment--><span></span><div>two</div>`,
)
render(renderFn(['two', 'one']), root)
expect(serializeInner(root)).toBe(
- `<!--comment--><span></span><div>two</div><!--comment--><span></span><div>one</div>`
+ `<!--comment--><span></span><div>two</div><!--comment--><span></span><div>one</div>`,
)
})
{ key: item },
[
createTextVNode('text'),
- createVNode('div', null, item, PatchFlags.TEXT)
+ createVNode('div', null, item, PatchFlags.TEXT),
],
- PatchFlags.STABLE_FRAGMENT
+ PatchFlags.STABLE_FRAGMENT,
)
)
}),
- PatchFlags.KEYED_FRAGMENT
+ PatchFlags.KEYED_FRAGMENT,
)
)
}
import {
- h,
Fragment,
+ type FunctionalComponent,
+ type SetupContext,
Teleport,
- createVNode,
- createCommentVNode,
- openBlock,
+ type TestElement,
+ type VNode,
+ createApp,
createBlock,
- render,
- nodeOps,
- TestElement,
- serialize,
+ createCommentVNode,
+ createTextVNode,
+ createVNode,
+ defineComponent,
+ h,
serializeInner as inner,
- VNode,
- ref,
nextTick,
- defineComponent,
- withCtx,
- renderSlot,
+ nodeOps,
onBeforeUnmount,
- createTextVNode,
- SetupContext,
- createApp,
- FunctionalComponent,
+ onUnmounted,
+ openBlock,
+ ref,
+ render,
renderList,
- onUnmounted
+ renderSlot,
+ serialize,
+ withCtx,
} from '@vue/runtime-test'
import { PatchFlags, SlotFlags } from '@vue/shared'
import { SuspenseImpl } from '../src/components/Suspense'
const renderWithBlock = (renderChildren: () => VNode[]) => {
render(
(openBlock(), (block = createBlock('div', null, renderChildren()))),
- root
+ root,
)
}
test('block can appear anywhere in the vdom tree', () => {
render(
h('div', (openBlock(), (block = createBlock('p', null, 'foo')))),
- root
+ root,
)
expect(block.dynamicChildren!.length).toBe(0)
test('block should collect dynamic vnodes', () => {
renderWithBlock(() => [
createVNode('p', null, 'foo', PatchFlags.TEXT),
- createVNode('i')
+ createVNode('i'),
])
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p>foo</p>'
+ '<p>foo</p>',
)
})
// disable tracking
(openBlock(true),
(block = createBlock('div', null, [
- createVNode('p', null, 'foo', PatchFlags.TEXT)
+ createVNode('p', null, 'foo', PatchFlags.TEXT),
]))),
- root
+ root,
)
expect(block.dynamicChildren!.length).toBe(0)
test('block as dynamic children', () => {
renderWithBlock(() => [
- (openBlock(), createBlock('div', { key: 0 }, [h('p')]))
+ (openBlock(), createBlock('div', { key: 0 }, [h('p')])),
])
expect(block!.dynamicChildren!.length).toBe(1)
expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(0)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<div><p></p></div>'
+ '<div><p></p></div>',
)
renderWithBlock(() => [
- (openBlock(), createBlock('div', { key: 1 }, [h('i')]))
+ (openBlock(), createBlock('div', { key: 1 }, [h('i')])),
])
expect(block!.dynamicChildren!.length).toBe(1)
expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(0)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<div><i></i></div>'
+ '<div><i></i></div>',
)
})
expect(inner(root)).toBe('<div><p>foo</p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p>foo</p>'
+ '<p>foo</p>',
)
renderWithBlock(() => [createVNode('p', null, 'bar', PatchFlags.TEXT)])
expect(inner(root)).toBe('<div><p>bar</p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p>bar</p>'
+ '<p>bar</p>',
)
})
test('PatchFlags: PatchFlags.CLASS', async () => {
renderWithBlock(() => [
- createVNode('p', { class: 'foo' }, '', PatchFlags.CLASS)
+ createVNode('p', { class: 'foo' }, '', PatchFlags.CLASS),
])
expect(inner(root)).toBe('<div><p class="foo"></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p class="foo"></p>'
+ '<p class="foo"></p>',
)
renderWithBlock(() => [
- createVNode('p', { class: 'bar' }, '', PatchFlags.CLASS)
+ createVNode('p', { class: 'bar' }, '', PatchFlags.CLASS),
])
expect(inner(root)).toBe('<div><p class="bar"></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p class="bar"></p>'
+ '<p class="bar"></p>',
)
})
test('PatchFlags: PatchFlags.STYLE', async () => {
renderWithBlock(() => [
- createVNode('p', { style: 'color: red' }, '', PatchFlags.STYLE)
+ createVNode('p', { style: 'color: red' }, '', PatchFlags.STYLE),
])
expect(inner(root)).toBe('<div><p style="color: red"></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p style="color: red"></p>'
+ '<p style="color: red"></p>',
)
renderWithBlock(() => [
- createVNode('p', { style: 'color: green' }, '', PatchFlags.STYLE)
+ createVNode('p', { style: 'color: green' }, '', PatchFlags.STYLE),
])
expect(inner(root)).toBe('<div><p style="color: green"></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p style="color: green"></p>'
+ '<p style="color: green"></p>',
)
})
test('PatchFlags: PatchFlags.PROPS', async () => {
renderWithBlock(() => [
- createVNode('p', { id: 'foo' }, '', PatchFlags.PROPS, ['id'])
+ createVNode('p', { id: 'foo' }, '', PatchFlags.PROPS, ['id']),
])
expect(inner(root)).toBe('<div><p id="foo"></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p id="foo"></p>'
+ '<p id="foo"></p>',
)
renderWithBlock(() => [
- createVNode('p', { id: 'bar' }, '', PatchFlags.PROPS, ['id'])
+ createVNode('p', { id: 'bar' }, '', PatchFlags.PROPS, ['id']),
])
expect(inner(root)).toBe('<div><p id="bar"></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p id="bar"></p>'
+ '<p id="bar"></p>',
)
})
let propName = 'foo'
renderWithBlock(() => [
- createVNode('p', { [propName]: 'dynamic' }, '', PatchFlags.FULL_PROPS)
+ createVNode('p', { [propName]: 'dynamic' }, '', PatchFlags.FULL_PROPS),
])
expect(inner(root)).toBe('<div><p foo="dynamic"></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p foo="dynamic"></p>'
+ '<p foo="dynamic"></p>',
)
propName = 'bar'
renderWithBlock(() => [
- createVNode('p', { [propName]: 'dynamic' }, '', PatchFlags.FULL_PROPS)
+ createVNode('p', { [propName]: 'dynamic' }, '', PatchFlags.FULL_PROPS),
])
expect(inner(root)).toBe('<div><p bar="dynamic"></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p bar="dynamic"></p>'
+ '<p bar="dynamic"></p>',
)
})
list.map(item => {
return createVNode('p', null, item, PatchFlags.TEXT)
}),
- PatchFlags.STABLE_FRAGMENT
+ PatchFlags.STABLE_FRAGMENT,
))),
- root
+ root,
)
expect(inner(root)).toBe('<p>foo</p><p>bar</p>')
expect(block.dynamicChildren!.length).toBe(2)
expect(serialize(block.dynamicChildren![0].el as TestElement)).toBe(
- '<p>foo</p>'
+ '<p>foo</p>',
)
expect(serialize(block.dynamicChildren![1].el as TestElement)).toBe(
- '<p>bar</p>'
+ '<p>bar</p>',
)
list = list.map(item => item.repeat(2))
list.map(item => {
return createVNode('p', null, item, PatchFlags.TEXT)
}),
- PatchFlags.STABLE_FRAGMENT
+ PatchFlags.STABLE_FRAGMENT,
)),
- root
+ root,
)
expect(inner(root)).toBe('<p>foofoo</p><p>barbar</p>')
expect(block.dynamicChildren!.length).toBe(2)
expect(serialize(block.dynamicChildren![0].el as TestElement)).toBe(
- '<p>foofoo</p>'
+ '<p>foofoo</p>',
)
expect(serialize(block.dynamicChildren![1].el as TestElement)).toBe(
- '<p>barbar</p>'
+ '<p>barbar</p>',
)
})
list.map(item => {
return createVNode(item.tag, null, item.text)
}),
- PatchFlags.UNKEYED_FRAGMENT
+ PatchFlags.UNKEYED_FRAGMENT,
))),
- root
+ root,
)
expect(inner(root)).toBe('<p>foo</p>')
list.map(item => {
return createVNode(item.tag, null, item.text)
}),
- PatchFlags.UNKEYED_FRAGMENT
+ PatchFlags.UNKEYED_FRAGMENT,
)),
- root
+ root,
)
expect(inner(root)).toBe('<i>bar</i><p>foo</p>')
list.map(item => {
return createVNode(item.tag, { key: item.tag }, item.text)
}),
- PatchFlags.KEYED_FRAGMENT
+ PatchFlags.KEYED_FRAGMENT,
))),
- root
+ root,
)
expect(inner(root)).toBe('<p>foo</p>')
list.map(item => {
return createVNode(item.tag, { key: item.tag }, item.text)
}),
- PatchFlags.KEYED_FRAGMENT
+ PatchFlags.KEYED_FRAGMENT,
)),
- root
+ root,
)
expect(inner(root)).toBe('<i>bar</i><p>foo</p>')
'p',
{ onVnodeMounted: spyMounted, onVnodeBeforeUpdate: spyUpdated },
'',
- PatchFlags.NEED_PATCH
- )
+ PatchFlags.NEED_PATCH,
+ ),
]))
)
}
- }
+ },
}
render(h(Comp), root)
expect(inner(root)).toBe('<div><p></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p></p>'
+ '<p></p>',
)
expect(spyMounted).toHaveBeenCalledTimes(1)
expect(spyUpdated).toHaveBeenCalledTimes(0)
expect(inner(root)).toBe('<div><p></p></div>')
expect(block!.dynamicChildren!.length).toBe(1)
expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p></p>'
+ '<p></p>',
)
expect(spyMounted).toHaveBeenCalledTimes(1)
expect(spyUpdated).toHaveBeenCalledTimes(1)
render(
(openBlock(),
(block = createBlock('div', null, [createVNode('p', null, 'foo')]))),
- root
+ root,
)
expect(inner(root)).toBe('<div><p>foo</p></div>')
'div',
null,
[createVNode('i', null, 'bar')],
- PatchFlags.BAIL
+ PatchFlags.BAIL,
))),
- root
+ root,
)
expect(inner(root)).toBe('<div><i>bar</i></div>')
(openBlock(),
(block = createBlock('div', null, {
default: withCtx(() => [renderSlot(slots, 'default')]),
- _: SlotFlags.FORWARDED
+ _: SlotFlags.FORWARDED,
})))
return vnode
}
- }
+ },
})
const foo = ref(0)
return () => {
return createVNode(Comp, null, {
default: withCtx(() => [
- createVNode('p', null, foo.value, PatchFlags.TEXT)
+ createVNode('p', null, foo.value, PatchFlags.TEXT),
]),
// Indicates that this is a stable slot to avoid bail out
- _: SlotFlags.STABLE
+ _: SlotFlags.STABLE,
})
}
- }
+ },
}
render(h(App), root)
expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(1)
expect(
serialize(
- block!.dynamicChildren![0].dynamicChildren![0].el as TestElement
- )
+ block!.dynamicChildren![0].dynamicChildren![0].el as TestElement,
+ ),
).toBe('<p>0</p>')
foo.value++
setup() {
onBeforeUnmount(spy)
return () => 'child'
- }
+ },
}
const Parent = () => (
openBlock(),
createBlock('div', null, [
- createVNode('div', { style: {} }, [createVNode(Child)], 4 /* STYLE */)
+ createVNode('div', { style: {} }, [createVNode(Child)], 4 /* STYLE */),
])
)
render(h(Parent), root)
setup() {
onBeforeUnmount(spyA)
return () => 'child'
- }
+ },
}
const ChildB = {
setup() {
onBeforeUnmount(spyB)
return () => 'child'
- }
+ },
}
const Parent = () => (
openBlock(),
Fragment,
null,
[createVNode(ChildA, { key: 0 })],
- 128 /* KEYED_FRAGMENT */
+ 128 /* KEYED_FRAGMENT */,
)),
(openBlock(true),
createBlock(
Fragment,
null,
[createVNode(ChildB)],
- 256 /* UNKEYED_FRAGMENT */
- ))
+ 256 /* UNKEYED_FRAGMENT */,
+ )),
])
)
render(h(Parent), root)
createBlock('div', null, [renderSlot(slots, 'default')])
)
}
- }
+ },
}
const Wrapper = {
return () => {
return slots.default!()[state.value]
}
- }
+ },
}
const app = createApp({
default: withCtx(() => [
createVNode(CompA, null, {
default: withCtx(() => [createTextVNode('Hello')]),
- _: 1 /* STABLE */
+ _: 1 /* STABLE */,
}),
createVNode(CompA, null, {
default: withCtx(() => [createTextVNode('World')]),
- _: 1 /* STABLE */
- })
+ _: 1 /* STABLE */,
+ }),
]),
- _: 1 /* STABLE */
+ _: 1 /* STABLE */,
})
)
}
- }
+ },
})
app.mount(root)
createBlock(
Teleport as any,
{
- to: target
+ to: target,
},
[
createVNode('div', null, [
createBlock(
Teleport as any,
{
- to: target
+ to: target,
},
- [createVNode('div', null, 'foo')]
- ))
- ])
- ]
- ))
+ [createVNode('div', null, 'foo')],
+ )),
+ ]),
+ ],
+ )),
])),
- root
+ root,
)
expect(inner(target)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--></div><div>foo</div>"`
+ `"<div><!--teleport start--><!--teleport end--></div><div>foo</div>"`,
)
expect(inner(root)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--></div>"`
+ `"<div><!--teleport start--><!--teleport end--></div>"`,
)
render(null, root)
return (
openBlock(),
(block = createBlock('section', null, [
- renderSlot(slots, 'default')
+ renderSlot(slots, 'default'),
]))
)
}
- }
+ },
}
let dynamicVNode: VNode
'div',
{
class: {
- foo: !!slots.default!()
- }
+ foo: !!slots.default!(),
+ },
},
null,
- PatchFlags.CLASS
- ))
+ PatchFlags.CLASS,
+ )),
]
}),
- _: 1
+ _: 1,
})
)
}
- }
+ },
}
const app = createApp({
render() {
default: withCtx(() => {
return [createVNode({}) /* component */]
}),
- _: 1
+ _: 1,
})
)
- }
+ },
})
app.mount(root)
expect(block!.dynamicChildren!.length).toBe(1)
expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(1)
expect(block!.dynamicChildren![0].dynamicChildren![0]).toEqual(
- dynamicVNode!
+ dynamicVNode!,
)
})
return () => {
return slots.default!()[index.value]
}
- }
+ },
}
const app = createApp({
: createCommentVNode('v-if', true),
true
? (openBlock(), createBlock('p', { key: 0 }, '2'))
- : createCommentVNode('v-if', true)
+ : createCommentVNode('v-if', true),
]),
- _: 1 /* STABLE */
+ _: 1 /* STABLE */,
})
)
}
- }
+ },
})
app.mount(root)
const Middle = {
setup(props: any, { slots }: any) {
return slots.default!
- }
+ },
}
const Comp = {
createVNode(Middle, null, {
default: withCtx(
() => [
- createVNode('div', null, [renderSlot(slots, 'default')])
+ createVNode('div', null, [renderSlot(slots, 'default')]),
],
- undefined
+ undefined,
),
- _: 3 /* FORWARDED */
- })
+ _: 3 /* FORWARDED */,
+ }),
])
)
}
- }
+ },
}
const loading = ref(false)
// important: write the slot content here
const content = h('span', loading.value ? 'loading' : 'loaded')
return h(Comp, null, {
- default: () => content
+ default: () => content,
})
}
- }
+ },
})
app.mount(root)
createBlock(SuspenseImpl, null, {
default: withCtx(() => [
createVNode('div', null, [
- createVNode('div', null, show.value, PatchFlags.TEXT)
- ])
+ createVNode('div', null, show.value, PatchFlags.TEXT),
+ ]),
]),
- _: SlotFlags.STABLE
- }))
+ _: SlotFlags.STABLE,
+ })),
],
- PatchFlags.STABLE_FRAGMENT
+ PatchFlags.STABLE_FRAGMENT,
)
)
- }
+ },
})
app.mount(root)
openBlock(),
createBlock(SuspenseImpl, null, {
default: withCtx(() => [renderSlot(slots, 'default')]),
- _: SlotFlags.FORWARDED
+ _: SlotFlags.FORWARDED,
})
)
- }
+ },
}
const Child = {
setup() {
onUnmounted(spyUnmounted)
return () => createVNode('div', null, show.value, PatchFlags.TEXT)
- }
+ },
}
const app = createApp({
{ key: 0 },
{
default: withCtx(() => [createVNode(Child)]),
- _: SlotFlags.STABLE
- }
+ _: SlotFlags.STABLE,
+ },
))
: createCommentVNode('v-if', true)
- }
+ },
})
app.mount(root)
renderList(1, item => {
return createVNode('li', null, [createVNode(Dummy)])
}),
- 64 /* STABLE_FRAGMENT */
- ))
- ])
+ 64 /* STABLE_FRAGMENT */,
+ )),
+ ]),
],
undefined,
- true
+ true,
),
- _: 1 /* STABLE */
- }
+ _: 1 /* STABLE */,
+ },
)
- }
+ },
})
app.mount(root)
import {
- ref,
- nodeOps,
+ defineComponent,
h,
- render,
nextTick,
- defineComponent,
+ nodeOps,
reactive,
+ ref,
+ render,
serializeInner,
- shallowRef
+ shallowRef,
} from '@vue/runtime-test'
// reference: https://vue-composition-api-rfc.netlify.com/api.html#template-refs
const Comp = {
setup() {
return {
- refKey: el
+ refKey: el,
}
},
render() {
return h('div', { ref: 'refKey' })
- }
+ },
}
render(h(Comp), root)
expect(el.value).toBe(root.children[0])
setup() {
return {
foo: fooEl,
- bar: barEl
+ bar: barEl,
}
},
render() {
return h('div', { ref: refKey.value })
- }
+ },
}
render(h(Comp), root)
expect(fooEl.value).toBe(root.children[0])
const Comp = {
setup() {
return {
- refKey: el
+ refKey: el,
}
},
render() {
return toggle.value ? h('div', { ref: 'refKey' }) : null
- }
+ },
}
render(h(Comp), root)
expect(el.value).toBe(root.children[0])
const toggle = ref(true)
const Comp = defineComponent(
- () => () => (toggle.value ? h('div', { ref: fn }) : null)
+ () => () => (toggle.value ? h('div', { ref: fn }) : null),
)
render(h(Comp), root)
expect(fn.mock.calls[0][0]).toBe(root.children[0])
const Comp = {
setup() {
return () => h('div', { ref: el })
- }
+ },
}
render(h(Comp), root)
expect(el.value).toBe(root.children[0])
const root = nodeOps.createElement('div')
const refs = {
foo: ref(null),
- bar: ref(null)
+ bar: ref(null),
}
const refKey = ref<keyof typeof refs>('foo')
const Comp = {
setup() {
return () => h('div', { ref: refs[refKey.value] })
- }
+ },
}
render(h(Comp), root)
expect(refs.foo.value).toBe(root.children[0])
const Comp = {
setup() {
return () => (toggle.value ? h('div', { ref: el }) : null)
- }
+ },
}
render(h(Comp), root)
expect(el.value).toBe(root.children[0])
const Child = {
render(this: any) {
return this.$slots.default()
- }
+ },
}
const Comp = {
},
mounted(this: any) {
spy(this.$refs.foo.tag)
- }
+ },
}
render(h(Comp), root)
it('should work with direct reactive property', () => {
const root = nodeOps.createElement('div')
const state = reactive({
- refKey: null
+ refKey: null,
})
const Comp = {
},
render() {
return h('div', { ref: 'refKey' })
- }
+ },
}
render(h(Comp), root)
expect(state.refKey).toBe(root.children[0])
return {
refKey1,
refKey2,
- refKey3
+ refKey3,
}
},
render() {
return [
h('div', { ref: 'refKey1' }),
h('div', { ref: 'refKey2' }),
- h('div', { ref: 'refKey3' })
+ h('div', { ref: 'refKey3' }),
]
- }
+ },
}
render(h(Comp), root)
expect(refKey1.value).toBe(root.children[1])
},
render(this: any) {
return h('div', { id: 'foo', ref: 'el' }, this.el && this.el.props.id)
- }
+ },
}
const root = nodeOps.createElement('div')
render(this: any) {
return [
h('p', { ref: refToggle.value ? 'foo' : 'bar' }),
- h('i', { ref: refToggle.value ? 'bar' : 'foo' })
+ h('i', { ref: refToggle.value ? 'bar' : 'foo' }),
]
},
mounted(this: any) {
},
updated(this: any) {
spy(this.$refs.foo.tag, this.$refs.bar.tag)
- }
+ },
}
const root = nodeOps.createElement('div')
},
updated(this: any) {
spy(this.$refs.foo.tag)
- }
+ },
}
const root = nodeOps.createElement('div')
// #2078
test('handling multiple merged refs', async () => {
const Foo = {
- render: () => h('div', 'foo')
+ render: () => h('div', 'foo'),
}
const Bar = {
- render: () => h('div', 'bar')
+ render: () => h('div', 'bar'),
}
const viewRef = shallowRef<any>(Foo)
}
const view = h(viewRef.value, { ref: elRef1 })
return h(view, { ref: elRef2 })
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(App), root)
'div',
{
ref: el,
- ref_key: 'el'
+ ref_key: 'el',
},
- 'hello'
+ 'hello',
)
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(App), root)
'li',
{
ref: listRefs,
- ref_for: true
+ ref_for: true,
},
- i
- )
- )
+ i,
+ ),
+ ),
)
: null
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(App), root)
'li',
{
ref: 'listRefs',
- ref_for: true
+ ref_for: true,
},
- i
- )
- )
+ i,
+ ),
+ ),
)
: null
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(App), root)
'li',
{
ref: 'listRefs',
- ref_for: true
+ ref_for: true,
},
- i
- )
- )
- )
+ i,
+ ),
+ ),
+ ),
])
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(App), root)
await nextTick()
expect(String(listRefs.value)).toBe(
- '[object Object],[object Object],[object Object]'
+ '[object Object],[object Object],[object Object]',
)
expect(serializeInner(root)).toBe(
- '<div><div>[object Object],[object Object],[object Object]</div><ul><li>1</li><li>2</li><li>3</li></ul></div>'
+ '<div><div>[object Object],[object Object],[object Object]</div><ul><li>1</li><li>2</li><li>3</li></ul></div>',
)
list.value.splice(0, 1)
await nextTick()
expect(String(listRefs.value)).toBe('[object Object],[object Object]')
expect(serializeInner(root)).toBe(
- '<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>'
+ '<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
)
})
})
import {
- queueJob,
+ flushPostFlushCbs,
+ flushPreFlushCbs,
+ invalidateJob,
nextTick,
+ queueJob,
queuePostFlushCb,
- invalidateJob,
- flushPostFlushCbs,
- flushPreFlushCbs
} from '../src/scheduler'
describe('scheduler', () => {
expect(e).toBe(err)
}
expect(
- `Unhandled error during execution of scheduler flush`
+ `Unhandled error during execution of scheduler flush`,
).toHaveBeenWarned()
// this one should no longer error
// simulate parent component that toggles child
const job1 = () => {
- // @ts-ignore
+ // @ts-expect-error
job2.active = false
}
// simulate child that's triggered by the same reactive change that
import {
h,
- render,
nodeOps,
- serializeInner,
+ popScopeId,
+ pushScopeId,
+ render,
renderSlot,
+ serializeInner,
withScopeId,
- pushScopeId,
- popScopeId
} from '@vue/runtime-test'
import { withCtx } from '../src/componentRenderContext'
test('should attach scopeId', () => {
const App = {
__scopeId: 'parent',
- render: () => h('div', [h('div')])
+ render: () => h('div', [h('div')]),
}
const root = nodeOps.createElement('div')
render(h(App), root)
test('should attach scopeId to components in parent component', () => {
const Child = {
__scopeId: 'child',
- render: () => h('div')
+ render: () => h('div'),
}
const App = {
__scopeId: 'parent',
- render: () => h('div', [h(Child)])
+ render: () => h('div', [h(Child)]),
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(
- `<div parent><div child parent></div></div>`
+ `<div parent><div child parent></div></div>`,
)
})
__scopeId: 'child',
render(this: any) {
return h('div', renderSlot(this.$slots, 'default'))
- }
+ },
}
const Child2 = {
__scopeId: 'child2',
- render: () => h('span')
+ render: () => h('span'),
}
const App = {
__scopeId: 'parent',
Child,
withCtx(() => {
return [h('div'), h(Child2)]
- })
+ }),
)
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(App), root)
// - slotted scopeId from slot owner
// - its own scopeId
`<span child2 parent child-s></span>` +
- `</div>`
+ `</div>`,
)
})
'default',
{},
undefined,
- true /* noSlotted */
- )
+ true /* noSlotted */,
+ ),
])
- }
+ },
}
const Slotted = {
render(this: any) {
// <Wrapper><slot/></Wrapper>
return h(Wrapper, null, {
- default: withCtx(() => [renderSlot(this.$slots, 'default')])
+ default: withCtx(() => [renderSlot(this.$slots, 'default')]),
})
- }
+ },
}
// simulate hoisted node
return h(Slotted, null, {
default: withCtx(() => {
return [hoisted, h('div', 'dynamic')]
- })
+ }),
})
- }
+ },
}
const root = nodeOps.createElement('div')
`<div wrapper slotted root class="wrapper">` +
`<div root slotted-s>hoisted</div>` +
`<div root slotted-s>dynamic</div>` +
- `</div>`
+ `</div>`,
)
const Root2 = {
return h(Slotted, null, {
default: withCtx(() => [
h(Wrapper, null, {
- default: withCtx(() => [hoisted, h('div', 'dynamic')])
- })
- ])
+ default: withCtx(() => [hoisted, h('div', 'dynamic')]),
+ }),
+ ]),
})
- }
+ },
}
const root2 = nodeOps.createElement('div')
render(h(Root2), root2)
`<div root>hoisted</div>` +
`<div root>dynamic</div>` +
`</div>` +
- `</div>`
+ `</div>`,
)
})
__scopeId: 'parent',
render: () => {
return h(Child)
- }
+ },
}
function Child() {
__scopeId: 'parent',
render: withParentId(() => {
return h('div', [h('div')])
- })
+ }),
}
const root = nodeOps.createElement('div')
render(h(App), root)
__scopeId: 'child',
render: withChildId(() => {
return h('div')
- })
+ }),
}
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h('div', [h(Child)])
- })
+ }),
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(
- `<div parent><div child parent></div></div>`
+ `<div parent><div child parent></div></div>`,
)
})
__scopeId: 'child',
render: withChildId(function (this: any) {
return h('div', renderSlot(this.$slots, 'default'))
- })
+ }),
}
const withChild2Id = withScopeId('child2')
const Child2 = {
__scopeId: 'child2',
- render: withChild2Id(() => h('span'))
+ render: withChild2Id(() => h('span')),
}
const App = {
__scopeId: 'parent',
Child,
withParentId(() => {
return [h('div'), h(Child2)]
- })
+ }),
)
- })
+ }),
}
const root = nodeOps.createElement('div')
render(h(App), root)
// - slotted scopeId from slot owner
// - its own scopeId
`<span child2 parent child-s></span>` +
- `</div>`
+ `</div>`,
)
})
__scopeId: 'parent',
render: withParentId(() => {
return h(Child)
- })
+ }),
}
function Child() {
const App = {
__scopeId: 'foobar',
- render: () => h('div', [hoisted])
+ render: () => h('div', [hoisted]),
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(
- `<div foobar><div foobar>hello</div></div>`
+ `<div foobar><div foobar>hello</div></div>`,
)
})
})
import {
- createBlock,
- createVNode,
- openBlock,
Comment,
Fragment,
Text,
cloneVNode,
+ createBlock,
+ createVNode,
+ isBlockTreeEnabled,
mergeProps,
normalizeVNode,
+ openBlock,
transformVNodeArgs,
- isBlockTreeEnabled
} from '../src/vnode'
-import { Data } from '../src/component'
-import { ShapeFlags, PatchFlags } from '@vue/shared'
-import { h, reactive, isReactive, setBlockTracking, ref, withCtx } from '../src'
+import type { Data } from '../src/component'
+import { PatchFlags, ShapeFlags } from '@vue/shared'
+import { h, isReactive, reactive, ref, setBlockTracking, withCtx } from '../src'
import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
import { setCurrentRenderingInstance } from '../src/componentRenderContext'
type: 'p',
props: {
id: 'foo',
- class: 'bar'
+ class: 'bar',
},
children: 'baz',
- shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN
+ shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN,
})
})
test('array<object>', () => {
const vnode = createVNode('p', {
- class: [{ foo: 'foo' }, { baz: 'baz' }]
+ class: [{ foo: 'foo' }, { baz: 'baz' }],
})
expect(vnode.props).toMatchObject({ class: 'foo baz' })
})
describe('style normalization', () => {
test('array', () => {
const vnode = createVNode('p', {
- style: [{ foo: 'foo' }, { baz: 'baz' }]
+ style: [{ foo: 'foo' }, { baz: 'baz' }],
})
expect(vnode.props).toMatchObject({ style: { foo: 'foo', baz: 'baz' } })
})
const vnode = createVNode('p', null, ['foo'])
expect(vnode.children).toMatchObject(['foo'])
expect(vnode.shapeFlag).toBe(
- ShapeFlags.ELEMENT | ShapeFlags.ARRAY_CHILDREN
+ ShapeFlags.ELEMENT | ShapeFlags.ARRAY_CHILDREN,
)
})
const vnode = createVNode({}, null, { foo: 'foo' })
expect(vnode.children).toMatchObject({ foo: 'foo' })
expect(vnode.shapeFlag).toBe(
- ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.SLOTS_CHILDREN
+ ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.SLOTS_CHILDREN,
)
})
const vnode = createVNode('p', null, nop)
expect(vnode.children).toMatchObject({ default: nop })
expect(vnode.shapeFlag).toBe(
- ShapeFlags.ELEMENT | ShapeFlags.SLOTS_CHILDREN
+ ShapeFlags.ELEMENT | ShapeFlags.SLOTS_CHILDREN,
)
})
const vnode = createVNode('p', null, 'foo')
expect(vnode.children).toBe('foo')
expect(vnode.shapeFlag).toBe(
- ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN
+ ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN,
)
})
test('element with slots', () => {
const children = [createVNode('span', null, 'hello')]
const vnode = createVNode('div', null, {
- default: () => children
+ default: () => children,
})
expect(vnode.children).toBe(children)
expect(vnode.shapeFlag).toBe(
- ShapeFlags.ELEMENT | ShapeFlags.ARRAY_CHILDREN
+ ShapeFlags.ELEMENT | ShapeFlags.ARRAY_CHILDREN,
)
})
})
expect(createVNode('div').shapeFlag).toBe(ShapeFlags.ELEMENT)
expect(createVNode({}).shapeFlag).toBe(ShapeFlags.STATEFUL_COMPONENT)
expect(createVNode(() => {}).shapeFlag).toBe(
- ShapeFlags.FUNCTIONAL_COMPONENT
+ ShapeFlags.FUNCTIONAL_COMPONENT,
)
expect(createVNode(Text).shapeFlag).toBe(0)
})
expect(original.ref).toMatchObject({
i: mockInstance1,
r: 'foo',
- f: false
+ f: false,
})
// clone and preserve original ref
expect(original3.ref).toMatchObject({
i: mockInstance2,
r: 'foo',
- f: true
+ f: true,
})
const cloned7 = cloneVNode(original3, { ref: 'bar', ref_for: true })
expect(cloned7.ref).toMatchObject({ i: mockInstance2, r: 'bar', f: true })
expect(original4.ref).toMatchObject({
i: mockInstance2,
r,
- k: 'foo'
+ k: 'foo',
})
const cloned8 = cloneVNode(original4)
expect(cloned8.ref).toMatchObject({ i: mockInstance2, r, k: 'foo' })
const cloned1 = cloneVNode(original, { ref: 'bar' }, true)
expect(cloned1.ref).toMatchObject([
{ i: mockInstance1, r: 'foo', f: false },
- { i: mockInstance2, r: 'bar', f: false }
+ { i: mockInstance2, r: 'bar', f: false },
])
setCurrentRenderingInstance(null)
test('cloneVNode class normalization', () => {
const vnode = createVNode('div')
const expectedProps = {
- class: 'a b'
+ class: 'a b',
}
expect(cloneVNode(vnode, { class: 'a b' }).props).toMatchObject(
- expectedProps
+ expectedProps,
)
expect(cloneVNode(vnode, { class: ['a', 'b'] }).props).toMatchObject(
- expectedProps
+ expectedProps,
)
expect(
- cloneVNode(vnode, { class: { a: true, b: true } }).props
+ cloneVNode(vnode, { class: { a: true, b: true } }).props,
).toMatchObject(expectedProps)
expect(
- cloneVNode(vnode, { class: [{ a: true, b: true }] }).props
+ cloneVNode(vnode, { class: [{ a: true, b: true }] }).props,
).toMatchObject(expectedProps)
})
const expectedProps = {
style: {
color: 'blue',
- width: '300px'
- }
+ width: '300px',
+ },
}
expect(
- cloneVNode(vnode, { style: 'color: blue; width: 300px;' }).props
+ cloneVNode(vnode, { style: 'color: blue; width: 300px;' }).props,
).toMatchObject(expectedProps)
expect(
cloneVNode(vnode, {
style: {
color: 'blue',
- width: '300px'
- }
- }).props
+ width: '300px',
+ },
+ }).props,
).toMatchObject(expectedProps)
expect(
cloneVNode(vnode, {
style: [
{
color: 'blue',
- width: '300px'
- }
- ]
- }).props
+ width: '300px',
+ },
+ ],
+ }).props,
).toMatchObject(expectedProps)
})
let props3: Data = { class: [{ ccc: true }] }
let props4: Data = { class: { cccc: true } }
expect(mergeProps(props1, props2, props3, props4)).toMatchObject({
- class: 'c cc ccc cccc'
+ class: 'c cc ccc cccc',
})
})
style: [
{
color: 'red',
- fontSize: 10
- }
- ]
+ fontSize: 10,
+ },
+ ],
}
let props2: Data = {
style: [
{
color: 'blue',
- width: '200px'
+ width: '200px',
},
{
width: '300px',
height: '300px',
- fontSize: 30
- }
- ]
+ fontSize: 30,
+ },
+ ],
}
expect(mergeProps(props1, props2)).toMatchObject({
style: {
color: 'blue',
width: '300px',
height: '300px',
- fontSize: 30
- }
+ fontSize: 30,
+ },
})
})
test('style w/ strings', () => {
let props1: Data = {
- style: 'width:100px;right:10;top:10'
+ style: 'width:100px;right:10;top:10',
}
let props2: Data = {
style: [
{
color: 'blue',
- width: '200px'
+ width: '200px',
},
{
width: '300px',
height: '300px',
- fontSize: 30
- }
- ]
+ fontSize: 30,
+ },
+ ],
}
expect(mergeProps(props1, props2)).toMatchObject({
style: {
height: '300px',
fontSize: 30,
right: '10',
- top: '10'
- }
+ top: '10',
+ },
})
})
let props2: Data = { onClick: clickHandler2, onFocus: focusHandler2 }
expect(mergeProps(props1, props2)).toMatchObject({
onClick: [clickHandler1, clickHandler2],
- onFocus: focusHandler2
+ onFocus: focusHandler2,
})
let props3: Data = { onClick: undefined }
expect(mergeProps(props1, props3)).toMatchObject({
- onClick: clickHandler1
+ onClick: clickHandler1,
})
})
expect(mergeProps(props1, props2, props3)).toMatchObject({
foo: {},
bar: ['cc'],
- baz: { ccc: true }
+ baz: { ccc: true },
})
})
})
(openBlock(),
createBlock('div', null, [
hoist,
- (vnode1 = createVNode('div', null, 'text', PatchFlags.TEXT))
+ (vnode1 = createVNode('div', null, 'text', PatchFlags.TEXT)),
]))
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
})
(openBlock(),
createBlock('div', null, [
hoist,
- createVNode('div', null, 'text', PatchFlags.NEED_HYDRATION)
+ createVNode('div', null, 'text', PatchFlags.NEED_HYDRATION),
]))
expect(vnode.dynamicChildren).toStrictEqual([])
})
(openBlock(),
createBlock('div', null, [
hoist,
- (vnode3 = createVNode('div', null, 'text', PatchFlags.TEXT))
- ])))
+ (vnode3 = createVNode('div', null, 'text', PatchFlags.TEXT)),
+ ]))),
]))
expect(vnode.dynamicChildren).toStrictEqual([vnode1, vnode2])
expect(vnode2.dynamicChildren).toStrictEqual([vnode3])
(openBlock(),
createBlock('div', null, [
hoist,
- (vnode1 = createVNode({}, null, 'text'))
+ (vnode1 = createVNode({}, null, 'text')),
]))
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
})
(openBlock(),
createBlock('div', null, [
hoist,
- (vnode1 = createVNode(() => {}, null, 'text'))
+ (vnode1 = createVNode(() => {}, null, 'text')),
]))
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
})
(openBlock(),
createBlock('div', null, [
hoist,
- (vnode1 = createVNode(() => {}, null, 'text'))
+ (vnode1 = createVNode(() => {}, null, 'text')),
]))
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
})
default: () => {
return [
hoist,
- (vnode1 = createVNode('div', null, 'text', PatchFlags.TEXT))
+ (vnode1 = createVNode('div', null, 'text', PatchFlags.TEXT)),
]
- }
+ },
}))
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
})
(openBlock(true),
createBlock(Fragment, null, [
hoist,
- /*vnode2*/ createVNode(() => {}, null, 'text')
- ])))
+ /*vnode2*/ createVNode(() => {}, null, 'text'),
+ ]))),
]))
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
expect(vnode1.dynamicChildren).toStrictEqual([])
(openBlock(),
createBlock(Fragment, null, [
hoist,
- (vnode2 = createVNode(() => {}, null, 'text'))
- ])))
+ (vnode2 = createVNode(() => {}, null, 'text')),
+ ]))),
]))
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
expect(vnode1.dynamicChildren).toStrictEqual([vnode2])
setBlockTracking(-1),
(vnode1 = (openBlock(), createBlock('div'))),
setBlockTracking(1),
- vnode1
+ vnode1,
]))
expect(vnode.dynamicChildren).toStrictEqual([])
})
() => {
throw new Error('slot execution error')
},
- { type: {}, appContext: {} } as any
+ { type: {}, appContext: {} } as any,
)
const Parent = {
setup(_: any, { slots }: any) {
slots.default()
} catch (e) {}
}
- }
+ },
}
const vnode =
(openBlock(), createBlock(Parent, null, { default: slotFn }))
type: 'div',
props: { id: 'foo' },
children: 'hello',
- shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN
+ shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN,
})
})
type: 'div',
props: { id: 'foo' },
children: 'hello',
- shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN
+ shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN,
})
})
name: 'Root Component',
render() {
return h('p') // this will be overwritten by the transform
- }
+ },
}
const root = nodeOps.createElement('div')
createApp(App).mount(root)
import {
+ type TestElement,
+ TestNodeTypes,
+ type VNode,
+ type VNodeProps,
h,
- render,
nodeOps,
- VNodeProps,
- TestElement,
- TestNodeTypes,
- VNode
+ render,
} from '@vue/runtime-test'
describe('renderer: vnode hooks', () => {
onVnodeBeforeUpdate: vi.fn(vnode => {
expect((vnode.el as TestElement).children[0]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'foo'
+ text: 'foo',
})
}),
onVnodeUpdated: vi.fn(vnode => {
expect((vnode.el as TestElement).children[0]).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'bar'
+ text: 'bar',
})
}),
onVnodeBeforeUnmount: vi.fn(),
- onVnodeUnmounted: vi.fn()
+ onVnodeUnmounted: vi.fn(),
}
assertHooks(hooks, h('div', hooks, 'foo'), h('div', hooks, 'bar'))
onVnodeBeforeUpdate: vi.fn(vnode => {
expect(vnode.el as TestElement).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'foo'
+ text: 'foo',
})
}),
onVnodeUpdated: vi.fn(vnode => {
expect(vnode.el as TestElement).toMatchObject({
type: TestNodeTypes.TEXT,
- text: 'bar'
+ text: 'bar',
})
}),
onVnodeBeforeUnmount: vi.fn(),
- onVnodeUnmounted: vi.fn()
+ onVnodeUnmounted: vi.fn(),
}
assertHooks(
hooks,
h(Comp, {
...hooks,
- msg: 'foo'
+ msg: 'foo',
}),
h(Comp, {
...hooks,
- msg: 'bar'
- })
+ msg: 'bar',
+ }),
)
})
})
import {
- Component,
- ConcreteComponent,
+ type Component,
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ type ConcreteComponent,
currentInstance,
- ComponentInternalInstance,
isInSSRComponentSetup,
- ComponentOptions
} from './component'
import { isFunction, isObject } from '@vue/shared'
-import { ComponentPublicInstance } from './componentPublicInstance'
-import { createVNode, VNode } from './vnode'
+import type { ComponentPublicInstance } from './componentPublicInstance'
+import { type VNode, createVNode } from './vnode'
import { defineComponent } from './apiDefineComponent'
import { warn } from './warning'
import { ref } from '@vue/reactivity'
-import { handleError, ErrorCodes } from './errorHandling'
+import { ErrorCodes, handleError } from './errorHandling'
import { isKeepAlive } from './components/KeepAlive'
import { queueJob } from './scheduler'
error: Error,
retry: () => void,
fail: () => void,
- attempts: number
+ attempts: number,
) => any
}
/*! #__NO_SIDE_EFFECTS__ */
export function defineAsyncComponent<
- T extends Component = { new (): ComponentPublicInstance }
+ T extends Component = { new (): ComponentPublicInstance },
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
if (isFunction(source)) {
source = { loader: source }
delay = 200,
timeout, // undefined = never times out
suspensible = true,
- onError: userOnError
+ onError: userOnError,
} = source
let pendingRequest: Promise<ConcreteComponent> | null = null
if (__DEV__ && !comp) {
warn(
`Async component loader resolved to undefined. ` +
- `If you are using retry(), make sure to return its return value.`
+ `If you are using retry(), make sure to return its return value.`,
)
}
// interop module default
err,
instance,
ErrorCodes.ASYNC_COMPONENT_LOADER,
- !errorComponent /* do not throw in dev if user provided error component */
+ !errorComponent /* do not throw in dev if user provided error component */,
)
}
return () =>
errorComponent
? createVNode(errorComponent as ConcreteComponent, {
- error: err
+ error: err,
})
: null
})
setTimeout(() => {
if (!loaded.value && !error.value) {
const err = new Error(
- `Async component timed out after ${timeout}ms.`
+ `Async component timed out after ${timeout}ms.`,
)
onError(err)
error.value = err
return createInnerComp(resolvedComp, instance)
} else if (error.value && errorComponent) {
return createVNode(errorComponent, {
- error: error.value
+ error: error.value,
})
} else if (loadingComponent && !delayed.value) {
return createVNode(loadingComponent)
}
}
- }
+ },
}) as T
}
function createInnerComp(
comp: ConcreteComponent,
- parent: ComponentInternalInstance
+ parent: ComponentInternalInstance,
) {
const { ref, props, children, ce } = parent.vnode
const vnode = createVNode(comp, props, children)
export const computed: typeof _computed = (
getterOrOptions: any,
- debugOptions?: any
+ debugOptions?: any,
) => {
- // @ts-ignore
+ // @ts-expect-error
return _computed(getterOrOptions, debugOptions, isInSSRComponentSetup)
}
import {
- ConcreteComponent,
- Data,
+ type Component,
+ type ComponentInternalInstance,
+ type ConcreteComponent,
+ type Data,
+ getExposeProxy,
validateComponentName,
- Component,
- ComponentInternalInstance,
- getExposeProxy
} from './component'
-import {
+import type {
ComponentOptions,
MergedComponentOptions,
- RuntimeCompilerOptions
+ RuntimeCompilerOptions,
} from './componentOptions'
-import {
+import type {
ComponentCustomProperties,
- ComponentPublicInstance
+ ComponentPublicInstance,
} from './componentPublicInstance'
-import { Directive, validateDirectiveName } from './directives'
-import { ElementNamespace, RootRenderFunction } from './renderer'
-import { InjectionKey } from './apiInject'
+import { type Directive, validateDirectiveName } from './directives'
+import type { ElementNamespace, RootRenderFunction } from './renderer'
+import type { InjectionKey } from './apiInject'
import { warn } from './warning'
-import { createVNode, cloneVNode, VNode } from './vnode'
-import { RootHydrateFunction } from './hydration'
+import { type VNode, cloneVNode, createVNode } from './vnode'
+import type { RootHydrateFunction } from './hydration'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
-import { isFunction, NO, isObject, extend } from '@vue/shared'
+import { NO, extend, isFunction, isObject } from '@vue/shared'
import { version } from '.'
import { installAppCompatProperties } from './compat/global'
-import { NormalizedPropsOptions } from './componentProps'
-import { ObjectEmitsOptions } from './componentEmits'
-import { DefineComponent } from './apiDefineComponent'
+import type { NormalizedPropsOptions } from './componentProps'
+import type { ObjectEmitsOptions } from './componentEmits'
+import type { DefineComponent } from './apiDefineComponent'
export interface App<HostElement = any> {
version: string
mount(
rootContainer: HostElement | string,
isHydrate?: boolean,
- namespace?: boolean | ElementNamespace
+ namespace?: boolean | ElementNamespace,
): ComponentPublicInstance
unmount(): void
provide<T>(key: InjectionKey<T> | string, value: T): this
errorHandler?: (
err: unknown,
instance: ComponentPublicInstance | null,
- info: string
+ info: string,
) => void
warnHandler?: (
msg: string,
instance: ComponentPublicInstance | null,
- trace: string
+ trace: string,
) => void
/**
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
- compilerOptions: {}
+ compilerOptions: {},
},
mixins: [],
components: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
- emitsCache: new WeakMap()
+ emitsCache: new WeakMap(),
}
}
export type CreateAppFunction<HostElement> = (
rootComponent: Component,
- rootProps?: Data | null
+ rootProps?: Data | null,
) => App<HostElement>
let uid = 0
export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
- hydrate?: RootHydrateFunction
+ hydrate?: RootHydrateFunction,
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
set config(v) {
if (__DEV__) {
warn(
- `app.config cannot be replaced. Modify individual options instead.`
+ `app.config cannot be replaced. Modify individual options instead.`,
)
}
},
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
- `function.`
+ `function.`,
)
}
return app
} else if (__DEV__) {
warn(
'Mixin has already been applied to target app' +
- (mixin.name ? `: ${mixin.name}` : '')
+ (mixin.name ? `: ${mixin.name}` : ''),
)
}
} else if (__DEV__) {
mount(
rootContainer: HostElement,
isHydrate?: boolean,
- namespace?: boolean | ElementNamespace
+ namespace?: boolean | ElementNamespace,
): any {
if (!isMounted) {
// #5571
warn(
`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
- ` you need to unmount the previous app by calling \`app.unmount()\` first.`
+ ` you need to unmount the previous app by calling \`app.unmount()\` first.`,
)
}
const vnode = createVNode(rootComponent, rootProps)
render(
cloneVNode(vnode),
rootContainer,
- namespace as ElementNamespace
+ namespace as ElementNamespace,
)
}
}
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
- `mount - e.g. \`const createMyApp = () => createApp(App)\``
+ `mount - e.g. \`const createMyApp = () => createApp(App)\``,
)
}
},
if (__DEV__ && (key as string | symbol) in context.provides) {
warn(
`App already provides property with key "${String(key)}". ` +
- `It will be overwritten with the new value.`
+ `It will be overwritten with the new value.`,
)
}
} finally {
currentApp = null
}
- }
+ },
})
if (__COMPAT__) {
-import {
- ComputedOptions,
- MethodOptions,
- ComponentOptionsWithoutProps,
+import type {
+ ComponentInjectOptions,
+ ComponentOptions,
+ ComponentOptionsBase,
+ ComponentOptionsMixin,
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps,
- ComponentOptionsMixin,
+ ComponentOptionsWithoutProps,
+ ComputedOptions,
+ MethodOptions,
RenderFunction,
- ComponentOptionsBase,
- ComponentInjectOptions,
- ComponentOptions
} from './componentOptions'
-import {
- SetupContext,
+import type {
AllowedComponentProps,
- ComponentCustomProps
+ ComponentCustomProps,
+ SetupContext,
} from './component'
-import {
- ExtractPropTypes,
+import type {
+ ComponentObjectPropsOptions,
ComponentPropsOptions,
ExtractDefaultPropTypes,
- ComponentObjectPropsOptions
+ ExtractPropTypes,
} from './componentProps'
-import { EmitsOptions, EmitsToProps } from './componentEmits'
+import type { EmitsOptions, EmitsToProps } from './componentEmits'
import { extend, isFunction } from '@vue/shared'
-import { VNodeProps } from './vnode'
-import {
+import type { VNodeProps } from './vnode'
+import type {
+ ComponentPublicInstanceConstructor,
CreateComponentPublicInstance,
- ComponentPublicInstanceConstructor
} from './componentPublicInstance'
-import { SlotsType } from './componentSlots'
+import type { SlotsType } from './componentSlots'
export type PublicProps = VNodeProps &
AllowedComponentProps &
PP = PublicProps,
Props = ResolveProps<PropsOrPropOptions, E>,
Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>,
- S extends SlotsType = {}
+ S extends SlotsType = {},
> = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
Props extends Record<string, any>,
E extends EmitsOptions = {},
EE extends string = string,
- S extends SlotsType = {}
+ S extends SlotsType = {},
>(
setup: (
props: Props,
- ctx: SetupContext<E, S>
+ ctx: SetupContext<E, S>,
) => RenderFunction | Promise<RenderFunction>,
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
props?: (keyof Props)[]
emits?: E | EE[]
slots?: S
- }
+ },
): (props: Props & EmitsToProps<E>) => any
export function defineComponent<
Props extends Record<string, any>,
E extends EmitsOptions = {},
EE extends string = string,
- S extends SlotsType = {}
+ S extends SlotsType = {},
>(
setup: (
props: Props,
- ctx: SetupContext<E, S>
+ ctx: SetupContext<E, S>,
) => RenderFunction | Promise<RenderFunction>,
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
props?: ComponentObjectPropsOptions<Props>
emits?: E | EE[]
slots?: S
- }
+ },
): (props: Props & EmitsToProps<E>) => any
// overload 2: object format with no props
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
- II extends string = string
+ II extends string = string,
>(
options: ComponentOptionsWithoutProps<
Props,
I,
II,
S
- >
+ >,
): DefineComponent<
Props,
RawBindings,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
- Props = Readonly<{ [key in PropNames]?: any }>
+ Props = Readonly<{ [key in PropNames]?: any }>,
>(
options: ComponentOptionsWithArrayProps<
PropNames,
I,
II,
S
- >
+ >,
): DefineComponent<
Props,
RawBindings,
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
- II extends string = string
+ II extends string = string,
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
I,
II,
S
- >
+ >,
): DefineComponent<
PropsOptions,
RawBindings,
/*! #__NO_SIDE_EFFECTS__ */
export function defineComponent(
options: unknown,
- extraOptions?: ComponentOptions
+ extraOptions?: ComponentOptions,
) {
return isFunction(options)
? // #8326: extend call and options.name access are considered side-effects
export function provide<T, K = InjectionKey<T> | string | number>(
key: K,
- value: K extends InjectionKey<infer V> ? V : T
+ value: K extends InjectionKey<infer V> ? V : T,
) {
if (!currentInstance) {
if (__DEV__) {
export function inject<T>(
key: InjectionKey<T> | string,
defaultValue: T,
- treatDefaultAsFactory?: false
+ treatDefaultAsFactory?: false,
): T
export function inject<T>(
key: InjectionKey<T> | string,
defaultValue: T | (() => T),
- treatDefaultAsFactory: true
+ treatDefaultAsFactory: true,
): T
export function inject(
key: InjectionKey<any> | string,
defaultValue?: unknown,
- treatDefaultAsFactory = false
+ treatDefaultAsFactory = false,
) {
// fallback to `currentRenderingInstance` so that this can be called in
// a functional component
import {
- ComponentInternalInstance,
+ type ComponentInternalInstance,
currentInstance,
isInSSRComponentSetup,
setCurrentInstance,
- unsetCurrentInstance
+ unsetCurrentInstance,
} from './component'
-import { ComponentPublicInstance } from './componentPublicInstance'
-import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling'
+import type { ComponentPublicInstance } from './componentPublicInstance'
+import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling'
import { warn } from './warning'
import { toHandlerKey } from '@vue/shared'
-import { DebuggerEvent, pauseTracking, resetTracking } from '@vue/reactivity'
+import {
+ type DebuggerEvent,
+ pauseTracking,
+ resetTracking,
+} from '@vue/reactivity'
import { LifecycleHooks } from './enums'
export { onActivated, onDeactivated } from './components/KeepAlive'
type: LifecycleHooks,
hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = currentInstance,
- prepend: boolean = false
+ prepend: boolean = false,
): Function | undefined {
if (target) {
const hooks = target[type] || (target[type] = [])
(__FEATURE_SUSPENSE__
? ` If you are using async setup(), make sure to register lifecycle ` +
`hooks before the first await statement.`
- : ``)
+ : ``),
)
}
}
export type DebuggerHook = (e: DebuggerEvent) => void
export const onRenderTriggered = createHook<DebuggerHook>(
- LifecycleHooks.RENDER_TRIGGERED
+ LifecycleHooks.RENDER_TRIGGERED,
)
export const onRenderTracked = createHook<DebuggerHook>(
- LifecycleHooks.RENDER_TRACKED
+ LifecycleHooks.RENDER_TRACKED,
)
export type ErrorCapturedHook<TError = unknown> = (
err: TError,
instance: ComponentPublicInstance | null,
- info: string
+ info: string,
) => boolean | void
export function onErrorCaptured<TError = Error>(
hook: ErrorCapturedHook<TError>,
- target: ComponentInternalInstance | null = currentInstance
+ target: ComponentInternalInstance | null = currentInstance,
) {
injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target)
}
import {
+ type LooseRequired,
+ type Prettify,
+ type UnionToIntersection,
+ extend,
+ hasChanged,
isArray,
- isPromise,
isFunction,
- Prettify,
- UnionToIntersection,
- extend,
- LooseRequired,
- hasChanged
+ isPromise,
} from '@vue/shared'
import {
+ type SetupContext,
+ createSetupContext,
getCurrentInstance,
setCurrentInstance,
- SetupContext,
- createSetupContext,
- unsetCurrentInstance
+ unsetCurrentInstance,
} from './component'
-import { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
-import {
+import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
+import type {
ComponentOptionsMixin,
ComponentOptionsWithoutProps,
ComputedOptions,
- MethodOptions
+ MethodOptions,
} from './componentOptions'
-import {
- ComponentPropsOptions,
+import type {
ComponentObjectPropsOptions,
+ ComponentPropsOptions,
ExtractPropTypes,
NormalizedProps,
- PropOptions
+ PropOptions,
} from './componentProps'
import { warn } from './warning'
-import { SlotsType, StrictUnwrapSlotsType } from './componentSlots'
-import { Ref, customRef, ref } from '@vue/reactivity'
+import type { SlotsType, StrictUnwrapSlotsType } from './componentSlots'
+import { type Ref, customRef, ref } from '@vue/reactivity'
import { watchSyncEffect } from '.'
// dev only
warn(
`${method}() is a compiler-hint helper that is only usable inside ` +
`<script setup> of a single file component. Its arguments should be ` +
- `compiled away and passing it at runtime has no effect.`
+ `compiled away and passing it at runtime has no effect.`,
)
/**
*/
// overload 1: runtime props w/ array
export function defineProps<PropNames extends string = string>(
- props: PropNames[]
+ props: PropNames[],
): Prettify<Readonly<{ [key in PropNames]?: any }>>
// overload 2: runtime props w/ object
export function defineProps<
- PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions
+ PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions,
>(props: PP): Prettify<Readonly<ExtractPropTypes<PP>>>
// overload 3: typed-based declaration
export function defineProps<TypeProps>(): DefineProps<
*/
// overload 1: runtime emits w/ array
export function defineEmits<EE extends string = string>(
- emitOptions: EE[]
+ emitOptions: EE[],
): EmitFn<EE[]>
export function defineEmits<E extends EmitsOptions = EmitsOptions>(
- emitOptions: E
+ emitOptions: E,
): EmitFn<E>
export function defineEmits<
- T extends ((...args: any[]) => any) | Record<string, any[]>
+ T extends ((...args: any[]) => any) | Record<string, any[]>,
>(): T extends (...args: any[]) => any ? T : ShortEmits<T>
// implementation
export function defineEmits() {
* @see {@link https://vuejs.org/api/sfc-script-setup.html#defineexpose}
*/
export function defineExpose<
- Exposed extends Record<string, any> = Record<string, any>
+ Exposed extends Record<string, any> = Record<string, any>,
>(exposed?: Exposed) {
if (__DEV__) {
warnRuntimeUsage(`defineExpose`)
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin
+ Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
>(
options?: ComponentOptionsWithoutProps<
{},
M,
Mixin,
Extends
- > & { emits?: undefined; expose?: undefined; slots?: undefined }
+ > & { emits?: undefined; expose?: undefined; slots?: undefined },
): void {
if (__DEV__) {
warnRuntimeUsage(`defineOptions`)
}
export function defineSlots<
- S extends Record<string, any> = Record<string, any>
+ S extends Record<string, any> = Record<string, any>,
>(): StrictUnwrapSlotsType<SlotsType<S>> {
if (__DEV__) {
warnRuntimeUsage(`defineSlots`)
* ```
*/
export function defineModel<T>(
- options: { required: true } & PropOptions<T>
+ options: { required: true } & PropOptions<T>,
): Ref<T>
export function defineModel<T>(
- options: { default: any } & PropOptions<T>
+ options: { default: any } & PropOptions<T>,
): Ref<T>
export function defineModel<T>(options?: PropOptions<T>): Ref<T | undefined>
export function defineModel<T>(
name: string,
- options: { required: true } & PropOptions<T>
+ options: { required: true } & PropOptions<T>,
): Ref<T>
export function defineModel<T>(
name: string,
- options: { default: any } & PropOptions<T>
+ options: { default: any } & PropOptions<T>,
): Ref<T>
export function defineModel<T>(
name: string,
- options?: PropOptions<T>
+ options?: PropOptions<T>,
): Ref<T | undefined>
export function defineModel(): any {
if (__DEV__) {
type PropsWithDefaults<
T,
Defaults extends InferDefaults<T>,
- BKeys extends keyof T
+ BKeys extends keyof T,
> = Readonly<Omit<T, keyof Defaults>> & {
readonly [K in keyof Defaults]-?: K extends keyof T
? Defaults[K] extends undefined
export function withDefaults<
T,
BKeys extends keyof T,
- Defaults extends InferDefaults<T>
+ Defaults extends InferDefaults<T>,
>(
props: DefineProps<T, BKeys>,
- defaults: Defaults
+ defaults: Defaults,
): PropsWithDefaults<T, Defaults, BKeys> {
if (__DEV__) {
warnRuntimeUsage(`withDefaults`)
export function useModel<T extends Record<string, any>, K extends keyof T>(
props: T,
- name: K
+ name: K,
): Ref<T[K]>
export function useModel(props: Record<string, any>, name: string): Ref {
const i = getCurrentInstance()!
trigger()
}
i.emit(`update:${name}`, value)
- }
+ },
}
})
}
* @internal
*/
export function normalizePropsOrEmits(
- props: ComponentPropsOptions | EmitsOptions
+ props: ComponentPropsOptions | EmitsOptions,
) {
return isArray(props)
? props.reduce(
(normalized, p) => ((normalized[p] = null), normalized),
- {} as ComponentObjectPropsOptions | ObjectEmitsOptions
+ {} as ComponentObjectPropsOptions | ObjectEmitsOptions,
)
: props
}
*/
export function mergeDefaults(
raw: ComponentPropsOptions,
- defaults: Record<string, any>
+ defaults: Record<string, any>,
): ComponentObjectPropsOptions {
const props = normalizePropsOrEmits(raw)
for (const key in defaults) {
*/
export function mergeModels(
a: ComponentPropsOptions | EmitsOptions,
- b: ComponentPropsOptions | EmitsOptions
+ b: ComponentPropsOptions | EmitsOptions,
) {
if (!a || !b) return a || b
if (isArray(a) && isArray(b)) return a.concat(b)
*/
export function createPropsRestProxy(
props: any,
- excludedKeys: string[]
+ excludedKeys: string[],
): Record<string, any> {
const ret: Record<string, any> = {}
for (const key in props) {
if (!excludedKeys.includes(key)) {
Object.defineProperty(ret, key, {
enumerable: true,
- get: () => props[key]
+ get: () => props[key],
})
}
}
if (__DEV__ && !ctx) {
warn(
`withAsyncContext called without active current instance. ` +
- `This is likely a bug.`
+ `This is likely a bug.`,
)
}
let awaitable = getAwaitable()
import {
- isRef,
- isShallow,
- Ref,
- ComputedRef,
+ type ComputedRef,
+ type DebuggerOptions,
+ type EffectScheduler,
ReactiveEffect,
- isReactive,
ReactiveFlags,
- EffectScheduler,
- DebuggerOptions,
- getCurrentScope
+ type Ref,
+ getCurrentScope,
+ isReactive,
+ isRef,
+ isShallow,
} from '@vue/reactivity'
-import { SchedulerJob, queueJob } from './scheduler'
+import { type SchedulerJob, queueJob } from './scheduler'
import {
EMPTY_OBJ,
- isObject,
+ NOOP,
+ extend,
+ hasChanged,
isArray,
isFunction,
- isString,
- hasChanged,
- NOOP,
- remove,
isMap,
- isSet,
+ isObject,
isPlainObject,
- extend
+ isSet,
+ isString,
+ remove,
} from '@vue/shared'
import {
+ type ComponentInternalInstance,
currentInstance,
- ComponentInternalInstance,
isInSSRComponentSetup,
setCurrentInstance,
- unsetCurrentInstance
+ unsetCurrentInstance,
} from './component'
import {
ErrorCodes,
+ callWithAsyncErrorHandling,
callWithErrorHandling,
- callWithAsyncErrorHandling
} from './errorHandling'
import { queuePostRenderEffect } from './renderer'
import { warn } from './warning'
import { DeprecationTypes } from './compat/compatConfig'
import { checkCompatEnabled, isCompatEnabled } from './compat/compatConfig'
-import { ObjectWatchOptionItem } from './componentOptions'
+import type { ObjectWatchOptionItem } from './componentOptions'
import { useSSRContext } from '@vue/runtime-core'
export type WatchEffect = (onCleanup: OnCleanup) => void
export type WatchCallback<V = any, OV = any> = (
value: V,
oldValue: OV,
- onCleanup: OnCleanup
+ onCleanup: OnCleanup,
) => any
type MapSources<T, Immediate> = {
// Simple effect.
export function watchEffect(
effect: WatchEffect,
- options?: WatchOptionsBase
+ options?: WatchOptionsBase,
): WatchStopHandle {
return doWatch(effect, null, options)
}
export function watchPostEffect(
effect: WatchEffect,
- options?: DebuggerOptions
+ options?: DebuggerOptions,
) {
return doWatch(
effect,
null,
- __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' }
+ __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
)
}
export function watchSyncEffect(
effect: WatchEffect,
- options?: DebuggerOptions
+ options?: DebuggerOptions,
) {
return doWatch(
effect,
null,
- __DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' }
+ __DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
)
}
// overload: array of multiple sources + cb
export function watch<
T extends MultiWatchSources,
- Immediate extends Readonly<boolean> = false
+ Immediate extends Readonly<boolean> = false,
>(
sources: [...T],
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
- options?: WatchOptions<Immediate>
+ options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: multiple sources w/ `as const`
// somehow [...T] breaks when the type is readonly
export function watch<
T extends Readonly<MultiWatchSources>,
- Immediate extends Readonly<boolean> = false
+ Immediate extends Readonly<boolean> = false,
>(
source: T,
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
- options?: WatchOptions<Immediate>
+ options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
- options?: WatchOptions<Immediate>
+ options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: watching reactive object w/ cb
export function watch<
T extends object,
- Immediate extends Readonly<boolean> = false
+ Immediate extends Readonly<boolean> = false,
>(
source: T,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
- options?: WatchOptions<Immediate>
+ options?: WatchOptions<Immediate>,
): WatchStopHandle
// implementation
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>,
cb: any,
- options?: WatchOptions<Immediate>
+ options?: WatchOptions<Immediate>,
): WatchStopHandle {
if (__DEV__ && !isFunction(cb)) {
warn(
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
- `supports \`watch(source, cb, options?) signature.`
+ `supports \`watch(source, cb, options?) signature.`,
)
}
return doWatch(source as any, cb, options)
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
- { immediate, deep, flush, once, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
+ {
+ immediate,
+ deep,
+ flush,
+ once,
+ onTrack,
+ onTrigger,
+ }: WatchOptions = EMPTY_OBJ,
): WatchStopHandle {
if (cb && once) {
const _cb = cb
if (immediate !== undefined) {
warn(
`watch() "immediate" option is only respected when using the ` +
- `watch(source, callback, options?) signature.`
+ `watch(source, callback, options?) signature.`,
)
}
if (deep !== undefined) {
warn(
`watch() "deep" option is only respected when using the ` +
- `watch(source, callback, options?) signature.`
+ `watch(source, callback, options?) signature.`,
)
}
if (once !== undefined) {
warn(
`watch() "once" option is only respected when using the ` +
- `watch(source, callback, options?) signature.`
+ `watch(source, callback, options?) signature.`,
)
}
}
`Invalid watch source: `,
s,
`A watch source can only be a getter/effect function, a ref, ` +
- `a reactive object, or an array of these types.`
+ `a reactive object, or an array of these types.`,
)
}
source,
instance,
ErrorCodes.WATCH_CALLBACK,
- [onCleanup]
+ [onCleanup],
)
}
}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
getter(),
isMultiSource ? [] : undefined,
- onCleanup
+ onCleanup,
])
}
if (flush === 'sync') {
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
- onCleanup
+ onCleanup,
])
oldValue = newValue
}
} else if (flush === 'post') {
queuePostRenderEffect(
effect.run.bind(effect),
- instance && instance.suspense
+ instance && instance.suspense,
)
} else {
effect.run()
this: ComponentInternalInstance,
source: string | Function,
value: WatchCallback | ObjectWatchOptionItem,
- options?: WatchOptions
+ options?: WatchOptions,
): WatchStopHandle {
const publicThis = this.proxy as any
const getter = isString(source)
import { isOn } from '@vue/shared'
-import { ComponentInternalInstance } from '../component'
+import type { ComponentInternalInstance } from '../component'
import { DeprecationTypes, isCompatEnabled } from './compatConfig'
export function shouldSkipAttr(
key: string,
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): boolean {
if (key === 'is') {
return true
import { extend, hasOwn, isArray, isFunction } from '@vue/shared'
import {
- Component,
- ComponentInternalInstance,
- ComponentOptions,
+ type Component,
+ type ComponentInternalInstance,
+ type ComponentOptions,
formatComponentName,
getComponentName,
getCurrentInstance,
- isRuntimeOnly
+ isRuntimeOnly,
} from '../component'
import { warn } from '../warning'
FILTERS = 'FILTERS',
- PRIVATE_APIS = 'PRIVATE_APIS'
+ PRIVATE_APIS = 'PRIVATE_APIS',
}
type DeprecationData = {
message:
`The global app bootstrapping API has changed: vm.$mount() and the "el" ` +
`option have been removed. Use createApp(RootComponent).mount() instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#mounting-app-instance`
+ link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#mounting-app-instance`,
},
[DeprecationTypes.GLOBAL_MOUNT_CONTAINER]: {
`Vue detected directives on the mount container. ` +
`In Vue 3, the container is no longer considered part of the template ` +
`and will not be processed/replaced.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/mount-changes.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/mount-changes.html`,
},
[DeprecationTypes.GLOBAL_EXTEND]: {
message:
`Vue.extend() has been removed in Vue 3. ` +
`Use defineComponent() instead.`,
- link: `https://vuejs.org/api/general.html#definecomponent`
+ link: `https://vuejs.org/api/general.html#definecomponent`,
},
[DeprecationTypes.GLOBAL_PROTOTYPE]: {
message:
`Vue.prototype is no longer available in Vue 3. ` +
`Use app.config.globalProperties instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#vue-prototype-replaced-by-config-globalproperties`
+ link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#vue-prototype-replaced-by-config-globalproperties`,
},
[DeprecationTypes.GLOBAL_SET]: {
message:
`Vue.set() has been removed as it is no longer needed in Vue 3. ` +
- `Simply use native JavaScript mutations.`
+ `Simply use native JavaScript mutations.`,
},
[DeprecationTypes.GLOBAL_DELETE]: {
message:
`Vue.delete() has been removed as it is no longer needed in Vue 3. ` +
- `Simply use native JavaScript mutations.`
+ `Simply use native JavaScript mutations.`,
},
[DeprecationTypes.GLOBAL_OBSERVABLE]: {
message:
`Vue.observable() has been removed. ` +
`Use \`import { reactive } from "vue"\` from Composition API instead.`,
- link: `https://vuejs.org/api/reactivity-core.html#reactive`
+ link: `https://vuejs.org/api/reactivity-core.html#reactive`,
},
[DeprecationTypes.GLOBAL_PRIVATE_UTIL]: {
message:
`Vue.util has been removed. Please refactor to avoid its usage ` +
- `since it was an internal API even in Vue 2.`
+ `since it was an internal API even in Vue 2.`,
},
[DeprecationTypes.CONFIG_SILENT]: {
message:
`config.silent has been removed because it is not good practice to ` +
`intentionally suppress warnings. You can use your browser console's ` +
- `filter features to focus on relevant messages.`
+ `filter features to focus on relevant messages.`,
},
[DeprecationTypes.CONFIG_DEVTOOLS]: {
message:
`config.devtools has been removed. To enable devtools for ` +
`production, configure the __VUE_PROD_DEVTOOLS__ compile-time flag.`,
- link: `https://github.com/vuejs/core/tree/main/packages/vue#bundler-build-feature-flags`
+ link: `https://github.com/vuejs/core/tree/main/packages/vue#bundler-build-feature-flags`,
},
[DeprecationTypes.CONFIG_KEY_CODES]: {
message:
`config.keyCodes has been removed. ` +
`In Vue 3, you can directly use the kebab-case key names as v-on modifiers.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html`,
},
[DeprecationTypes.CONFIG_PRODUCTION_TIP]: {
message: `config.productionTip has been removed.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#config-productiontip-removed`
+ link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#config-productiontip-removed`,
},
[DeprecationTypes.CONFIG_IGNORED_ELEMENTS]: {
}
return msg
},
- link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#config-ignoredelements-is-now-config-iscustomelement`
+ link: `https://v3-migration.vuejs.org/breaking-changes/global-api.html#config-ignoredelements-is-now-config-iscustomelement`,
},
[DeprecationTypes.CONFIG_WHITESPACE]: {
message:
`Vue 3 compiler's whitespace option will default to "condense" instead of ` +
`"preserve". To suppress this warning, provide an explicit value for ` +
- `\`config.compilerOptions.whitespace\`.`
+ `\`config.compilerOptions.whitespace\`.`,
},
[DeprecationTypes.CONFIG_OPTION_MERGE_STRATS]: {
message:
`config.optionMergeStrategies no longer exposes internal strategies. ` +
- `Use custom merge functions instead.`
+ `Use custom merge functions instead.`,
},
[DeprecationTypes.INSTANCE_SET]: {
message:
`vm.$set() has been removed as it is no longer needed in Vue 3. ` +
- `Simply use native JavaScript mutations.`
+ `Simply use native JavaScript mutations.`,
},
[DeprecationTypes.INSTANCE_DELETE]: {
message:
`vm.$delete() has been removed as it is no longer needed in Vue 3. ` +
- `Simply use native JavaScript mutations.`
+ `Simply use native JavaScript mutations.`,
},
[DeprecationTypes.INSTANCE_DESTROY]: {
message: `vm.$destroy() has been removed. Use app.unmount() instead.`,
- link: `https://vuejs.org/api/application.html#app-unmount`
+ link: `https://vuejs.org/api/application.html#app-unmount`,
},
[DeprecationTypes.INSTANCE_EVENT_EMITTER]: {
message:
`vm.$on/$once/$off() have been removed. ` +
`Use an external event emitter library instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/events-api.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/events-api.html`,
},
[DeprecationTypes.INSTANCE_EVENT_HOOKS]: {
`should be changed to @vue:${event.slice(5)}. ` +
`From JavaScript, use Composition API to dynamically register lifecycle ` +
`hooks.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/vnode-lifecycle-events.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/vnode-lifecycle-events.html`,
},
[DeprecationTypes.INSTANCE_CHILDREN]: {
message:
`vm.$children has been removed. Consider refactoring your logic ` +
`to avoid relying on direct access to child components.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/children.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/children.html`,
},
[DeprecationTypes.INSTANCE_LISTENERS]: {
`included in vm.$attrs and it is no longer necessary to separately use ` +
`v-on="$listeners" if you are already using v-bind="$attrs". ` +
`(Note: the Vue 3 behavior only applies if this compat config is disabled)`,
- link: `https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html`,
},
[DeprecationTypes.INSTANCE_SCOPED_SLOTS]: {
message: `vm.$scopedSlots has been removed. Use vm.$slots instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/slots-unification.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/slots-unification.html`,
},
[DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE]: {
`If you are binding $attrs to a non-root element and expecting ` +
`class/style to fallthrough on root, you will need to now manually bind ` +
`them on root via :class="$attrs.class".`,
- link: `https://v3-migration.vuejs.org/breaking-changes/attrs-includes-class-style.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/attrs-includes-class-style.html`,
},
[DeprecationTypes.OPTIONS_DATA_FN]: {
message:
`The "data" option can no longer be a plain object. ` +
`Always use a function.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/data-option.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/data-option.html`,
},
[DeprecationTypes.OPTIONS_DATA_MERGE]: {
message: (key: string) =>
`Detected conflicting key "${key}" when merging data option values. ` +
`In Vue 3, data keys are merged shallowly and will override one another.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/data-option.html#mixin-merge-behavior-change`
+ link: `https://v3-migration.vuejs.org/breaking-changes/data-option.html#mixin-merge-behavior-change`,
},
[DeprecationTypes.OPTIONS_BEFORE_DESTROY]: {
- message: `\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`
+ message: `\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`,
},
[DeprecationTypes.OPTIONS_DESTROYED]: {
- message: `\`destroyed\` has been renamed to \`unmounted\`.`
+ message: `\`destroyed\` has been renamed to \`unmounted\`.`,
},
[DeprecationTypes.WATCH_ARRAY]: {
`If current usage is intended, you can disable the compat behavior and ` +
`suppress this warning with:` +
`\n\n configureCompat({ ${DeprecationTypes.WATCH_ARRAY}: false })\n`,
- link: `https://v3-migration.vuejs.org/breaking-changes/watch.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/watch.html`,
},
[DeprecationTypes.PROPS_DEFAULT_THIS]: {
`props default value function no longer has access to "this". The compat ` +
`build only offers access to this.$options.` +
`(found in prop "${key}")`,
- link: `https://v3-migration.vuejs.org/breaking-changes/props-default-this.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/props-default-this.html`,
},
[DeprecationTypes.CUSTOM_DIR]: {
message: (legacyHook: string, newHook: string) =>
`Custom directive hook "${legacyHook}" has been removed. ` +
`Use "${newHook}" instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/custom-directives.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/custom-directives.html`,
},
[DeprecationTypes.V_ON_KEYCODE_MODIFIER]: {
message:
`Using keyCode as v-on modifier is no longer supported. ` +
`Use kebab-case key name modifiers instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html`,
},
[DeprecationTypes.ATTR_FALSE_VALUE]: {
`use \`null\` or \`undefined\` instead. If the usage is intended, ` +
`you can disable the compat behavior and suppress this warning with:` +
`\n\n configureCompat({ ${DeprecationTypes.ATTR_FALSE_VALUE}: false })\n`,
- link: `https://v3-migration.vuejs.org/breaking-changes/attribute-coercion.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/attribute-coercion.html`,
},
[DeprecationTypes.ATTR_ENUMERATED_COERCION]: {
`If the usage is intended, ` +
`you can disable the compat behavior and suppress this warning with:` +
`\n\n configureCompat({ ${DeprecationTypes.ATTR_ENUMERATED_COERCION}: false })\n`,
- link: `https://v3-migration.vuejs.org/breaking-changes/attribute-coercion.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/attribute-coercion.html`,
},
[DeprecationTypes.TRANSITION_CLASSES]: {
- message: `` // this feature cannot be runtime-detected
+ message: ``, // this feature cannot be runtime-detected
},
[DeprecationTypes.TRANSITION_GROUP_ROOT]: {
`for styling, you can disable the compat behavior and suppress this ` +
`warning with:` +
`\n\n configureCompat({ ${DeprecationTypes.TRANSITION_GROUP_ROOT}: false })\n`,
- link: `https://v3-migration.vuejs.org/breaking-changes/transition-group.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/transition-group.html`,
},
[DeprecationTypes.COMPONENT_ASYNC]: {
`\n\n configureCompat({ ${DeprecationTypes.COMPONENT_ASYNC}: false })\n`
)
},
- link: `https://v3-migration.vuejs.org/breaking-changes/async-components.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/async-components.html`,
},
[DeprecationTypes.COMPONENT_FUNCTIONAL]: {
`been disabled.`
)
},
- link: `https://v3-migration.vuejs.org/breaking-changes/functional-components.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/functional-components.html`,
},
[DeprecationTypes.COMPONENT_V_MODEL]: {
`"update:modelValue" event. You can update the usage and then ${configMsg}`
)
},
- link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`,
},
[DeprecationTypes.RENDER_FUNCTION]: {
`You can opt-in to the new API with:` +
`\n\n configureCompat({ ${DeprecationTypes.RENDER_FUNCTION}: false })\n` +
`\n (This can also be done per-component via the "compatConfig" option.)`,
- link: `https://v3-migration.vuejs.org/breaking-changes/render-function-api.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/render-function-api.html`,
},
[DeprecationTypes.FILTERS]: {
`filters have been removed in Vue 3. ` +
`The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
`Use method calls or computed properties instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/filters.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/filters.html`,
},
[DeprecationTypes.PRIVATE_APIS]: {
message: name =>
`"${name}" is a Vue 2 private API that no longer exists in Vue 3. ` +
`If you are seeing this warning only due to a dependency, you can ` +
- `suppress this warning via { PRIVATE_APIS: 'suppress-warning' }.`
- }
+ `suppress this warning via { PRIVATE_APIS: 'suppress-warning' }.`,
+ },
}
const instanceWarned: Record<string, true> = Object.create(null)
warn(
`(deprecation ${key}) ${
typeof message === 'function' ? message(...args) : message
- }${link ? `\n Details: ${link}` : ``}`
+ }${link ? `\n Details: ${link}` : ``}`,
)
if (!isCompatEnabled(key, instance, true)) {
console.error(
`^ The above deprecation's compat behavior is disabled and will likely ` +
- `lead to runtime errors.`
+ `lead to runtime errors.`,
)
}
}
}
export const globalCompatConfig: CompatConfig = {
- MODE: 2
+ MODE: 2,
}
export function configureCompat(config: CompatConfig) {
// dev only
export function validateCompatConfig(
config: CompatConfig,
- instance?: ComponentInternalInstance
+ instance?: ComponentInternalInstance,
) {
if (seenConfigObjects.has(config)) {
return
`Deprecation config "${key}" is compiler-specific and you are ` +
`running a runtime-only build of Vue. This deprecation should be ` +
`configured via compiler options in your build setup instead.\n` +
- `Details: https://v3-migration.vuejs.org/breaking-changes/migration-build.html`
+ `Details: https://v3-migration.vuejs.org/breaking-changes/migration-build.html`,
)
}
} else {
if (instance && config[DeprecationTypes.OPTIONS_DATA_MERGE] != null) {
warn(
- `Deprecation config "${DeprecationTypes.OPTIONS_DATA_MERGE}" can only be configured globally.`
+ `Deprecation config "${DeprecationTypes.OPTIONS_DATA_MERGE}" can only be configured globally.`,
)
}
}
export function getCompatConfigForKey(
key: DeprecationTypes | 'MODE',
- instance: ComponentInternalInstance | null
+ instance: ComponentInternalInstance | null,
) {
const instanceConfig =
instance && (instance.type as ComponentOptions).compatConfig
export function isCompatEnabled(
key: DeprecationTypes,
instance: ComponentInternalInstance | null,
- enableForBuiltIn = false
+ enableForBuiltIn = false,
): boolean {
// skip compat for built-in components
if (!enableForBuiltIn && instance && instance.type.__isBuiltIn) {
// run tests in v3 mode by default
if (__TEST__) {
configureCompat({
- MODE: 3
+ MODE: 3,
})
}
import { isFunction, isObject } from '@vue/shared'
-import { Component, ComponentInternalInstance } from '../component'
+import type { Component, ComponentInternalInstance } from '../component'
import {
- checkCompatEnabled,
DeprecationTypes,
- softAssertCompatEnabled
+ checkCompatEnabled,
+ softAssertCompatEnabled,
} from './compatConfig'
import { convertLegacyAsyncComponent } from './componentAsync'
import { convertLegacyFunctionalComponent } from './componentFunctional'
export function convertLegacyComponent(
comp: any,
- instance: ComponentInternalInstance | null
+ instance: ComponentInternalInstance | null,
): Component {
if (comp.__isBuiltIn) {
return comp
softAssertCompatEnabled(
DeprecationTypes.COMPONENT_FUNCTIONAL,
instance,
- comp
+ comp,
)
) {
return convertLegacyFunctionalComponent(comp)
import { isArray, isObject, isPromise } from '@vue/shared'
import { defineAsyncComponent } from '../apiAsyncComponent'
-import { Component } from '../component'
+import type { Component } from '../component'
import { isVNode } from '../vnode'
interface LegacyAsyncOptions {
type LegacyAsyncComponent = (
resolve?: (res: LegacyAsyncReturnValue) => void,
- reject?: (reason?: any) => void
+ reject?: (reason?: any) => void,
) => LegacyAsyncReturnValue | undefined
const normalizedAsyncComponentMap = new WeakMap<
loadingComponent: res.loading,
errorComponent: res.error,
delay: res.delay,
- timeout: res.timeout
+ timeout: res.timeout,
})
} else if (res == null) {
converted = defineAsyncComponent(() => fallbackPromise)
import {
- ComponentOptions,
- FunctionalComponent,
- getCurrentInstance
+ type ComponentOptions,
+ type FunctionalComponent,
+ getCurrentInstance,
} from '../component'
import { resolveInjections } from '../componentOptions'
-import { InternalSlots } from '../componentSlots'
+import type { InternalSlots } from '../componentSlots'
import { getCompatListeners } from './instanceListeners'
import { compatH } from './renderFn'
get(target, key: string) {
const slot = target[key]
return slot && slot()
- }
+ },
}
export function convertLegacyFunctionalComponent(comp: ComponentOptions) {
return injections
}
return {}
- }
+ },
}
return legacyFn(compatH, legacyCtx)
}
-import { extend, ShapeFlags } from '@vue/shared'
-import { ComponentInternalInstance, ComponentOptions } from '../component'
-import { callWithErrorHandling, ErrorCodes } from '../errorHandling'
-import { VNode } from '../vnode'
+import { ShapeFlags, extend } from '@vue/shared'
+import type { ComponentInternalInstance, ComponentOptions } from '../component'
+import { ErrorCodes, callWithErrorHandling } from '../errorHandling'
+import type { VNode } from '../vnode'
import { popWarningContext, pushWarningContext } from '../warning'
import {
DeprecationTypes,
+ isCompatEnabled,
warnDeprecation,
- isCompatEnabled
} from './compatConfig'
export const compatModelEventPrefix = `onModelCompat:`
// this is a special case where we want to use the vnode component's
// compat config instead of the current rendering instance (which is the
// parent of the component that exposes v-model)
- { type } as any
+ { type } as any,
)
) {
return
export function compatModelEmit(
instance: ComponentInternalInstance,
event: string,
- args: any[]
+ args: any[],
) {
if (!isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)) {
return
modelHandler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
- args
+ args,
)
}
}
import { isArray } from '@vue/shared'
-import { ComponentInternalInstance } from '../component'
-import { ObjectDirective, DirectiveHook } from '../directives'
-import { softAssertCompatEnabled, DeprecationTypes } from './compatConfig'
+import type { ComponentInternalInstance } from '../component'
+import type { DirectiveHook, ObjectDirective } from '../directives'
+import { DeprecationTypes, softAssertCompatEnabled } from './compatConfig'
export interface LegacyDirective {
bind?: DirectiveHook
beforeMount: 'bind',
mounted: 'inserted',
updated: ['update', 'componentUpdated'],
- unmounted: 'unbind'
+ unmounted: 'unbind',
}
export function mapCompatDirectiveHook(
name: keyof ObjectDirective,
dir: ObjectDirective & LegacyDirective,
- instance: ComponentInternalInstance | null
+ instance: ComponentInternalInstance | null,
): DirectiveHook | DirectiveHook[] | undefined {
const mappedName = legacyDirectiveHookMap[name]
if (mappedName) {
DeprecationTypes.CUSTOM_DIR,
instance,
mapped,
- name
+ name,
)
hook.push(mappedHook)
}
DeprecationTypes.CUSTOM_DIR,
instance,
mappedName,
- name
+ name,
)
}
return dir[mappedName]
import {
+ TrackOpTypes,
+ TriggerOpTypes,
isReactive,
reactive,
track,
- TrackOpTypes,
trigger,
- TriggerOpTypes
} from '@vue/reactivity'
import {
- isFunction,
- extend,
NOOP,
+ extend,
+ invokeArrayFns,
isArray,
+ isFunction,
isObject,
isString,
- invokeArrayFns
} from '@vue/shared'
import { warn } from '../warning'
import { cloneVNode, createVNode } from '../vnode'
-import { ElementNamespace, RootRenderFunction } from '../renderer'
-import {
+import type { ElementNamespace, RootRenderFunction } from '../renderer'
+import type {
App,
AppConfig,
AppContext,
CreateAppFunction,
- Plugin
+ Plugin,
} from '../apiCreateApp'
import {
- Component,
- ComponentOptions,
+ type Component,
+ type ComponentOptions,
createComponentInstance,
finishComponentSetup,
isRuntimeOnly,
- setupComponent
+ setupComponent,
} from '../component'
import {
- RenderFunction,
+ type RenderFunction,
+ internalOptionMergeStrats,
mergeOptions,
- internalOptionMergeStrats
} from '../componentOptions'
-import { ComponentPublicInstance } from '../componentPublicInstance'
+import type { ComponentPublicInstance } from '../componentPublicInstance'
import { devtoolsInitApp, devtoolsUnmountApp } from '../devtools'
-import { Directive } from '../directives'
+import type { Directive } from '../directives'
import { nextTick } from '../scheduler'
import { version } from '..'
import {
+ type LegacyConfig,
installLegacyConfigWarnings,
installLegacyOptionMergeStrats,
- LegacyConfig
} from './globalConfig'
-import { LegacyDirective } from './customDirective'
+import type { LegacyDirective } from './customDirective'
import {
- warnDeprecation,
DeprecationTypes,
assertCompatEnabled,
configureCompat,
isCompatEnabled,
- softAssertCompatEnabled
+ softAssertCompatEnabled,
+ warnDeprecation,
} from './compatConfig'
-import { LegacyPublicInstance } from './instance'
+import type { LegacyPublicInstance } from './instance'
/**
* @deprecated the default `Vue` export has been removed in Vue 3. The type for
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
directive<T = any, V = any>(
name: string,
- directive: Directive<T, V>
+ directive: Directive<T, V>,
): CompatVue
compile(template: string): RenderFunction
// Legacy global Vue constructor
export function createCompatVue(
createApp: CreateAppFunction<Element>,
- createSingletonApp: CreateAppFunction<Element>
+ createSingletonApp: CreateAppFunction<Element>,
): CompatVue {
singletonApp = createSingletonApp({})
const Vue: CompatVue = (singletonCtor = function Vue(
- options: ComponentOptions = {}
+ options: ComponentOptions = {},
) {
return createCompatApp(options, Vue)
} as any)
mergeOptions(
extend({}, SubVue.options),
inlineOptions,
- internalOptionMergeStrats as any
+ internalOptionMergeStrats as any,
),
- SubVue
+ SubVue,
)
}
}
SubVue.options = mergeOptions(
mergeBase,
extendOptions,
- internalOptionMergeStrats as any
+ internalOptionMergeStrats as any,
)
SubVue.options._base = SubVue
mergeOptions(
parent,
child,
- vm ? undefined : (internalOptionMergeStrats as any)
+ vm ? undefined : (internalOptionMergeStrats as any),
),
- defineReactive
+ defineReactive,
}
Object.defineProperty(Vue, 'util', {
get() {
assertCompatEnabled(DeprecationTypes.GLOBAL_PRIVATE_UTIL, null)
return util
- }
+ },
})
Vue.configureCompat = configureCompat
export function installAppCompatProperties(
app: App,
context: AppContext,
- render: RootRenderFunction<any>
+ render: RootRenderFunction<any>,
) {
installFilterMethod(app, context)
installLegacyOptionMergeStrats(app.config)
get() {
__DEV__ && warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE, null)
return app.config.globalProperties
- }
+ },
},
nextTick: { value: nextTick },
extend: { value: singletonCtor.extend },
util: {
get() {
return singletonCtor.util
- }
- }
+ },
+ },
})
}
// copy over asset registries and deopt flag
app._context.mixins = [...singletonApp._context.mixins]
;['components', 'directives', 'filters'].forEach(key => {
- // @ts-ignore
+ // @ts-expect-error
app._context[key] = Object.create(singletonApp._context[key])
})
continue
}
const val = singletonApp.config[key as keyof AppConfig]
- // @ts-ignore
+ // @ts-expect-error
app.config[key] = isObject(val) ? Object.create(val) : val
// compat for runtime ignoredElements -> isCustomElement
Object.defineProperty(
app.config.globalProperties,
key,
- descriptors[key]
+ descriptors[key],
)
}
}
function installCompatMount(
app: App,
context: AppContext,
- render: RootRenderFunction
+ render: RootRenderFunction,
) {
let isMounted = false
if (!result) {
__DEV__ &&
warn(
- `Failed to mount root instance: selector "${selectorOrEl}" returned null.`
+ `Failed to mount root instance: selector "${selectorOrEl}" returned null.`,
)
return
}
'unshift',
'splice',
'sort',
- 'reverse'
+ 'reverse',
]
const patched = new WeakSet<object>()
const reactiveVal = reactive(val)
if (isArray(val)) {
methodsToPatch.forEach(m => {
- // @ts-ignore
+ // @ts-expect-error
val[m] = (...args: any[]) => {
- // @ts-ignore
+ // @ts-expect-error
Array.prototype[m].call(reactiveVal, ...args)
}
})
set(newVal) {
val = isObject(newVal) ? reactive(newVal) : newVal
trigger(obj, TriggerOpTypes.SET, key, newVal)
- }
+ },
})
}
-import { AppConfig } from '../apiCreateApp'
+import type { AppConfig } from '../apiCreateApp'
import {
DeprecationTypes,
softAssertCompatEnabled,
- warnDeprecation
+ warnDeprecation,
} from './compatConfig'
import { isCopyingConfig } from './global'
import { internalOptionMergeStrats } from '../componentOptions'
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
ignoredElements: DeprecationTypes.CONFIG_IGNORED_ELEMENTS,
keyCodes: DeprecationTypes.CONFIG_KEY_CODES,
- productionTip: DeprecationTypes.CONFIG_PRODUCTION_TIP
+ productionTip: DeprecationTypes.CONFIG_PRODUCTION_TIP,
}
Object.keys(legacyConfigOptions).forEach(key => {
warnDeprecation(legacyConfigOptions[key], null)
}
val = newVal
- }
+ },
})
})
}
key in internalOptionMergeStrats &&
softAssertCompatEnabled(
DeprecationTypes.CONFIG_OPTION_MERGE_STRATS,
- null
+ null,
)
) {
return internalOptionMergeStrats[
key as keyof typeof internalOptionMergeStrats
]
}
- }
+ },
})
}
import {
+ NOOP,
extend,
looseEqual,
looseIndexOf,
looseToNumber,
- NOOP,
- toDisplayString
+ toDisplayString,
} from '@vue/shared'
-import {
+import type {
ComponentPublicInstance,
- PublicPropertiesMap
+ PublicPropertiesMap,
} from '../componentPublicInstance'
import { getCompatChildren } from './instanceChildren'
import {
DeprecationTypes,
assertCompatEnabled,
- isCompatEnabled
+ isCompatEnabled,
} from './compatConfig'
import { off, on, once } from './instanceEventEmitter'
import { getCompatListeners } from './instanceListeners'
legacyPrependModifier,
legacyRenderSlot,
legacyRenderStatic,
- legacyresolveScopedSlots
+ legacyresolveScopedSlots,
} from './renderHelpers'
import { resolveFilter } from '../helpers/resolveAssets'
-import { InternalSlots, Slots } from '../componentSlots'
-import { ContextualRenderFn } from '../componentRenderContext'
+import type { InternalSlots, Slots } from '../componentSlots'
+import type { ContextualRenderFn } from '../componentRenderContext'
import { resolveMergedOptions } from '../componentOptions'
export type LegacyPublicInstance = ComponentPublicInstance &
$mount: i => {
assertCompatEnabled(
DeprecationTypes.GLOBAL_MOUNT,
- null /* this warning is global */
+ null /* this warning is global */,
)
// root mount override from ./global.ts in installCompatMount
return i.ctx._compat_mount || NOOP
$off: i => off.bind(null, i),
$children: getCompatChildren,
- $listeners: getCompatListeners
+ $listeners: getCompatListeners,
} as PublicPropertiesMap)
/* istanbul ignore if */
_u: () => legacyresolveScopedSlots,
_g: () => legacyBindObjectListeners,
_d: () => legacyBindDynamicKeys,
- _p: () => legacyPrependModifier
+ _p: () => legacyPrependModifier,
} as PublicPropertiesMap)
}
}
import { ShapeFlags } from '@vue/shared'
-import { ComponentInternalInstance } from '../component'
-import { ComponentPublicInstance } from '../componentPublicInstance'
-import { VNode } from '../vnode'
-import { assertCompatEnabled, DeprecationTypes } from './compatConfig'
+import type { ComponentInternalInstance } from '../component'
+import type { ComponentPublicInstance } from '../componentPublicInstance'
+import type { VNode } from '../vnode'
+import { DeprecationTypes, assertCompatEnabled } from './compatConfig'
export function getCompatChildren(
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): ComponentPublicInstance[] {
assertCompatEnabled(DeprecationTypes.INSTANCE_CHILDREN, instance)
const root = instance.subTree
import { isArray } from '@vue/shared'
-import { ComponentInternalInstance } from '../component'
-import { callWithAsyncErrorHandling, ErrorCodes } from '../errorHandling'
-import { assertCompatEnabled, DeprecationTypes } from './compatConfig'
+import type { ComponentInternalInstance } from '../component'
+import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
+import { DeprecationTypes, assertCompatEnabled } from './compatConfig'
interface EventRegistry {
[event: string]: Function[] | undefined
>()
export function getRegistry(
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): EventRegistry {
let events = eventRegistryMap.get(instance)
if (!events) {
export function on(
instance: ComponentInternalInstance,
event: string | string[],
- fn: Function
+ fn: Function,
) {
if (isArray(event)) {
event.forEach(e => on(instance, e, fn))
assertCompatEnabled(
DeprecationTypes.INSTANCE_EVENT_HOOKS,
instance,
- event
+ event,
)
} else {
assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER, instance)
export function once(
instance: ComponentInternalInstance,
event: string,
- fn: Function
+ fn: Function,
) {
const wrapped = (...args: any[]) => {
off(instance, event, wrapped)
export function off(
instance: ComponentInternalInstance,
event?: string | string[],
- fn?: Function
+ fn?: Function,
) {
assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER, instance)
const vm = instance.proxy
export function emit(
instance: ComponentInternalInstance,
event: string,
- args: any[]
+ args: any[],
) {
const cbs = getRegistry(instance)[event]
if (cbs) {
cbs.map(cb => cb.bind(instance.proxy)),
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
- args
+ args,
)
}
return instance.proxy
import { isOn } from '@vue/shared'
-import { ComponentInternalInstance } from '../component'
-import { assertCompatEnabled, DeprecationTypes } from './compatConfig'
+import type { ComponentInternalInstance } from '../component'
+import { DeprecationTypes, assertCompatEnabled } from './compatConfig'
export function getCompatListeners(instance: ComponentInternalInstance) {
assertCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
import { isArray } from '@vue/shared'
import { inject } from '../apiInject'
-import { ComponentInternalInstance, Data } from '../component'
-import { ComponentOptions, resolveMergedOptions } from '../componentOptions'
+import type { ComponentInternalInstance, Data } from '../component'
+import {
+ type ComponentOptions,
+ resolveMergedOptions,
+} from '../componentOptions'
import { DeprecationTypes, warnDeprecation } from './compatConfig'
export function createPropsDefaultThis(
instance: ComponentInternalInstance,
rawProps: Data,
- propKey: string
+ propKey: string,
) {
return new Proxy(
{},
return inject(key)
}
}
- }
- }
+ },
+ },
)
}
import {
+ ShapeFlags,
extend,
hyphenate,
isArray,
makeMap,
normalizeClass,
normalizeStyle,
- ShapeFlags,
- toHandlerKey
+ toHandlerKey,
} from '@vue/shared'
-import {
+import type {
Component,
ComponentInternalInstance,
ComponentOptions,
Data,
- InternalRenderFunction
+ InternalRenderFunction,
} from '../component'
import { currentRenderingInstance } from '../componentRenderContext'
-import { DirectiveArguments, withDirectives } from '../directives'
+import { type DirectiveArguments, withDirectives } from '../directives'
import {
resolveDirective,
- resolveDynamicComponent
+ resolveDynamicComponent,
} from '../helpers/resolveAssets'
import {
Comment,
+ type VNode,
+ type VNodeArrayChildren,
+ type VNodeProps,
createVNode,
isVNode,
normalizeChildren,
- VNode,
- VNodeArrayChildren,
- VNodeProps
} from '../vnode'
import {
- checkCompatEnabled,
DeprecationTypes,
- isCompatEnabled
+ checkCompatEnabled,
+ isCompatEnabled,
} from './compatConfig'
import { compatModelEventPrefix } from './componentVModel'
// v2 render function, try to provide compat
if (checkCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)) {
const wrapped = (Component.render = function compatRender() {
- // @ts-ignore
+ // @ts-expect-error
return render.call(this, compatH)
})
- // @ts-ignore
+ // @ts-expect-error
wrapped._compatWrapped = true
}
}
export function compatH(
type: string | Component,
- children?: LegacyVNodeChildren
+ children?: LegacyVNodeChildren,
): VNode
export function compatH(
type: string | Component,
props?: Data & LegacyVNodeProps,
- children?: LegacyVNodeChildren
+ children?: LegacyVNodeChildren,
): VNode
export function compatH(
type: any,
propsOrChildren?: any,
- children?: any
+ children?: any,
): VNode {
if (!type) {
type = Comment
return convertLegacySlots(
convertLegacyDirectives(
createVNode(type, convertLegacyProps(propsOrChildren, type)),
- propsOrChildren
- )
+ propsOrChildren,
+ ),
)
} else {
// omit props
return convertLegacySlots(
convertLegacyDirectives(
createVNode(type, convertLegacyProps(propsOrChildren, type), children),
- propsOrChildren
- )
+ propsOrChildren,
+ ),
)
}
}
const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap(
- 'staticStyle,staticClass,directives,model,hook'
+ 'staticStyle,staticClass,directives,model,hook',
)
function convertLegacyProps(
legacyProps: LegacyVNodeProps | undefined,
- type: any
+ type: any,
): (Data & VNodeProps) | null {
if (!legacyProps) {
return null
function convertLegacyDirectives(
vnode: VNode,
- props?: LegacyVNodeProps
+ props?: LegacyVNodeProps,
): VNode {
if (props && props.directives) {
return withDirectives(
resolveDirective(name)!,
value,
arg,
- modifiers
+ modifiers,
] as DirectiveArguments[number]
- })
+ }),
)
}
return vnode
isCompatEnabled(
DeprecationTypes.RENDER_FUNCTION,
currentRenderingInstance,
- true /* enable for built-ins */
+ true /* enable for built-ins */,
) &&
isCompatEnabled(
DeprecationTypes.PRIVATE_APIS,
currentRenderingInstance,
- true /* enable for built-ins */
+ true /* enable for built-ins */,
)
) {
const context = currentRenderingInstance
return (componentOptions = {
Ctor: vnode.type,
propsData: vnode.props,
- children: vnode.children
+ children: vnode.children,
})
}
- }
- }
+ },
+ },
})
}
}
isArray,
isObject,
isReservedProp,
- normalizeClass
+ normalizeClass,
} from '@vue/shared'
-import { ComponentInternalInstance } from '../component'
-import { Slot } from '../componentSlots'
+import type { ComponentInternalInstance } from '../component'
+import type { Slot } from '../componentSlots'
import { createSlots } from '../helpers/createSlots'
import { renderSlot } from '../helpers/renderSlot'
import { toHandlers } from '../helpers/toHandlers'
-import { mergeProps, VNode } from '../vnode'
+import { type VNode, mergeProps } from '../vnode'
function toObject(arr: Array<any>): Object {
const res = {}
_tag: string,
value: any,
_asProp: boolean,
- isSync?: boolean
+ isSync?: boolean,
) {
if (value && isObject(value)) {
if (isArray(value)) {
name: string,
fallback?: VNode[],
props?: any,
- bindObject?: any
+ bindObject?: any,
) {
if (bindObject) {
props = mergeProps(props, bindObject)
fns: LegacyScopedSlotsData,
raw?: Record<string, Slot>,
// the following are added in 2.6
- hasDynamicKeys?: boolean
+ hasDynamicKeys?: boolean,
) {
// v2 default slot doesn't have name
return createSlots(
raw || ({ $stable: !hasDynamicKeys } as any),
- mapKeyToName(fns)
+ mapKeyToName(fns),
)
}
export function legacyRenderStatic(
instance: ComponentInternalInstance,
- index: number
+ index: number,
) {
let cache = staticCacheMap.get(instance)
if (!cache) {
key: string,
builtInKeyCode?: number | number[],
eventKeyName?: string,
- builtInKeyName?: string | string[]
+ builtInKeyName?: string | string[],
) {
const config = instance.appContext.config as any
const configKeyCodes = config.keyCodes || {}
-import { VNode, VNodeChild, isVNode } from './vnode'
+import { type VNode, type VNodeChild, isVNode } from './vnode'
import {
+ EffectScope,
+ type ReactiveEffect,
+ TrackOpTypes,
isRef,
+ markRaw,
pauseTracking,
+ proxyRefs,
resetTracking,
shallowReadonly,
- proxyRefs,
- EffectScope,
- markRaw,
track,
- TrackOpTypes,
- ReactiveEffect
} from '@vue/reactivity'
import {
- ComponentPublicInstance,
+ type ComponentPublicInstance,
+ type ComponentPublicInstanceConstructor,
PublicInstanceProxyHandlers,
+ RuntimeCompiledPublicInstanceProxyHandlers,
createDevRenderContext,
exposePropsOnRenderContext,
exposeSetupStateOnRenderContext,
- ComponentPublicInstanceConstructor,
publicPropertiesMap,
- RuntimeCompiledPublicInstanceProxyHandlers
} from './componentPublicInstance'
import {
- ComponentPropsOptions,
- NormalizedPropsOptions,
+ type ComponentPropsOptions,
+ type NormalizedPropsOptions,
initProps,
- normalizePropsOptions
+ normalizePropsOptions,
} from './componentProps'
import {
+ type InternalSlots,
+ type Slots,
+ type SlotsType,
+ type UnwrapSlotsType,
initSlots,
- InternalSlots,
- Slots,
- SlotsType,
- UnwrapSlotsType
} from './componentSlots'
import { warn } from './warning'
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
-import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
-import { Directive, validateDirectiveName } from './directives'
import {
+ type AppConfig,
+ type AppContext,
+ createAppContext,
+} from './apiCreateApp'
+import { type Directive, validateDirectiveName } from './directives'
+import {
+ type ComponentOptions,
+ type ComputedOptions,
+ type MethodOptions,
applyOptions,
- ComponentOptions,
- ComputedOptions,
- MethodOptions,
- resolveMergedOptions
+ resolveMergedOptions,
} from './componentOptions'
import {
- EmitsOptions,
- ObjectEmitsOptions,
- EmitFn,
+ type EmitFn,
+ type EmitsOptions,
+ type EmitsToProps,
+ type ObjectEmitsOptions,
+ type ShortEmitsToObject,
emit,
normalizeEmitsOptions,
- EmitsToProps,
- ShortEmitsToObject
} from './componentEmits'
import {
EMPTY_OBJ,
- isArray,
- isFunction,
- NOOP,
- isObject,
+ type IfAny,
NO,
- makeMap,
- isPromise,
+ NOOP,
ShapeFlags,
extend,
getGlobalThis,
- IfAny
+ isArray,
+ isFunction,
+ isObject,
+ isPromise,
+ makeMap,
} from '@vue/shared'
-import { SuspenseBoundary } from './components/Suspense'
-import { CompilerOptions } from '@vue/compiler-core'
+import type { SuspenseBoundary } from './components/Suspense'
+import type { CompilerOptions } from '@vue/compiler-core'
import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
-import { startMeasure, endMeasure } from './profiling'
+import { endMeasure, startMeasure } from './profiling'
import { convertLegacyRenderFn } from './compat/renderFn'
import {
- CompatConfig,
+ type CompatConfig,
globalCompatConfig,
- validateCompatConfig
+ validateCompatConfig,
} from './compat/compatConfig'
-import { SchedulerJob } from './scheduler'
-import { LifecycleHooks } from './enums'
+import type { SchedulerJob } from './scheduler'
+import type { LifecycleHooks } from './enums'
export type Data = Record<string, unknown>
P = {},
E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any,
- EE extends EmitsOptions = ShortEmitsToObject<E>
+ EE extends EmitsOptions = ShortEmitsToObject<E>,
> extends ComponentInternalOptions {
// use of any here is intentional so it can be a valid JSX Element constructor
(
props: P & EmitsToProps<EE>,
- ctx: Omit<SetupContext<EE, IfAny<S, {}, SlotsType<S>>>, 'expose'>
+ ctx: Omit<SetupContext<EE, IfAny<S, {}, SlotsType<S>>>, 'expose'>,
): any
props?: ComponentPropsOptions<P>
emits?: EE | (keyof EE)[]
C extends ComputedOptions = ComputedOptions,
M extends MethodOptions = MethodOptions,
E extends EmitsOptions | Record<string, any[]> = {},
- S extends Record<string, any> = any
+ S extends Record<string, any> = any,
> =
| ComponentOptions<Props, RawBindings, D, C, M>
| FunctionalComponent<Props, E, S>
C extends ComputedOptions = ComputedOptions,
M extends MethodOptions = MethodOptions,
E extends EmitsOptions | Record<string, any[]> = {},
- S extends Record<string, any> = any
+ S extends Record<string, any> = any,
> =
| ConcreteComponent<Props, RawBindings, D, C, M, E, S>
| ComponentPublicInstanceConstructor<Props>
// use `E extends any` to force evaluating type to fix #2362
export type SetupContext<
E = EmitsOptions,
- S extends SlotsType = {}
+ S extends SlotsType = {},
> = E extends any
? {
attrs: Data
$props: ComponentInternalInstance['props'],
$setup: ComponentInternalInstance['setupState'],
$data: ComponentInternalInstance['data'],
- $options: ComponentInternalInstance['ctx']
+ $options: ComponentInternalInstance['ctx'],
): VNodeChild
_rc?: boolean // isRuntimeCompiled
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
- suspense: SuspenseBoundary | null
+ suspense: SuspenseBoundary | null,
) {
const type = vnode.type as ConcreteComponent
// inherit parent app context - or - if root, adopt from root vnode
rtg: null,
rtc: null,
ec: null,
- sp: null
+ sp: null,
}
if (__DEV__) {
instance.ctx = createDevRenderContext(instance)
currentInstance || currentRenderingInstance
let internalSetCurrentInstance: (
- instance: ComponentInternalInstance | null
+ instance: ComponentInternalInstance | null,
) => void
let setInSSRSetupState: (state: boolean) => void
}
internalSetCurrentInstance = registerGlobalSetter(
`__VUE_INSTANCE_SETTERS__`,
- v => (currentInstance = v)
+ v => (currentInstance = v),
)
// also make `isInSSRComponentSetup` sharable across copies of Vue.
// this is needed in the SFC playground when SSRing async components, since
// contain duplicated copies of Vue runtime code.
setInSSRSetupState = registerGlobalSetter(
`__VUE_SSR_SETTERS__`,
- v => (isInSSRComponentSetup = v)
+ v => (isInSSRComponentSetup = v),
)
} else {
internalSetCurrentInstance = i => {
const appIsNativeTag = config.isNativeTag || NO
if (isBuiltInTag(name) || appIsNativeTag(name)) {
warn(
- 'Do not use built-in or reserved HTML elements as component id: ' + name
+ 'Do not use built-in or reserved HTML elements as component id: ' + name,
)
}
}
export function setupComponent(
instance: ComponentInternalInstance,
- isSSR = false
+ isSSR = false,
) {
isSSR && setInSSRSetupState(isSSR)
function setupStatefulComponent(
instance: ComponentInternalInstance,
- isSSR: boolean
+ isSSR: boolean,
) {
const Component = instance.type as ComponentOptions
warn(
`"compilerOptions" is only supported when using a build of Vue that ` +
`includes the runtime compiler. Since you are using a runtime-only ` +
- `build, the options should be passed via your build tool config instead.`
+ `build, the options should be passed via your build tool config instead.`,
)
}
}
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
- [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
+ [
+ __DEV__ ? shallowReadonly(instance.props) : instance.props,
+ setupContext,
+ ],
)
resetTracking()
unsetCurrentInstance()
`Component <${name}>: setup function returned a promise, but no ` +
`<Suspense> boundary was found in the parent component tree. ` +
`A component with async setup() must be nested in a <Suspense> ` +
- `in order to be rendered.`
+ `in order to be rendered.`,
)
}
} else if (__DEV__) {
warn(
`setup() returned a Promise, but the version of Vue you are using ` +
- `does not support it yet.`
+ `does not support it yet.`,
)
}
} else {
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
- isSSR: boolean
+ isSSR: boolean,
) {
if (isFunction(setupResult)) {
// setup returned an inline render function
if (__DEV__ && isVNode(setupResult)) {
warn(
`setup() should not return VNodes directly - ` +
- `return a render function instead.`
+ `return a render function instead.`,
)
}
// setup returned bindings.
warn(
`setup() should return an object. Received: ${
setupResult === null ? 'null' : typeof setupResult
- }`
+ }`,
)
}
finishComponentSetup(instance, isSSR)
type CompileFunction = (
template: string | object,
- options?: CompilerOptions
+ options?: CompilerOptions,
) => InternalRenderFunction
let compile: CompileFunction | undefined
export function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean,
- skipOptions?: boolean
+ skipOptions?: boolean,
) {
const Component = instance.type as ComponentOptions
extend(
{
isCustomElement,
- delimiters
+ delimiters,
},
- compilerOptions
+ compilerOptions,
),
- componentCompilerOptions
+ componentCompilerOptions,
)
if (__COMPAT__) {
// pass runtime compat config into the compiler
? ` Use "vue.esm-browser.js" instead.`
: __GLOBAL__
? ` Use "vue.global.js" instead.`
- : ``) /* should not happen */
+ : ``) /* should not happen */,
)
} else {
warn(`Component is missing template or render function.`)
deleteProperty() {
warn(`setupContext.attrs is readonly.`)
return false
- }
+ },
}
: {
get(target, key: string) {
track(instance, TrackOpTypes.GET, '$attrs')
return target[key]
- }
- }
+ },
+ },
))
)
}
get(target, key: string) {
track(instance, TrackOpTypes.GET, '$slots')
return target[key]
- }
+ },
}))
)
}
export function createSetupContext(
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): SetupContext {
const expose: SetupContext['expose'] = exposed => {
if (__DEV__) {
}
if (exposedType !== 'object') {
warn(
- `expose() should be passed a plain object, received ${exposedType}.`
+ `expose() should be passed a plain object, received ${exposedType}.`,
)
}
}
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
},
- expose
+ expose,
})
} else {
return {
},
slots: instance.slots,
emit: instance.emit,
- expose
+ expose,
}
}
}
},
has(target, key: string) {
return key in target || key in publicPropertiesMap
- }
+ },
}))
)
}
export function getComponentName(
Component: ConcreteComponent,
- includeInferred = true
+ includeInferred = true,
): string | false | undefined {
return isFunction(Component)
? Component.displayName || Component.name
export function formatComponentName(
instance: ComponentInternalInstance | null,
Component: ConcreteComponent,
- isRoot = false
+ isRoot = false,
): string {
let name = getComponentName(Component)
if (!name && Component.__file) {
name =
inferFromRegistry(
instance.components ||
- (instance.parent.type as ComponentOptions).components
+ (instance.parent.type as ComponentOptions).components,
) || inferFromRegistry(instance.appContext.components)
}
import {
- camelize,
EMPTY_OBJ,
- toHandlerKey,
+ type UnionToIntersection,
+ camelize,
extend,
hasOwn,
hyphenate,
isArray,
isFunction,
isObject,
- isString,
isOn,
- UnionToIntersection,
- looseToNumber
+ isString,
+ looseToNumber,
+ toHandlerKey,
} from '@vue/shared'
import {
- ComponentInternalInstance,
- ComponentOptions,
- ConcreteComponent,
- formatComponentName
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ type ConcreteComponent,
+ formatComponentName,
} from './component'
-import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
+import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import { warn } from './warning'
import { devtoolsComponentEmit } from './devtools'
-import { AppContext } from './apiCreateApp'
+import type { AppContext } from './apiCreateApp'
import { emit as compatInstanceEmit } from './compat/instanceEventEmitter'
import {
+ compatModelEmit,
compatModelEventPrefix,
- compatModelEmit
} from './compat/componentVModel'
export type ObjectEmitsOptions = Record<
export type EmitFn<
Options = ObjectEmitsOptions,
- Event extends keyof Options = keyof Options
+ Event extends keyof Options = keyof Options,
> = Options extends Array<infer V>
? (event: V, ...args: any[]) => void
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
if (__DEV__) {
const {
emitsOptions,
- propsOptions: [propsOptions]
+ propsOptions: [propsOptions],
} = instance
if (emitsOptions) {
if (
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
- `the emits option nor as an "${toHandlerKey(event)}" prop.`
+ `the emits option nor as an "${toHandlerKey(event)}" prop.`,
)
}
} else {
const isValid = validator(...rawArgs)
if (!isValid) {
warn(
- `Invalid event arguments: event validation failed for event "${event}".`
+ `Invalid event arguments: event validation failed for event "${event}".`,
)
}
}
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(
instance,
- instance.type
+ instance.type,
)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
- `You should probably use "${hyphenate(event)}" instead of "${event}".`
+ `You should probably use "${hyphenate(
+ event,
+ )}" instead of "${event}".`,
)
}
}
handler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
- args
+ args,
)
}
onceHandler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
- args
+ args,
)
}
export function normalizeEmitsOptions(
comp: ConcreteComponent,
appContext: AppContext,
- asMixin = false
+ asMixin = false,
): ObjectEmitsOptions | null {
const cache = appContext.emitsCache
const cached = cache.get(comp)
// both considered matched listeners.
export function isEmitListener(
options: ObjectEmitsOptions | null,
- key: string
+ key: string,
): boolean {
if (!options || !isOn(key)) {
return false
-import {
+import type {
+ Component,
ComponentInternalInstance,
- Data,
- SetupContext,
ComponentInternalOptions,
- Component,
ConcreteComponent,
- InternalRenderFunction
+ Data,
+ InternalRenderFunction,
+ SetupContext,
} from './component'
import {
- isFunction,
+ type LooseRequired,
+ NOOP,
+ type Prettify,
extend,
- isString,
- isObject,
isArray,
- NOOP,
+ isFunction,
+ isObject,
isPromise,
- LooseRequired,
- Prettify
+ isString,
} from '@vue/shared'
-import { isRef, Ref } from '@vue/reactivity'
+import { type Ref, isRef } from '@vue/reactivity'
import { computed } from './apiComputed'
import {
+ type WatchCallback,
+ type WatchOptions,
+ createPathGetter,
watch,
- WatchOptions,
- WatchCallback,
- createPathGetter
} from './apiWatch'
-import { provide, inject } from './apiInject'
+import { inject, provide } from './apiInject'
import {
+ type DebuggerHook,
+ type ErrorCapturedHook,
+ onActivated,
onBeforeMount,
- onMounted,
+ onBeforeUnmount,
onBeforeUpdate,
- onUpdated,
+ onDeactivated,
onErrorCaptured,
+ onMounted,
onRenderTracked,
- onBeforeUnmount,
- onUnmounted,
- onActivated,
- onDeactivated,
onRenderTriggered,
- DebuggerHook,
- ErrorCapturedHook,
- onServerPrefetch
+ onServerPrefetch,
+ onUnmounted,
+ onUpdated,
} from './apiLifecycle'
import {
+ type ComputedGetter,
+ type WritableComputedOptions,
reactive,
- ComputedGetter,
- WritableComputedOptions
} from '@vue/reactivity'
-import {
+import type {
ComponentObjectPropsOptions,
- ExtractPropTypes,
+ ComponentPropsOptions,
ExtractDefaultPropTypes,
- ComponentPropsOptions
+ ExtractPropTypes,
} from './componentProps'
-import { EmitsOptions, EmitsToProps } from './componentEmits'
-import { Directive } from './directives'
+import type { EmitsOptions, EmitsToProps } from './componentEmits'
+import type { Directive } from './directives'
import {
- CreateComponentPublicInstance,
- ComponentPublicInstance,
+ type ComponentPublicInstance,
+ type CreateComponentPublicInstance,
+ type IntersectionMixin,
+ type UnwrapMixinsType,
isReservedPrefix,
- IntersectionMixin,
- UnwrapMixinsType
} from './componentPublicInstance'
import { warn } from './warning'
-import { VNodeChild } from './vnode'
+import type { VNodeChild } from './vnode'
import { callWithAsyncErrorHandling } from './errorHandling'
import { deepMergeData } from './compat/data'
import { DeprecationTypes } from './compat/compatConfig'
import {
- CompatConfig,
+ type CompatConfig,
isCompatEnabled,
- softAssertCompatEnabled
+ softAssertCompatEnabled,
} from './compat/compatConfig'
-import { OptionMergeFunction } from './apiCreateApp'
+import type { OptionMergeFunction } from './apiCreateApp'
import { LifecycleHooks } from './enums'
-import { SlotsType } from './componentSlots'
+import type { SlotsType } from './componentSlots'
import { normalizePropsOrEmits } from './apiSetupHelpers'
/**
Defaults = {},
I extends ComponentInjectOptions = {},
II extends string = string,
- S extends SlotsType = {}
+ S extends SlotsType = {},
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, I, II>,
ComponentInternalOptions,
ComponentCustomOptions {
>
>
>,
- ctx: SetupContext<E, S>
+ ctx: SetupContext<E, S>,
) => Promise<RawBindings> | RawBindings | RenderFunction | void
name?: string
template?: string | object // can be a direct DOM node
$props: ComponentInternalInstance['props'],
$setup: ComponentInternalInstance['setupState'],
$data: ComponentInternalInstance['data'],
- $options: ComponentInternalInstance['ctx']
+ $options: ComponentInternalInstance['ctx'],
) => void
/**
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
- PE = Props & EmitsToProps<E>
+ PE = Props & EmitsToProps<E>,
> = ComponentOptionsBase<
PE,
RawBindings,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
- Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>
+ Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>,
> = ComponentOptionsBase<
Props,
RawBindings,
II extends string = string,
S extends SlotsType = {},
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
- Defaults = ExtractDefaultPropTypes<PropsOptions>
+ Defaults = ExtractDefaultPropTypes<PropsOptions>,
> = ComponentOptionsBase<
Props,
RawBindings,
Mixin extends ComponentOptionsMixin = any,
Extends extends ComponentOptionsMixin = any,
E extends EmitsOptions = any,
- S extends SlotsType = any
+ S extends SlotsType = any,
> = ComponentOptionsBase<
Props,
RawBindings,
Mixin extends ComponentOptionsMixin,
Extends extends ComponentOptionsMixin,
I extends ComponentInjectOptions,
- II extends string
+ II extends string,
> {
compatConfig?: CompatConfig
MethodOptions,
Mixin,
Extends
- >
+ >,
) => D
computed?: C
methods?: M
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
- Defaults = {}
+ Defaults = {},
> = {
P: P
B: B
DATA = 'Data',
COMPUTED = 'Computed',
METHODS = 'Methods',
- INJECT = 'Inject'
+ INJECT = 'Inject',
}
function createDuplicateChecker() {
// assets
components,
directives,
- filters
+ filters,
} = options
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
value: methodHandler.bind(publicThis),
configurable: true,
enumerable: true,
- writable: true
+ writable: true,
})
} else {
ctx[key] = methodHandler.bind(publicThis)
} else if (__DEV__) {
warn(
`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
- `Did you reference the function correctly?`
+ `Did you reference the function correctly?`,
)
}
}
if (__DEV__ && !isFunction(dataOptions)) {
warn(
`The data option must be a function. ` +
- `Plain object usage is no longer supported.`
+ `Plain object usage is no longer supported.`,
)
}
const data = dataOptions.call(publicThis, publicThis)
warn(
`data() returned a Promise - note data() cannot be async; If you ` +
`intend to perform data fetching before component renders, use ` +
- `async setup() + <Suspense>.`
+ `async setup() + <Suspense>.`,
)
}
if (!isObject(data)) {
configurable: true,
enumerable: true,
get: () => data[key],
- set: NOOP
+ set: NOOP,
})
}
}
: __DEV__
? () => {
warn(
- `Write operation failed: computed property "${key}" is readonly.`
+ `Write operation failed: computed property "${key}" is readonly.`,
)
}
: NOOP
const c = computed({
get,
- set
+ set,
})
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => c.value,
- set: v => (c.value = v)
+ set: v => (c.value = v),
})
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.COMPUTED, key)
function registerLifecycleHook(
register: Function,
- hook?: Function | Function[]
+ hook?: Function | Function[],
) {
if (isArray(hook)) {
hook.forEach(_hook => register(_hook.bind(publicThis)))
expose.forEach(key => {
Object.defineProperty(exposed, key, {
get: () => publicThis[key],
- set: val => (publicThis[key] = val)
+ set: val => (publicThis[key] = val),
})
})
} else if (!instance.exposed) {
export function resolveInjections(
injectOptions: ComponentInjectOptions,
ctx: any,
- checkDuplicateProperties = NOOP as any
+ checkDuplicateProperties = NOOP as any,
) {
if (isArray(injectOptions)) {
injectOptions = normalizeInject(injectOptions)!
injected = inject(
opt.from || key,
opt.default,
- true /* treat default function as factory */
+ true /* treat default function as factory */,
)
} else {
injected = inject(opt.from || key)
enumerable: true,
configurable: true,
get: () => (injected as Ref).value,
- set: v => ((injected as Ref).value = v)
+ set: v => ((injected as Ref).value = v),
})
} else {
ctx[key] = injected
function callHook(
hook: Function,
instance: ComponentInternalInstance,
- type: LifecycleHooks
+ type: LifecycleHooks,
) {
callWithAsyncErrorHandling(
isArray(hook)
? hook.map(h => h.bind(instance.proxy!))
: hook.bind(instance.proxy!),
instance,
- type
+ type,
)
}
raw: ComponentWatchOptionItem,
ctx: Data,
publicThis: ComponentPublicInstance,
- key: string
+ key: string,
) {
const getter = key.includes('.')
? createPathGetter(publicThis, key)
* instances.
*/
export function resolveMergedOptions(
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): MergedComponentOptions {
const base = instance.type as ComponentOptions
const { mixins, extends: extendsOptions } = base
const {
mixins: globalMixins,
optionsCache: cache,
- config: { optionMergeStrategies }
+ config: { optionMergeStrategies },
} = instance.appContext
const cached = cache.get(base)
resolved = {}
if (globalMixins.length) {
globalMixins.forEach(m =>
- mergeOptions(resolved, m, optionMergeStrategies, true)
+ mergeOptions(resolved, m, optionMergeStrategies, true),
)
}
mergeOptions(resolved, base, optionMergeStrategies)
to: any,
from: any,
strats: Record<string, OptionMergeFunction>,
- asMixin = false
+ asMixin = false,
) {
if (__COMPAT__ && isFunction(from)) {
from = from.options
}
if (mixins) {
mixins.forEach((m: ComponentOptionsMixin) =>
- mergeOptions(to, m, strats, true)
+ mergeOptions(to, m, strats, true),
)
}
__DEV__ &&
warn(
`"expose" option is ignored when declared in mixins or extends. ` +
- `It should only be declared in the base component itself.`
+ `It should only be declared in the base component itself.`,
)
} else {
const strat = internalOptionMergeStrats[key] || (strats && strats[key])
watch: mergeWatchOptions,
// provide / inject
provide: mergeDataFn,
- inject: mergeInject
+ inject: mergeInject,
}
if (__COMPAT__) {
: extend
)(
isFunction(to) ? to.call(this, this) : to,
- isFunction(from) ? from.call(this, this) : from
+ isFunction(from) ? from.call(this, this) : from,
)
}
}
function mergeInject(
to: ComponentInjectOptions | undefined,
- from: ComponentInjectOptions
+ from: ComponentInjectOptions,
) {
return mergeObjectOptions(normalizeInject(to), normalizeInject(from))
}
function normalizeInject(
- raw: ComponentInjectOptions | undefined
+ raw: ComponentInjectOptions | undefined,
): ObjectInjectOptions | undefined {
if (isArray(raw)) {
const res: ObjectInjectOptions = {}
function mergeEmitsOrPropsOptions(
to: EmitsOptions | undefined,
- from: EmitsOptions | undefined
+ from: EmitsOptions | undefined,
): EmitsOptions | undefined
function mergeEmitsOrPropsOptions(
to: ComponentPropsOptions | undefined,
- from: ComponentPropsOptions | undefined
+ from: ComponentPropsOptions | undefined,
): ComponentPropsOptions | undefined
function mergeEmitsOrPropsOptions(
to: ComponentPropsOptions | EmitsOptions | undefined,
- from: ComponentPropsOptions | EmitsOptions | undefined
+ from: ComponentPropsOptions | EmitsOptions | undefined,
) {
if (to) {
if (isArray(to) && isArray(from)) {
return extend(
Object.create(null),
normalizePropsOrEmits(to),
- normalizePropsOrEmits(from ?? {})
+ normalizePropsOrEmits(from ?? {}),
)
} else {
return from
function mergeWatchOptions(
to: ComponentWatchOptions | undefined,
- from: ComponentWatchOptions | undefined
+ from: ComponentWatchOptions | undefined,
) {
if (!to) return from
if (!from) return to
import {
- toRaw,
+ TriggerOpTypes,
shallowReactive,
+ shallowReadonly,
+ toRaw,
trigger,
- TriggerOpTypes,
- shallowReadonly
} from '@vue/reactivity'
import {
+ EMPTY_ARR,
EMPTY_OBJ,
+ type IfAny,
+ PatchFlags,
camelize,
- hyphenate,
capitalize,
- isString,
- isFunction,
- isArray,
- isObject,
- hasOwn,
- toRawType,
- PatchFlags,
- makeMap,
- isReservedProp,
- EMPTY_ARR,
def,
extend,
+ hasOwn,
+ hyphenate,
+ isArray,
+ isFunction,
+ isObject,
isOn,
- IfAny
+ isReservedProp,
+ isString,
+ makeMap,
+ toRawType,
} from '@vue/shared'
import { warn } from './warning'
import {
- Data,
- ComponentInternalInstance,
- ComponentOptions,
- ConcreteComponent,
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ type ConcreteComponent,
+ type Data,
setCurrentInstance,
- unsetCurrentInstance
+ unsetCurrentInstance,
} from './component'
import { isEmitListener } from './componentEmits'
import { InternalObjectKey } from './vnode'
-import { AppContext } from './apiCreateApp'
+import type { AppContext } from './apiCreateApp'
import { createPropsDefaultThis } from './compat/props'
import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig'
import { DeprecationTypes } from './compat/compatConfig'
| PropMethod<T>
type PropMethod<T, TConstructor = any> = [T] extends [
- ((...args: any) => any) | undefined
+ ((...args: any) => any) | undefined,
] // if is function with args, allowing non-required functions
? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
: never
enum BooleanFlags {
shouldCast,
- shouldCastTrue
+ shouldCastTrue,
}
// extract props which defined with default from prop options
instance: ComponentInternalInstance,
rawProps: Data | null,
isStateful: number, // result of bitwise flag comparison
- isSSR = false
+ isSSR = false,
) {
const props: Data = {}
const attrs: Data = {}
instance: ComponentInternalInstance,
rawProps: Data | null,
rawPrevProps: Data | null,
- optimized: boolean
+ optimized: boolean,
) {
const {
props,
attrs,
- vnode: { patchFlag }
+ vnode: { patchFlag },
} = instance
const rawCurrentProps = toRaw(props)
const [options] = instance.propsOptions
camelizedKey,
value,
instance,
- false /* isAbsent */
+ false /* isAbsent */,
)
}
} else {
key,
undefined,
instance,
- true /* isAbsent */
+ true /* isAbsent */,
)
}
} else {
instance: ComponentInternalInstance,
rawProps: Data | null,
props: Data,
- attrs: Data
+ attrs: Data,
) {
const [options, needCastKeys] = instance.propsOptions
let hasAttrsChanged = false
softAssertCompatEnabled(
DeprecationTypes.INSTANCE_EVENT_HOOKS,
instance,
- key.slice(2).toLowerCase()
+ key.slice(2).toLowerCase(),
)
}
if (key === 'inline-template') {
key,
castValues[key],
instance,
- !hasOwn(castValues, key)
+ !hasOwn(castValues, key),
)
}
}
key: string,
value: unknown,
instance: ComponentInternalInstance,
- isAbsent: boolean
+ isAbsent: boolean,
) {
const opt = options[key]
if (opt != null) {
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
? createPropsDefaultThis(instance, props, key)
: null,
- props
+ props,
)
unsetCurrentInstance()
}
export function normalizePropsOptions(
comp: ConcreteComponent,
appContext: AppContext,
- asMixin = false
+ asMixin = false,
): NormalizedPropsOptions {
const cache = appContext.propsCache
const cached = cache.get(comp)
function getTypeIndex(
type: Prop<any>,
- expectedTypes: PropType<any> | void | null | true
+ expectedTypes: PropType<any> | void | null | true,
): number {
if (isArray(expectedTypes)) {
return expectedTypes.findIndex(t => isSameType(t, type))
function validateProps(
rawProps: Data,
props: Data,
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
) {
const resolvedValues = toRaw(props)
const options = instance.propsOptions[0]
resolvedValues[key],
opt,
__DEV__ ? shallowReadonly(resolvedValues) : resolvedValues,
- !hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key))
+ !hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key)),
)
}
}
value: unknown,
prop: PropOptions,
props: Data,
- isAbsent: boolean
+ isAbsent: boolean,
) {
const { type, required, validator, skipCheck } = prop
// required!
}
const isSimpleType = /*#__PURE__*/ makeMap(
- 'String,Number,Boolean,Function,Symbol,BigInt'
+ 'String,Number,Boolean,Function,Symbol,BigInt',
)
type AssertionResult = {
}
return {
valid,
- expectedType
+ expectedType,
}
}
function getInvalidTypeMessage(
name: string,
value: unknown,
- expectedTypes: string[]
+ expectedTypes: string[],
): string {
if (expectedTypes.length === 0) {
return (
import {
- ComponentInternalInstance,
- Data,
+ type ComponentInternalInstance,
+ type Data,
getExposeProxy,
- isStatefulComponent
+ isStatefulComponent,
} from './component'
import { nextTick, queueJob } from './scheduler'
-import { instanceWatch, WatchOptions, WatchStopHandle } from './apiWatch'
+import {
+ type WatchOptions,
+ type WatchStopHandle,
+ instanceWatch,
+} from './apiWatch'
import {
EMPTY_OBJ,
- hasOwn,
- isGloballyAllowed,
+ type IfAny,
NOOP,
+ type Prettify,
+ type UnionToIntersection,
extend,
- isString,
+ hasOwn,
isFunction,
- UnionToIntersection,
- Prettify,
- IfAny
+ isGloballyAllowed,
+ isString,
} from '@vue/shared'
import {
- toRaw,
+ type ShallowUnwrapRef,
+ TrackOpTypes,
+ type UnwrapNestedRefs,
shallowReadonly,
+ toRaw,
track,
- TrackOpTypes,
- ShallowUnwrapRef,
- UnwrapNestedRefs
} from '@vue/reactivity'
import {
- ExtractComputedReturns,
- ComponentOptionsBase,
- ComputedOptions,
- MethodOptions,
- ComponentOptionsMixin,
- OptionTypesType,
- OptionTypesKeys,
+ type ComponentInjectOptions,
+ type ComponentOptionsBase,
+ type ComponentOptionsMixin,
+ type ComputedOptions,
+ type ExtractComputedReturns,
+ type InjectToObject,
+ type MergedComponentOptionsOverride,
+ type MethodOptions,
+ type OptionTypesKeys,
+ type OptionTypesType,
resolveMergedOptions,
shouldCacheAccess,
- MergedComponentOptionsOverride,
- InjectToObject,
- ComponentInjectOptions
} from './componentOptions'
-import { EmitsOptions, EmitFn } from './componentEmits'
-import { SlotsType, UnwrapSlotsType } from './componentSlots'
+import type { EmitFn, EmitsOptions } from './componentEmits'
+import type { SlotsType, UnwrapSlotsType } from './componentSlots'
import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning'
export type UnwrapMixinsType<
T,
- Type extends OptionTypesKeys
+ Type extends OptionTypesKeys,
> = T extends OptionTypesType ? T[Type] : never
type EnsureNonVoid<T> = T extends void ? {} : T
RawBindings = any,
D = any,
C extends ComputedOptions = ComputedOptions,
- M extends MethodOptions = MethodOptions
+ M extends MethodOptions = MethodOptions,
> = {
__isFragment?: never
__isTeleport?: never
PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> &
EnsureNonVoid<M>,
PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
- EnsureNonVoid<Defaults>
+ EnsureNonVoid<Defaults>,
> = ComponentPublicInstance<
PublicP,
PublicB,
MakeDefaultsOptional extends boolean = false,
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>,
I extends ComponentInjectOptions = {},
- S extends SlotsType = {}
+ S extends SlotsType = {},
> = {
$: ComponentInternalInstance
$data: D
cb: T extends (...args: any) => infer R
? (...args: [R, R]) => any
: (...args: any) => any,
- options?: WatchOptions
+ options?: WatchOptions,
): WatchStopHandle
} & IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> &
ShallowUnwrapRef<B> &
* public $parent chains, skip functional ones and go to the parent instead.
*/
const getPublicInstance = (
- i: ComponentInternalInstance | null
+ i: ComponentInternalInstance | null,
): ComponentPublicInstance | ComponentInternalInstance['exposed'] | null => {
if (!i) return null
if (isStatefulComponent(i)) return getExposeProxy(i) || i.proxy
queueJob(i.update)
}),
$nextTick: i => i.n || (i.n = nextTick.bind(i.proxy!)),
- $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
+ $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP),
} as PublicPropertiesMap)
if (__COMPAT__) {
SETUP,
DATA,
PROPS,
- CONTEXT
+ CONTEXT,
}
export interface ComponentRenderContext {
if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {
warn(
`Property ${JSON.stringify(
- key
+ key,
)} must be accessed via $data because it starts with a reserved ` +
- `character ("$" or "_") and is not proxied on the render context.`
+ `character ("$" or "_") and is not proxied on the render context.`,
)
} else if (instance === currentRenderingInstance) {
warn(
`Property ${JSON.stringify(key)} was accessed during render ` +
- `but is not defined on instance.`
+ `but is not defined on instance.`,
)
}
}
set(
{ _: instance }: ComponentRenderContext,
key: string,
- value: any
+ value: any,
): boolean {
const { data, setupState, ctx } = instance
if (hasSetupBinding(setupState, key)) {
__DEV__ &&
warn(
`Attempting to mutate public property "${key}". ` +
- `Properties starting with $ are reserved and readonly.`
+ `Properties starting with $ are reserved and readonly.`,
)
return false
} else {
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
- value
+ value,
})
} else {
ctx[key] = value
has(
{
- _: { data, setupState, accessCache, ctx, appContext, propsOptions }
+ _: { data, setupState, accessCache, ctx, appContext, propsOptions },
}: ComponentRenderContext,
- key: string
+ key: string,
) {
let normalizedProps
return (
defineProperty(
target: ComponentRenderContext,
key: string,
- descriptor: PropertyDescriptor
+ descriptor: PropertyDescriptor,
) {
if (descriptor.get != null) {
// invalidate key cache of a getter based property #5417
this.set!(target, key, descriptor.value, null)
}
return Reflect.defineProperty(target, key, descriptor)
- }
+ },
}
if (__DEV__ && !__TEST__) {
PublicInstanceProxyHandlers.ownKeys = (target: ComponentRenderContext) => {
warn(
`Avoid app logic that relies on enumerating keys on a component instance. ` +
- `The keys will be empty in production mode to avoid performance overhead.`
+ `The keys will be empty in production mode to avoid performance overhead.`,
)
return Reflect.ownKeys(target)
}
if (__DEV__ && !has && PublicInstanceProxyHandlers.has!(_, key)) {
warn(
`Property ${JSON.stringify(
- key
- )} should not start with _ which is a reserved prefix for Vue internals.`
+ key,
+ )} should not start with _ which is a reserved prefix for Vue internals.`,
)
}
return has
- }
- }
+ },
+ },
)
// dev only
Object.defineProperty(target, `_`, {
configurable: true,
enumerable: false,
- get: () => instance
+ get: () => instance,
})
// expose public properties
get: () => publicPropertiesMap[key](instance),
// intercepted by the proxy so no need for implementation,
// but needed to prevent set errors
- set: NOOP
+ set: NOOP,
})
})
// dev only
export function exposePropsOnRenderContext(
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
) {
const {
ctx,
- propsOptions: [propsOptions]
+ propsOptions: [propsOptions],
} = instance
if (propsOptions) {
Object.keys(propsOptions).forEach(key => {
enumerable: true,
configurable: true,
get: () => instance.props[key],
- set: NOOP
+ set: NOOP,
})
})
}
// dev only
export function exposeSetupStateOnRenderContext(
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
) {
const { ctx, setupState } = instance
Object.keys(toRaw(setupState)).forEach(key => {
if (isReservedPrefix(key[0])) {
warn(
`setup() return property ${JSON.stringify(
- key
+ key,
)} should not start with "$" or "_" ` +
- `which are reserved prefixes for Vue internals.`
+ `which are reserved prefixes for Vue internals.`,
)
return
}
enumerable: true,
configurable: true,
get: () => setupState[key],
- set: NOOP
+ set: NOOP,
})
}
})
-import { ComponentInternalInstance } from './component'
+import type { ComponentInternalInstance } from './component'
import { devtoolsComponentUpdated } from './devtools'
import { setBlockTracking } from './vnode'
* ```
*/
export function setCurrentRenderingInstance(
- instance: ComponentInternalInstance | null
+ instance: ComponentInternalInstance | null,
): ComponentInternalInstance | null {
const prev = currentRenderingInstance
currentRenderingInstance = instance
export function withCtx(
fn: Function,
ctx: ComponentInternalInstance | null = currentRenderingInstance,
- isNonScopedSlot?: boolean // __COMPAT__ only
+ isNonScopedSlot?: boolean, // __COMPAT__ only
) {
if (!ctx) return fn
import {
- ComponentInternalInstance,
- FunctionalComponent,
- Data,
- getComponentName
+ type ComponentInternalInstance,
+ type Data,
+ type FunctionalComponent,
+ getComponentName,
} from './component'
import {
- VNode,
- normalizeVNode,
- createVNode,
Comment,
+ type VNode,
+ type VNodeArrayChildren,
+ blockStack,
cloneVNode,
- VNodeArrayChildren,
+ createVNode,
isVNode,
- blockStack
+ normalizeVNode,
} from './vnode'
-import { handleError, ErrorCodes } from './errorHandling'
-import { PatchFlags, ShapeFlags, isOn, isModelListener } from '@vue/shared'
+import { ErrorCodes, handleError } from './errorHandling'
+import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared'
import { warn } from './warning'
import { isHmrUpdating } from './hmr'
-import { NormalizedProps } from './componentProps'
+import type { NormalizedProps } from './componentProps'
import { isEmitListener } from './componentEmits'
import { setCurrentRenderingInstance } from './componentRenderContext'
import {
DeprecationTypes,
isCompatEnabled,
- warnDeprecation
+ warnDeprecation,
} from './compat/compatConfig'
/**
type SetRootFn = ((root: VNode) => void) | undefined
export function renderComponentRoot(
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): VNode {
const {
type: Component,
data,
setupState,
ctx,
- inheritAttrs
+ inheritAttrs,
} = instance
let result
get(target, key, receiver) {
warn(
`Property '${String(
- key
- )}' was accessed via 'this'. Avoid using 'this' in templates.`
+ key,
+ )}' was accessed via 'this'. Avoid using 'this' in templates.`,
)
return Reflect.get(target, key, receiver)
- }
+ },
})
: proxyToUse
result = normalizeVNode(
props,
setupState,
data,
- ctx
- )
+ ctx,
+ ),
)
fallthroughAttrs = attrs
} else {
return attrs
},
slots,
- emit
+ emit,
}
- : { attrs, slots, emit }
+ : { attrs, slots, emit },
)
- : render(props, null as any /* we know it doesn't need it */)
+ : render(props, null as any /* we know it doesn't need it */),
)
fallthroughAttrs = Component.props
? attrs
// related: #1543, #1643, #1989
fallthroughAttrs = filterModelListeners(
fallthroughAttrs,
- propsOptions
+ propsOptions,
)
}
root = cloneVNode(root, fallthroughAttrs)
`Extraneous non-props attributes (` +
`${extraAttrs.join(', ')}) ` +
`were passed to component but could not be automatically inherited ` +
- `because component renders fragment or text root nodes.`
+ `because component renders fragment or text root nodes.`,
)
}
if (eventAttrs.length) {
`were passed to component but could not be automatically inherited ` +
`because component renders fragment or text root nodes. ` +
`If the listener is intended to be a component custom event listener only, ` +
- `declare it using the "emits" option.`
+ `declare it using the "emits" option.`,
)
}
}
warnDeprecation(
DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE,
instance,
- getComponentName(instance.type)
+ getComponentName(instance.type),
)
}
root = cloneVNode(root, {
class: cls,
- style: style
+ style: style,
})
}
}
if (__DEV__ && !isElementRoot(root)) {
warn(
`Runtime directive used on component with non-element root node. ` +
- `The directives will not function as intended.`
+ `The directives will not function as intended.`,
)
}
// clone before mutating since the root may be a hoisted vnode
if (__DEV__ && !isElementRoot(root)) {
warn(
`Component inside <Transition> renders non-element root node ` +
- `that cannot be animated.`
+ `that cannot be animated.`,
)
}
root.transition = vnode.transition
}
export function filterSingleRoot(
- children: VNodeArrayChildren
+ children: VNodeArrayChildren,
): VNode | undefined {
let singleRoot
for (let i = 0; i < children.length; i++) {
export function shouldUpdateComponent(
prevVNode: VNode,
nextVNode: VNode,
- optimized?: boolean
+ optimized?: boolean,
): boolean {
const { props: prevProps, children: prevChildren, component } = prevVNode
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
function hasPropsChanged(
prevProps: Data,
nextProps: Data,
- emitsOptions: ComponentInternalInstance['emitsOptions']
+ emitsOptions: ComponentInternalInstance['emitsOptions'],
): boolean {
const nextKeys = Object.keys(nextProps)
if (nextKeys.length !== Object.keys(prevProps).length) {
export function updateHOCHostEl(
{ vnode, parent }: ComponentInternalInstance,
- el: typeof vnode.el // HostNode
+ el: typeof vnode.el, // HostNode
) {
if (!el) return
while (parent) {
-import { ComponentInternalInstance, currentInstance } from './component'
+import { type ComponentInternalInstance, currentInstance } from './component'
import {
- VNode,
- VNodeNormalizedChildren,
+ InternalObjectKey,
+ type VNode,
+ type VNodeChild,
+ type VNodeNormalizedChildren,
normalizeVNode,
- VNodeChild,
- InternalObjectKey
} from './vnode'
import {
- isArray,
- isFunction,
EMPTY_OBJ,
+ type IfAny,
+ type Prettify,
ShapeFlags,
- extend,
- def,
SlotFlags,
- Prettify,
- IfAny
+ def,
+ extend,
+ isArray,
+ isFunction,
} from '@vue/shared'
import { warn } from './warning'
import { isKeepAlive } from './components/KeepAlive'
-import { ContextualRenderFn, withCtx } from './componentRenderContext'
+import { type ContextualRenderFn, withCtx } from './componentRenderContext'
import { isHmrUpdating } from './hmr'
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
import { toRaw } from '@vue/reactivity'
export type StrictUnwrapSlotsType<
S extends SlotsType,
- T = NonNullable<S[typeof SlotSymbol]>
+ T = NonNullable<S[typeof SlotSymbol]>,
> = [keyof S] extends [never] ? Slots : Readonly<T> & T
export type UnwrapSlotsType<
S extends SlotsType,
- T = NonNullable<S[typeof SlotSymbol]>
+ T = NonNullable<S[typeof SlotSymbol]>,
> = [keyof S] extends [never]
? Slots
: Readonly<
const normalizeSlot = (
key: string,
rawSlot: Function,
- ctx: ComponentInternalInstance | null | undefined
+ ctx: ComponentInternalInstance | null | undefined,
): Slot => {
if ((rawSlot as any)._n) {
// already normalized - #5353
warn(
`Slot "${key}" invoked outside of the render function: ` +
`this will not track dependencies used in the slot. ` +
- `Invoke the slot function inside the render function instead.`
+ `Invoke the slot function inside the render function instead.`,
)
}
return normalizeSlotValue(rawSlot(...args))
const normalizeObjectSlots = (
rawSlots: RawSlots,
slots: InternalSlots,
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
) => {
const ctx = rawSlots._ctx
for (const key in rawSlots) {
) {
warn(
`Non-function value encountered for slot "${key}". ` +
- `Prefer function slots for better performance.`
+ `Prefer function slots for better performance.`,
)
}
const normalized = normalizeSlotValue(value)
const normalizeVNodeSlots = (
instance: ComponentInternalInstance,
- children: VNodeNormalizedChildren
+ children: VNodeNormalizedChildren,
) => {
if (
__DEV__ &&
) {
warn(
`Non-function value encountered for default slot. ` +
- `Prefer function slots for better performance.`
+ `Prefer function slots for better performance.`,
)
}
const normalized = normalizeSlotValue(children)
export const initSlots = (
instance: ComponentInternalInstance,
- children: VNodeNormalizedChildren
+ children: VNodeNormalizedChildren,
) => {
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
const type = (children as RawSlots)._
normalizeObjectSlots(
children as RawSlots,
(instance.slots = {}),
- instance
+ instance,
)
}
} else {
export const updateSlots = (
instance: ComponentInternalInstance,
children: VNodeNormalizedChildren,
- optimized: boolean
+ optimized: boolean,
) => {
const { vnode, slots } = instance
let needDeletionCheck = true
import {
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ type SetupContext,
getCurrentInstance,
- SetupContext,
- ComponentInternalInstance,
- ComponentOptions
} from '../component'
import {
- cloneVNode,
Comment,
+ Fragment,
+ type VNode,
+ type VNodeArrayChildren,
+ cloneVNode,
isSameVNodeType,
- VNode,
- VNodeArrayChildren,
- Fragment
} from '../vnode'
import { warn } from '../warning'
import { isKeepAlive } from './KeepAlive'
import { toRaw } from '@vue/reactivity'
-import { callWithAsyncErrorHandling, ErrorCodes } from '../errorHandling'
-import { ShapeFlags, PatchFlags, isArray } from '@vue/shared'
+import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
+import { PatchFlags, ShapeFlags, isArray } from '@vue/shared'
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
-import { RendererElement } from '../renderer'
+import type { RendererElement } from '../renderer'
type Hook<T = () => void> = T | T[]
delayLeave?(
el: HostElement,
earlyRemove: () => void,
- delayedLeave: () => void
+ delayedLeave: () => void,
): void
delayedLeave?(): void
}
export type TransitionHookCaller = <T extends any[] = [el: any]>(
hook: Hook<(...args: T) => void> | undefined,
- args?: T
+ args?: T,
) => void
export type PendingCallback = (cancelled?: boolean) => void
isMounted: false,
isLeaving: false,
isUnmounting: false,
- leavingVNodes: new Map()
+ leavingVNodes: new Map(),
}
onMounted(() => {
state.isMounted = true
onBeforeAppear: TransitionHookValidator,
onAppear: TransitionHookValidator,
onAfterAppear: TransitionHookValidator,
- onAppearCancelled: TransitionHookValidator
+ onAppearCancelled: TransitionHookValidator,
}
const BaseTransitionImpl: ComponentOptions = {
// warn more than one non-comment child
warn(
'<transition> can only be used on a single element or component. ' +
- 'Use <transition-group> for lists.'
+ 'Use <transition-group> for lists.',
)
break
}
innerChild,
rawProps,
state,
- instance
+ instance,
)
setTransitionHooks(innerChild, enterHooks)
oldInnerChild,
rawProps,
state,
- instance
+ instance,
)
// update old tree's hooks in case of dynamic transition
setTransitionHooks(oldInnerChild, leavingHooks)
leavingHooks.delayLeave = (
el: TransitionElement,
earlyRemove,
- delayedLeave
+ delayedLeave,
) => {
const leavingVNodesCache = getLeavingNodesForType(
state,
- oldInnerChild
+ oldInnerChild,
)
leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild
// early removal callback
return child
}
- }
+ },
}
if (__COMPAT__) {
function getLeavingNodesForType(
state: TransitionState,
- vnode: VNode
+ vnode: VNode,
): Record<string, VNode> {
const { leavingVNodes } = state
let leavingVNodesCache = leavingVNodes.get(vnode.type)!
vnode: VNode,
props: BaseTransitionProps<any>,
state: TransitionState,
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): TransitionHooks {
const {
appear,
onBeforeAppear,
onAppear,
onAfterAppear,
- onAppearCancelled
+ onAppearCancelled,
} = props
const key = String(vnode.key)
const leavingVNodesCache = getLeavingNodesForType(state, vnode)
hook,
instance,
ErrorCodes.TRANSITION_HOOK,
- args
+ args,
)
}
const callAsyncHook = (
hook: Hook<(el: any, done: () => void) => void>,
- args: [TransitionElement, () => void]
+ args: [TransitionElement, () => void],
) => {
const done = args[1]
callHook(hook, args)
clone(vnode) {
return resolveTransitionHooks(vnode, props, state, instance)
- }
+ },
}
return hooks
export function getTransitionRawChildren(
children: VNode[],
keepComment: boolean = false,
- parentKey?: VNode['key']
+ parentKey?: VNode['key'],
): VNode[] {
let ret: VNode[] = []
let keyedFragmentCount = 0
if (child.type === Fragment) {
if (child.patchFlag & PatchFlags.KEYED_FRAGMENT) keyedFragmentCount++
ret = ret.concat(
- getTransitionRawChildren(child.children as VNode[], keepComment, key)
+ getTransitionRawChildren(child.children as VNode[], keepComment, key),
)
}
// comment placeholders should be skipped, e.g. v-if
import {
- ConcreteComponent,
- getCurrentInstance,
- SetupContext,
- ComponentInternalInstance,
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ type ConcreteComponent,
+ type SetupContext,
currentInstance,
getComponentName,
- ComponentOptions
+ getCurrentInstance,
} from '../component'
import {
- VNode,
+ type VNode,
+ type VNodeProps,
cloneVNode,
- isVNode,
- VNodeProps,
invokeVNodeHook,
- isSameVNodeType
+ isSameVNodeType,
+ isVNode,
} from '../vnode'
import { warn } from '../warning'
import {
- onBeforeUnmount,
injectHook,
- onUnmounted,
+ onBeforeUnmount,
onMounted,
- onUpdated
+ onUnmounted,
+ onUpdated,
} from '../apiLifecycle'
import {
- isString,
+ ShapeFlags,
+ invokeArrayFns,
isArray,
isRegExp,
- ShapeFlags,
+ isString,
remove,
- invokeArrayFns
} from '@vue/shared'
import { watch } from '../apiWatch'
import {
- RendererInternals,
- queuePostRenderEffect,
+ type ElementNamespace,
MoveType,
- RendererElement,
- RendererNode,
- ElementNamespace
+ type RendererElement,
+ type RendererInternals,
+ type RendererNode,
+ queuePostRenderEffect,
} from '../renderer'
import { setTransitionHooks } from './BaseTransition'
-import { ComponentRenderContext } from '../componentPublicInstance'
+import type { ComponentRenderContext } from '../componentPublicInstance'
import { devtoolsComponentAdded } from '../devtools'
import { isAsyncWrapper } from '../apiAsyncComponent'
import { isSuspense } from './Suspense'
container: RendererElement,
anchor: RendererNode | null,
namespace: ElementNamespace,
- optimized: boolean
+ optimized: boolean,
) => void
deactivate: (vnode: VNode) => void
}
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
- max: [String, Number]
+ max: [String, Number],
},
setup(props: KeepAliveProps, { slots }: SetupContext) {
p: patch,
m: move,
um: _unmount,
- o: { createElement }
- }
+ o: { createElement },
+ },
} = sharedContext
const storageContainer = createElement('div')
container,
anchor,
namespace,
- optimized
+ optimized,
) => {
const instance = vnode.component!
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
parentSuspense,
namespace,
vnode.slotScopeIds,
- optimized
+ optimized,
)
queuePostRenderEffect(() => {
instance.isDeactivated = false
exclude && pruneCache(name => !matches(exclude, name))
},
// prune post-render after `current` has been updated
- { flush: 'post', deep: true }
+ { flush: 'post', deep: true },
)
// cache sub tree after render
const name = getComponentName(
isAsyncWrapper(vnode)
? (vnode.type as ComponentOptions).__asyncResolved || {}
- : comp
+ : comp,
)
const { include, exclude, max } = props
current = vnode
return isSuspense(rawVNode.type) ? rawVNode : vnode
}
- }
+ },
}
if (__COMPAT__) {
export function onActivated(
hook: Function,
- target?: ComponentInternalInstance | null
+ target?: ComponentInternalInstance | null,
) {
registerKeepAliveHook(hook, LifecycleHooks.ACTIVATED, target)
}
export function onDeactivated(
hook: Function,
- target?: ComponentInternalInstance | null
+ target?: ComponentInternalInstance | null,
) {
registerKeepAliveHook(hook, LifecycleHooks.DEACTIVATED, target)
}
function registerKeepAliveHook(
hook: Function & { __wdc?: Function },
type: LifecycleHooks,
- target: ComponentInternalInstance | null = currentInstance
+ target: ComponentInternalInstance | null = currentInstance,
) {
// cache the deactivate branch check wrapper for injected hooks so the same
// hook can be properly deduped by the scheduler. "__wdc" stands for "with
hook: Function & { __weh?: Function },
type: LifecycleHooks,
target: ComponentInternalInstance,
- keepAliveRoot: ComponentInternalInstance
+ keepAliveRoot: ComponentInternalInstance,
) {
// injectHook wraps the original for error handling, so make sure to remove
// the wrapped version.
import {
- VNode,
- normalizeVNode,
- VNodeProps,
- isSameVNodeType,
- openBlock,
- closeBlock,
- currentBlock,
Comment,
+ type VNode,
+ type VNodeProps,
+ closeBlock,
createVNode,
- isBlockTreeEnabled
+ currentBlock,
+ isBlockTreeEnabled,
+ isSameVNodeType,
+ normalizeVNode,
+ openBlock,
} from '../vnode'
-import { isFunction, isArray, ShapeFlags, toNumber } from '@vue/shared'
-import { ComponentInternalInstance, handleSetupResult } from '../component'
-import { Slots } from '../componentSlots'
+import { ShapeFlags, isArray, isFunction, toNumber } from '@vue/shared'
+import { type ComponentInternalInstance, handleSetupResult } from '../component'
+import type { Slots } from '../componentSlots'
import {
- RendererInternals,
+ type ElementNamespace,
MoveType,
- SetupRenderEffectFn,
- RendererNode,
- RendererElement,
- ElementNamespace
+ type RendererElement,
+ type RendererInternals,
+ type RendererNode,
+ type SetupRenderEffectFn,
} from '../renderer'
import { queuePostFlushCb } from '../scheduler'
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
import {
- pushWarningContext,
+ assertNumber,
popWarningContext,
+ pushWarningContext,
warn,
- assertNumber
} from '../warning'
-import { handleError, ErrorCodes } from '../errorHandling'
+import { ErrorCodes, handleError } from '../errorHandling'
import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'
export interface SuspenseProps {
slotScopeIds: string[] | null,
optimized: boolean,
// platform-specific impl passed from renderer
- rendererInternals: RendererInternals
+ rendererInternals: RendererInternals,
) {
if (n1 == null) {
mountSuspense(
namespace,
slotScopeIds,
optimized,
- rendererInternals
+ rendererInternals,
)
} else {
patchSuspense(
namespace,
slotScopeIds,
optimized,
- rendererInternals
+ rendererInternals,
)
}
},
hydrate: hydrateSuspense,
create: createSuspenseBoundary,
- normalize: normalizeSuspenseChildren
+ normalize: normalizeSuspenseChildren,
}
// Force-casted public typing for h and TSX props inference
function triggerEvent(
vnode: VNode,
- name: 'onResolve' | 'onPending' | 'onFallback'
+ name: 'onResolve' | 'onPending' | 'onFallback',
) {
const eventListener = vnode.props && vnode.props[name]
if (isFunction(eventListener)) {
namespace: ElementNamespace,
slotScopeIds: string[] | null,
optimized: boolean,
- rendererInternals: RendererInternals
+ rendererInternals: RendererInternals,
) {
const {
p: patch,
- o: { createElement }
+ o: { createElement },
} = rendererInternals
const hiddenContainer = createElement('div')
const suspense = (vnode.suspense = createSuspenseBoundary(
namespace,
slotScopeIds,
optimized,
- rendererInternals
+ rendererInternals,
))
// start mounting the content subtree in an off-dom container
parentComponent,
suspense,
namespace,
- slotScopeIds
+ slotScopeIds,
)
// now check if we have encountered any async deps
if (suspense.deps > 0) {
parentComponent,
null, // fallback tree will not have suspense context
namespace,
- slotScopeIds
+ slotScopeIds,
)
setActiveBranch(suspense, vnode.ssFallback!)
} else {
namespace: ElementNamespace,
slotScopeIds: string[] | null,
optimized: boolean,
- { p: patch, um: unmount, o: { createElement } }: RendererInternals
+ { p: patch, um: unmount, o: { createElement } }: RendererInternals,
) {
const suspense = (n2.suspense = n1.suspense)!
suspense.vnode = n2
suspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
if (suspense.deps <= 0) {
suspense.resolve()
null, // fallback tree will not have suspense context
namespace,
slotScopeIds,
- optimized
+ optimized,
)
setActiveBranch(suspense, newFallback)
}
suspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
if (suspense.deps <= 0) {
suspense.resolve()
null, // fallback tree will not have suspense context
namespace,
slotScopeIds,
- optimized
+ optimized,
)
setActiveBranch(suspense, newFallback)
}
suspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
// force resolve
suspense.resolve(true)
suspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
if (suspense.deps <= 0) {
suspense.resolve()
suspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
setActiveBranch(suspense, newBranch)
} else {
suspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
if (suspense.deps <= 0) {
// incoming branch has no async deps, resolve now.
move(
container: RendererElement,
anchor: RendererNode | null,
- type: MoveType
+ type: MoveType,
): void
next(): RendererNode | null
registerDep(
instance: ComponentInternalInstance,
- setupRenderEffect: SetupRenderEffectFn
+ setupRenderEffect: SetupRenderEffectFn,
): void
unmount(parentSuspense: SuspenseBoundary | null, doRemove?: boolean): void
}
slotScopeIds: string[] | null,
optimized: boolean,
rendererInternals: RendererInternals,
- isHydrating = false
+ isHydrating = false,
): SuspenseBoundary {
/* istanbul ignore if */
if (__DEV__ && !__TEST__ && !hasWarned) {
hasWarned = true
- // @ts-ignore `console.info` cannot be null error
+ // @ts-expect-error `console.info` cannot be null error
console[console.info ? 'info' : 'log'](
- `<Suspense> is an experimental feature and its API will likely change.`
+ `<Suspense> is an experimental feature and its API will likely change.`,
)
}
m: move,
um: unmount,
n: next,
- o: { parentNode, remove }
+ o: { parentNode, remove },
} = rendererInternals
// if set `suspensible: true`, set the current suspense as a dep of parent suspense
if (__DEV__) {
if (!resume && !suspense.pendingBranch) {
throw new Error(
- `suspense.resolve() is called without a pending branch.`
+ `suspense.resolve() is called without a pending branch.`,
)
}
if (suspense.isUnmounted) {
throw new Error(
- `suspense.resolve() is called on an already unmounted suspense boundary.`
+ `suspense.resolve() is called on an already unmounted suspense boundary.`,
)
}
}
pendingId,
effects,
parentComponent,
- container
+ container,
} = suspense
// if there's a transition happening we need to wait it to finish.
pendingBranch!,
container,
next(activeBranch!),
- MoveType.ENTER
+ MoveType.ENTER,
)
queuePostFlushCb(effects)
}
null, // fallback tree will not have suspense context
namespace,
slotScopeIds,
- optimized
+ optimized,
)
setActiveBranch(suspense, fallbackVNode)
}
activeBranch!,
parentComponent,
null, // no suspense so unmount hooks fire now
- true // shouldRemove
+ true, // shouldRemove
)
if (!delayEnter) {
hydratedEl ? null : next(instance.subTree),
suspense,
namespace,
- optimized
+ optimized,
)
if (placeholder) {
remove(placeholder)
suspense.activeBranch,
parentComponent,
parentSuspense,
- doRemove
+ doRemove,
)
}
if (suspense.pendingBranch) {
suspense.pendingBranch,
parentComponent,
parentSuspense,
- doRemove
+ doRemove,
)
}
- }
+ },
}
return suspense
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
- optimized: boolean
- ) => Node | null
+ optimized: boolean,
+ ) => Node | null,
): Node | null {
- /* eslint-disable no-restricted-globals */
const suspense = (vnode.suspense = createSuspenseBoundary(
vnode,
parentSuspense,
parentComponent,
node.parentNode!,
+ // eslint-disable-next-line no-restricted-globals
document.createElement('div'),
null,
namespace,
slotScopeIds,
optimized,
rendererInternals,
- true /* hydrating */
+ true /* hydrating */,
))
// there are two possible scenarios for server-rendered suspense:
// - success: ssr content should be fully resolved
parentComponent,
suspense,
slotScopeIds,
- optimized
+ optimized,
)
if (suspense.deps === 0) {
suspense.resolve(false, true)
const { shapeFlag, children } = vnode
const isSlotChildren = shapeFlag & ShapeFlags.SLOTS_CHILDREN
vnode.ssContent = normalizeSuspenseSlot(
- isSlotChildren ? (children as Slots).default : children
+ isSlotChildren ? (children as Slots).default : children,
)
vnode.ssFallback = isSlotChildren
? normalizeSuspenseSlot((children as Slots).fallback)
export function queueEffectWithSuspense(
fn: Function | Function[],
- suspense: SuspenseBoundary | null
+ suspense: SuspenseBoundary | null,
): void {
if (suspense && suspense.pendingBranch) {
if (isArray(fn)) {
-import { ComponentInternalInstance } from '../component'
-import { SuspenseBoundary } from './Suspense'
+import type { ComponentInternalInstance } from '../component'
+import type { SuspenseBoundary } from './Suspense'
import {
- RendererInternals,
+ type ElementNamespace,
MoveType,
- RendererElement,
- RendererNode,
- RendererOptions,
+ type RendererElement,
+ type RendererInternals,
+ type RendererNode,
+ type RendererOptions,
traverseStaticChildren,
- ElementNamespace
} from '../renderer'
-import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
-import { isString, ShapeFlags } from '@vue/shared'
+import type { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
+import { ShapeFlags, isString } from '@vue/shared'
import { warn } from '../warning'
import { isHmrUpdating } from '../hmr'
const resolveTarget = <T = RendererElement>(
props: TeleportProps | null,
- select: RendererOptions['querySelector']
+ select: RendererOptions['querySelector'],
): T | null => {
const targetSelector = props && props.to
if (isString(targetSelector)) {
__DEV__ &&
warn(
`Current renderer does not support string target for Teleports. ` +
- `(missing querySelector renderer option)`
+ `(missing querySelector renderer option)`,
)
return null
} else {
`Failed to locate Teleport target with selector "${targetSelector}". ` +
`Note the target element must exist before the component is mounted - ` +
`i.e. the target cannot be rendered by the component itself, and ` +
- `ideally should be outside of the entire Vue component tree.`
+ `ideally should be outside of the entire Vue component tree.`,
)
}
return target as T
namespace: ElementNamespace,
slotScopeIds: string[] | null,
optimized: boolean,
- internals: RendererInternals
+ internals: RendererInternals,
) {
const {
mc: mountChildren,
pc: patchChildren,
pbc: patchBlockChildren,
- o: { insert, querySelector, createText, createComment }
+ o: { insert, querySelector, createText, createComment },
} = internals
const disabled = isTeleportDisabled(n2.props)
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
}
}
parentComponent,
parentSuspense,
namespace,
- slotScopeIds
+ slotScopeIds,
)
// even in block tree mode we need to make sure all root-level nodes
// in the teleport inherit previous DOM references so that they can
parentSuspense,
namespace,
slotScopeIds,
- false
+ false,
)
}
container,
mainAnchor,
internals,
- TeleportMoveTypes.TOGGLE
+ TeleportMoveTypes.TOGGLE,
)
} else {
// #7835
if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
const nextTarget = (n2.target = resolveTarget(
n2.props,
- querySelector
+ querySelector,
))
if (nextTarget) {
moveTeleport(
nextTarget,
null,
internals,
- TeleportMoveTypes.TARGET_CHANGE
+ TeleportMoveTypes.TARGET_CHANGE,
)
} else if (__DEV__) {
warn(
'Invalid Teleport target on update:',
target,
- `(${typeof target})`
+ `(${typeof target})`,
)
}
} else if (wasDisabled) {
target,
targetAnchor,
internals,
- TeleportMoveTypes.TOGGLE
+ TeleportMoveTypes.TOGGLE,
)
}
}
parentSuspense: SuspenseBoundary | null,
optimized: boolean,
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
- doRemove: boolean
+ doRemove: boolean,
) {
const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
parentComponent,
parentSuspense,
shouldRemove,
- !!child.dynamicChildren
+ !!child.dynamicChildren,
)
}
}
},
move: moveTeleport,
- hydrate: hydrateTeleport
+ hydrate: hydrateTeleport,
}
export enum TeleportMoveTypes {
TARGET_CHANGE,
TOGGLE, // enable / disable
- REORDER // moved in the main view
+ REORDER, // moved in the main view
}
function moveTeleport(
container: RendererElement,
parentAnchor: RendererNode | null,
{ o: { insert }, m: move }: RendererInternals,
- moveType: TeleportMoveTypes = TeleportMoveTypes.REORDER
+ moveType: TeleportMoveTypes = TeleportMoveTypes.REORDER,
) {
// move target anchor if this is a target change.
if (moveType === TeleportMoveTypes.TARGET_CHANGE) {
(children as VNode[])[i],
container,
parentAnchor,
- MoveType.REORDER
+ MoveType.REORDER,
)
}
}
slotScopeIds: string[] | null,
optimized: boolean,
{
- o: { nextSibling, parentNode, querySelector }
+ o: { nextSibling, parentNode, querySelector },
}: RendererInternals<Node, Element>,
hydrateChildren: (
node: Node | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
- optimized: boolean
- ) => Node | null
+ optimized: boolean,
+ ) => Node | null,
): Node | null {
const target = (vnode.target = resolveTarget<Element>(
vnode.props,
- querySelector
+ querySelector,
))
if (target) {
// if multiple teleports rendered to the same target element, we need to
parentComponent,
parentSuspense,
slotScopeIds,
- optimized
+ optimized,
)
vnode.targetAnchor = targetNode
} else {
parentComponent,
parentSuspense,
slotScopeIds,
- optimized
+ optimized,
)
}
}
-import { isReactive, isReadonly, isRef, Ref, toRaw } from '@vue/reactivity'
+import { type Ref, isReactive, isReadonly, isRef, toRaw } from '@vue/reactivity'
import { EMPTY_OBJ, extend, isArray, isFunction, isObject } from '@vue/shared'
import { isShallow } from '../../reactivity/src/reactive'
-import { ComponentInternalInstance, ComponentOptions } from './component'
-import { ComponentPublicInstance } from './componentPublicInstance'
+import type { ComponentInternalInstance, ComponentOptions } from './component'
+import type { ComponentPublicInstance } from './componentPublicInstance'
export function initCustomFormatter() {
/* eslint-disable no-restricted-globals */
['span', vueStyle, genRefFlag(obj)],
'<',
formatValue(obj.value),
- `>`
+ `>`,
]
} else if (isReactive(obj)) {
return [
['span', vueStyle, isShallow(obj) ? 'ShallowReactive' : 'Reactive'],
'<',
formatValue(obj),
- `>${isReadonly(obj) ? ` (readonly)` : ``}`
+ `>${isReadonly(obj) ? ` (readonly)` : ``}`,
]
} else if (isReadonly(obj)) {
return [
['span', vueStyle, isShallow(obj) ? 'ShallowReadonly' : 'Readonly'],
'<',
formatValue(obj),
- '>'
+ '>',
]
}
return null
return [
'div',
{},
- ...formatInstance((obj as ComponentPublicInstance).$)
+ ...formatInstance((obj as ComponentPublicInstance).$),
]
}
- }
+ },
}
function formatInstance(instance: ComponentInternalInstance) {
[
'span',
{
- style: keywordStyle.style + ';opacity:0.66'
+ style: keywordStyle.style + ';opacity:0.66',
},
- '$ (internal): '
+ '$ (internal): ',
],
- ['object', { object: instance }]
+ ['object', { object: instance }],
])
return blocks
}
[
'div',
{
- style: 'color:#476582'
+ style: 'color:#476582',
},
- type
+ type,
],
[
'div',
{
- style: 'padding-left:1.25em'
+ style: 'padding-left:1.25em',
},
...Object.keys(target).map(key => {
return [
'div',
{},
['span', keywordStyle, key + ': '],
- formatValue(target[key], false)
+ formatValue(target[key], false),
]
- })
- ]
+ }),
+ ],
]
}
/* eslint-disable no-restricted-globals */
-import { App } from './apiCreateApp'
-import { Fragment, Text, Comment, Static } from './vnode'
-import { ComponentInternalInstance } from './component'
+import type { App } from './apiCreateApp'
+import { Comment, Fragment, Static, Text } from './vnode'
+import type { ComponentInternalInstance } from './component'
interface AppRecord {
id: number
COMPONENT_REMOVED = 'component:removed',
COMPONENT_EMIT = 'component:emit',
PERFORMANCE_START = 'perf:start',
- PERFORMANCE_END = 'perf:end'
+ PERFORMANCE_END = 'perf:end',
}
export interface DevtoolsHook {
Fragment,
Text,
Comment,
- Static
+ Static,
})
}
}
export const devtoolsComponentAdded = /*#__PURE__*/ createDevtoolsComponentHook(
- DevtoolsHooks.COMPONENT_ADDED
+ DevtoolsHooks.COMPONENT_ADDED,
)
export const devtoolsComponentUpdated =
/*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED)
const _devtoolsComponentRemoved = /*#__PURE__*/ createDevtoolsComponentHook(
- DevtoolsHooks.COMPONENT_REMOVED
+ DevtoolsHooks.COMPONENT_REMOVED,
)
export const devtoolsComponentRemoved = (
- component: ComponentInternalInstance
+ component: ComponentInternalInstance,
) => {
if (
devtools &&
component.appContext.app,
component.uid,
component.parent ? component.parent.uid : undefined,
- component
+ component,
)
}
}
export const devtoolsPerfStart = /*#__PURE__*/ createDevtoolsPerformanceHook(
- DevtoolsHooks.PERFORMANCE_START
+ DevtoolsHooks.PERFORMANCE_START,
)
export const devtoolsPerfEnd = /*#__PURE__*/ createDevtoolsPerformanceHook(
- DevtoolsHooks.PERFORMANCE_END
+ DevtoolsHooks.PERFORMANCE_END,
)
function createDevtoolsPerformanceHook(hook: DevtoolsHooks) {
export function devtoolsComponentEmit(
component: ComponentInternalInstance,
event: string,
- params: any[]
+ params: any[],
) {
emit(
DevtoolsHooks.COMPONENT_EMIT,
component.appContext.app,
component,
event,
- params
+ params,
)
}
])
*/
-import { VNode } from './vnode'
-import { isFunction, EMPTY_OBJ, isBuiltInDirective } from '@vue/shared'
+import type { VNode } from './vnode'
+import { EMPTY_OBJ, isBuiltInDirective, isFunction } from '@vue/shared'
import { warn } from './warning'
-import { ComponentInternalInstance, Data, getExposeProxy } from './component'
+import {
+ type ComponentInternalInstance,
+ type Data,
+ getExposeProxy,
+} from './component'
import { currentRenderingInstance } from './componentRenderContext'
-import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
-import { ComponentPublicInstance } from './componentPublicInstance'
+import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
+import type { ComponentPublicInstance } from './componentPublicInstance'
import { mapCompatDirectiveHook } from './compat/customDirective'
import { pauseTracking, resetTracking } from '@vue/reactivity'
import { traverse } from './apiWatch'
el: T,
binding: DirectiveBinding<V>,
vnode: VNode<any, T>,
- prevVNode: Prev
+ prevVNode: Prev,
) => void
export type SSRDirectiveHook = (
binding: DirectiveBinding,
- vnode: VNode
+ vnode: VNode,
) => Data | undefined
export interface ObjectDirective<T = any, V = any> {
*/
export function withDirectives<T extends VNode>(
vnode: T,
- directives: DirectiveArguments
+ directives: DirectiveArguments,
): T {
const internalInstance = currentRenderingInstance
if (internalInstance === null) {
if (isFunction(dir)) {
dir = {
mounted: dir,
- updated: dir
+ updated: dir,
} as ObjectDirective
}
if (dir.deep) {
value,
oldValue: void 0,
arg,
- modifiers
+ modifiers,
})
}
}
vnode: VNode,
prevVNode: VNode | null,
instance: ComponentInternalInstance | null,
- name: keyof ObjectDirective
+ name: keyof ObjectDirective,
) {
const bindings = vnode.dirs!
const oldBindings = prevVNode && prevVNode.dirs!
vnode.el,
binding,
vnode,
- prevVNode
+ prevVNode,
])
resetTracking()
}
RENDER_TRIGGERED = 'rtg',
RENDER_TRACKED = 'rtc',
ERROR_CAPTURED = 'ec',
- SERVER_PREFETCH = 'sp'
+ SERVER_PREFETCH = 'sp',
}
-import { VNode } from './vnode'
-import { ComponentInternalInstance } from './component'
-import { warn, pushWarningContext, popWarningContext } from './warning'
-import { isPromise, isFunction } from '@vue/shared'
+import type { VNode } from './vnode'
+import type { ComponentInternalInstance } from './component'
+import { popWarningContext, pushWarningContext, warn } from './warning'
+import { isFunction, isPromise } from '@vue/shared'
import { LifecycleHooks } from './enums'
// contexts where user provided function may be executed, in addition to
APP_WARN_HANDLER,
FUNCTION_REF,
ASYNC_COMPONENT_LOADER,
- SCHEDULER
+ SCHEDULER,
}
export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
[ErrorCodes.SCHEDULER]:
'scheduler flush. This is likely a Vue internals bug. ' +
- 'Please open an issue at https://github.com/vuejs/core .'
+ 'Please open an issue at https://github.com/vuejs/core .',
}
export type ErrorTypes = LifecycleHooks | ErrorCodes
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
- args?: unknown[]
+ args?: unknown[],
) {
let res
try {
fn: Function | Function[],
instance: ComponentInternalInstance | null,
type: ErrorTypes,
- args?: unknown[]
+ args?: unknown[],
): any[] {
if (isFunction(fn)) {
const res = callWithErrorHandling(fn, instance, type, args)
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
- throwInDev = true
+ throwInDev = true,
) {
const contextVNode = instance ? instance.vnode : null
if (instance) {
appErrorHandler,
null,
ErrorCodes.APP_ERROR_HANDLER,
- [err, exposedInstance, errorInfo]
+ [err, exposedInstance, errorInfo],
)
return
}
err: unknown,
type: ErrorTypes,
contextVNode: VNode | null,
- throwInDev = true
+ throwInDev = true,
) {
if (__DEV__) {
const info = ErrorTypeStrings[type]
`which expects these compile-time feature flags to be globally injected ` +
`via the bundler config in order to get better tree-shaking in the ` +
`production bundle.\n\n` +
- `For more details, see https://link.vuejs.org/feature-flags.`
+ `For more details, see https://link.vuejs.org/feature-flags.`,
)
}
}
import {
- VNode,
- VNodeProps,
+ type Comment,
+ type Fragment,
+ type Text,
+ type VNode,
+ type VNodeArrayChildren,
+ type VNodeProps,
createVNode,
- VNodeArrayChildren,
- Fragment,
- Text,
- Comment,
- isVNode
+ isVNode,
} from './vnode'
-import { Teleport, TeleportProps } from './components/Teleport'
-import { Suspense, SuspenseProps } from './components/Suspense'
-import { isObject, isArray } from '@vue/shared'
-import { RawSlots } from './componentSlots'
-import {
- FunctionalComponent,
+import type { Teleport, TeleportProps } from './components/Teleport'
+import type { Suspense, SuspenseProps } from './components/Suspense'
+import { isArray, isObject } from '@vue/shared'
+import type { RawSlots } from './componentSlots'
+import type {
Component,
ComponentOptions,
- ConcreteComponent
+ ConcreteComponent,
+ FunctionalComponent,
} from './component'
-import { EmitsOptions } from './componentEmits'
-import { DefineComponent } from './apiDefineComponent'
+import type { EmitsOptions } from './componentEmits'
+import type { DefineComponent } from './apiDefineComponent'
// `h` is a more user-friendly version of `createVNode` that allows omitting the
// props when possible. It is intended for manually written render functions.
type HTMLElementEventHandler = {
[K in keyof HTMLElementEventMap as `on${Capitalize<K>}`]?: (
- ev: HTMLElementEventMap[K]
+ ev: HTMLElementEventMap[K],
) => any
}
// element
export function h<K extends keyof HTMLElementTagNameMap>(
type: K,
- children?: RawChildren
+ children?: RawChildren,
): VNode
export function h<K extends keyof HTMLElementTagNameMap>(
type: K,
props?: (RawProps & HTMLElementEventHandler) | null,
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// custom element
export function h(
type: string,
props?: RawProps | null,
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// text/comment
export function h(
type: typeof Text | typeof Comment,
- children?: string | number | boolean
+ children?: string | number | boolean,
): VNode
export function h(
type: typeof Text | typeof Comment,
props?: null,
- children?: string | number | boolean
+ children?: string | number | boolean,
): VNode
// fragment
export function h(type: typeof Fragment, children?: VNodeArrayChildren): VNode
export function h(
type: typeof Fragment,
props?: RawProps | null,
- children?: VNodeArrayChildren
+ children?: VNodeArrayChildren,
): VNode
// teleport (target prop is required)
export function h(
type: typeof Teleport,
props: RawProps & TeleportProps,
- children: RawChildren | RawSlots
+ children: RawChildren | RawSlots,
): VNode
// suspense
export function h(
type: typeof Suspense,
props?: (RawProps & SuspenseProps) | null,
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// functional component
export function h<
P,
E extends EmitsOptions = {},
- S extends Record<string, any> = {}
+ S extends Record<string, any> = {},
>(
type: FunctionalComponent<P, E, S>,
props?: (RawProps & P) | ({} extends P ? null : never),
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// catch-all for generic component types
// concrete component
export function h<P>(
type: ConcreteComponent | string,
- children?: RawChildren
+ children?: RawChildren,
): VNode
export function h<P>(
type: ConcreteComponent<P> | string,
props?: (RawProps & P) | ({} extends P ? null : never),
- children?: RawChildren
+ children?: RawChildren,
): VNode
// component without props
export function h<P>(
type: Component<P>,
props?: (RawProps & P) | null,
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// exclude `defineComponent` constructors
export function h<P>(
type: ComponentOptions<P>,
props?: (RawProps & P) | ({} extends P ? null : never),
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// fake constructor type returned by `defineComponent` or class component
export function h<P>(
type: Constructor<P>,
props?: (RawProps & P) | ({} extends P ? null : never),
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// fake constructor type returned by `defineComponent`
export function h<P>(
type: DefineComponent<P>,
props?: (RawProps & P) | ({} extends P ? null : never),
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// catch all types
export function h<P>(
type: string | Component<P>,
props?: (RawProps & P) | ({} extends P ? null : never),
- children?: RawChildren | RawSlots
+ children?: RawChildren | RawSlots,
): VNode
// Actual implementation
import { isArray } from '@vue/shared'
-import { VNode } from '../vnode'
+import type { VNode } from '../vnode'
// #6651 res can be undefined in SSR in string push mode
type SSRSlot = (...args: any[]) => VNode[] | undefined
| CompiledSlotDescriptor
| CompiledSlotDescriptor[]
| undefined
- )[]
+ )[],
): Record<string, SSRSlot> {
for (let i = 0; i < dynamicSlots.length; i++) {
const slot = dynamicSlots[i]
-import { VNode, VNodeChild } from '../vnode'
-import { isArray, isString, isObject } from '@vue/shared'
+import type { VNode, VNodeChild } from '../vnode'
+import { isArray, isObject, isString } from '@vue/shared'
import { warn } from '../warning'
/**
*/
export function renderList(
source: string,
- renderItem: (value: string, index: number) => VNodeChild
+ renderItem: (value: string, index: number) => VNodeChild,
): VNodeChild[]
/**
*/
export function renderList(
source: number,
- renderItem: (value: number, index: number) => VNodeChild
+ renderItem: (value: number, index: number) => VNodeChild,
): VNodeChild[]
/**
*/
export function renderList<T>(
source: T[],
- renderItem: (value: T, index: number) => VNodeChild
+ renderItem: (value: T, index: number) => VNodeChild,
): VNodeChild[]
/**
*/
export function renderList<T>(
source: Iterable<T>,
- renderItem: (value: T, index: number) => VNodeChild
+ renderItem: (value: T, index: number) => VNodeChild,
): VNodeChild[]
/**
renderItem: <K extends keyof T>(
value: T[K],
key: K,
- index: number
- ) => VNodeChild
+ index: number,
+ ) => VNodeChild,
): VNodeChild[]
/**
source: any,
renderItem: (...args: any[]) => VNodeChild,
cache?: any[],
- index?: number
+ index?: number,
): VNodeChild[] {
let ret: VNodeChild[]
const cached = (cache && cache[index!]) as VNode[] | undefined
} else if (isObject(source)) {
if (source[Symbol.iterator as any]) {
ret = Array.from(source as Iterable<any>, (item, i) =>
- renderItem(item, i, undefined, cached && cached[i])
+ renderItem(item, i, undefined, cached && cached[i]),
)
} else {
const keys = Object.keys(source)
-import { Data } from '../component'
-import { Slots, RawSlots } from '../componentSlots'
+import type { Data } from '../component'
+import type { RawSlots, Slots } from '../componentSlots'
import {
- ContextualRenderFn,
- currentRenderingInstance
+ type ContextualRenderFn,
+ currentRenderingInstance,
} from '../componentRenderContext'
import {
Comment,
+ Fragment,
+ type VNode,
+ type VNodeArrayChildren,
+ createBlock,
isVNode,
- VNodeArrayChildren,
openBlock,
- createBlock,
- Fragment,
- VNode
} from '../vnode'
import { PatchFlags, SlotFlags } from '@vue/shared'
import { warn } from '../warning'
// this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be a function returning an array
fallback?: () => VNodeArrayChildren,
- noSlotted?: boolean
+ noSlotted?: boolean,
): VNode {
if (
currentRenderingInstance!.isCE ||
warn(
`SSR-optimized slot function detected in a non-SSR-optimized render ` +
`function. You need to mark this component with $dynamic-slots in the ` +
- `parent template.`
+ `parent template.`,
)
slot = () => []
}
// slot content array of a dynamic conditional slot may have a branch
// key attached in the `createSlots` helper, respect that
(validSlotContent && (validSlotContent as any).key) ||
- `_${name}`
+ `_${name}`,
},
validSlotContent || (fallback ? fallback() : []),
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
? PatchFlags.STABLE_FRAGMENT
- : PatchFlags.BAIL
+ : PatchFlags.BAIL,
)
if (!noSlotted && rendered.scopeId) {
rendered.slotScopeIds = [rendered.scopeId + '-s']
import {
+ type ComponentOptions,
+ type ConcreteComponent,
currentInstance,
- ConcreteComponent,
- ComponentOptions,
- getComponentName
+ getComponentName,
} from '../component'
import { currentRenderingInstance } from '../componentRenderContext'
-import { Directive } from '../directives'
+import type { Directive } from '../directives'
import { camelize, capitalize, isString } from '@vue/shared'
import { warn } from '../warning'
-import { VNodeTypes } from '../vnode'
+import type { VNodeTypes } from '../vnode'
export const COMPONENTS = 'components'
export const DIRECTIVES = 'directives'
*/
export function resolveComponent(
name: string,
- maybeSelfReference?: boolean
+ maybeSelfReference?: boolean,
): ConcreteComponent | string {
return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
}
type: typeof COMPONENTS,
name: string,
warnMissing?: boolean,
- maybeSelfReference?: boolean
+ maybeSelfReference?: boolean,
): ConcreteComponent | undefined
// overload 2: directives
function resolveAsset(
type: typeof DIRECTIVES,
- name: string
+ name: string,
): Directive | undefined
// implementation
// overload 3: filters (compat only)
type: AssetTypes,
name: string,
warnMissing = true,
- maybeSelfReference = false
+ maybeSelfReference = false,
) {
const instance = currentRenderingInstance || currentInstance
if (instance) {
if (type === COMPONENTS) {
const selfName = getComponentName(
Component,
- false /* do not include inferred name to avoid breaking existing code */
+ false /* do not include inferred name to avoid breaking existing code */,
)
if (
selfName &&
} else if (__DEV__) {
warn(
`resolve${capitalize(type.slice(0, -1))} ` +
- `can only be used in render() or setup().`
+ `can only be used in render() or setup().`,
)
}
}
-import { toHandlerKey, isObject } from '@vue/shared'
+import { isObject, toHandlerKey } from '@vue/shared'
import { warn } from '../warning'
/**
*/
export function toHandlers(
obj: Record<string, any>,
- preserveCaseIfNecessary?: boolean
+ preserveCaseIfNecessary?: boolean,
): Record<string, any> {
const ret: Record<string, any> = {}
if (__DEV__ && !isObject(obj)) {
__DEV__ &&
warn(
`Server rendering context not provided. Make sure to only call ` +
- `useSSRContext() conditionally in the server build.`
+ `useSSRContext() conditionally in the server build.`,
)
}
return ctx
import { hasChanged } from '@vue/shared'
-import { currentBlock, isBlockTreeEnabled, VNode } from '../vnode'
+import { type VNode, currentBlock, isBlockTreeEnabled } from '../vnode'
export function withMemo(
memo: any[],
render: () => VNode<any, any>,
cache: any[],
- index: number
+ index: number,
) {
const cached = cache[index] as VNode | undefined
if (cached && isMemoSame(cached, memo)) {
/* eslint-disable no-restricted-globals */
import {
- ConcreteComponent,
- ComponentInternalInstance,
- ComponentOptions,
- InternalRenderFunction,
- ClassComponent,
- isClassComponent
+ type ClassComponent,
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ type ConcreteComponent,
+ type InternalRenderFunction,
+ isClassComponent,
} from './component'
import { queueJob, queuePostFlushCb } from './scheduler'
import { extend, getGlobalThis } from '@vue/shared'
getGlobalThis().__VUE_HMR_RUNTIME__ = {
createRecord: tryWrap(createRecord),
rerender: tryWrap(rerender),
- reload: tryWrap(reload)
+ reload: tryWrap(reload),
} as HMRRuntime
}
}
map.set(id, {
initialDef: normalizeClassComponent(initialDef),
- instances: new Set()
+ instances: new Set(),
})
return true
}
window.location.reload()
} else {
console.warn(
- '[HMR] Root or manually mounted instance modified. Full reload required.'
+ '[HMR] Root or manually mounted instance modified. Full reload required.',
)
}
}
queuePostFlushCb(() => {
for (const instance of instances) {
hmrDirtyComponents.delete(
- normalizeClassComponent(instance.type as HMRComponent)
+ normalizeClassComponent(instance.type as HMRComponent),
)
}
})
function updateComponentDef(
oldComp: ComponentOptions,
- newComp: ComponentOptions
+ newComp: ComponentOptions,
) {
extend(oldComp, newComp)
for (const key in oldComp) {
console.error(e)
console.warn(
`[HMR] Something went wrong during Vue component hot-reload. ` +
- `Full reload required.`
+ `Full reload required.`,
)
}
}
import {
- VNode,
- normalizeVNode,
- Text,
Comment,
- Static,
Fragment,
- VNodeHook,
- createVNode,
+ Static,
+ Text,
+ type VNode,
+ type VNodeHook,
createTextVNode,
- invokeVNodeHook
+ createVNode,
+ invokeVNodeHook,
+ normalizeVNode,
} from './vnode'
import { flushPostFlushCbs } from './scheduler'
-import { ComponentInternalInstance } from './component'
+import type { ComponentInternalInstance } from './component'
import { invokeDirectiveHook } from './directives'
import { warn } from './warning'
import {
PatchFlags,
ShapeFlags,
- isReservedProp,
+ includeBooleanAttr,
+ isBooleanAttr,
+ isKnownHtmlAttr,
+ isKnownSvgAttr,
isOn,
+ isReservedProp,
+ isString,
normalizeClass,
normalizeStyle,
stringifyStyle,
- isBooleanAttr,
- isString,
- includeBooleanAttr,
- isKnownHtmlAttr,
- isKnownSvgAttr
} from '@vue/shared'
-import { needTransition, RendererInternals } from './renderer'
+import { type RendererInternals, needTransition } from './renderer'
import { setRef } from './rendererTemplateRef'
import {
- SuspenseImpl,
- SuspenseBoundary,
- queueEffectWithSuspense
+ type SuspenseBoundary,
+ type SuspenseImpl,
+ queueEffectWithSuspense,
} from './components/Suspense'
-import { TeleportImpl, TeleportVNode } from './components/Teleport'
+import type { TeleportImpl, TeleportVNode } from './components/Teleport'
import { isAsyncWrapper } from './apiAsyncComponent'
export type RootHydrateFunction = (
vnode: VNode<Node, Element>,
- container: (Element | ShadowRoot) & { _vnode?: VNode }
+ container: (Element | ShadowRoot) & { _vnode?: VNode },
) => void
enum DOMNodeTypes {
ELEMENT = 1,
TEXT = 3,
- COMMENT = 8
+ COMMENT = 8,
}
let hasMismatch = false
// Hydration also depends on some renderer internal logic which needs to be
// passed in via arguments.
export function createHydrationFunctions(
- rendererInternals: RendererInternals<Node, Element>
+ rendererInternals: RendererInternals<Node, Element>,
) {
const {
mt: mountComponent,
parentNode,
remove,
insert,
- createComment
- }
+ createComment,
+ },
} = rendererInternals
const hydrate: RootHydrateFunction = (vnode, container) => {
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
warn(
`Attempting to hydrate existing markup but container is empty. ` +
- `Performing full mount instead.`
+ `Performing full mount instead.`,
)
patch(null, vnode, container)
flushPostFlushCbs()
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
- optimized = false
+ optimized = false,
): Node | null => {
const isFragmentStart = isComment(node) && node.data === '['
const onMismatch = () =>
parentComponent,
parentSuspense,
slotScopeIds,
- isFragmentStart
+ isFragmentStart,
)
const { type, ref, shapeFlag, patchFlag } = vnode
if (!('__vnode' in node)) {
Object.defineProperty(node, '__vnode', {
value: vnode,
- enumerable: false
+ enumerable: false,
})
}
if (!('__vueParentComponent' in node)) {
Object.defineProperty(node, '__vueParentComponent', {
value: parentComponent,
- enumerable: false
+ enumerable: false,
})
}
}
`Hydration text mismatch in`,
node.parentNode,
`\n - rendered on server: ${JSON.stringify(
- (node as Text).data
+ (node as Text).data,
)}` +
- `\n - expected on client: ${JSON.stringify(vnode.children)}`
+ `\n - expected on client: ${JSON.stringify(vnode.children)}`,
)
;(node as Text).data = vnode.children as string
}
replaceNode(
(vnode.el = node.content.firstChild!),
node,
- parentComponent
+ parentComponent,
)
} else if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
nextNode = onMismatch()
parentComponent,
parentSuspense,
slotScopeIds,
- optimized
+ optimized,
)
}
break
parentComponent,
parentSuspense,
slotScopeIds,
- optimized
+ optimized,
)
}
} else if (shapeFlag & ShapeFlags.COMPONENT) {
parentComponent,
parentSuspense,
getContainerType(container),
- optimized
+ optimized,
)
// #3787
slotScopeIds,
optimized,
rendererInternals,
- hydrateChildren
+ hydrateChildren,
)
}
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
slotScopeIds,
optimized,
rendererInternals,
- hydrateNode
+ hydrateNode,
)
} else if (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) {
warn('Invalid HostVNode type:', type, `(${typeof type})`)
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
optimized = optimized || !!vnode.dynamicChildren
const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode
parentComponent,
parentSuspense,
slotScopeIds,
- optimized
+ optimized,
)
let hasWarned = false
while (next) {
warn(
`Hydration children mismatch on`,
el,
- `\nServer rendered element contains more child nodes than client vdom.`
+ `\nServer rendered element contains more child nodes than client vdom.`,
)
hasWarned = true
}
`Hydration text content mismatch on`,
el,
`\n - rendered on server: ${el.textContent}` +
- `\n - expected on client: ${vnode.children as string}`
+ `\n - expected on client: ${vnode.children as string}`,
)
el.textContent = vnode.children as string
}
props[key],
undefined,
undefined,
- parentComponent
+ parentComponent,
)
}
}
props.onClick,
undefined,
undefined,
- parentComponent
+ parentComponent,
)
}
}
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
): Node | null => {
optimized = optimized || !!parentVNode.dynamicChildren
const children = parentVNode.children as VNode[]
parentComponent,
parentSuspense,
slotScopeIds,
- optimized
+ optimized,
)
} else if (vnode.type === Text && !vnode.children) {
continue
warn(
`Hydration children mismatch on`,
container,
- `\nServer rendered element contains fewer child nodes than client vdom.`
+ `\nServer rendered element contains fewer child nodes than client vdom.`,
)
hasWarned = true
}
parentComponent,
parentSuspense,
getContainerType(container),
- slotScopeIds
+ slotScopeIds,
)
}
}
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
const { slotScopeIds: fragmentSlotScopeIds } = vnode
if (fragmentSlotScopeIds) {
parentComponent,
parentSuspense,
slotScopeIds,
- optimized
+ optimized,
)
if (next && isComment(next) && next.data === ']') {
return nextSibling((vnode.anchor = next))
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
slotScopeIds: string[] | null,
- isFragment: boolean
+ isFragment: boolean,
): Node | null => {
hasMismatch = true
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
? `(start of fragment)`
: ``,
`\n- expected on client:`,
- vnode.type
+ vnode.type,
)
vnode.el = null
parentComponent,
parentSuspense,
getContainerType(container),
- slotScopeIds
+ slotScopeIds,
)
return next
}
const locateClosingAnchor = (
node: Node | null,
open = '[',
- close = ']'
+ close = ']',
): Node | null => {
let match = 0
while (node) {
const replaceNode = (
newNode: Node,
oldNode: Node,
- parentComponent: ComponentInternalInstance | null
+ parentComponent: ComponentInternalInstance | null,
): void => {
// replace node
const parentNode = oldNode.parentNode
`\n - expected on client: ${format(expected)}` +
`\n Note: this mismatch is check-only. The DOM will not be rectified ` +
`in production due to performance overhead.` +
- `\n You should fix the source of the mismatch.`
+ `\n You should fix the source of the mismatch.`,
)
return true
}
effectScope,
EffectScope,
getCurrentScope,
- onScopeDispose
+ onScopeDispose,
} from '@vue/reactivity'
export { computed } from './apiComputed'
export {
watch,
watchEffect,
watchPostEffect,
- watchSyncEffect
+ watchSyncEffect,
} from './apiWatch'
export {
onBeforeMount,
onRenderTracked,
onRenderTriggered,
onErrorCaptured,
- onServerPrefetch
+ onServerPrefetch,
} from './apiLifecycle'
export { provide, inject, hasInjectionContext } from './apiInject'
export { nextTick } from './scheduler'
defineSlots,
defineModel,
withDefaults,
- useModel
+ useModel,
} from './apiSetupHelpers'
/**
mergeDefaults,
mergeModels,
createPropsRestProxy,
- withAsyncContext
+ withAsyncContext,
} from './apiSetupHelpers'
// Advanced API ----------------------------------------------------------------
export {
BaseTransition,
BaseTransitionPropsValidators,
- type BaseTransitionProps
+ type BaseTransitionProps,
} from './components/BaseTransition'
// For using custom directives
export { withDirectives } from './directives'
handleError,
callWithErrorHandling,
callWithAsyncErrorHandling,
- ErrorCodes
+ ErrorCodes,
} from './errorHandling'
export {
resolveComponent,
resolveDirective,
- resolveDynamicComponent
+ resolveDynamicComponent,
} from './helpers/resolveAssets'
// For integration with runtime compiler
export { registerRuntimeCompiler, isRuntimeOnly } from './component'
useTransitionState,
resolveTransitionHooks,
setTransitionHooks,
- getTransitionRawChildren
+ getTransitionRawChildren,
} from './components/BaseTransition'
export { initCustomFormatter } from './customFormatter'
// For devtools
import {
+ type DevtoolsHook,
devtools as _devtools,
setDevtoolsHook as _setDevtoolsHook,
- DevtoolsHook
} from './devtools'
export const devtools = (
// Types -------------------------------------------------------------------------
-import { VNode } from './vnode'
-import { ComponentInternalInstance } from './component'
+import type { VNode } from './vnode'
+import type { ComponentInternalInstance } from './component'
// Augment Ref unwrap bail types.
declare module '@vue/reactivity' {
DebuggerOptions,
DebuggerEvent,
DebuggerEventExtraInfo,
- Raw
+ Raw,
} from '@vue/reactivity'
export type {
WatchEffect,
WatchOptionsBase,
WatchCallback,
WatchSource,
- WatchStopHandle
+ WatchStopHandle,
} from './apiWatch'
export type { InjectionKey } from './apiInject'
export type {
ObjectPlugin,
FunctionPlugin,
CreateAppFunction,
- OptionMergeFunction
+ OptionMergeFunction,
} from './apiCreateApp'
export type {
VNode,
VNodeTypes,
VNodeProps,
VNodeArrayChildren,
- VNodeNormalizedChildren
+ VNodeNormalizedChildren,
} from './vnode'
export type {
Component,
SetupContext,
ComponentCustomProps,
AllowedComponentProps,
- ComponentInstance
+ ComponentInstance,
} from './component'
export type { DefineComponent, PublicProps } from './apiDefineComponent'
export type {
MethodOptions,
ComputedOptions,
RuntimeCompilerOptions,
- ComponentInjectOptions
+ ComponentInjectOptions,
} from './componentOptions'
export type { EmitsOptions, ObjectEmitsOptions } from './componentEmits'
export type {
ComponentPublicInstance,
ComponentCustomProperties,
- CreateComponentPublicInstance
+ CreateComponentPublicInstance,
} from './componentPublicInstance'
export type {
Renderer,
HydrationRenderer,
RendererOptions,
RootRenderFunction,
- ElementNamespace
+ ElementNamespace,
} from './renderer'
export type { RootHydrateFunction } from './hydration'
export type { Slot, Slots, SlotsType } from './componentSlots'
ComponentObjectPropsOptions,
ExtractPropTypes,
ExtractPublicPropTypes,
- ExtractDefaultPropTypes
+ ExtractDefaultPropTypes,
} from './componentProps'
export type {
Directive,
DirectiveHook,
ObjectDirective,
FunctionDirective,
- DirectiveArguments
+ DirectiveArguments,
} from './directives'
export type { SuspenseBoundary } from './components/Suspense'
export type {
TransitionState,
- TransitionHooks
+ TransitionHooks,
} from './components/BaseTransition'
export type {
AsyncComponentOptions,
- AsyncComponentLoader
+ AsyncComponentLoader,
} from './apiAsyncComponent'
export type { HMRRuntime } from './hmr'
withCtx,
pushScopeId,
popScopeId,
- withScopeId
+ withScopeId,
} from './componentRenderContext'
export { renderList } from './helpers/renderList'
export { toHandlers } from './helpers/toHandlers'
createStaticVNode,
createElementVNode,
createElementBlock,
- guardReactiveProps
+ guardReactiveProps,
} from './vnode'
export {
toDisplayString,
toHandlerKey,
normalizeProps,
normalizeClass,
- normalizeStyle
+ normalizeStyle,
} from '@vue/shared'
// For test-utils
renderComponentRoot,
setCurrentRenderingInstance,
isVNode,
- normalizeVNode
+ normalizeVNode,
}
/**
import { warnDeprecation } from './compat/compatConfig'
import { createCompatVue } from './compat/global'
import {
- isCompatEnabled,
checkCompatEnabled,
- softAssertCompatEnabled
+ isCompatEnabled,
+ softAssertCompatEnabled,
} from './compat/compatConfig'
import { resolveFilter as _resolveFilter } from './helpers/resolveAssets'
import { NOOP } from '@vue/shared'
createCompatVue,
isCompatEnabled,
checkCompatEnabled,
- softAssertCompatEnabled
+ softAssertCompatEnabled,
}
/**
/* eslint-disable no-restricted-globals */
-import { ComponentInternalInstance, formatComponentName } from './component'
+import {
+ type ComponentInternalInstance,
+ formatComponentName,
+} from './component'
import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools'
let supported: boolean
export function startMeasure(
instance: ComponentInternalInstance,
- type: string
+ type: string,
) {
if (instance.appContext.config.performance && isSupported()) {
perf.mark(`vue-${type}-${instance.uid}`)
perf.measure(
`<${formatComponentName(instance, instance.type)}> ${type}`,
startTag,
- endTag
+ endTag,
)
perf.clearMarks(startTag)
perf.clearMarks(endTag)
import {
- Text,
- Fragment,
Comment,
+ Fragment,
+ Static,
+ Text,
+ type VNode,
+ type VNodeArrayChildren,
+ type VNodeHook,
+ type VNodeProps,
cloneIfMounted,
- normalizeVNode,
- VNode,
- VNodeArrayChildren,
createVNode,
+ invokeVNodeHook,
isSameVNodeType,
- Static,
- VNodeHook,
- VNodeProps,
- invokeVNodeHook
+ normalizeVNode,
} from './vnode'
import {
- ComponentInternalInstance,
- ComponentOptions,
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ type Data,
createComponentInstance,
- Data,
- setupComponent
+ setupComponent,
} from './component'
import {
filterSingleRoot,
renderComponentRoot,
shouldUpdateComponent,
- updateHOCHostEl
+ updateHOCHostEl,
} from './componentRenderUtils'
import {
- EMPTY_OBJ,
EMPTY_ARR,
- isReservedProp,
+ EMPTY_OBJ,
+ NOOP,
PatchFlags,
ShapeFlags,
- NOOP,
+ getGlobalThis,
invokeArrayFns,
isArray,
- getGlobalThis
+ isReservedProp,
} from '@vue/shared'
import {
- queueJob,
- queuePostFlushCb,
+ type SchedulerJob,
flushPostFlushCbs,
- invalidateJob,
flushPreFlushCbs,
- SchedulerJob
+ invalidateJob,
+ queueJob,
+ queuePostFlushCb,
} from './scheduler'
-import { pauseTracking, resetTracking, ReactiveEffect } from '@vue/reactivity'
+import { ReactiveEffect, pauseTracking, resetTracking } from '@vue/reactivity'
import { updateProps } from './componentProps'
import { updateSlots } from './componentSlots'
-import { pushWarningContext, popWarningContext, warn } from './warning'
-import { createAppAPI, CreateAppFunction } from './apiCreateApp'
+import { popWarningContext, pushWarningContext, warn } from './warning'
+import { type CreateAppFunction, createAppAPI } from './apiCreateApp'
import { setRef } from './rendererTemplateRef'
import {
- SuspenseBoundary,
+ type SuspenseBoundary,
+ type SuspenseImpl,
queueEffectWithSuspense,
- SuspenseImpl
} from './components/Suspense'
-import { TeleportImpl, TeleportVNode } from './components/Teleport'
-import { isKeepAlive, KeepAliveContext } from './components/KeepAlive'
-import { registerHMR, unregisterHMR, isHmrUpdating } from './hmr'
-import { createHydrationFunctions, RootHydrateFunction } from './hydration'
+import type { TeleportImpl, TeleportVNode } from './components/Teleport'
+import { type KeepAliveContext, isKeepAlive } from './components/KeepAlive'
+import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr'
+import { type RootHydrateFunction, createHydrationFunctions } from './hydration'
import { invokeDirectiveHook } from './directives'
-import { startMeasure, endMeasure } from './profiling'
+import { endMeasure, startMeasure } from './profiling'
import {
devtoolsComponentAdded,
devtoolsComponentRemoved,
devtoolsComponentUpdated,
- setDevtoolsHook
+ setDevtoolsHook,
} from './devtools'
import { initFeatureFlags } from './featureFlags'
import { isAsyncWrapper } from './apiAsyncComponent'
import { isCompatEnabled } from './compat/compatConfig'
import { DeprecationTypes } from './compat/compatConfig'
-import { TransitionHooks } from './components/BaseTransition'
+import type { TransitionHooks } from './components/BaseTransition'
export interface Renderer<HostElement = RendererElement> {
render: RootRenderFunction<HostElement>
export type RootRenderFunction<HostElement = RendererElement> = (
vnode: VNode | null,
container: HostElement,
- namespace?: ElementNamespace
+ namespace?: ElementNamespace,
) => void
export interface RendererOptions<
HostNode = RendererNode,
- HostElement = RendererElement
+ HostElement = RendererElement,
> {
patchProp(
el: HostElement,
prevChildren?: VNode<HostNode, HostElement>[],
parentComponent?: ComponentInternalInstance | null,
parentSuspense?: SuspenseBoundary | null,
- unmountChildren?: UnmountChildrenFn
+ unmountChildren?: UnmountChildrenFn,
): void
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
remove(el: HostNode): void
type: string,
namespace?: ElementNamespace,
isCustomizedBuiltIn?: string,
- vnodeProps?: (VNodeProps & { [key: string]: any }) | null
+ vnodeProps?: (VNodeProps & { [key: string]: any }) | null,
): HostElement
createText(text: string): HostNode
createComment(text: string): HostNode
anchor: HostNode | null,
namespace: ElementNamespace,
start?: HostNode | null,
- end?: HostNode | null
+ end?: HostNode | null,
): [HostNode, HostNode]
}
// to optimize bundle size.
export interface RendererInternals<
HostNode = RendererNode,
- HostElement = RendererElement
+ HostElement = RendererElement,
> {
p: PatchFn
um: UnmountFn
parentSuspense?: SuspenseBoundary | null,
namespace?: ElementNamespace,
slotScopeIds?: string[] | null,
- optimized?: boolean
+ optimized?: boolean,
) => void
type MountChildrenFn = (
namespace: ElementNamespace,
slotScopeIds: string[] | null,
optimized: boolean,
- start?: number
+ start?: number,
) => void
type PatchChildrenFn = (
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => void
type PatchBlockChildrenFn = (
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
- slotScopeIds: string[] | null
+ slotScopeIds: string[] | null,
) => void
type MoveFn = (
container: RendererElement,
anchor: RendererNode | null,
type: MoveType,
- parentSuspense?: SuspenseBoundary | null
+ parentSuspense?: SuspenseBoundary | null,
) => void
type NextFn = (vnode: VNode) => RendererNode | null
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
doRemove?: boolean,
- optimized?: boolean
+ optimized?: boolean,
) => void
type RemoveFn = (vnode: VNode) => void
parentSuspense: SuspenseBoundary | null,
doRemove?: boolean,
optimized?: boolean,
- start?: number
+ start?: number,
) => void
export type MountComponentFn = (
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
- optimized: boolean
+ optimized: boolean,
) => void
type ProcessTextOrCommentFn = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
- anchor: RendererNode | null
+ anchor: RendererNode | null,
) => void
export type SetupRenderEffectFn = (
anchor: RendererNode | null,
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
- optimized: boolean
+ optimized: boolean,
) => void
export enum MoveType {
ENTER,
LEAVE,
- REORDER
+ REORDER,
}
export const queuePostRenderEffect = __FEATURE_SUSPENSE__
*/
export function createRenderer<
HostNode = RendererNode,
- HostElement = RendererElement
+ HostElement = RendererElement,
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
// Hydration logic is only used when calling this function, making it
// tree-shakable.
export function createHydrationRenderer(
- options: RendererOptions<Node, Element>
+ options: RendererOptions<Node, Element>,
) {
return baseCreateRenderer(options, createHydrationFunctions)
}
// overload 1: no hydration
function baseCreateRenderer<
HostNode = RendererNode,
- HostElement = RendererElement
+ HostElement = RendererElement,
>(options: RendererOptions<HostNode, HostElement>): Renderer<HostElement>
// overload 2: with hydration
function baseCreateRenderer(
options: RendererOptions<Node, Element>,
- createHydrationFns: typeof createHydrationFunctions
+ createHydrationFns: typeof createHydrationFunctions,
): HydrationRenderer
// implementation
function baseCreateRenderer(
options: RendererOptions,
- createHydrationFns?: typeof createHydrationFunctions
+ createHydrationFns?: typeof createHydrationFunctions,
): any {
// compile-time feature flags check
if (__ESM_BUNDLER__ && !__TEST__) {
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
- insertStaticContent: hostInsertStaticContent
+ insertStaticContent: hostInsertStaticContent,
} = options
// Note: functions inside this closure should use `const xxx = () => {}`
parentSuspense = null,
namespace = undefined,
slotScopeIds = null,
- optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
+ optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren,
) => {
if (n1 === n2) {
return
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
break
default:
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(
namespace,
slotScopeIds,
optimized,
- internals
+ internals,
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(
namespace,
slotScopeIds,
optimized,
- internals
+ internals,
)
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
hostInsert(
(n2.el = hostCreateText(n2.children as string)),
container,
- anchor
+ anchor,
)
} else {
const el = (n2.el = n1.el!)
n1,
n2,
container,
- anchor
+ anchor,
) => {
if (n1 == null) {
hostInsert(
(n2.el = hostCreateComment((n2.children as string) || '')),
container,
- anchor
+ anchor,
)
} else {
// there's no support for dynamic comments
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
- namespace: ElementNamespace
+ namespace: ElementNamespace,
) => {
// static nodes are only present when used with compiler-dom/runtime-dom
// which guarantees presence of hostInsertStaticContent.
anchor,
namespace,
n2.el,
- n2.anchor
+ n2.anchor,
)
}
n1: VNode,
n2: VNode,
container: RendererElement,
- namespace: ElementNamespace
+ namespace: ElementNamespace,
) => {
// static nodes are only patched during dev for HMR
if (n2.children !== n1.children) {
n2.children as string,
container,
anchor,
- namespace
+ namespace,
)
} else {
n2.el = n1.el
const moveStaticNode = (
{ el, anchor }: VNode,
container: RendererElement,
- nextSibling: RendererNode | null
+ nextSibling: RendererNode | null,
) => {
let next
while (el && el !== anchor) {
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
if (n2.type === 'svg') {
namespace = 'svg'
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
} else {
patchElement(
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
}
}
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
let el: RendererElement
let vnodeHook: VNodeHook | undefined | null
vnode.type as string,
namespace,
props && props.is,
- props
+ props,
)
// mount children first, since some props may rely on child content
parentSuspense,
resolveChildrenNamespace(vnode, namespace),
slotScopeIds,
- optimized
+ optimized,
)
}
vnode.children as VNode[],
parentComponent,
parentSuspense,
- unmountChildren
+ unmountChildren,
)
}
}
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
Object.defineProperty(el, '__vnode', {
value: vnode,
- enumerable: false
+ enumerable: false,
})
Object.defineProperty(el, '__vueParentComponent', {
value: parentComponent,
- enumerable: false
+ enumerable: false,
})
}
if (dirs) {
vnode: VNode,
scopeId: string | null,
slotScopeIds: string[] | null,
- parentComponent: ComponentInternalInstance | null
+ parentComponent: ComponentInternalInstance | null,
) => {
if (scopeId) {
hostSetScopeId(el, scopeId)
parentVNode,
parentVNode.scopeId,
parentVNode.slotScopeIds,
- parentComponent.parent
+ parentComponent.parent,
)
}
}
namespace: ElementNamespace,
slotScopeIds,
optimized,
- start = 0
+ start = 0,
) => {
for (let i = start; i < children.length; i++) {
const child = (children[i] = optimized
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
}
}
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
const el = (n2.el = n1.el!)
let { patchFlag, dynamicChildren, dirs } = n2
parentComponent,
parentSuspense,
resolveChildrenNamespace(n2, namespace),
- slotScopeIds
+ slotScopeIds,
)
if (__DEV__) {
// necessary for HMR
parentSuspense,
resolveChildrenNamespace(n2, namespace),
slotScopeIds,
- false
+ false,
)
}
newProps,
parentComponent,
parentSuspense,
- namespace
+ namespace,
)
} else {
// class
n1.children as VNode[],
parentComponent,
parentSuspense,
- unmountChildren
+ unmountChildren,
)
}
}
newProps,
parentComponent,
parentSuspense,
- namespace
+ namespace,
)
}
parentComponent,
parentSuspense,
namespace: ElementNamespace,
- slotScopeIds
+ slotScopeIds,
) => {
for (let i = 0; i < newChildren.length; i++) {
const oldVNode = oldChildren[i]
parentSuspense,
namespace,
slotScopeIds,
- true
+ true,
)
}
}
newProps: Data,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
- namespace: ElementNamespace
+ namespace: ElementNamespace,
) => {
if (oldProps !== newProps) {
if (oldProps !== EMPTY_OBJ) {
vnode.children as VNode[],
parentComponent,
parentSuspense,
- unmountChildren
+ unmountChildren,
)
}
}
vnode.children as VNode[],
parentComponent,
parentSuspense,
- unmountChildren
+ unmountChildren,
)
}
}
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!
const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
} else {
if (
parentComponent,
parentSuspense,
namespace,
- slotScopeIds
+ slotScopeIds,
)
if (__DEV__) {
// necessary for HMR
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
}
}
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
container,
anchor,
namespace,
- optimized
+ optimized,
)
} else {
mountComponent(
parentComponent,
parentSuspense,
namespace,
- optimized
+ optimized,
)
}
} else {
parentComponent,
parentSuspense,
namespace: ElementNamespace,
- optimized
+ optimized,
) => {
// 2.x compat may pre-create the component instance before actually
// mounting
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
- parentSuspense
+ parentSuspense,
))
if (__DEV__ && instance.type.__hmrId) {
anchor,
parentSuspense,
namespace,
- optimized
+ optimized,
)
}
anchor,
parentSuspense,
namespace: ElementNamespace,
- optimized
+ optimized,
) => {
const componentUpdateFn = () => {
if (!instance.isMounted) {
instance.subTree,
instance,
parentSuspense,
- null
+ null,
)
if (__DEV__) {
endMeasure(instance, `hydrate`)
// which means it won't track dependencies - but it's ok because
// a server-rendered async wrapper is already in resolved state
// and it will never need to change.
- () => !instance.isUnmounted && hydrateSubTree()
+ () => !instance.isUnmounted && hydrateSubTree(),
)
} else {
hydrateSubTree()
anchor,
instance,
parentSuspense,
- namespace
+ namespace,
)
if (__DEV__) {
endMeasure(instance, `patch`)
const scopedInitialVNode = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
- parentSuspense
+ parentSuspense,
)
}
if (
) {
queuePostRenderEffect(
() => instance.emit('hook:mounted'),
- parentSuspense
+ parentSuspense,
)
}
) {
queuePostRenderEffect(
() => instance.emit('hook:activated'),
- parentSuspense
+ parentSuspense,
)
}
}
getNextHostNode(prevTree),
instance,
parentSuspense,
- namespace
+ namespace,
)
if (__DEV__) {
endMeasure(instance, `patch`)
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
- parentSuspense
+ parentSuspense,
)
}
if (
) {
queuePostRenderEffect(
() => instance.emit('hook:updated'),
- parentSuspense
+ parentSuspense,
)
}
componentUpdateFn,
NOOP,
() => queueJob(update),
- instance.scope // track it in component's effect scope
+ instance.scope, // track it in component's effect scope
))
const update: SchedulerJob = (instance.update = () => {
const updateComponentPreRender = (
instance: ComponentInternalInstance,
nextVNode: VNode,
- optimized: boolean
+ optimized: boolean,
) => {
nextVNode.component = instance
const prevProps = instance.vnode.props
parentSuspense,
namespace: ElementNamespace,
slotScopeIds,
- optimized = false
+ optimized = false,
) => {
const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
return
} else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
return
}
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
} else {
// no new children, just unmount old
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
}
}
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
c1 = c1 || EMPTY_ARR
c2 = c2 || EMPTY_ARR
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
}
if (oldLength > newLength) {
parentSuspense,
true,
false,
- commonLength
+ commonLength,
)
} else {
// mount new
namespace,
slotScopeIds,
optimized,
- commonLength
+ commonLength,
)
}
}
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
- optimized: boolean
+ optimized: boolean,
) => {
let i = 0
const l2 = c2.length
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
} else {
break
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
} else {
break
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
i++
}
warn(
`Duplicate keys found during update:`,
JSON.stringify(nextChild.key),
- `Make sure keys are unique.`
+ `Make sure keys are unique.`,
)
}
keyToNewIndexMap.set(nextChild.key, i)
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
patched++
}
parentSuspense,
namespace,
slotScopeIds,
- optimized
+ optimized,
)
} else if (moved) {
// move if:
container,
anchor,
moveType,
- parentSuspense = null
+ parentSuspense = null,
) => {
const { el, type, transition, children, shapeFlag } = vnode
if (shapeFlag & ShapeFlags.COMPONENT) {
parentComponent,
parentSuspense,
doRemove = false,
- optimized = false
+ optimized = false,
) => {
const {
type,
dynamicChildren,
shapeFlag,
patchFlag,
- dirs
+ dirs,
} = vnode
// unset ref
if (ref != null) {
parentSuspense,
optimized,
internals,
- doRemove
+ doRemove,
)
} else if (
dynamicChildren &&
parentComponent,
parentSuspense,
false,
- true
+ true,
)
} else if (
(type === Fragment &&
const unmountComponent = (
instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null,
- doRemove?: boolean
+ doRemove?: boolean,
) => {
if (__DEV__ && instance.type.__hmrId) {
unregisterHMR(instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:destroyed'),
- parentSuspense
+ parentSuspense,
)
}
queuePostRenderEffect(() => {
parentSuspense,
doRemove = false,
optimized = false,
- start = 0
+ start = 0,
) => {
for (let i = start; i < children.length; i++) {
unmount(children[i], parentComponent, parentSuspense, doRemove, optimized)
null,
null,
null,
- namespace
+ namespace,
)
}
flushPreFlushCbs()
pc: patchChildren,
pbc: patchBlockChildren,
n: getNextHostNode,
- o: options
+ o: options,
}
let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
if (createHydrationFns) {
;[hydrate, hydrateNode] = createHydrationFns(
- internals as RendererInternals<Node, Element>
+ internals as RendererInternals<Node, Element>,
)
}
return {
render,
hydrate,
- createApp: createAppAPI(render, hydrate)
+ createApp: createAppAPI(render, hydrate),
}
}
function resolveChildrenNamespace(
{ type, props }: VNode,
- currentNamespace: ElementNamespace
+ currentNamespace: ElementNamespace,
): ElementNamespace {
return (currentNamespace === 'svg' && type === 'foreignObject') ||
(currentNamespace === 'mathml' &&
function toggleRecurse(
{ effect, update }: ComponentInternalInstance,
- allowed: boolean
+ allowed: boolean,
) {
effect.allowRecurse = update.allowRecurse = allowed
}
export function needTransition(
parentSuspense: SuspenseBoundary | null,
- transition: TransitionHooks | null
+ transition: TransitionHooks | null,
) {
return (
(!parentSuspense || (parentSuspense && !parentSuspense.pendingBranch)) &&
}
function locateNonHydratedAsyncRoot(
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): ComponentInternalInstance | undefined {
const subComponent = instance.subTree.component
if (subComponent) {
-import { SuspenseBoundary } from './components/Suspense'
-import { VNode, VNodeNormalizedRef, VNodeNormalizedRefAtom } from './vnode'
+import type { SuspenseBoundary } from './components/Suspense'
+import type { VNode, VNodeNormalizedRef, VNodeNormalizedRefAtom } from './vnode'
import {
EMPTY_OBJ,
+ ShapeFlags,
hasOwn,
isArray,
isFunction,
isString,
remove,
- ShapeFlags
} from '@vue/shared'
import { isAsyncWrapper } from './apiAsyncComponent'
import { getExposeProxy } from './component'
import { warn } from './warning'
import { isRef } from '@vue/reactivity'
-import { callWithErrorHandling, ErrorCodes } from './errorHandling'
-import { SchedulerJob } from './scheduler'
+import { ErrorCodes, callWithErrorHandling } from './errorHandling'
+import type { SchedulerJob } from './scheduler'
import { queuePostRenderEffect } from './renderer'
/**
oldRawRef: VNodeNormalizedRef | null,
parentSuspense: SuspenseBoundary | null,
vnode: VNode,
- isUnmount = false
+ isUnmount = false,
) {
if (isArray(rawRef)) {
rawRef.forEach((r, i) =>
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
parentSuspense,
vnode,
- isUnmount
- )
+ isUnmount,
+ ),
)
return
}
if (__DEV__ && !owner) {
warn(
`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
- `A vnode with ref must be created inside the render function.`
+ `A vnode with ref must be created inside the render function.`,
)
return
}
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
-import { Awaited, isArray, NOOP } from '@vue/shared'
-import { ComponentInternalInstance, getComponentName } from './component'
+import { type Awaited, NOOP, isArray } from '@vue/shared'
+import { type ComponentInternalInstance, getComponentName } from './component'
export interface SchedulerJob extends Function {
id?: number
export function nextTick<T = void, R = void>(
this: T,
- fn?: (this: T) => R
+ fn?: (this: T) => R,
): Promise<Awaited<R>> {
const p = currentFlushPromise || resolvedPromise
return fn ? p.then(this ? fn.bind(this) : fn) : p
!queue.length ||
!queue.includes(
job,
- isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
+ isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex,
)
) {
if (job.id == null) {
!activePostFlushCbs ||
!activePostFlushCbs.includes(
cb,
- cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex
+ cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex,
)
) {
pendingPostFlushCbs.push(cb)
instance?: ComponentInternalInstance,
seen?: CountMap,
// if currently flushing, skip the current job itself
- i = isFlushing ? flushIndex + 1 : 0
+ i = isFlushing ? flushIndex + 1 : 0,
) {
if (__DEV__) {
seen = seen || new Map()
`include component template, render function, updated hook or ` +
`watcher source function.`,
null,
- ErrorCodes.APP_ERROR_HANDLER
+ ErrorCodes.APP_ERROR_HANDLER,
)
return true
} else {
import {
+ EMPTY_ARR,
+ PatchFlags,
+ ShapeFlags,
+ SlotFlags,
+ extend,
isArray,
isFunction,
- isString,
isObject,
- EMPTY_ARR,
- extend,
+ isOn,
+ isString,
normalizeClass,
normalizeStyle,
- PatchFlags,
- ShapeFlags,
- SlotFlags,
- isOn
} from '@vue/shared'
import {
- ComponentInternalInstance,
- Data,
- ConcreteComponent,
- ClassComponent,
- Component,
- isClassComponent
+ type ClassComponent,
+ type Component,
+ type ComponentInternalInstance,
+ type ConcreteComponent,
+ type Data,
+ isClassComponent,
} from './component'
-import { RawSlots } from './componentSlots'
-import { isProxy, Ref, toRaw, ReactiveFlags, isRef } from '@vue/reactivity'
-import { AppContext } from './apiCreateApp'
+import type { RawSlots } from './componentSlots'
+import {
+ type ReactiveFlags,
+ type Ref,
+ isProxy,
+ isRef,
+ toRaw,
+} from '@vue/reactivity'
+import type { AppContext } from './apiCreateApp'
import {
- Suspense,
- SuspenseImpl,
+ type Suspense,
+ type SuspenseBoundary,
+ type SuspenseImpl,
isSuspense,
- SuspenseBoundary
} from './components/Suspense'
-import { DirectiveBinding } from './directives'
-import { TransitionHooks } from './components/BaseTransition'
+import type { DirectiveBinding } from './directives'
+import type { TransitionHooks } from './components/BaseTransition'
import { warn } from './warning'
-import { Teleport, TeleportImpl, isTeleport } from './components/Teleport'
+import {
+ type Teleport,
+ type TeleportImpl,
+ isTeleport,
+} from './components/Teleport'
import {
currentRenderingInstance,
- currentScopeId
+ currentScopeId,
} from './componentRenderContext'
-import { RendererNode, RendererElement } from './renderer'
+import type { RendererElement, RendererNode } from './renderer'
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
import { hmrDirtyComponents } from './hmr'
import { convertLegacyComponent } from './compat/component'
import { convertLegacyVModelProps } from './compat/componentVModel'
import { defineLegacyVNodeProperties } from './compat/renderFn'
-import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
-import { ComponentPublicInstance } from './componentPublicInstance'
+import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
+import type { ComponentPublicInstance } from './componentPublicInstance'
export const Fragment = Symbol.for('v-fgt') as any as {
__isFragment: true
| Ref
| ((
ref: Element | ComponentPublicInstance | null,
- refs: Record<string, any>
+ refs: Record<string, any>,
) => void)
export type VNodeNormalizedRefAtom = {
export interface VNode<
HostNode = RendererNode,
HostElement = RendererElement,
- ExtraProps = { [key: string]: any }
+ ExtraProps = { [key: string]: any },
> {
/**
* @internal
children?: any,
patchFlag?: number,
dynamicProps?: string[],
- shapeFlag?: number
+ shapeFlag?: number,
) {
return setupBlock(
createBaseVNode(
patchFlag,
dynamicProps,
shapeFlag,
- true /* isBlock */
- )
+ true /* isBlock */,
+ ),
)
}
props?: Record<string, any> | null,
children?: any,
patchFlag?: number,
- dynamicProps?: string[]
+ dynamicProps?: string[],
): VNode {
return setupBlock(
createVNode(
children,
patchFlag,
dynamicProps,
- true /* isBlock: prevent a block from tracking itself */
- )
+ true /* isBlock: prevent a block from tracking itself */,
+ ),
)
}
let vnodeArgsTransformer:
| ((
args: Parameters<typeof _createVNode>,
- instance: ComponentInternalInstance | null
+ instance: ComponentInternalInstance | null,
) => Parameters<typeof _createVNode>)
| undefined
return _createVNode(
...(vnodeArgsTransformer
? vnodeArgsTransformer(args, currentRenderingInstance)
- : args)
+ : args),
)
}
const normalizeRef = ({
ref,
ref_key,
- ref_for
+ ref_for,
}: VNodeProps): VNodeNormalizedRefAtom | null => {
if (typeof ref === 'number') {
ref = '' + ref
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
- needFullChildrenNormalization = false
+ needFullChildrenNormalization = false,
) {
const vnode = {
__v_isVNode: true,
dynamicProps,
dynamicChildren: null,
appContext: null,
- ctx: currentRenderingInstance
+ ctx: currentRenderingInstance,
} as VNode
if (needFullChildrenNormalization) {
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
- isBlockNode = false
+ isBlockNode = false,
): VNode {
if (!type || type === NULL_DYNAMIC_COMPONENT) {
if (__DEV__ && !type) {
`marking the component with \`markRaw\` or using \`shallowRef\` ` +
`instead of \`ref\`.`,
`\nComponent that was made reactive: `,
- type
+ type,
)
}
dynamicProps,
shapeFlag,
isBlockNode,
- true
+ true,
)
}
export function cloneVNode<T, U>(
vnode: VNode<T, U>,
extraProps?: (Data & VNodeProps) | null,
- mergeRef = false
+ mergeRef = false,
): VNode<T, U> {
// This is intentionally NOT using spread or extend to avoid the runtime
// key enumeration cost.
el: vnode.el,
anchor: vnode.anchor,
ctx: vnode.ctx,
- ce: vnode.ce
+ ce: vnode.ce,
}
if (__COMPAT__) {
defineLegacyVNodeProperties(cloned as VNode)
*/
export function createStaticVNode(
content: string,
- numberOfNodes: number
+ numberOfNodes: number,
): VNode {
// A static vnode can contain multiple stringified elements, and the number
// of elements is necessary for hydration.
text: string = '',
// when used as the v-else branch, the comment node must be created as a
// block to ensure correct updates.
- asBlock: boolean = false
+ asBlock: boolean = false,
): VNode {
return asBlock
? (openBlock(), createBlock(Comment, null, text))
Fragment,
null,
// #3666, avoid reference pollution when reusing vnode
- child.slice()
+ child.slice(),
)
} else if (typeof child === 'object') {
// already vnode, this should be the most common since compiled templates
hook: VNodeHook,
instance: ComponentInternalInstance | null,
vnode: VNode,
- prevVNode: VNode | null = null
+ prevVNode: VNode | null = null,
) {
callWithAsyncErrorHandling(hook, instance, ErrorCodes.VNODE_HOOK, [
vnode,
- prevVNode
+ prevVNode,
])
}
-import { VNode } from './vnode'
+import type { VNode } from './vnode'
import {
- Data,
- ComponentInternalInstance,
- ConcreteComponent,
- formatComponentName
+ type ComponentInternalInstance,
+ type ConcreteComponent,
+ type Data,
+ formatComponentName,
} from './component'
-import { isString, isFunction } from '@vue/shared'
-import { toRaw, isRef, pauseTracking, resetTracking } from '@vue/reactivity'
-import { callWithErrorHandling, ErrorCodes } from './errorHandling'
+import { isFunction, isString } from '@vue/shared'
+import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
+import { ErrorCodes, callWithErrorHandling } from './errorHandling'
type ComponentVNode = VNode & {
type: ConcreteComponent
instance && instance.proxy,
trace
.map(
- ({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>`
+ ({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>`,
)
.join('\n'),
- trace
- ]
+ trace,
+ ],
)
} else {
const warnArgs = [`[Vue warn]: ${msg}`, ...args]
} else {
normalizedStack.push({
vnode: currentVNode as ComponentVNode,
- recurseCount: 0
+ recurseCount: 0,
})
}
const parentInstance: ComponentInternalInstance | null =
const open = ` at <${formatComponentName(
vnode.component,
vnode.type,
- isRoot
+ isRoot,
)}`
const close = `>` + postfix
return vnode.props
createApp({
render() {
return h('g')
- }
+ },
}).mount(root)
expect(root.children.length).toBe(1)
expect(root.children[0]).toBeInstanceOf(SVGElement)
const originalObj = {
data() {
return {
- counter: 0
+ counter: 0,
}
- }
+ },
}
const handler = vi.fn(msg => {
import {
+ type Ref,
+ type VueElement,
defineAsyncComponent,
defineComponent,
defineCustomElement,
h,
inject,
nextTick,
- Ref,
ref,
renderSlot,
- VueElement
} from '../src'
describe('defineCustomElement', () => {
props: {
msg: {
type: String,
- default: 'hello'
- }
+ default: 'hello',
+ },
},
render() {
return h('div', this.msg)
- }
+ },
})
customElements.define('my-element', E)
render() {
return [
h('div', null, this.foo),
- h('div', null, this.bazQux || (this.bar && this.bar.x))
+ h('div', null, this.bazQux || (this.bar && this.bar.x)),
]
- }
+ },
})
customElements.define('my-el-props', E)
e.setAttribute('baz-qux', 'changed')
await nextTick()
expect(e.shadowRoot!.innerHTML).toBe(
- '<div>changed</div><div>changed</div>'
+ '<div>changed</div><div>changed</div>',
)
})
props: {
fooBar: Number, // test casting of camelCase prop names
bar: Boolean,
- baz: String
+ baz: String,
},
render() {
return [
this.bar,
typeof this.bar,
this.baz,
- typeof this.baz
+ typeof this.baz,
].join(' ')
- }
+ },
})
customElements.define('my-el-props-cast', E)
container.innerHTML = `<my-el-props-cast foo-bar="1" baz="12345"></my-el-props-cast>`
const e = container.childNodes[0] as VueElement
expect(e.shadowRoot!.innerHTML).toBe(
- `1 number false boolean 12345 string`
+ `1 number false boolean 12345 string`,
)
e.setAttribute('bar', '')
e.setAttribute('foo-bar', '2e1')
await nextTick()
expect(e.shadowRoot!.innerHTML).toBe(
- `20 number true boolean 12345 string`
+ `20 number true boolean 12345 string`,
)
e.setAttribute('baz', '2e1')
test('attr casting w/ programmatic creation', () => {
const E = defineCustomElement({
props: {
- foo: Number
+ foo: Number,
},
render() {
return `foo type: ${typeof this.foo}`
- }
+ },
})
customElements.define('my-element-programmatic', E)
const el = document.createElement('my-element-programmatic') as any
const E = defineCustomElement({
props: {
foo: String,
- dataAge: Number
+ dataAge: Number,
},
setup(props) {
expect(props.foo).toBe('hello')
},
render() {
return h('div', `foo: ${this.foo}`)
- }
+ },
})
const el = document.createElement('my-el-upgrade') as any
el.foo = 'hello'
const E = defineCustomElement({
props: {
foo: String,
- post: Object
+ post: Object,
},
setup(props) {
expect(props.foo).toBe('hello')
},
render() {
return JSON.stringify(this.post)
- }
+ },
})
customElements.define('my-el-preconnect', E)
const el = document.createElement('my-el-preconnect') as any
const E = defineCustomElement({
render() {
return h('div', 'foo')
- }
+ },
})
customElements.define('my-element-noprops', E)
const el = document.createElement('my-element-noprops')
test('set number value in dom property', () => {
const E = defineCustomElement({
props: {
- 'max-age': Number
+ 'max-age': Number,
},
render() {
- // @ts-ignore
+ // @ts-expect-error
return `max age: ${this.maxAge}/type: ${typeof this.maxAge}`
- }
+ },
})
customElements.define('my-element-number-property', E)
const el = document.createElement('my-element-number-property') as any
const E = defineCustomElement({
render() {
return [h('div', null, this.$attrs.foo as string)]
- }
+ },
})
customElements.define('my-el-attrs', E)
test('non-declared properties should not show up in $attrs', () => {
const e = new E()
- // @ts-ignore
+ // @ts-expect-error
e.foo = '123'
container.appendChild(e)
expect(e.shadowRoot!.innerHTML).toBe('<div></div>')
},
onMousedown: () => {
emit('myEvent', 1) // validate hyphenation
- }
+ },
})
- }
+ },
})
const E = defineCustomElement(CompDef)
customElements.define('my-el-emits', E)
e.shadowRoot!.childNodes[0].dispatchEvent(new CustomEvent('click'))
expect(spy).toHaveBeenCalledTimes(1)
expect(spy.mock.calls[0][0]).toMatchObject({
- detail: [1]
+ detail: [1],
})
})
e.shadowRoot!.childNodes[0].dispatchEvent(new CustomEvent('click'))
expect(spy).toHaveBeenCalled()
expect(spy.mock.calls[0][0]).toMatchObject({
- detail: [1]
+ detail: [1],
})
})
// #7293
test('emit in an async component wrapper with properties bound', async () => {
const E = defineCustomElement(
defineAsyncComponent(
- () => new Promise<typeof CompDef>(res => res(CompDef as any))
- )
+ () => new Promise<typeof CompDef>(res => res(CompDef as any)),
+ ),
)
customElements.define('my-async-el-props-emits', E)
container.innerHTML = `<my-async-el-props-emits id="my_async_el_props_emits"></my-async-el-props-emits>`
e.shadowRoot!.childNodes[0].dispatchEvent(new CustomEvent('click'))
expect(spy).toHaveBeenCalled()
expect(spy.mock.calls[0][0]).toMatchObject({
- detail: [1]
+ detail: [1],
})
})
})
return [
h('div', null, [
renderSlot(this.$slots, 'default', undefined, () => [
- h('div', 'fallback')
- ])
+ h('div', 'fallback'),
+ ]),
]),
- h('div', null, renderSlot(this.$slots, 'named'))
+ h('div', null, renderSlot(this.$slots, 'named')),
]
- }
+ },
})
customElements.define('my-el-slots', E)
// native slots allocation does not affect innerHTML, so we just
// verify that we've rendered the correct native slots here...
expect(e.shadowRoot!.innerHTML).toBe(
- `<div><slot><div>fallback</div></slot></div><div><slot name="named"></slot></div>`
+ `<div><slot><div>fallback</div></slot></div><div><slot name="named"></slot></div>`,
)
})
})
setup() {
const foo = inject<Ref>('foo')!
return () => h('div', foo.value)
- }
+ },
})
customElements.define('my-consumer', Consumer)
const foo = ref('injected!')
const Provider = defineCustomElement({
provide: {
- foo
+ foo,
},
render() {
return h('my-consumer')
- }
+ },
})
customElements.define('my-provider', Provider)
container.innerHTML = `<my-provider><my-provider>`
const foo = ref('injected!')
const Provider = defineCustomElement({
provide: {
- foo
+ foo,
},
render() {
return renderSlot(this.$slots, 'default')
- }
+ },
})
customElements.define('my-provider-2', Provider)
const fooB = ref('FooB!')
const ProviderA = defineCustomElement({
provide: {
- fooA
+ fooA,
},
render() {
return h('provider-b')
- }
+ },
})
const ProviderB = defineCustomElement({
provide: {
- fooB
+ fooB,
},
render() {
return h('my-multi-consumer')
- }
+ },
})
const Consumer = defineCustomElement({
const fooA = inject<Ref>('fooA')!
const fooB = inject<Ref>('fooB')!
return () => h('div', `${fooA.value} ${fooB.value}`)
- }
+ },
})
customElements.define('provider-a', ProviderA)
fooB.value = 'changedB!'
await nextTick()
expect(consumer.shadowRoot!.innerHTML).toBe(
- `<div>changedA! changedB!</div>`
+ `<div>changedA! changedB!</div>`,
)
})
})
styles: [`div { color: red; }`],
render() {
return h('div', 'hello')
- }
+ },
})
customElements.define('my-el-with-styles', Foo)
container.innerHTML = `<my-el-with-styles></my-el-with-styles>`
styles: [`div { color: red }`],
render(this: any) {
return h('div', null, this.msg)
- }
+ },
})
- })
+ }),
)
customElements.define('my-el-async', E)
container.innerHTML =
// should inject styles
expect(e1.shadowRoot!.innerHTML).toBe(
- `<style>div { color: red }</style><div>hello</div>`
+ `<style>div { color: red }</style><div>hello</div>`,
)
expect(e2.shadowRoot!.innerHTML).toBe(
- `<style>div { color: red }</style><div>world</div>`
+ `<style>div { color: red }</style><div>world</div>`,
)
// attr
await nextTick()
expect((e1 as any).msg).toBe('attr')
expect(e1.shadowRoot!.innerHTML).toBe(
- `<style>div { color: red }</style><div>attr</div>`
+ `<style>div { color: red }</style><div>attr</div>`,
)
// props
;(e1 as any).msg = 'prop'
expect(e1.getAttribute('msg')).toBe('prop')
expect(e1.shadowRoot!.innerHTML).toBe(
- `<style>div { color: red }</style><div>prop</div>`
+ `<style>div { color: red }</style><div>prop</div>`,
)
})
},
render(this: any) {
return h('div', this.msg)
- }
+ },
})
- })
+ }),
)
customElements.define('my-el-async-2', E)
},
render(this: any) {
return h('div', this.n + ',' + typeof this.n)
- }
+ },
})
- })
+ }),
)
customElements.define('my-el-async-3', E)
container.innerHTML = `<my-el-async-3 n="2e1"></my-el-async-3>`
return [
h('div', null, [
renderSlot(this.$slots, 'default', undefined, () => [
- h('div', 'fallback')
- ])
+ h('div', 'fallback'),
+ ]),
]),
- h('div', null, renderSlot(this.$slots, 'named'))
+ h('div', null, renderSlot(this.$slots, 'named')),
]
- }
+ },
})
- })
+ }),
)
customElements.define('my-el-async-slots', E)
container.innerHTML = `<my-el-async-slots><span>hi</span></my-el-async-slots>`
const e = container.childNodes[0] as VueElement
expect(e.shadowRoot!.innerHTML).toBe(
- `<div><slot><div>fallback</div></slot></div><div><slot name="named"></slot></div>`
+ `<div><slot><div>fallback</div></slot></div><div><slot name="named"></slot></div>`,
)
})
})
-import { type MockInstance } from 'vitest'
-import { render, h } from '@vue/runtime-dom'
+import type { MockInstance } from 'vitest'
+import { h, render } from '@vue/runtime-dom'
describe('customized built-in elements support', () => {
let createElement: MockInstance
render(h('button', { is: 'plastic-button' }), root)
expect(createElement.mock.calls[0]).toMatchObject([
'button',
- { is: 'plastic-button' }
+ { is: 'plastic-button' },
])
// should also render the attribute
expect(root.innerHTML).toBe(`<button is="plastic-button"></button>`)
const root = document.createElement('div')
root.setAttribute('v-cloak', '')
createApp({
- render() {}
+ render() {},
}).mount(root)
expect(root.hasAttribute('v-cloak')).toBe(false)
})
import {
+ type VNode,
+ defineComponent,
h,
- render,
nextTick,
- defineComponent,
+ ref,
+ render,
vModelDynamic,
withDirectives,
- VNode,
- ref
} from '@vue/runtime-dom'
const triggerEvent = (type: string, el: Element) => {
'onUpdate:modelValue': setValue.bind(this),
onInput: () => {
manualListener(data.value)
- }
+ },
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
withVModel(
h('input', {
type: 'number',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
h('input', {
id: 'input_num1',
type: 'number',
- 'onUpdate:modelValue': setValue1.bind(this)
+ 'onUpdate:modelValue': setValue1.bind(this),
}),
- this.value1
+ this.value1,
),
withVModel(
h('input', {
id: 'input_num2',
type: 'number',
- 'onUpdate:modelValue': setValue2.bind(this)
+ 'onUpdate:modelValue': setValue2.bind(this),
}),
- this.value2
- )
+ this.value2,
+ ),
]
- }
+ },
})
render(h(component), root)
const data = root._vnode.component.data
return [
withVModel(
h('input', {
- 'onUpdate:modelValue': [setValue.bind(this), spy]
+ 'onUpdate:modelValue': [setValue.bind(this), spy],
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
return [
withVModel(
h('input', {
- 'onUpdate:modelValue': toggle.value ? spy1 : spy2
+ 'onUpdate:modelValue': toggle.value ? spy1 : spy2,
}),
- 'foo'
- )
+ 'foo',
+ ),
]
- }
+ },
})
render(h(component), root)
return [
withVModel(
h('textarea', {
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
class: 'number',
'onUpdate:modelValue': (val: any) => {
this.number = val
- }
+ },
}),
this.number,
{
- number: true
- }
+ number: true,
+ },
),
withVModel(
h('input', {
class: 'trim',
'onUpdate:modelValue': (val: any) => {
this.trim = val
- }
+ },
}),
this.trim,
{
- trim: true
- }
+ trim: true,
+ },
),
withVModel(
h('input', {
class: 'trim-number',
'onUpdate:modelValue': (val: any) => {
this.trimNumber = val
- }
+ },
}),
this.trimNumber,
{
trim: true,
- number: true
- }
+ number: true,
+ },
),
withVModel(
h('input', {
class: 'lazy',
'onUpdate:modelValue': (val: any) => {
this.lazy = val
- }
+ },
}),
this.lazy,
{
- lazy: true
- }
- )
+ lazy: true,
+ },
+ ),
]
- }
+ },
})
render(h(component), root)
min: 1,
max: 100,
class: 'foo',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
this.value,
{
- number: true
- }
+ number: true,
+ },
),
withVModel(
h('input', {
min: 1,
max: 100,
class: 'bar',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
this.value,
{
- lazy: true
- }
- )
+ lazy: true,
+ },
+ ),
]
- }
+ },
})
render(h(component), root)
withVModel(
h('input', {
type: 'checkbox',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
type: 'checkbox',
'true-value': 'yes',
'false-value': 'no',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
type: 'checkbox',
'true-value': { yes: 'yes' },
'false-value': { no: 'no' },
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
type: 'checkbox',
class: 'foo',
value: 'foo',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
+ this.value,
),
withVModel(
h('input', {
type: 'checkbox',
class: 'bar',
value: 'bar',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
type: 'checkbox',
class: 'foo',
value: 'foo',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
+ this.value,
),
withVModel(
h('input', {
type: 'checkbox',
class: 'bar',
value: 'bar',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
type: 'radio',
class: 'foo',
value: 'foo',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
+ this.value,
),
withVModel(
h('input', {
type: 'radio',
class: 'bar',
value: 'bar',
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
'select',
{
value: null,
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
},
- [h('option', { value: 'foo' }), h('option', { value: 'bar' })]
+ [h('option', { value: 'foo' }), h('option', { value: 'bar' })],
),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
{
value: null,
multiple: true,
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
},
- [h('option', { value: 'foo' }), h('option', { value: 'bar' })]
+ [h('option', { value: 'foo' }), h('option', { value: 'bar' })],
),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
'select',
{
value: null,
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
},
- [h('option', { value: '1' }), h('option', { value: '2' })]
+ [h('option', { value: '1' }), h('option', { value: '2' })],
),
this.value,
{
- number: true
- }
- )
+ number: true,
+ },
+ ),
]
- }
+ },
})
render(h(component), root)
{
value: null,
multiple: true,
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
},
- [h('option', { value: '1' }), h('option', { value: '2' })]
+ [h('option', { value: '1' }), h('option', { value: '2' })],
),
this.value,
{
- number: true
- }
- )
+ number: true,
+ },
+ ),
]
- }
+ },
})
render(h(component), root)
{
value: null,
multiple: true,
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
},
[
h('option', { value: fooValue }),
- h('option', { value: barValue })
- ]
+ h('option', { value: barValue }),
+ ],
),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
{
value: null,
multiple: true,
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
},
- [h('option', { value: 'foo' }), h('option', { value: 'bar' })]
+ [h('option', { value: 'foo' }), h('option', { value: 'bar' })],
),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
{
value: null,
multiple: true,
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
},
[
h('option', { value: fooValue }),
- h('option', { value: barValue })
- ]
+ h('option', { value: barValue }),
+ ],
),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
return [
withVModel(
h('input', {
- 'onUpdate:modelValue': setValue.bind(this)
+ 'onUpdate:modelValue': setValue.bind(this),
}),
- this.value
- )
+ this.value,
+ ),
]
- }
+ },
})
render(h(component), root)
import { patchEvent } from '../../src/modules/events'
-import { withModifiers, withKeys } from '@vue/runtime-dom'
+import { withKeys, withModifiers } from '@vue/runtime-dom'
function triggerEvent(
target: Element,
event: string,
- process?: (e: any) => any
+ process?: (e: any) => any,
) {
const e = new Event(event, {
bubbles: true,
- cancelable: true
+ cancelable: true,
})
if (event === 'click') {
;(e as any).button = 0
// <div @keyup[keyName].esc="test"/>
const nextValue = withKeys(withModifiers(fn, [keyName]), [
'esc',
- 'arrow-left'
+ 'arrow-left',
])
patchEvent(el, 'onKeyup', null, nextValue, null)
import {
- withDirectives,
+ type VNode,
defineComponent,
h,
nextTick,
- VNode,
ref,
- watch
+ watch,
+ withDirectives,
} from '@vue/runtime-core'
-import { render, Transition, vShow } from '@vue/runtime-dom'
+import { Transition, render, vShow } from '@vue/runtime-dom'
const withVShow = (node: VNode, exp: any) =>
withDirectives(node, [[vShow, exp]])
},
render() {
return [withVShow(h('div'), this.value)]
- }
+ },
})
render(h(component), root)
},
render() {
return [withVShow(h('div'), this.value)]
- }
+ },
})
render(h(component), root)
},
render() {
return [withVShow(h('div'), this.value)]
- }
+ },
})
render(h(component), root)
},
render() {
return [
- withVShow(h('div', { style: { display: 'block' } }), this.value)
+ withVShow(h('div', { style: { display: 'block' } }), this.value),
]
- }
+ },
})
render(h(component), root)
const component = defineComponent({
render() {
return withVShow(h('div', { style: style.value }), display.value)
- }
+ },
})
render(h(component), root)
return h(Transition, () =>
withVShow(
h('div', { style: style.value }, innerValue.value),
- display.value
- )
+ display.value,
+ ),
)
}
- }
+ },
})
render(h(component), root)
-import { render, h, nodeOps } from '@vue/runtime-test'
+import { h, nodeOps, render } from '@vue/runtime-test'
import { useCssModule } from '../../src/helpers/useCssModule'
describe('useCssModule', () => {
__cssModules: modules,
setup() {
res = useCssModule(name)
- }
+ },
}),
- nodeOps.createElement('div')
+ nodeOps.createElement('div'),
)
return res
}
test('basic usage', () => {
const modules = {
$style: {
- red: 'red'
- }
+ red: 'red',
+ },
}
expect(mountWithModule(modules)).toMatchObject(modules.$style)
})
test('basic usage', () => {
const modules = {
foo: {
- red: 'red'
- }
+ red: 'red',
+ },
}
expect(mountWithModule(modules, 'foo')).toMatchObject(modules.foo)
})
import {
- ref,
- render,
- useCssVars,
+ type ComponentOptions,
+ type FunctionalComponent,
+ Suspense,
+ Teleport,
createStaticVNode,
h,
- reactive,
nextTick,
- ComponentOptions,
- Suspense,
- Teleport,
- FunctionalComponent
+ reactive,
+ ref,
+ render,
+ useCssVars,
} from '@vue/runtime-dom'
describe('useCssVars', () => {
setup() {
// test receiving render context
useCssVars((ctx: any) => ({
- color: ctx.color
+ color: ctx.color,
}))
return state
},
render() {
return h('div')
- }
+ },
}))
})
setup() {
useCssVars(() => state)
return () => [h('div'), h('div')]
- }
+ },
}))
})
setup() {
useCssVars(() => state)
return () => h(Child)
- }
+ },
}))
})
}
})
return asyncPromise
- }
+ },
}
const App = {
return () =>
h(Suspense, null, {
default: h(AsyncComp),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
render(h(App), root)
setup() {
useCssVars(() => state)
return () => (value.value ? [h('div')] : [h('div'), h('div')])
- }
+ },
}
render(h(App), root)
useCssVars(() => state)
return () =>
h(Child, null, () =>
- value.value ? [h('div')] : [h('div'), h('div')]
+ value.value ? [h('div')] : [h('div'), h('div')],
)
- }
+ },
}
render(h(App), root)
return () => [
h('div'),
createStaticVNode('<div>1</div><div><span>2</span></div>', 2),
- h('div')
+ h('div'),
]
- }
+ },
}
render(h(App), root)
setup() {
useCssVars(() => state)
return () => [h(Teleport, { to: target }, [h('div')])]
- }
+ },
}
render(h(App), root)
setup() {
useCssVars(() => state)
return () => h(Child, () => [h(Teleport, { to: target }, [h('div')])])
- }
+ },
}
render(h(App), root)
return () => [
h(Teleport, { to: target }, [
h('div'),
- toggle.value ? h('div') : null
- ])
+ toggle.value ? h('div') : null,
+ ]),
]
- }
+ },
}
render(h(App), root)
setup() {
useCssVars(() => state)
return () => [h(Teleport, { to: target, disabled: true }, [h('div')])]
- }
+ },
}
expect(() => render(h(App), root)).not.toThrow(TypeError)
h(
'div',
{ style: disabled.value ? 'pointer-events: none' : undefined },
- 'foo'
- )
+ 'foo',
+ ),
]
- }
+ },
}
render(h(App), root)
await nextTick()
describe('runtime-dom: node-ops', () => {
test("the <select>'s multiple attr should be set in createElement", () => {
const el = nodeOps.createElement('select', undefined, undefined, {
- multiple: ''
+ multiple: '',
}) as HTMLSelectElement
const option1 = nodeOps.createElement('option') as HTMLOptionElement
const option2 = nodeOps.createElement('option') as HTMLOptionElement
content,
parent,
null,
- undefined
+ undefined,
)
expect(parent.innerHTML).toBe(content)
expect(nodes[0]).toBe(parent.firstChild)
content,
parent,
anchor,
- undefined
+ undefined,
)
expect(parent.innerHTML).toBe(content + existing)
expect(nodes[0]).toBe(parent.firstChild)
content,
parent,
null,
- 'svg'
+ 'svg',
)
expect(parent.innerHTML).toBe(content)
expect(first).toBe(parent.firstChild)
content,
parent,
anchor,
- 'svg'
+ 'svg',
)
expect(parent.innerHTML).toBe(content + existing)
expect(first).toBe(parent.firstChild)
anchor,
undefined,
cached.firstChild,
- cached.lastChild
+ cached.lastChild,
)
expect(parent.innerHTML).toBe(content + existing)
expect(nodes[0]).toBe(parent.firstChild)
import { patchProp } from '../src/patchProp'
-import { ElementWithTransition, vtcKey } from '../src/components/Transition'
+import {
+ type ElementWithTransition,
+ vtcKey,
+} from '../src/components/Transition'
import { svgNS } from '../src/nodeOps'
describe('runtime-dom: class patching', () => {
}
window.customElements.define('test-element', TestElement)
const testElement = document.createElement('test-element', {
- is: 'test-element'
+ is: 'test-element',
})
const fn1 = vi.fn()
const fn2 = vi.fn()
import { patchProp } from '../src/patchProp'
-import { render, h } from '../src'
+import { h, render } from '../src'
describe('runtime-dom: props patching', () => {
test('basic', () => {
const fn = vi.fn()
const comp = {
render: () => 'foo',
- unmounted: fn
+ unmounted: fn,
}
const root = document.createElement('div')
render(h('div', null, [h(comp)]), root)
const fn = vi.fn()
const comp = {
render: () => 'foo',
- unmounted: fn
+ unmounted: fn,
}
const root = document.createElement('div')
render(h('div', null, [h(comp)]), root)
const fn = vi.fn()
const comp = {
render: () => 'foo',
- unmounted: fn
+ unmounted: fn,
}
const root = document.createElement('div')
render(h('div', null, [h(comp)]), root)
Object.defineProperty(el, 'someProp', {
set() {
throw new TypeError('Invalid type')
- }
+ },
})
patchProp(el, 'someProp', null, 'foo')
patchProp(el, 'willValidate', true, null)
expect(el.willValidate).toBe(true)
expect(
- 'Failed setting prop "willValidate" on <select>'
+ 'Failed setting prop "willValidate" on <select>',
).toHaveBeenWarnedLast()
})
render(
h('select', { value: 'foo' }, [
h('option', { value: 'foo' }, 'foo'),
- h('option', { value: 'bar' }, 'bar')
+ h('option', { value: 'bar' }, 'bar'),
]),
- root
+ root,
)
const el = root.children[0] as HTMLSelectElement
expect(el.value).toBe('foo')
render(
h('select', { value: 'baz' }, [
h('option', { value: 'foo' }, 'foo'),
- h('option', { value: 'baz' }, 'baz')
+ h('option', { value: 'baz' }, 'baz'),
]),
- root
+ root,
)
expect(el.value).toBe('baz')
})
get(): any {
return value
},
- set: fn
+ set: fn,
})
patchProp(el, 'style', value, value)
expect(el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
const el = document.createElement('div')
patchProp(el, 'style', null, {
color: undefined,
- borderRadius: null
+ borderRadius: null,
})
expect(el.style.cssText.replace(/\s/g, '')).toBe('')
el,
'style',
{ color: 'red' },
- { color: null, borderRadius: undefined }
+ { color: null, borderRadius: undefined },
)
expect(el.style.cssText.replace(/\s/g, '')).toBe('')
})
const el = document.createElement('div')
patchProp(el, 'style', {}, { marginRight: '10px !important' })
expect(el.style.cssText.replace(/\s/g, '')).toBe(
- 'margin-right:10px!important;'
+ 'margin-right:10px!important;',
)
})
const el = document.createElement('div')
patchProp(el, 'style', null, { color: 'red;' })
expect(
- `Unexpected semicolon at the end of 'color' style value: 'red;'`
+ `Unexpected semicolon at the end of 'color' style value: 'red;'`,
).toHaveBeenWarned()
patchProp(el, 'style', null, { '--custom': '100; ' })
expect(
- `Unexpected semicolon at the end of '--custom' style value: '100; '`
+ `Unexpected semicolon at the end of '--custom' style value: '100; '`,
).toHaveBeenWarned()
})
el as any,
'style',
{ borderBottom: '1px solid red' },
- { border: '1px solid green' }
+ { border: '1px solid green' },
)
expect(el.style.border).toBe('1px solid green')
expect(el.style.borderBottom).toBe('1px solid green')
},
getPropertyValue(key: string) {
return store[key]
- }
- }
+ },
+ },
}
}
el as any,
'style',
{},
- { display: ['-webkit-box', '-ms-flexbox', 'flex'] }
+ { display: ['-webkit-box', '-ms-flexbox', 'flex'] },
)
expect(el.style.display).toBe('flex')
})
import {
- ComponentOptionsMixin,
- ComponentOptionsWithArrayProps,
- ComponentOptionsWithObjectProps,
- ComponentOptionsWithoutProps,
- ComponentPropsOptions,
- ComputedOptions,
- EmitsOptions,
- MethodOptions,
- RenderFunction,
- SetupContext,
- ComponentInternalInstance,
- VNode,
- RootHydrateFunction,
- ExtractPropTypes,
+ type ComponentInjectOptions,
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ type ComponentOptionsMixin,
+ type ComponentOptionsWithArrayProps,
+ type ComponentOptionsWithObjectProps,
+ type ComponentOptionsWithoutProps,
+ type ComponentPropsOptions,
+ type ComputedOptions,
+ type ConcreteComponent,
+ type DefineComponent,
+ type EmitsOptions,
+ type ExtractPropTypes,
+ type MethodOptions,
+ type RenderFunction,
+ type RootHydrateFunction,
+ type SetupContext,
+ type SlotsType,
+ type VNode,
createVNode,
defineComponent,
nextTick,
warn,
- ConcreteComponent,
- ComponentOptions,
- ComponentInjectOptions,
- SlotsType,
- DefineComponent
} from '@vue/runtime-core'
import { camelize, extend, hyphenate, isArray, toNumber } from '@vue/shared'
import { hydrate, render } from '.'
export function defineCustomElement<Props, RawBindings = object>(
setup: (
props: Readonly<Props>,
- ctx: SetupContext
- ) => RawBindings | RenderFunction
+ ctx: SetupContext,
+ ) => RawBindings | RenderFunction,
): VueElementConstructor<Props>
// overload 2: object format with no props
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
- S extends SlotsType = {}
+ S extends SlotsType = {},
>(
options: ComponentOptionsWithoutProps<
Props,
I,
II,
S
- > & { styles?: string[] }
+ > & { styles?: string[] },
): VueElementConstructor<Props>
// overload 3: object format with array props declaration
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
- S extends SlotsType = {}
+ S extends SlotsType = {},
>(
options: ComponentOptionsWithArrayProps<
PropNames,
I,
II,
S
- > & { styles?: string[] }
+ > & { styles?: string[] },
): VueElementConstructor<{ [K in PropNames]: any }>
// overload 4: object format with object props declaration
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
- S extends SlotsType = {}
+ S extends SlotsType = {},
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
I,
II,
S
- > & { styles?: string[] }
+ > & { styles?: string[] },
): VueElementConstructor<ExtractPropTypes<PropsOptions>>
// overload 5: defining a custom element from the returned value of
// `defineComponent`
export function defineCustomElement<P>(
- options: DefineComponent<P, any, any, any>
+ options: DefineComponent<P, any, any, any>,
): VueElementConstructor<ExtractPropTypes<P>>
/*! #__NO_SIDE_EFFECTS__ */
export function defineCustomElement(
options: any,
- hydrate?: RootHydrateFunction
+ hydrate?: RootHydrateFunction,
): VueElementConstructor {
const Comp = defineComponent(options) as any
class VueCustomElement extends VueElement {
/*! #__NO_SIDE_EFFECTS__ */
export const defineSSRCustomElement = ((options: any) => {
- // @ts-ignore
+ // @ts-expect-error
return defineCustomElement(options, hydrate)
}) as typeof defineCustomElement
constructor(
private _def: InnerComponentDef,
private _props: Record<string, any> = {},
- hydrate?: RootHydrateFunction
+ hydrate?: RootHydrateFunction,
) {
super()
if (this.shadowRoot && hydrate) {
if (__DEV__ && this.shadowRoot) {
warn(
`Custom element has pre-rendered declarative shadow root but is not ` +
- `defined as hydratable. Use \`defineSSRCustomElement\`.`
+ `defined as hydratable. Use \`defineSSRCustomElement\`.`,
)
}
this.attachShadow({ mode: 'open' })
},
set(val) {
this._setProp(key, val)
- }
+ },
})
}
}
key: string,
val: any,
shouldReflect = true,
- shouldUpdate = true
+ shouldUpdate = true,
) {
if (val !== this._props[key]) {
this._props[key] = val
const dispatch = (event: string, args: any[]) => {
this.dispatchEvent(
new CustomEvent(event, {
- detail: args
- })
+ detail: args,
+ }),
)
}
import {
BaseTransition,
- BaseTransitionProps,
+ type BaseTransitionProps,
BaseTransitionPropsValidators,
- h,
+ DeprecationTypes,
+ type FunctionalComponent,
assertNumber,
- FunctionalComponent,
compatUtils,
- DeprecationTypes
+ h,
} from '@vue/runtime-core'
-import { isObject, toNumber, extend, isArray } from '@vue/shared'
+import { extend, isArray, isObject, toNumber } from '@vue/shared'
const TRANSITION = 'transition'
const ANIMATION = 'animation'
// base Transition component, with DOM-specific logic.
export const Transition: FunctionalComponent<TransitionProps> = (
props,
- { slots }
+ { slots },
) => h(BaseTransition, resolveTransitionProps(props), slots)
Transition.displayName = 'Transition'
type: String,
css: {
type: Boolean,
- default: true
+ default: true,
},
duration: [String, Number, Object],
enterFromClass: String,
appearToClass: String,
leaveFromClass: String,
leaveActiveClass: String,
- leaveToClass: String
+ leaveToClass: String,
}
export const TransitionPropsValidators = (Transition.props =
/*#__PURE__*/ extend(
{},
BaseTransitionPropsValidators as any,
- DOMTransitionPropsValidators
+ DOMTransitionPropsValidators,
))
/**
*/
const callHook = (
hook: Function | Function[] | undefined,
- args: any[] = []
+ args: any[] = [],
) => {
if (isArray(hook)) {
hook.forEach(h => h(...args))
* intends to explicitly control the end of the transition.
*/
const hasExplicitCallback = (
- hook: Function | Function[] | undefined
+ hook: Function | Function[] | undefined,
): boolean => {
return hook
? isArray(hook)
}
export function resolveTransitionProps(
- rawProps: TransitionProps
+ rawProps: TransitionProps,
): BaseTransitionProps<Element> {
const baseProps: BaseTransitionProps<Element> = {}
for (const key in rawProps) {
appearToClass = enterToClass,
leaveFromClass = `${name}-leave-from`,
leaveActiveClass = `${name}-leave-active`,
- leaveToClass = `${name}-leave-to`
+ leaveToClass = `${name}-leave-to`,
} = rawProps
// legacy transition class compat
onLeaveCancelled,
onBeforeAppear = onBeforeEnter,
onAppear = onEnter,
- onAppearCancelled = onEnterCancelled
+ onAppearCancelled = onEnterCancelled,
} = baseProps
const finishEnter = (el: Element, isAppear: boolean, done?: () => void) => {
const finishLeave = (
el: Element & { _isLeaving?: boolean },
- done?: () => void
+ done?: () => void,
) => {
el._isLeaving = false
removeTransitionClass(el, leaveFromClass)
onLeaveCancelled(el) {
finishLeave(el)
callHook(onLeaveCancelled, [el])
- }
+ },
} as BaseTransitionProps<Element>)
}
function normalizeDuration(
- duration: TransitionProps['duration']
+ duration: TransitionProps['duration'],
): [number, number] | null {
if (duration == null) {
return null
el: Element & { _endId?: number },
expectedType: TransitionProps['type'] | undefined,
explicitTimeout: number | null,
- resolve: () => void
+ resolve: () => void,
) {
const id = (el._endId = ++endId)
const resolveIfNotStale = () => {
export function getTransitionInfo(
el: Element,
- expectedType?: TransitionProps['type']
+ expectedType?: TransitionProps['type'],
): CSSTransitionInfo {
const styles = window.getComputedStyle(el) as Pick<
CSSStyleDeclaration,
const hasTransform =
type === TRANSITION &&
/\b(transform|all)(,|$)/.test(
- getStyleProperties(`${TRANSITION}Property`).toString()
+ getStyleProperties(`${TRANSITION}Property`).toString(),
)
return {
type,
timeout,
propCount,
- hasTransform
+ hasTransform,
}
}
import {
- TransitionProps,
+ type ElementWithTransition,
+ type TransitionProps,
+ TransitionPropsValidators,
addTransitionClass,
- removeTransitionClass,
- ElementWithTransition,
+ forceReflow,
getTransitionInfo,
+ removeTransitionClass,
resolveTransitionProps,
- TransitionPropsValidators,
- forceReflow,
- vtcKey
+ vtcKey,
} from './Transition'
import {
+ type ComponentOptions,
+ DeprecationTypes,
Fragment,
- VNode,
- warn,
- resolveTransitionHooks,
- useTransitionState,
- getTransitionRawChildren,
- getCurrentInstance,
- setTransitionHooks,
+ type SetupContext,
+ type VNode,
+ compatUtils,
createVNode,
+ getCurrentInstance,
+ getTransitionRawChildren,
onUpdated,
- SetupContext,
+ resolveTransitionHooks,
+ setTransitionHooks,
toRaw,
- compatUtils,
- DeprecationTypes,
- ComponentOptions
+ useTransitionState,
+ warn,
} from '@vue/runtime-core'
import { extend } from '@vue/shared'
props: /*#__PURE__*/ extend({}, TransitionPropsValidators, {
tag: String,
- moveClass: String
+ moveClass: String,
}),
setup(props: TransitionGroupProps, { slots }: SetupContext) {
!hasCSSTransform(
prevChildren[0].el as ElementWithTransition,
instance.vnode.el as Node,
- moveClass
+ moveClass,
)
) {
return
!rawProps.tag &&
compatUtils.checkCompatEnabled(
DeprecationTypes.TRANSITION_GROUP_ROOT,
- instance.parent
+ instance.parent,
)
) {
tag = 'span'
if (child.key != null) {
setTransitionHooks(
child,
- resolveTransitionHooks(child, cssTransitionProps, state, instance)
+ resolveTransitionHooks(child, cssTransitionProps, state, instance),
)
} else if (__DEV__) {
warn(`<TransitionGroup> children must be keyed.`)
const child = prevChildren[i]
setTransitionHooks(
child,
- resolveTransitionHooks(child, cssTransitionProps, state, instance)
+ resolveTransitionHooks(child, cssTransitionProps, state, instance),
)
positionMap.set(child, (child.el as Element).getBoundingClientRect())
}
return createVNode(tag, null, children)
}
- }
+ },
}
if (__COMPAT__) {
function hasCSSTransform(
el: ElementWithTransition,
root: Node,
- moveClass: string
+ moveClass: string,
): boolean {
// Detect whether an element with the move class applied has
// CSS transitions. Since the element may be inside an entering
import {
- ObjectDirective,
- VNode,
- DirectiveHook,
- DirectiveBinding,
- warn
+ type DirectiveBinding,
+ type DirectiveHook,
+ type ObjectDirective,
+ type VNode,
+ warn,
} from '@vue/runtime-core'
import { addEventListener } from '../modules/events'
import {
+ invokeArrayFns,
isArray,
+ isSet,
looseEqual,
looseIndexOf,
- invokeArrayFns,
looseToNumber,
- isSet
} from '@vue/shared'
type AssignerFn = (value: any) => void
}
el.value = newValue
- }
+ },
}
export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
beforeUpdate(el, binding, vnode) {
el[assignKey] = getModelAssigner(vnode)
setChecked(el, binding, vnode)
- }
+ },
}
function setChecked(
el: HTMLInputElement,
{ value, oldValue }: DirectiveBinding,
- vnode: VNode
+ vnode: VNode,
) {
// store the v-model value on the element so it can be accessed by the
// change listener.
if (value !== oldValue) {
el.checked = looseEqual(value, vnode.props!.value)
}
- }
+ },
}
export const vModelSelect: ModelDirective<HTMLSelectElement> = {
const selectedVal = Array.prototype.filter
.call(el.options, (o: HTMLOptionElement) => o.selected)
.map((o: HTMLOptionElement) =>
- number ? looseToNumber(getValue(o)) : getValue(o)
+ number ? looseToNumber(getValue(o)) : getValue(o),
)
el[assignKey](
el.multiple
? isSetModel
? new Set(selectedVal)
: selectedVal
- : selectedVal[0]
+ : selectedVal[0],
)
})
el[assignKey] = getModelAssigner(vnode)
},
updated(el, { value }) {
setSelected(el, value)
- }
+ },
}
function setSelected(el: HTMLSelectElement, value: any) {
__DEV__ &&
warn(
`<select multiple v-model> expects an Array or Set value for its binding, ` +
- `but got ${Object.prototype.toString.call(value).slice(8, -1)}.`
+ `but got ${Object.prototype.toString.call(value).slice(8, -1)}.`,
)
return
}
// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
function getCheckboxValue(
el: HTMLInputElement & { _trueValue?: any; _falseValue?: any },
- checked: boolean
+ checked: boolean,
) {
const key = checked ? '_trueValue' : '_falseValue'
return key in el ? el[key] : checked
},
updated(el, binding, vnode, prevVNode) {
callModelHook(el, binding, vnode, prevVNode, 'updated')
- }
+ },
}
function resolveDynamicModel(tagName: string, type: string | undefined) {
binding: DirectiveBinding,
vnode: VNode,
prevVNode: VNode | null,
- hook: keyof ObjectDirective
+ hook: keyof ObjectDirective,
) {
const modelToUse = resolveDynamicModel(
el.tagName,
- vnode.props && vnode.props.type
+ vnode.props && vnode.props.type,
)
const fn = modelToUse[hook] as DirectiveHook
fn && fn(el, binding, vnode, prevVNode)
const modelToUse = resolveDynamicModel(
// resolveDynamicModel expects an uppercase tag name, but vnode.type is lowercase
vnode.type.toUpperCase(),
- vnode.props && vnode.props.type
+ vnode.props && vnode.props.type,
)
if (modelToUse.getSSRProps) {
return modelToUse.getSSRProps(binding, vnode)
import {
- getCurrentInstance,
+ type ComponentInternalInstance,
DeprecationTypes,
- LegacyConfig,
+ type LegacyConfig,
compatUtils,
- ComponentInternalInstance
+ getCurrentInstance,
} from '@vue/runtime-core'
import { hyphenate, isArray } from '@vue/shared'
middle: e => 'button' in e && (e as MouseEvent).button !== 1,
right: e => 'button' in e && (e as MouseEvent).button !== 2,
exact: (e, modifiers) =>
- systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m))
+ systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)),
}
/**
* @private
*/
export const withModifiers = <
- T extends (event: Event, ...args: unknown[]) => any
+ T extends (event: Event, ...args: unknown[]) => any,
>(
fn: T & { _withMods?: { [key: string]: T } },
- modifiers: string[]
+ modifiers: string[],
) => {
const cache = fn._withMods || (fn._withMods = {})
const cacheKey = modifiers.join('.')
left: 'arrow-left',
right: 'arrow-right',
down: 'arrow-down',
- delete: 'backspace'
+ delete: 'backspace',
}
/**
*/
export const withKeys = <T extends (event: KeyboardEvent) => any>(
fn: T & { _withKeys?: { [k: string]: T } },
- modifiers: string[]
+ modifiers: string[],
) => {
let globalKeyCodes: LegacyConfig['keyCodes']
let instance: ComponentInternalInstance | null = null
if (__DEV__ && modifiers.some(m => /^\d+$/.test(m))) {
compatUtils.warnDeprecation(
DeprecationTypes.V_ON_KEYCODE_MODIFIER,
- instance
+ instance,
)
}
}
if (
compatUtils.isCompatEnabled(
DeprecationTypes.V_ON_KEYCODE_MODIFIER,
- instance
+ instance,
) &&
modifiers.some(mod => mod == keyCode)
) {
-import { ObjectDirective } from '@vue/runtime-core'
+import type { ObjectDirective } from '@vue/runtime-core'
export const vShowOldKey = Symbol('_vod')
},
beforeUnmount(el, { value }) {
setDisplay(el, value)
- }
+ },
}
function setDisplay(el: VShowElement, value: unknown): void {
-import { warn, getCurrentInstance } from '@vue/runtime-core'
+import { getCurrentInstance, warn } from '@vue/runtime-core'
import { EMPTY_OBJ } from '@vue/shared'
export function useCssModule(name = '$style'): Record<string, string> {
import {
- getCurrentInstance,
- warn,
- VNode,
Fragment,
Static,
- watchPostEffect,
+ type VNode,
+ getCurrentInstance,
onMounted,
- onUnmounted
+ onUnmounted,
+ warn,
+ watchPostEffect,
} from '@vue/runtime-core'
import { ShapeFlags } from '@vue/shared'
const updateTeleports = (instance.ut = (vars = getter(instance.proxy)) => {
Array.from(
- document.querySelectorAll(`[data-v-owner="${instance.uid}"]`)
+ document.querySelectorAll(`[data-v-owner="${instance.uid}"]`),
).forEach(node => setVarsOnNode(node, vars))
})
import {
- createRenderer,
- createHydrationRenderer,
- warn,
- RootRenderFunction,
- CreateAppFunction,
- Renderer,
- HydrationRenderer,
- App,
- RootHydrateFunction,
- isRuntimeOnly,
+ type App,
+ type CreateAppFunction,
DeprecationTypes,
+ type ElementNamespace,
+ type HydrationRenderer,
+ type Renderer,
+ type RootHydrateFunction,
+ type RootRenderFunction,
compatUtils,
- ElementNamespace
+ createHydrationRenderer,
+ createRenderer,
+ isRuntimeOnly,
+ warn,
} from '@vue/runtime-core'
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'
// Importing from the compiler, will be tree-shaken in prod
import {
+ NOOP,
+ extend,
isFunction,
- isString,
isHTMLTag,
+ isMathMLTag,
isSVGTag,
- extend,
- NOOP,
- isMathMLTag
+ isString,
} from '@vue/shared'
declare module '@vue/reactivity' {
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
compatUtils.warnDeprecation(
DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
- null
+ null,
)
break
}
// this is used for component name validation (dev only)
Object.defineProperty(app.config, 'isNativeTag', {
value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag),
- writable: false
+ writable: false,
})
}
set() {
warn(
`The \`isCustomElement\` config option is deprecated. Use ` +
- `\`compilerOptions.isCustomElement\` instead.`
+ `\`compilerOptions.isCustomElement\` instead.`,
)
- }
+ },
})
const compilerOptions = app.config.compilerOptions
},
set() {
warn(msg)
- }
+ },
})
}
}
function normalizeContainer(
- container: Element | ShadowRoot | string
+ container: Element | ShadowRoot | string,
): Element | null {
if (isString(container)) {
const res = document.querySelector(container)
if (__DEV__ && !res) {
warn(
- `Failed to mount app: mount target selector "${container}" returned null.`
+ `Failed to mount app: mount target selector "${container}" returned null.`,
)
}
return res
container.mode === 'closed'
) {
warn(
- `mounting on a ShadowRoot with \`{mode: "closed"}\` may lead to unpredictable bugs`
+ `mounting on a ShadowRoot with \`{mode: "closed"}\` may lead to unpredictable bugs`,
)
}
return container as any
defineCustomElement,
defineSSRCustomElement,
VueElement,
- type VueElementConstructor
+ type VueElementConstructor,
} from './apiCustomElement'
// SFC CSS utilities
export { Transition, type TransitionProps } from './components/Transition'
export {
TransitionGroup,
- type TransitionGroupProps
+ type TransitionGroupProps,
} from './components/TransitionGroup'
// **Internal** DOM-only runtime directive helpers
vModelCheckbox,
vModelRadio,
vModelSelect,
- vModelDynamic
+ vModelDynamic,
} from './directives/vModel'
export { withModifiers, withKeys } from './directives/vOn'
export { vShow } from './directives/vShow'
// Kanitkorn Sujautra <https://github.com/lukyth>
// Sebastian Silbermann <https://github.com/eps1lon>
-import * as CSS from 'csstype'
+import type * as CSS from 'csstype'
export interface CSSProperties
extends CSS.Properties<string | number>,
: (payload: E[K]) => void
}
-import { VNodeRef } from '@vue/runtime-core'
+import type { VNodeRef } from '@vue/runtime-core'
export type ReservedProps = {
key?: string | number | symbol
import {
+ NOOP,
includeBooleanAttr,
isSpecialBooleanAttr,
makeMap,
- NOOP
} from '@vue/shared'
import {
+ type ComponentInternalInstance,
+ DeprecationTypes,
compatUtils,
- ComponentInternalInstance,
- DeprecationTypes
} from '@vue/runtime-core'
export const xlinkNS = 'http://www.w3.org/1999/xlink'
key: string,
value: any,
isSVG: boolean,
- instance?: ComponentInternalInstance | null
+ instance?: ComponentInternalInstance | null,
) {
if (isSVG && key.startsWith('xlink:')) {
if (value == null) {
el: Element,
key: string,
value: unknown,
- instance: ComponentInternalInstance | null = null
+ instance: ComponentInternalInstance | null = null,
): boolean {
if (isEnumeratedAttr(key)) {
const v2CoercedValue =
instance,
key,
value,
- v2CoercedValue
+ v2CoercedValue,
)
) {
el.setAttribute(key, v2CoercedValue)
compatUtils.softAssertCompatEnabled(
DeprecationTypes.ATTR_FALSE_VALUE,
instance,
- key
+ key,
)
) {
el.removeAttribute(key)
-import { ElementWithTransition, vtcKey } from '../components/Transition'
+import { type ElementWithTransition, vtcKey } from '../components/Transition'
// compiler should normalize class + :class bindings on the same element
// into a single binding ['staticClass', dynamic]
import { hyphenate, isArray } from '@vue/shared'
import {
+ type ComponentInternalInstance,
ErrorCodes,
- ComponentInternalInstance,
- callWithAsyncErrorHandling
+ callWithAsyncErrorHandling,
} from '@vue/runtime-core'
interface Invoker extends EventListener {
el: Element,
event: string,
handler: EventListener,
- options?: EventListenerOptions
+ options?: EventListenerOptions,
) {
el.addEventListener(event, handler, options)
}
el: Element,
event: string,
handler: EventListener,
- options?: EventListenerOptions
+ options?: EventListenerOptions,
) {
el.removeEventListener(event, handler, options)
}
rawName: string,
prevValue: EventValue | null,
nextValue: EventValue | null,
- instance: ComponentInternalInstance | null = null
+ instance: ComponentInternalInstance | null = null,
) {
// vei = vue event invokers
const invokers = el[veiKey] || (el[veiKey] = {})
function createInvoker(
initialValue: EventValue,
- instance: ComponentInternalInstance | null
+ instance: ComponentInternalInstance | null,
) {
const invoker: Invoker = (e: Event & { _vts?: number }) => {
// async edge case vuejs/vue#6566
patchStopImmediatePropagation(e, invoker.value),
instance,
ErrorCodes.NATIVE_EVENT_HANDLER,
- [e]
+ [e],
)
}
invoker.value = initialValue
function patchStopImmediatePropagation(
e: Event,
- value: EventValue
+ value: EventValue,
): EventValue {
if (isArray(value)) {
const originalStop = e.stopImmediatePropagation
// Reason: potentially setting innerHTML.
// This can come from explicit usage of v-html or innerHTML as a prop in render
-import { warn, DeprecationTypes, compatUtils } from '@vue/runtime-core'
+import { DeprecationTypes, compatUtils, warn } from '@vue/runtime-core'
import { includeBooleanAttr } from '@vue/shared'
// functions. The user is responsible for using them with only trusted content.
prevChildren: any,
parentComponent: any,
parentSuspense: any,
- unmountChildren: any
+ unmountChildren: any,
) {
if (key === 'innerHTML' || key === 'textContent') {
if (prevChildren) {
value === false &&
compatUtils.isCompatEnabled(
DeprecationTypes.ATTR_FALSE_VALUE,
- parentComponent
+ parentComponent,
)
) {
const type = typeof el[key]
compatUtils.warnDeprecation(
DeprecationTypes.ATTR_FALSE_VALUE,
parentComponent,
- key
+ key,
)
value = type === 'number' ? 0 : ''
needRemove = true
warn(
`Failed setting prop "${key}" on <${tag.toLowerCase()}>: ` +
`value ${value} is invalid.`,
- e
+ e,
)
}
}
-import { isString, hyphenate, capitalize, isArray } from '@vue/shared'
+import { capitalize, hyphenate, isArray, isString } from '@vue/shared'
import { camelize, warn } from '@vue/runtime-core'
import { vShowOldKey } from '../directives/vShow'
import { CSS_VAR_TEXT } from '../helpers/useCssVars'
function setStyle(
style: CSSStyleDeclaration,
name: string,
- val: string | string[]
+ val: string | string[],
) {
if (isArray(val)) {
val.forEach(v => setStyle(style, name, v))
if (__DEV__) {
if (semicolonRE.test(val)) {
warn(
- `Unexpected semicolon at the end of '${name}' style value: '${val}'`
+ `Unexpected semicolon at the end of '${name}' style value: '${val}'`,
)
}
}
style.setProperty(
hyphenate(prefixed),
val.replace(importantRE, ''),
- 'important'
+ 'important',
)
} else {
style[prefixed as any] = val
-import { RendererOptions } from '@vue/runtime-core'
+import type { RendererOptions } from '@vue/runtime-core'
export const svgNS = 'http://www.w3.org/2000/svg'
export const mathmlNS = 'http://www.w3.org/1998/Math/MathML'
// first
before ? before.nextSibling! : parent.firstChild!,
// last
- anchor ? anchor.previousSibling! : parent.lastChild!
+ anchor ? anchor.previousSibling! : parent.lastChild!,
]
- }
+ },
}
import { patchAttr } from './modules/attrs'
import { patchDOMProp } from './modules/props'
import { patchEvent } from './modules/events'
-import { isOn, isString, isFunction, isModelListener } from '@vue/shared'
-import { RendererOptions } from '@vue/runtime-core'
+import { isFunction, isModelListener, isOn, isString } from '@vue/shared'
+import type { RendererOptions } from '@vue/runtime-core'
const isNativeOn = (key: string) =>
key.charCodeAt(0) === 111 /* o */ &&
prevChildren,
parentComponent,
parentSuspense,
- unmountChildren
+ unmountChildren,
) => {
const isSVG = namespace === 'svg'
if (key === 'class') {
prevChildren,
parentComponent,
parentSuspense,
- unmountChildren
+ unmountChildren,
)
} else {
// special case for <input v-model type="checkbox"> with
el: Element,
key: string,
value: unknown,
- isSVG: boolean
+ isSVG: boolean,
) {
if (isSVG) {
// most keys must be set as attribute on svg elements to work
import {
+ NodeOpTypes,
+ type TestElement,
+ TestNodeTypes,
+ type TestText,
+ dumpOps,
h,
- render,
+ nextTick,
nodeOps,
- TestNodeTypes,
- TestElement,
- TestText,
- ref,
reactive,
- dumpOps,
+ ref,
+ render,
resetOps,
- NodeOpTypes,
- nextTick,
serialize,
- triggerEvent
+ triggerEvent,
} from '../src'
describe('test renderer', () => {
h(
'div',
{
- id: 'test'
+ id: 'test',
},
- 'hello'
+ 'hello',
),
- root
+ root,
)
expect(root.children.length).toBe(1)
it('should record ops', async () => {
const state = reactive({
id: 'test',
- text: 'hello'
+ text: 'hello',
})
const App = {
return h(
'div',
{
- id: state.id
+ id: state.id,
},
- state.text
+ state.text,
)
- }
+ },
}
const root = nodeOps.createElement('div')
type: NodeOpTypes.CREATE,
nodeType: TestNodeTypes.ELEMENT,
tag: 'div',
- targetNode: root.children[0]
+ targetNode: root.children[0],
})
expect(ops[1]).toEqual({
type: NodeOpTypes.SET_ELEMENT_TEXT,
text: 'hello',
- targetNode: root.children[0]
+ targetNode: root.children[0],
})
expect(ops[2]).toEqual({
targetNode: root.children[0],
propKey: 'id',
propPrevValue: null,
- propNextValue: 'test'
+ propNextValue: 'test',
})
expect(ops[3]).toEqual({
type: NodeOpTypes.INSERT,
targetNode: root.children[0],
parentNode: root,
- refNode: null
+ refNode: null,
})
// test update ops
expect(updateOps[0]).toEqual({
type: NodeOpTypes.SET_ELEMENT_TEXT,
targetNode: root.children[0],
- text: 'bar'
+ text: 'bar',
})
expect(updateOps[1]).toEqual({
targetNode: root.children[0],
propKey: 'id',
propPrevValue: 'test',
- propNextValue: 'foo'
+ propNextValue: 'foo',
})
})
'div',
{
id: 'test',
- boolean: ''
+ boolean: '',
},
- [h('span', 'foo'), 'hello']
+ [h('span', 'foo'), 'hello'],
)
- }
+ },
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serialize(root)).toEqual(
- `<div><div id="test" boolean><span>foo</span>hello</div></div>`
+ `<div><div id="test" boolean><span>foo</span>hello</div></div>`,
)
// indented output
expect(serialize(root, 2)).toEqual(
</span>
hello
</div>
-</div>`
+</div>`,
)
})
{
onClick: () => {
count.value++
- }
+ },
},
- count.value
+ count.value,
)
}
},
() => {
count2.value++
- }
- ]
+ },
+ ],
},
- `${count.value}, ${count2.value}`
+ `${count.value}, ${count2.value}`,
)
}
import {
+ type CreateAppFunction,
+ type RootRenderFunction,
+ type VNode,
createRenderer,
- VNode,
- RootRenderFunction,
- CreateAppFunction
} from '@vue/runtime-core'
-import { nodeOps, TestElement } from './nodeOps'
+import { type TestElement, nodeOps } from './nodeOps'
import { patchProp } from './patchProp'
import { serializeInner } from './serialize'
import { extend } from '@vue/shared'
const { render: baseRender, createApp: baseCreateApp } = createRenderer(
- extend({ patchProp }, nodeOps)
+ extend({ patchProp }, nodeOps),
)
export const render = baseRender as RootRenderFunction<TestElement>
export enum TestNodeTypes {
TEXT = 'text',
ELEMENT = 'element',
- COMMENT = 'comment'
+ COMMENT = 'comment',
}
export enum NodeOpTypes {
REMOVE = 'remove',
SET_TEXT = 'setText',
SET_ELEMENT_TEXT = 'setElementText',
- PATCH = 'patch'
+ PATCH = 'patch',
}
export interface TestElement {
children: [],
props: {},
parentNode: null,
- eventListeners: null
+ eventListeners: null,
}
logNodeOp({
type: NodeOpTypes.CREATE,
nodeType: TestNodeTypes.ELEMENT,
targetNode: node,
- tag
+ tag,
})
// avoid test nodes from being observed
markRaw(node)
id: nodeId++,
type: TestNodeTypes.TEXT,
text,
- parentNode: null
+ parentNode: null,
}
logNodeOp({
type: NodeOpTypes.CREATE,
nodeType: TestNodeTypes.TEXT,
targetNode: node,
- text
+ text,
})
// avoid test nodes from being observed
markRaw(node)
id: nodeId++,
type: TestNodeTypes.COMMENT,
text,
- parentNode: null
+ parentNode: null,
}
logNodeOp({
type: NodeOpTypes.CREATE,
nodeType: TestNodeTypes.COMMENT,
targetNode: node,
- text
+ text,
})
// avoid test nodes from being observed
markRaw(node)
logNodeOp({
type: NodeOpTypes.SET_TEXT,
targetNode: node,
- text
+ text,
})
node.text = text
}
type: NodeOpTypes.INSERT,
targetNode: child,
parentNode: parent,
- refNode: ref
+ refNode: ref,
})
// remove the node first, but don't log it as a REMOVE op
remove(child, false)
logNodeOp({
type: NodeOpTypes.REMOVE,
targetNode: child,
- parentNode: parent
+ parentNode: parent,
})
}
const i = parent.children.indexOf(child)
logNodeOp({
type: NodeOpTypes.SET_ELEMENT_TEXT,
targetNode: el,
- text
+ text,
})
el.children.forEach(c => {
c.parentNode = null
id: nodeId++,
type: TestNodeTypes.TEXT,
text,
- parentNode: el
- }
+ parentNode: el,
+ },
]
}
}
parentNode,
nextSibling,
querySelector,
- setScopeId
+ setScopeId,
}
-import { TestElement, logNodeOp, NodeOpTypes } from './nodeOps'
+import { NodeOpTypes, type TestElement, logNodeOp } from './nodeOps'
import { isOn } from '@vue/shared'
export function patchProp(
el: TestElement,
key: string,
prevValue: any,
- nextValue: any
+ nextValue: any,
) {
logNodeOp({
type: NodeOpTypes.PATCH,
targetNode: el,
propKey: key,
propPrevValue: prevValue,
- propNextValue: nextValue
+ propNextValue: nextValue,
})
el.props[key] = nextValue
if (isOn(key)) {
import {
- TestElement,
- TestNode,
+ type TestComment,
+ type TestElement,
+ type TestNode,
TestNodeTypes,
- TestText,
- TestComment
+ type TestText,
} from './nodeOps'
import { isOn } from '@vue/shared'
export function serialize(
node: TestNode,
indent: number = 0,
- depth: number = 0
+ depth: number = 0,
): string {
if (node.type === TestNodeTypes.ELEMENT) {
return serializeElement(node, indent, depth)
export function serializeInner(
node: TestElement,
indent: number = 0,
- depth: number = 0
+ depth: number = 0,
) {
const newLine = indent ? `\n` : ``
return node.children.length
function serializeElement(
node: TestElement,
indent: number,
- depth: number
+ depth: number,
): string {
const props = Object.keys(node.props)
.map(key => {
function serializeText(
node: TestText | TestComment,
indent: number,
- depth: number
+ depth: number,
): string {
const padding = indent ? ` `.repeat(indent).repeat(depth) : ``
return (
import { isArray } from '@vue/shared'
-import { TestElement } from './nodeOps'
+import type { TestElement } from './nodeOps'
export function triggerEvent(
el: TestElement,
event: string,
- payload: any[] = []
+ payload: any[] = [],
) {
const { eventListeners } = el
if (eventListeners) {
import {
+ type ComponentOptions,
+ KeepAlive,
+ Transition,
+ computed,
createApp,
- h,
createCommentVNode,
- resolveComponent,
- ComponentOptions,
- ref,
- defineComponent,
- createTextVNode,
+ createSSRApp,
createStaticVNode,
- withCtx,
- KeepAlive,
- Transition,
- watchEffect,
+ createTextVNode,
createVNode,
- resolveDynamicComponent,
- renderSlot,
+ defineComponent,
+ getCurrentInstance,
+ h,
onErrorCaptured,
onServerPrefetch,
- getCurrentInstance,
reactive,
- computed,
- createSSRApp
+ ref,
+ renderSlot,
+ resolveComponent,
+ resolveDynamicComponent,
+ watchEffect,
+ withCtx,
} from 'vue'
import { escapeHtml } from '@vue/shared'
import { renderToString } from '../src/renderToString'
-import { renderToNodeStream, pipeToNodeWritable } from '../src/renderToStream'
-import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
+import { pipeToNodeWritable, renderToNodeStream } from '../src/renderToStream'
+import { type SSRSlot, ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
-import { Readable, Transform } from 'stream'
+import { type Readable, Transform } from 'node:stream'
import { ssrRenderVNode } from '../src'
const promisifyStream = (stream: Readable) => {
transform(data, _encoding, cb) {
this.push(data)
cb()
- }
+ },
})
pipeToNodeWritable(app, context, stream)
return promisifyStream(stream)
render() {
const Foo = resolveComponent('foo') as ComponentOptions
return h(Foo)
- }
+ },
})
app.component('foo', {
- render: () => h('div', 'foo')
+ render: () => h('div', 'foo'),
})
const html = await render(app)
expect(html).toBe(`<div>foo</div>`)
},
render(this: any) {
return h('div', this.msg)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div>hello</div>`)
})
setup() {
const msg = ref('hello')
return () => h('div', msg.value)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div>hello</div>`)
})
defineComponent(() => {
const msg = ref('hello')
return () => h('div', msg.value)
- })
- )
- )
+ }),
+ ),
+ ),
).toBe(`<div>hello</div>`)
})
},
render() {
return h('div', this.msg)
- }
- })
- })
- )
- )
+ },
+ }),
+ }),
+ ),
+ ),
).toBe(`<div>hello</div>`)
})
},
render() {
return h('div', this.msg)
- }
- })
- ]
- })
- )
- )
+ },
+ }),
+ ],
+ }),
+ ),
+ ),
).toBe(`<div>hello</div>`)
})
},
ssrRender(ctx, push) {
push(`<div>${ctx.msg}</div>`)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div>hello</div>`)
})
props: ['msg'],
render(this: any) {
return h('div', this.msg)
- }
+ },
}
expect(
createApp({
render() {
return h('div', ['parent', h(Child, { msg: 'hello' })])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div>parent<div>hello</div></div>`)
})
props: ['msg'],
ssrRender(ctx: any, push: any) {
push(`<div>${ctx.msg}</div>`)
- }
+ },
}
expect(
push(`<div>parent`)
push(ssrRenderComponent(Child, { msg: 'hello' }, null, parent))
push(`</div>`)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div>parent<div>hello</div></div>`)
})
test('nested template components', async () => {
const Child = {
props: ['msg'],
- template: `<div>{{ msg }}</div>`
+ template: `<div>{{ msg }}</div>`,
}
const app = createApp({
- template: `<div>parent<Child msg="hello" /></div>`
+ template: `<div>parent<Child msg="hello" /></div>`,
})
app.component('Child', Child)
test('template components with dynamic class attribute after static', async () => {
const app = createApp({
- template: `<div><div class="child" :class="'dynamic'"></div></div>`
+ template: `<div><div class="child" :class="'dynamic'"></div></div>`,
})
expect(await render(app)).toBe(
- `<div><div class="dynamic child"></div></div>`
+ `<div><div class="dynamic child"></div></div>`,
)
})
test('template components with dynamic class attribute before static', async () => {
const app = createApp({
- template: `<div><div :class="'dynamic'" class="child"></div></div>`
+ template: `<div><div :class="'dynamic'" class="child"></div></div>`,
})
expect(await render(app)).toBe(
- `<div><div class="dynamic child"></div></div>`
+ `<div><div class="dynamic child"></div></div>`,
)
})
props: ['msg'],
ssrRender(ctx: any, push: any) {
push(`<div>${ctx.msg}</div>`)
- }
+ },
}
const VNodeChild = {
props: ['msg'],
render(this: any) {
return h('div', this.msg)
- }
+ },
}
const TemplateChild = {
props: ['msg'],
- template: `<div>{{ msg }}</div>`
+ template: `<div>{{ msg }}</div>`,
}
expect(
OptimizedChild,
{ msg: 'opt' },
null,
- parent
- )
+ parent,
+ ),
)
push(
- ssrRenderComponent(VNodeChild, { msg: 'vnode' }, null, parent)
+ ssrRenderComponent(
+ VNodeChild,
+ { msg: 'vnode' },
+ null,
+ parent,
+ ),
)
push(
ssrRenderComponent(
TemplateChild,
{ msg: 'template' },
null,
- parent
- )
+ parent,
+ ),
)
push(`</div>`)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(
- `<div>parent<div>opt</div><div>vnode</div><div>template</div></div>`
+ `<div>parent<div>opt</div><div>vnode</div><div>template</div></div>`,
)
})
// should wait for resolved render context from setup()
async setup() {
return {
- msg: 'hello'
+ msg: 'hello',
}
},
ssrRender(ctx: any, push: any) {
push(`<div>${ctx.msg}</div>`)
- }
+ },
}
expect(
push(`<div>parent`)
push(ssrRenderComponent(Child, null, null, parent))
push(`</div>`)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div>parent<div>hello</div></div>`)
})
props: ['msg'],
async setup(props: any) {
return {
- localMsg: props.msg + '!'
+ localMsg: props.msg + '!',
}
},
ssrRender(ctx: any, push: any) {
push(`<div>${ctx.localMsg}</div>`)
- }
+ },
}
const VNodeChild = {
props: ['msg'],
async setup(props: any) {
return {
- localMsg: props.msg + '!'
+ localMsg: props.msg + '!',
}
},
render(this: any) {
return h('div', this.localMsg)
- }
+ },
}
expect(
OptimizedChild,
{ msg: 'opt' },
null,
- parent
- )
+ parent,
+ ),
)
push(
- ssrRenderComponent(VNodeChild, { msg: 'vnode' }, null, parent)
+ ssrRenderComponent(
+ VNodeChild,
+ { msg: 'vnode' },
+ null,
+ parent,
+ ),
)
push(`</div>`)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div>parent<div>opt!</div><div>vnode!</div></div>`)
})
})
push(`fallback`)
},
push,
- parent
+ parent,
)
push(`</div>`)
- }
+ },
}
expect(
push(`<span>${msg}</span>`)
}) as SSRSlot,
// important to avoid slots being normalized
- _: 1 as any
+ _: 1 as any,
},
- parent
- )
+ parent,
+ ),
)
push(`</div>`)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(
`<div>parent<div class="child">` +
`<!--[--><span>from slot</span><!--]-->` +
- `</div></div>`
+ `</div></div>`,
)
// test fallback
push(`<div>parent`)
push(ssrRenderComponent(Child, { msg: 'hello' }, null, parent))
push(`</div>`)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(
- `<div>parent<div class="child"><!--[-->fallback<!--]--></div></div>`
+ `<div>parent<div class="child"><!--[-->fallback<!--]--></div></div>`,
)
})
{ msg: 'from slot' },
null,
push,
- parent
+ parent,
)
push(`</div>`)
- }
+ },
}
expect(
// bailed slots returning raw vnodes
default: ({ msg }: any) => {
return h('span', msg)
- }
+ },
},
- parent
- )
+ parent,
+ ),
)
push(`</div>`)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(
`<div>parent<div class="child">` +
`<!--[--><span>from slot</span><!--]-->` +
- `</div></div>`
+ `</div></div>`,
)
})
test('nested components with template slots', async () => {
const Child = {
props: ['msg'],
- template: `<div class="child"><slot msg="from slot"></slot></div>`
+ template: `<div class="child"><slot msg="from slot"></slot></div>`,
}
const app = createApp({
components: { Child },
- template: `<div>parent<Child v-slot="{ msg }"><span>{{ msg }}</span></Child></div>`
+ template: `<div>parent<Child v-slot="{ msg }"><span>{{ msg }}</span></Child></div>`,
})
expect(await render(app)).toBe(
`<div>parent<div class="child">` +
`<!--[--><span>from slot</span><!--]-->` +
- `</div></div>`
+ `</div></div>`,
)
})
return h(
'div',
{
- class: 'child'
+ class: 'child',
},
- this.$slots.default({ msg: 'from slot' })
+ this.$slots.default({ msg: 'from slot' }),
)
- }
+ },
}
const app = createApp({
- template: `<div>parent<Child v-slot="{ msg }"><span>{{ msg }}</span></Child></div>`
+ template: `<div>parent<Child v-slot="{ msg }"><span>{{ msg }}</span></Child></div>`,
})
app.component('Child', Child)
`<div>parent<div class="child">` +
// no comment anchors because slot is used directly as element children
`<span>from slot</span>` +
- `</div></div>`
+ `</div></div>`,
)
})
test('template slots forwarding', async () => {
const Child = {
- template: `<div><slot/></div>`
+ template: `<div><slot/></div>`,
}
const Parent = {
components: { Child },
- template: `<Child><slot/></Child>`
+ template: `<Child><slot/></Child>`,
}
const app = createApp({
components: { Parent },
- template: `<Parent>hello</Parent>`
+ template: `<Parent>hello</Parent>`,
})
expect(await render(app)).toBe(
- `<div><!--[--><!--[-->hello<!--]--><!--]--></div>`
+ `<div><!--[--><!--[-->hello<!--]--><!--]--></div>`,
)
})
test('template slots forwarding, empty slot', async () => {
const Child = {
- template: `<div><slot/></div>`
+ template: `<div><slot/></div>`,
}
const Parent = {
components: { Child },
- template: `<Child><slot/></Child>`
+ template: `<Child><slot/></Child>`,
}
const app = createApp({
components: { Parent },
- template: `<Parent></Parent>`
+ template: `<Parent></Parent>`,
})
expect(await render(app)).toBe(
// should only have a single fragment
- `<div><!--[--><!--]--></div>`
+ `<div><!--[--><!--]--></div>`,
)
})
test('template slots forwarding, empty slot w/ fallback', async () => {
const Child = {
- template: `<div><slot>fallback</slot></div>`
+ template: `<div><slot>fallback</slot></div>`,
}
const Parent = {
components: { Child },
- template: `<Child><slot/></Child>`
+ template: `<Child><slot/></Child>`,
}
const app = createApp({
components: { Parent },
- template: `<Parent></Parent>`
+ template: `<Parent></Parent>`,
})
expect(await render(app)).toBe(
// should only have a single fragment
- `<div><!--[-->fallback<!--]--></div>`
+ `<div><!--[-->fallback<!--]--></div>`,
)
})
})
describe('vnode element', () => {
test('props', async () => {
expect(
- await render(h('div', { id: 'foo&', class: ['bar', 'baz'] }, 'hello'))
+ await render(
+ h('div', { id: 'foo&', class: ['bar', 'baz'] }, 'hello'),
+ ),
).toBe(`<div id="foo&" class="bar baz">hello</div>`)
})
'foo',
h('span', 'bar'),
[h('span', 'baz')],
- createCommentVNode('qux')
- ])
- )
+ createCommentVNode('qux'),
+ ]),
+ ),
).toBe(
- `<div>foo<span>bar</span><!--[--><span>baz</span><!--]--><!--qux--></div>`
+ `<div>foo<span>bar</span><!--[--><span>baz</span><!--]--><!--qux--></div>`,
)
})
h(
'div',
{
- innerHTML: `<span>hello</span>`
+ innerHTML: `<span>hello</span>`,
},
- 'ignored'
- )
- )
+ 'ignored',
+ ),
+ ),
).toBe(`<div><span>hello</span></div>`)
})
h(
'div',
{
- textContent: `<span>hello</span>`
+ textContent: `<span>hello</span>`,
},
- 'ignored'
- )
- )
+ 'ignored',
+ ),
+ ),
).toBe(`<div>${escapeHtml(`<span>hello</span>`)}</div>`)
})
h(
'textarea',
{
- value: `<span>hello</span>`
+ value: `<span>hello</span>`,
},
- 'ignored'
- )
- )
+ 'ignored',
+ ),
+ ),
).toBe(`<textarea>${escapeHtml(`<span>hello</span>`)}</textarea>`)
})
})
describe('vnode component', () => {
test('KeepAlive', async () => {
const MyComp = {
- render: () => h('p', 'hello')
+ render: () => h('p', 'hello'),
}
expect(await render(h(KeepAlive, () => h(MyComp)))).toBe(`<p>hello</p>`)
})
test('Transition', async () => {
const MyComp = {
- render: () => h('p', 'hello')
+ render: () => h('p', 'hello'),
}
expect(await render(h(Transition, () => h(MyComp)))).toBe(
- `<p>hello</p>`
+ `<p>hello</p>`,
)
})
})
describe('raw vnode types', () => {
test('Text', async () => {
expect(await render(createTextVNode('hello <div>'))).toBe(
- `hello <div>`
+ `hello <div>`,
)
})
createCommentVNode('>foo'),
createCommentVNode('->foo'),
createCommentVNode('<!--foo-->'),
- createCommentVNode('--!>foo<!-')
- ])
- )
+ createCommentVNode('--!>foo<!-'),
+ ]),
+ ),
).toBe(`<div><!--foo--><!--foo--><!--foo--><!--foo--></div>`)
})
__scopeId: 'data-v-test',
render() {
return h('div')
- }
+ },
}
expect(await render(h(Foo))).toBe(`<div data-v-test></div>`)
})
__scopeId: 'data-v-child',
render: function (this: any) {
return h('div', null, [renderSlot(this.$slots, 'default')])
- }
+ },
}
const Parent = {
__scopeId: 'data-v-test',
render: () => {
return h(Child, null, {
- default: withCtx(() => [h('span', 'slot')])
+ default: withCtx(() => [h('span', 'slot')]),
})
- }
+ },
}
expect(await render(h(Parent))).toBe(
`<div data-v-child data-v-test>` +
`<!--[--><span data-v-test data-v-child-s>slot</span><!--]-->` +
- `</div>`
+ `</div>`,
)
})
})
data() {
return { msg: 'hello' }
},
- template: `<div>{{ msg }}</div>`
- })
- )
+ template: `<div>{{ msg }}</div>`,
+ }),
+ ),
).toBe(`<div>hello</div>`)
})
test('handle compiler errors', async () => {
await render(
// render different content since compilation is cached
- createApp({ template: `<div>${type}</` })
+ createApp({ template: `<div>${type}</` }),
)
expect(
- `Template compilation error: Unexpected EOF in tag.`
+ `Template compilation error: Unexpected EOF in tag.`,
).toHaveBeenWarned()
expect(`Element is missing end tag`).toHaveBeenWarned()
})
data() {
return { msg: null }
},
- template: `<div>{{ msg.text }}</div>`
- })
+ template: `<div>{{ msg.text }}</div>`,
+ }),
)
} catch {}
expect(getCurrentInstance()).toBe(prev)
data() {
throw new Error()
},
- template: `<div>hello</div>`
- })
+ template: `<div>hello</div>`,
+ }),
)
} catch {}
expect(getCurrentInstance()).toBe(null)
const Child = {
created() {
throw new Error()
- }
+ },
}
try {
await render(
errorCaptured() {
throw new Error()
},
- render: () => h(Child)
- })
+ render: () => h(Child),
+ }),
)
} catch {}
expect(
- 'Unhandled error during execution of errorCaptured hook'
+ 'Unhandled error during execution of errorCaptured hook',
).toHaveBeenWarned()
expect(getCurrentInstance()).toBe(null)
})
const app = createApp({
data() {
return {
- msg: ''
+ msg: '',
}
},
async serverPrefetch() {
},
render() {
return h('div', this.msg)
- }
+ },
})
const html = await render(app)
expect(html).toBe(`<div>hello</div>`)
async setup() {
return Promise.reject('async child error')
},
- template: `<div>asyncChildren</div>`
+ template: `<div>asyncChildren</div>`,
})
const app = createApp({
name: 'App',
components: {
- asyncChildren
+ asyncChildren,
},
template: `<div class="app"><async-children /></div>`,
errorCaptured(error) {
fn(error)
- }
+ },
})
app.config.errorHandler = error => {
setup: () => {
watchEffect(onInvalidate => onInvalidate(noop))
},
- render: noop
+ render: noop,
})
expect(await render(app)).toBe('<!---->')
})
A: {
ssrRender(_ctx, _push) {
_push(`<div>A</div>`)
- }
+ },
},
B: {
- render: () => h('div', 'B')
- }
+ render: () => h('div', 'B'),
+ },
},
ssrRender(_ctx, _push, _parent) {
const A: any = resolveComponent('A')
ssrRenderVNode(
_push,
createVNode(resolveDynamicComponent('B'), null, null),
- _parent
+ _parent,
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div>A</div><div>B</div>`)
})
message.value = await msg
})
return {
- message
+ message,
}
},
render() {
return h('div', this.message)
- }
+ },
})
const html = await render(app)
expect(html).toBe(`<div>hello</div>`)
return {
message,
message2,
- message3
+ message3,
}
},
render() {
return h('div', `${this.message} ${this.message2} ${this.message3}`)
- }
+ },
})
const html = await render(app)
expect(html).toBe(`<div>hello hi bonjour</div>`)
},
render() {
return h('div', '')
- }
+ },
})
await render(app)
expect(first).toHaveBeenCalled()
const app = createApp({
data() {
return {
- message: ''
+ message: '',
}
},
message2.value = await msg2
})
return {
- message2
+ message2,
}
},
render() {
return h('div', `${this.message} ${this.message2}`)
- }
+ },
})
const html = await render(app)
expect(html).toBe(`<div>hello hi</div>`)
const app = createApp({
data() {
return {
- msg: ''
+ msg: '',
}
},
mixins: [
{
async serverPrefetch() {
this.msg = await msg
- }
- }
+ },
+ },
],
render() {
return h('div', this.msg)
- }
+ },
})
const html = await render(app)
expect(html).toBe(`<div>hello</div>`)
return {
foo: '',
bar: '',
- baz: ''
+ baz: '',
}
},
mixins: [
{
async serverPrefetch() {
this.foo = await foo
- }
+ },
},
{
async serverPrefetch() {
this.bar = await bar
- }
- }
+ },
+ },
],
async serverPrefetch() {
this.baz = await baz
},
render() {
return h('div', `${this.foo}${this.bar}${this.baz}`)
- }
+ },
})
const html = await render(app)
expect(html).toBe(`<div>foobarbaz</div>`)
},
render() {
return h('span')
- }
+ },
}
const app = createApp({
},
render() {
return h('div', h(Child))
- }
+ },
})
try {
// pretend to fetch some data from an api
async fetchData() {
this.state.items = ['hello', 'world']
- }
+ },
}
const getterSpy = vi.fn()
describe('ssr: attr fallthrough', () => {
test('basic', async () => {
const Child = {
- template: `<div class="foo" />`
+ template: `<div class="foo" />`,
}
const Parent = {
components: { Child },
- template: `<child class="bar"/>`
+ template: `<child class="bar"/>`,
}
const app = createApp(Parent)
expect(await renderToString(app)).toBe(`<div class="foo bar"></div>`)
test('with v-if', async () => {
const Child = {
props: ['ok'],
- template: `<div v-if="ok" class="foo" /><span v-else />`
+ template: `<div v-if="ok" class="foo" /><span v-else />`,
}
const Parent = {
props: ['ok'],
components: { Child },
- template: `<child :ok="ok" class="bar"/>`
+ template: `<child :ok="ok" class="bar"/>`,
}
expect(await renderToString(createApp(Parent, { ok: true }))).toBe(
- `<div class="foo bar"></div>`
+ `<div class="foo bar"></div>`,
)
expect(await renderToString(createApp(Parent, { ok: false }))).toBe(
- `<span class="bar"></span>`
+ `<span class="bar"></span>`,
)
})
test('with v-model', async () => {
const Child = {
props: ['text'],
- template: `<input v-model="text">`
+ template: `<input v-model="text">`,
}
const Parent = {
components: { Child },
- template: `<child text="hello" class="bar"/>`
+ template: `<child text="hello" class="bar"/>`,
}
expect(await renderToString(createApp(Parent))).toBe(
- `<input class="bar" value="hello">`
+ `<input class="bar" value="hello">`,
)
})
test('with v-bind', async () => {
const Child = {
props: ['obj'],
- template: `<div v-bind="obj" />`
+ template: `<div v-bind="obj" />`,
}
const Parent = {
components: { Child },
- template: `<child :obj="{ class: 'foo' }" class="bar"/>`
+ template: `<child :obj="{ class: 'foo' }" class="bar"/>`,
}
expect(await renderToString(createApp(Parent))).toBe(
- `<div class="foo bar"></div>`
+ `<div class="foo bar"></div>`,
)
})
test('nested fallthrough', async () => {
const Child = {
props: ['id'],
- template: `<div :id="id"></div>`
+ template: `<div :id="id"></div>`,
}
const Parent = {
components: { Child },
- template: `<child id="foo" class="bar"/>`
+ template: `<child id="foo" class="bar"/>`,
}
// pass to parent, fallthrough to child and merge
const app = createApp(Parent, { class: 'baz' })
expect(await renderToString(app)).toBe(
- `<div id="foo" class="bar baz"></div>`
+ `<div id="foo" class="bar baz"></div>`,
)
})
})
describe('ssr: compiler options', () => {
test('config.isCustomElement (deprecated)', async () => {
const app = createApp({
- template: `<div><x-button/></div>`
+ template: `<div><x-button/></div>`,
})
app.config.isCustomElement = tag => tag.startsWith('x-')
expect(await renderToString(app)).toBe(`<div><x-button></x-button></div>`)
test('config.compilerOptions.isCustomElement', async () => {
const app = createApp({
- template: `<div><x-panel/></div>`
+ template: `<div><x-panel/></div>`,
})
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('x-')
expect(await renderToString(app)).toBe(`<div><x-panel></x-panel></div>`)
const app = createApp({
template: `<div><x-card/><y-child/></div>`,
compilerOptions: {
- isCustomElement: (tag: string) => tag.startsWith('x-')
+ isCustomElement: (tag: string) => tag.startsWith('x-'),
},
components: {
YChild: {
- template: `<div><y-button/></div>`
- }
- }
+ template: `<div><y-button/></div>`,
+ },
+ },
})
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('y-')
expect(await renderToString(app)).toBe(
- `<div><x-card></x-card><div><y-button></y-button></div></div>`
+ `<div><x-card></x-card><div><y-button></y-button></div></div>`,
)
})
test('component.delimiters (deprecated)', async () => {
const app = createApp({
template: `<div>[[ 1 + 1 ]]</div>`,
- delimiters: ['[[', ']]']
+ delimiters: ['[[', ']]'],
})
expect(await renderToString(app)).toBe(`<div>2</div>`)
})
test('config.compilerOptions.delimiters', async () => {
const app = createApp({
- template: `<div>[( 1 + 1 )]</div>`
+ template: `<div>[( 1 + 1 )]</div>`,
})
app.config.compilerOptions.delimiters = ['[(', ')]']
expect(await renderToString(app)).toBe(`<div>2</div>`)
const app = createApp({
template: `<div>[[ 1 + 1 ]]<ChildComponent/></div>`,
compilerOptions: {
- delimiters: ['[[', ']]']
+ delimiters: ['[[', ']]'],
},
components: {
ChildComponent: {
- template: `<div>(( 2 + 2 ))</div>`
- }
- }
+ template: `<div>(( 2 + 2 ))</div>`,
+ },
+ },
})
app.config.compilerOptions.delimiters = ['((', '))']
expect(await renderToString(app)).toBe(`<div>2<div>4</div></div>`)
const app = createApp({
template: `<div><span>Hello world</span><ChildComponent/></div>`,
compilerOptions: {
- whitespace: 'condense'
+ whitespace: 'condense',
},
components: {
ChildComponent: {
- template: `<span>Hello world</span>`
- }
- }
+ template: `<span>Hello world</span>`,
+ },
+ },
})
app.config.compilerOptions.whitespace = 'preserve'
expect(await renderToString(app)).toBe(
- `<div><span>Hello world</span><span>Hello world</span></div>`
+ `<div><span>Hello world</span><span>Hello world</span></div>`,
)
})
template: `<div><ChildOne/><ChildTwo/><ChildThree/></div>`,
components: {
ChildOne: {
- template
+ template,
},
ChildTwo: {
template,
compilerOptions: {
- whitespace: 'preserve'
- }
+ whitespace: 'preserve',
+ },
},
ChildThree: {
template,
compilerOptions: {
- delimiters: ['[[', ']]']
- }
- }
- }
+ delimiters: ['[[', ']]'],
+ },
+ },
+ },
})
expect(await renderToString(app)).toBe(
- `<div><div>2 [[1 + 1]]</div><div>2 [[1 + 1]]</div><div>{{1 + 1}} 2</div></div>`
+ `<div><div>2 [[1 + 1]]</div><div>2 [[1 + 1]]</div><div>{{1 + 1}} 2</div></div>`,
)
})
MyChild: {
template,
compilerOptions: {
- isCustomElement: tag => tag.startsWith('x-')
+ isCustomElement: tag => tag.startsWith('x-'),
},
components: {
MyChild: {
template,
compilerOptions: {
- isCustomElement: tag => tag.startsWith('My')
- }
- }
- }
- }
- }
+ isCustomElement: tag => tag.startsWith('My'),
+ },
+ },
+ },
+ },
+ },
})
expect(await renderToString(app)).toBe(
- `<div><div><div><MyChild></MyChild></div></div></div>`
+ `<div><div><div><MyChild></MyChild></div></div></div>`,
)
})
})
-import { createSSRApp, defineComponent, h, computed, reactive } from 'vue'
+import { computed, createSSRApp, defineComponent, h, reactive } from 'vue'
import { renderToString } from '../src/renderToString'
// #5208 reported memory leak of keeping computed alive during SSR
// pretend to fetch some data from an api
async fetchData() {
this.state.items = ['hello', 'world']
- }
+ },
}
const getterSpy = vi.fn()
import {
createApp,
h,
- withDirectives,
- vShow,
- vModelText,
- vModelRadio,
+ resolveDirective,
vModelCheckbox,
vModelDynamic,
- resolveDirective
+ vModelRadio,
+ vModelText,
+ vShow,
+ withDirectives,
} from 'vue'
import { ssrGetDirectiveProps, ssrRenderAttrs } from '../src'
expect(
await renderToString(
createApp({
- template: `<div v-show="true"/>`
- })
- )
+ template: `<div v-show="true"/>`,
+ }),
+ ),
).toBe(`<div style=""></div>`)
expect(
await renderToString(
createApp({
- template: `<div v-show="false"/>`
- })
- )
+ template: `<div v-show="false"/>`,
+ }),
+ ),
).toBe(`<div style="display:none;"></div>`)
})
expect(
await renderToString(
createApp({
- template: `<div style="color:red" v-show="false"/>`
- })
- )
+ template: `<div style="color:red" v-show="false"/>`,
+ }),
+ ),
).toBe(`<div style="color:red;display:none;"></div>`)
})
await renderToString(
createApp({
data: () => ({ style: { color: 'red' } }),
- template: `<div :style="style" v-show="false"/>`
- })
- )
+ template: `<div :style="style" v-show="false"/>`,
+ }),
+ ),
).toBe(`<div style="color:red;display:none;"></div>`)
})
await renderToString(
createApp({
data: () => ({ style: { color: 'red' } }),
- template: `<div :style="style" style="font-size:12;" v-show="false"/>`
- })
- )
+ template: `<div :style="style" style="font-size:12;" v-show="false"/>`,
+ }),
+ ),
).toBe(`<div style="color:red;font-size:12;display:none;"></div>`)
})
})
await renderToString(
createApp({
data: () => ({ text: 'hello' }),
- template: `<input v-model="text">`
- })
- )
+ template: `<input v-model="text">`,
+ }),
+ ),
).toBe(`<input value="hello">`)
})
await renderToString(
createApp({
data: () => ({ selected: 'foo' }),
- template: `<input type="radio" value="foo" v-model="selected">`
- })
- )
+ template: `<input type="radio" value="foo" v-model="selected">`,
+ }),
+ ),
).toBe(`<input type="radio" value="foo" checked>`)
expect(
await renderToString(
createApp({
data: () => ({ selected: 'foo' }),
- template: `<input type="radio" value="bar" v-model="selected">`
- })
- )
+ template: `<input type="radio" value="bar" v-model="selected">`,
+ }),
+ ),
).toBe(`<input type="radio" value="bar">`)
// non-string values
await renderToString(
createApp({
data: () => ({ selected: 'foo' }),
- template: `<input type="radio" :value="{}" v-model="selected">`
- })
- )
+ template: `<input type="radio" :value="{}" v-model="selected">`,
+ }),
+ ),
).toBe(`<input type="radio">`)
})
await renderToString(
createApp({
data: () => ({ model: 1 }),
- template: `<select v-model="model"><option value="0"></option><option value="1"></option></select>`
- })
- )
+ template: `<select v-model="model"><option value="0"></option><option value="1"></option></select>`,
+ }),
+ ),
).toBe(
- `<select><option value="0"></option><option value="1" selected></option></select>`
+ `<select><option value="0"></option><option value="1" selected></option></select>`,
)
expect(
await renderToString(
createApp({
data: () => ({ model: [0, 1] }),
- template: `<select multiple v-model="model"><option value="0"></option><option value="1"></option></select>`
- })
- )
+ template: `<select multiple v-model="model"><option value="0"></option><option value="1"></option></select>`,
+ }),
+ ),
).toBe(
- `<select multiple><option value="0" selected></option><option value="1" selected></option></select>`
+ `<select multiple><option value="0" selected></option><option value="1" selected></option></select>`,
)
})
await renderToString(
createApp({
data: () => ({ checked: true }),
- template: `<input type="checkbox" v-model="checked">`
- })
- )
+ template: `<input type="checkbox" v-model="checked">`,
+ }),
+ ),
).toBe(`<input type="checkbox" checked>`)
expect(
await renderToString(
createApp({
data: () => ({ checked: false }),
- template: `<input type="checkbox" v-model="checked">`
- })
- )
+ template: `<input type="checkbox" v-model="checked">`,
+ }),
+ ),
).toBe(`<input type="checkbox">`)
expect(
await renderToString(
createApp({
data: () => ({ checked: ['foo'] }),
- template: `<input type="checkbox" value="foo" v-model="checked">`
- })
- )
+ template: `<input type="checkbox" value="foo" v-model="checked">`,
+ }),
+ ),
).toBe(`<input type="checkbox" value="foo" checked>`)
expect(
await renderToString(
createApp({
data: () => ({ checked: [] }),
- template: `<input type="checkbox" value="foo" v-model="checked">`
- })
- )
+ template: `<input type="checkbox" value="foo" v-model="checked">`,
+ }),
+ ),
).toBe(`<input type="checkbox" value="foo">`)
})
await renderToString(
createApp({
data: () => ({ foo: 'hello' }),
- template: `<textarea v-model="foo"/>`
- })
- )
+ template: `<textarea v-model="foo"/>`,
+ }),
+ ),
).toBe(`<textarea>hello</textarea>`)
})
await renderToString(
createApp({
data: () => ({ type: 'text', model: 'hello' }),
- template: `<input :type="type" v-model="model">`
- })
- )
+ template: `<input :type="type" v-model="model">`,
+ }),
+ ),
).toBe(`<input type="text" value="hello">`)
expect(
await renderToString(
createApp({
data: () => ({ type: 'checkbox', model: true }),
- template: `<input :type="type" v-model="model">`
- })
- )
+ template: `<input :type="type" v-model="model">`,
+ }),
+ ),
).toBe(`<input type="checkbox" checked>`)
expect(
await renderToString(
createApp({
data: () => ({ type: 'checkbox', model: false }),
- template: `<input :type="type" v-model="model">`
- })
- )
+ template: `<input :type="type" v-model="model">`,
+ }),
+ ),
).toBe(`<input type="checkbox">`)
expect(
await renderToString(
createApp({
data: () => ({ type: 'checkbox', model: ['hello'] }),
- template: `<input :type="type" value="hello" v-model="model">`
- })
- )
+ template: `<input :type="type" value="hello" v-model="model">`,
+ }),
+ ),
).toBe(`<input type="checkbox" value="hello" checked>`)
expect(
await renderToString(
createApp({
data: () => ({ type: 'checkbox', model: [] }),
- template: `<input :type="type" value="hello" v-model="model">`
- })
- )
+ template: `<input :type="type" value="hello" v-model="model">`,
+ }),
+ ),
).toBe(`<input type="checkbox" value="hello">`)
expect(
await renderToString(
createApp({
data: () => ({ type: 'radio', model: 'hello' }),
- template: `<input :type="type" value="hello" v-model="model">`
- })
- )
+ template: `<input :type="type" value="hello" v-model="model">`,
+ }),
+ ),
).toBe(`<input type="radio" value="hello" checked>`)
expect(
await renderToString(
createApp({
data: () => ({ type: 'radio', model: 'hello' }),
- template: `<input :type="type" value="bar" v-model="model">`
- })
- )
+ template: `<input :type="type" value="bar" v-model="model">`,
+ }),
+ ),
).toBe(`<input type="radio" value="bar">`)
})
createApp({
data: () => ({
obj: { type: 'radio', value: 'hello' },
- model: 'hello'
+ model: 'hello',
}),
- template: `<input v-bind="obj" v-model="model">`
- })
- )
+ template: `<input v-bind="obj" v-model="model">`,
+ }),
+ ),
).toBe(`<input type="radio" value="hello" checked>`)
})
})
createApp({
render() {
return withDirectives(h('div'), [[vShow, true]])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div></div>`)
expect(
createApp({
render() {
return withDirectives(h('div'), [[vShow, false]])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div style="display:none;"></div>`)
})
return withDirectives(
h('div', {
style: {
- color: 'red'
- }
+ color: 'red',
+ },
}),
- [[vShow, false]]
+ [[vShow, false]],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div style="color:red;display:none;"></div>`)
})
})
createApp({
render() {
return withDirectives(h('input'), [[vModelText, 'hello']])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input value="hello">`)
})
render() {
return withDirectives(
h('input', { type: 'radio', value: 'hello' }),
- [[vModelRadio, 'hello']]
+ [[vModelRadio, 'hello']],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="radio" value="hello" checked>`)
expect(
render() {
return withDirectives(
h('input', { type: 'radio', value: 'hello' }),
- [[vModelRadio, 'foo']]
+ [[vModelRadio, 'foo']],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="radio" value="hello">`)
})
createApp({
render() {
return withDirectives(h('input', { type: 'checkbox' }), [
- [vModelCheckbox, true]
+ [vModelCheckbox, true],
])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="checkbox" checked>`)
expect(
createApp({
render() {
return withDirectives(h('input', { type: 'checkbox' }), [
- [vModelCheckbox, false]
+ [vModelCheckbox, false],
])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="checkbox">`)
expect(
render() {
return withDirectives(
h('input', { type: 'checkbox', value: 'foo' }),
- [[vModelCheckbox, ['foo']]]
+ [[vModelCheckbox, ['foo']]],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="checkbox" value="foo" checked>`)
expect(
render() {
return withDirectives(
h('input', { type: 'checkbox', value: 'foo' }),
- [[vModelCheckbox, []]]
+ [[vModelCheckbox, []]],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="checkbox" value="foo">`)
})
})
createApp({
render() {
return withDirectives(h('input'), [[vModelDynamic, 'hello']])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input value="hello">`)
})
render() {
return withDirectives(
h('input', { type: 'radio', value: 'hello' }),
- [[vModelDynamic, 'hello']]
+ [[vModelDynamic, 'hello']],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="radio" value="hello" checked>`)
expect(
render() {
return withDirectives(
h('input', { type: 'radio', value: 'hello' }),
- [[vModelDynamic, 'foo']]
+ [[vModelDynamic, 'foo']],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="radio" value="hello">`)
})
createApp({
render() {
return withDirectives(h('input', { type: 'checkbox' }), [
- [vModelDynamic, true]
+ [vModelDynamic, true],
])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="checkbox" checked>`)
expect(
createApp({
render() {
return withDirectives(h('input', { type: 'checkbox' }), [
- [vModelDynamic, false]
+ [vModelDynamic, false],
])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="checkbox">`)
expect(
render() {
return withDirectives(
h('input', { type: 'checkbox', value: 'foo' }),
- [[vModelDynamic, ['foo']]]
+ [[vModelDynamic, ['foo']]],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="checkbox" value="foo" checked>`)
expect(
render() {
return withDirectives(
h('input', { type: 'checkbox', value: 'foo' }),
- [[vModelDynamic, []]]
+ [[vModelDynamic, []]],
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<input type="checkbox" value="foo">`)
})
})
{
getSSRProps({ value }) {
return { id: value }
- }
+ },
},
- 'foo'
- ]
+ 'foo',
+ ],
])
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div id="foo"></div>`)
})
createApp({
data() {
return {
- x: 'foo'
+ x: 'foo',
}
},
directives: {
xxx: {
getSSRProps({ value, arg, modifiers }) {
return { id: [value, arg, modifiers.ok].join('-') }
- }
- }
+ },
+ },
},
ssrRender(_ctx, _push, _parent, _attrs) {
const _directive_xxx = resolveDirective('xxx')!
_push(
`<div${ssrRenderAttrs(
ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.x, 'arg', {
- ok: true
- })
- )}></div>`
+ ok: true,
+ }),
+ )}></div>`,
)
- }
- })
- )
+ },
+ }),
+ ),
).toBe(`<div id="foo-arg-true"></div>`)
})
})
createApp({
components: {
one: {
- template: `<div><slot/></div>`
- }
+ template: `<div><slot/></div>`,
+ },
},
- template: `<component :is="'one'"><span>slot</span></component>`
- })
- )
+ template: `<component :is="'one'"><span>slot</span></component>`,
+ }),
+ ),
).toBe(`<div><!--[--><span>slot</span><!--]--></div>`)
})
expect(
await renderToString(
createApp({
- template: `<component :is="'p'"><span>slot</span></component>`
- })
- )
+ template: `<component :is="'p'"><span>slot</span></component>`,
+ }),
+ ),
).toBe(`<p><span>slot</span></p>`)
})
test('resolve to component vnode', async () => {
const Child = {
props: ['id'],
- template: `<div>{{ id }}<slot/></div>`
+ template: `<div>{{ id }}<slot/></div>`,
}
expect(
await renderToString(
createApp({
setup() {
return {
- vnode: createVNode(Child, { id: 'test' })
+ vnode: createVNode(Child, { id: 'test' }),
}
},
- template: `<component :is="vnode"><span>slot</span></component>`
- })
- )
+ template: `<component :is="vnode"><span>slot</span></component>`,
+ }),
+ ),
).toBe(`<div>test<!--[--><span>slot</span><!--]--></div>`)
})
createApp({
setup() {
return {
- vnode: createVNode('div', { id: 'test' })
+ vnode: createVNode('div', { id: 'test' }),
}
},
- template: `<component :is="vnode"><span>slot</span></component>`
- })
- )
+ template: `<component :is="vnode"><span>slot</span></component>`,
+ }),
+ ),
).toBe(`<div id="test"><span>slot</span></div>`)
})
})
expect(ssrInterpolate(`<div>`)).toBe(`<div>`)
// should escape interpolated values
expect(ssrInterpolate([1, 2, 3])).toBe(
- escapeHtml(JSON.stringify([1, 2, 3], null, 2))
+ escapeHtml(JSON.stringify([1, 2, 3], null, 2)),
)
expect(
ssrInterpolate({
foo: 1,
- bar: `<div>`
- })
+ bar: `<div>`,
+ }),
).toBe(
escapeHtml(
JSON.stringify(
{
foo: 1,
- bar: `<div>`
+ bar: `<div>`,
},
null,
- 2
- )
- )
+ 2,
+ ),
+ ),
)
})
import {
+ ssrRenderAttr,
ssrRenderAttrs,
ssrRenderClass,
ssrRenderStyle,
- ssrRenderAttr
} from '../src/helpers/ssrRenderAttrs'
import { escapeHtml } from '@vue/shared'
ref_key: 'foo',
ref_for: 'bar',
ref: () => {},
- onClick: () => {}
- })
+ onClick: () => {},
+ }),
).toBe('')
})
expect(
ssrRenderAttrs({
id: 'foo',
- title: 'bar'
- })
+ title: 'bar',
+ }),
).toBe(` id="foo" title="bar"`)
})
test('empty value attrs', () => {
expect(
ssrRenderAttrs({
- 'data-v-abc': ''
- })
+ 'data-v-abc': '',
+ }),
).toBe(` data-v-abc`)
})
test('escape attrs', () => {
expect(
ssrRenderAttrs({
- id: '"><script'
- })
+ id: '"><script',
+ }),
).toBe(` id=""><script"`)
})
checked: true,
multiple: false,
readonly: 0,
- disabled: ''
- })
+ disabled: '',
+ }),
).toBe(` checked disabled`) // boolean attr w/ false should be ignored
})
ssrRenderAttrs({
foo: false,
title: null,
- baz: undefined
- })
+ baz: undefined,
+ }),
).toBe(` foo="false"`) // non boolean should render `false` as is
})
ssrRenderAttrs({
foo: {},
bar: [],
- baz: () => {}
- })
+ baz: () => {},
+ }),
).toBe(``)
})
expect(
ssrRenderAttrs({
readOnly: true, // simple lower case conversion
- htmlFor: 'foobar' // special cases
- })
+ htmlFor: 'foobar', // special cases
+ }),
).toBe(` readonly for="foobar"`)
})
expect(
ssrRenderAttrs(
{
- fooBar: 'ok'
+ fooBar: 'ok',
},
- 'my-el'
- )
+ 'my-el',
+ ),
).toBe(` fooBar="ok"`)
})
expect(
ssrRenderAttrs(
{
- viewBox: 'foo'
+ viewBox: 'foo',
},
- 'svg'
- )
+ 'svg',
+ ),
).toBe(` viewBox="foo"`)
})
})
test('escape', () => {
expect(ssrRenderAttr('foo', '<script>')).toBe(
- ` foo="${escapeHtml(`<script>`)}"`
+ ` foo="${escapeHtml(`<script>`)}"`,
)
})
})
test('via renderProps', () => {
expect(
ssrRenderAttrs({
- class: ['foo', 'bar']
- })
+ class: ['foo', 'bar'],
+ }),
).toBe(` class="foo bar"`)
})
style: {
color: 'red',
'--a': 2,
- '-webkit-line-clamp': 1
- }
- })
+ '-webkit-line-clamp': 1,
+ },
+ }),
).toBe(` style="color:red;--a:2;-webkit-line-clamp:1;"`)
})
expect(ssrRenderStyle(`color:red`)).toBe(`color:red`)
expect(
ssrRenderStyle({
- color: `red`
- })
+ color: `red`,
+ }),
).toBe(`color:red;`)
expect(
ssrRenderStyle([
{ color: `red` },
- { fontSize: `15px` } // case conversion
- ])
+ { fontSize: `15px` }, // case conversion
+ ]),
).toBe(`color:red;font-size:15px;`)
})
expect(
ssrRenderStyle({
fontSize: null, // invalid value should be ignored
- opacity: 0.5
- })
+ opacity: 0.5,
+ }),
).toBe(`opacity:0.5;`)
})
expect(ssrRenderStyle(`"><script`)).toBe(`"><script`)
expect(
ssrRenderStyle({
- color: `"><script`
- })
+ color: `"><script`,
+ }),
).toBe(`color:"><script;`)
})
})
it('should render items in an array', () => {
ssrRenderList(['1', '2', '3'], (item, index) =>
- stack.push(`node ${index}: ${item}`)
+ stack.push(`node ${index}: ${item}`),
)
expect(stack).toEqual(['node 0: 1', 'node 1: 2', 'node 2: 3'])
})
it('should warn when given a non-integer N', () => {
ssrRenderList(3.1, () => {})
expect(
- `The v-for range expect an integer value but got 3.1.`
+ `The v-for range expect an integer value but got 3.1.`,
).toHaveBeenWarned()
})
it('should render properties in an object', () => {
ssrRenderList({ a: 1, b: 2, c: 3 }, (item, key, index) =>
- stack.push(`node ${index}/${key}: ${item}`)
+ stack.push(`node ${index}/${key}: ${item}`),
)
expect(stack).toEqual(['node 0/a: 1', 'node 1/b: 2', 'node 2/c: 3'])
})
}
ssrRenderList(iterable(), (item, index) =>
- stack.push(`node ${index}: ${item}`)
+ stack.push(`node ${index}: ${item}`),
)
expect(stack).toEqual(['node 0: 1', 'node 1: 2', 'node 2: 3'])
})
it('should not render items when source is undefined', () => {
ssrRenderList(undefined, (item, index) =>
- stack.push(`node ${index}: ${item}`)
+ stack.push(`node ${index}: ${item}`),
)
expect(stack).toEqual([])
})
import { createApp, h, mergeProps, withCtx } from 'vue'
import { renderToString } from '../src/renderToString'
-import { ssrRenderComponent, ssrRenderAttrs, ssrRenderSlot } from '../src'
+import { ssrRenderAttrs, ssrRenderComponent, ssrRenderSlot } from '../src'
describe('ssr: scopedId runtime behavior', () => {
test('id on component root', async () => {
const Child = {
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
push(`<div${ssrRenderAttrs(attrs)}></div>`)
- }
+ },
}
const Comp = {
__scopeId: 'parent',
ssrRender: (ctx: any, push: any, parent: any) => {
push(ssrRenderComponent(Child), null, null, parent)
- }
+ },
}
const result = await renderToString(createApp(Comp))
// <div></div>
ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
push(`<div${ssrRenderAttrs(attrs)} child></div>`)
- }
+ },
}
const Wrapper = {
null,
push,
parent,
- 'wrapper-s'
+ 'wrapper-s',
)
- }
+ },
}
const Comp = {
default: withCtx(
(_: any, push: any, parent: any, scopeId: string) => {
push(ssrRenderComponent(Child, null, null, parent, scopeId))
- }
+ },
),
- _: 1
+ _: 1,
} as any,
- parent
- )
+ parent,
+ ),
)
- }
+ },
}
const result = await renderToString(createApp(Comp))
// <div class="wrapper"><slot/></div>
push(
`<div${ssrRenderAttrs(
- mergeProps({ class: 'wrapper' }, attrs)
- )} wrapper>`
+ mergeProps({ class: 'wrapper' }, attrs),
+ )} wrapper>`,
)
ssrRenderSlot(
ctx.$slots,
null,
push,
parent,
- 'wrapper-s'
+ 'wrapper-s',
)
push(`</div>`)
- }
+ },
}
const Slotted = {
null,
push,
parent,
- 'slotted-s' + scopeId
+ 'slotted-s' + scopeId,
)
- }
+ },
),
- _: 1
+ _: 1,
} as any,
- parent
- )
+ parent,
+ ),
)
- }
+ },
}
const Root = {
default: withCtx(
(_: any, push: any, parent: any, scopeId: string) => {
push(`<div root${scopeId}></div>`)
- }
+ },
),
- _: 1
+ _: 1,
} as any,
- parent
- )
+ parent,
+ ),
)
- }
+ },
}
const result = await renderToString(createApp(Root))
expect(result).toBe(
`<div class="wrapper" root slotted wrapper>` +
`<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--]-->` +
- `</div>`
+ `</div>`,
)
})
const Child = {
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
push(`<div${ssrRenderAttrs(attrs)}></div>`)
- }
+ },
}
const Middle = {
render() {
return h(Child)
- }
+ },
}
const Comp = {
__scopeId: 'parent',
ssrRender: (ctx: any, push: any, parent: any) => {
push(ssrRenderComponent(Middle, null, null, parent))
- }
+ },
}
const result = await renderToString(createApp(Comp)) // output: `<div></div>`
const components = {
one: {
- template: `<div><slot/></div>`
- }
+ template: `<div><slot/></div>`,
+ },
}
describe('ssr: slot', () => {
await renderToString(
createApp({
components,
- template: `<one>hello</one>`
- })
- )
+ template: `<one>hello</one>`,
+ }),
+ ),
).toBe(`<div><!--[-->hello<!--]--></div>`)
})
await renderToString(
createApp({
components,
- template: `<one><div>hi</div></one>`
- })
- )
+ template: `<one><div>hi</div></one>`,
+ }),
+ ),
).toBe(`<div><!--[--><div>hi</div><!--]--></div>`)
})
createApp({
components: {
one: {
- template: `<div><slot/></div>`
- }
+ template: `<div><slot/></div>`,
+ },
},
- template: `<one><template v-if="false"/></one>`
- })
- )
+ template: `<one><template v-if="false"/></one>`,
+ }),
+ ),
).toBe(`<div><!--[--><!--]--></div>`)
})
createApp({
components: {
one: {
- template: `<div><slot/></div>`
- }
+ template: `<div><slot/></div>`,
+ },
},
- template: `<one><!--hello--></one>`
- })
- )
+ template: `<one><!--hello--></one>`,
+ }),
+ ),
).toBe(`<div><!--[--><!--]--></div>`)
})
createApp({
components: {
one: {
- template: `<div><slot/></div>`
- }
+ template: `<div><slot/></div>`,
+ },
},
- template: `<one><!--he\nllo--></one>`
- })
- )
+ template: `<one><!--he\nllo--></one>`,
+ }),
+ ),
).toBe(`<div><!--[--><!--]--></div>`)
})
await renderToString(
createApp({
components,
- template: `<one><div>one</div><div>two</div></one>`
- })
- )
+ template: `<one><div>one</div><div>two</div></one>`,
+ }),
+ ),
).toBe(`<div><!--[--><div>one</div><div>two</div><!--]--></div>`)
})
await renderToString(
createApp({
components,
- template: `<one><template v-if="true">hello</template></one>`
- })
- )
+ template: `<one><template v-if="true">hello</template></one>`,
+ }),
+ ),
).toBe(`<div><!--[--><!--[-->hello<!--]--><!--]--></div>`)
})
await renderToString(
createApp({
components,
- template: `<one><template v-if="true"><div>one</div><div>two</div></template></one>`
- })
- )
+ template: `<one><template v-if="true"><div>one</div><div>two</div></template></one>`,
+ }),
+ ),
).toBe(
- `<div><!--[--><!--[--><div>one</div><div>two</div><!--]--><!--]--></div>`
+ `<div><!--[--><!--[--><div>one</div><div>two</div><!--]--><!--]--></div>`,
)
})
createApp({
components: {
one: {
- template: `<transition><slot/></transition>`
- }
+ template: `<transition><slot/></transition>`,
+ },
},
- template: `<one><div v-if="false">foo</div></one>`
- })
- )
+ template: `<one><div v-if="false">foo</div></one>`,
+ }),
+ ),
).toBe(`<!---->`)
expect(
createApp({
components: {
one: {
- template: `<transition><slot/></transition>`
- }
+ template: `<transition><slot/></transition>`,
+ },
},
- template: `<one><div v-if="true">foo</div></one>`
- })
- )
+ template: `<one><div v-if="true">foo</div></one>`,
+ }),
+ ),
).toBe(`<div>foo</div>`)
})
})
-import { createApp, h, Suspense } from 'vue'
+import { Suspense, createApp, h } from 'vue'
import { renderToString } from '../src/renderToString'
describe('SSR Suspense', () => {
const ResolvingAsync = {
async setup() {
return () => h('div', 'async')
- }
+ },
}
const RejectingAsync = {
setup() {
return new Promise((_, reject) => reject('foo'))
- }
+ },
}
test('content', async () => {
render() {
return h(Suspense, null, {
default: h(ResolvingAsync),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
expect(await renderToString(createApp(Comp))).toBe(`<div>async</div>`)
render() {
return h(Suspense, null, {
default: h(RejectingAsync),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
expect(await renderToString(createApp(Comp))).toBe(`<!---->`)
render() {
return h(Suspense, null, {
default: h('div', [h(ResolvingAsync), h(ResolvingAsync)]),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
expect(await renderToString(createApp(Comp))).toBe(
- `<div><div>async</div><div>async</div></div>`
+ `<div><div>async</div><div>async</div></div>`,
)
})
render() {
return h(Suspense, null, {
default: h('div', [h(ResolvingAsync), h(RejectingAsync)]),
- fallback: h('div', 'fallback')
+ fallback: h('div', 'fallback'),
})
- }
+ },
}
expect(await renderToString(createApp(Comp))).toBe(
- `<div><div>async</div><!----></div>`
+ `<div><div>async</div><!----></div>`,
)
expect(Comp.errorCaptured).toHaveBeenCalledTimes(1)
h(ResolvingAsync),
h(Suspense, null, {
default: h('div', [h(RejectingAsync)]),
- fallback: h('div', 'fallback 2')
- })
+ fallback: h('div', 'fallback 2'),
+ }),
]),
- fallback: h('div', 'fallback 1')
+ fallback: h('div', 'fallback 1'),
})
- }
+ },
}
expect(await renderToString(createApp(Comp))).toBe(
- `<div><div>async</div><div><!----></div></div>`
+ `<div><div>async</div><div><!----></div></div>`,
)
expect(Comp.errorCaptured).toHaveBeenCalledTimes(1)
h(RejectingAsync),
h(Suspense, null, {
default: h('div', [h(ResolvingAsync)]),
- fallback: h('div', 'fallback 2')
- })
+ fallback: h('div', 'fallback 2'),
+ }),
]),
- fallback: h('div', 'fallback 1')
+ fallback: h('div', 'fallback 1'),
})
- }
+ },
}
expect(await renderToString(createApp(Comp))).toBe(
- `<div><!----><div><div>async</div></div></div>`
+ `<div><!----><div><div>async</div></div></div>`,
)
expect(Comp.errorCaptured).toHaveBeenCalledTimes(1)
expect('missing template').toHaveBeenWarned()
-import { createApp, h, Teleport } from 'vue'
+import { Teleport, createApp, h } from 'vue'
import { renderToString } from '../src/renderToString'
import { renderToSimpleStream } from '../src/renderToStream'
-import { SSRContext } from '../src/render'
+import type { SSRContext } from '../src/render'
import { ssrRenderTeleport } from '../src/helpers/ssrRenderTeleport'
describe('ssrRenderTeleport', () => {
},
'#target',
false,
- _parent
+ _parent,
)
- }
+ },
}),
- ctx
+ ctx,
)
expect(html).toBe('<!--teleport start--><!--teleport end-->')
expect(ctx.teleports!['#target']).toBe(
- `<div>content</div><!--teleport anchor-->`
+ `<div>content</div><!--teleport anchor-->`,
)
})
},
'#target',
true,
- _parent
+ _parent,
)
- }
+ },
}),
- ctx
+ ctx,
)
expect(html).toBe(
- '<!--teleport start--><div>content</div><!--teleport end-->'
+ '<!--teleport start--><div>content</div><!--teleport end-->',
)
expect(ctx.teleports!['#target']).toBe(`<!--teleport anchor-->`)
})
h(
Teleport,
{
- to: `#target`
+ to: `#target`,
},
- h('span', 'hello')
+ h('span', 'hello'),
),
- ctx
+ ctx,
)
expect(html).toBe('<!--teleport start--><!--teleport end-->')
expect(ctx.teleports!['#target']).toBe(
- '<span>hello</span><!--teleport anchor-->'
+ '<span>hello</span><!--teleport anchor-->',
)
})
Teleport,
{
to: `#target`,
- disabled: true
+ disabled: true,
},
- h('span', 'hello')
+ h('span', 'hello'),
),
- ctx
+ ctx,
)
expect(html).toBe(
- '<!--teleport start--><span>hello</span><!--teleport end-->'
+ '<!--teleport start--><span>hello</span><!--teleport end-->',
)
expect(ctx.teleports!['#target']).toBe(`<!--teleport anchor-->`)
})
h(
Teleport,
{
- to: `#target`
+ to: `#target`,
},
- h('span', 'hello')
+ h('span', 'hello'),
),
- h(Teleport, { to: `#target` }, 'world')
+ h(Teleport, { to: `#target` }, 'world'),
]),
- ctx
+ ctx,
)
expect(html).toBe(
- '<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>'
+ '<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>',
)
expect(ctx.teleports!['#target']).toBe(
- '<span>hello</span><!--teleport anchor-->world<!--teleport anchor-->'
+ '<span>hello</span><!--teleport anchor-->world<!--teleport anchor-->',
)
})
const ctx: SSRContext = {}
const asyncComponent = {
template: '<teleport to="#target"><div>content</div></teleport>',
- async setup() {}
+ async setup() {},
}
const html = await renderToString(
h({
template: '<async-component />',
- components: { asyncComponent }
+ components: { asyncComponent },
}),
- ctx
+ ctx,
)
expect(html).toBe('<!--teleport start--><!--teleport end-->')
expect(ctx.teleports!['#target']).toBe(
- `<div>content</div><!--teleport anchor-->`
+ `<div>content</div><!--teleport anchor-->`,
)
})
const ctx: SSRContext = {}
const asyncComponent = {
template: '<teleport to="#target"><div>content</div></teleport>',
- async setup() {}
+ async setup() {},
}
let html = ''
let resolve: any
renderToSimpleStream(
h({
template: '<async-component />',
- components: { asyncComponent }
+ components: { asyncComponent },
}),
ctx,
{
},
destroy(err) {
throw err
- }
- }
+ },
+ },
)
await p
expect(html).toBe('<!--teleport start--><!--teleport end-->')
expect(ctx.teleports!['#target']).toBe(
- `<div>content</div><!--teleport anchor-->`
+ `<div>content</div><!--teleport anchor-->`,
)
})
})
import {
+ ssrGetDynamicModelProps,
ssrRenderDynamicModel,
- ssrGetDynamicModelProps
// ssrGetDynamicModelProps
} from '../src/helpers/ssrVModelHelpers'
expect(
ssrGetDynamicModelProps(
{
- type: 'text'
+ type: 'text',
},
- 'foo'
- )
+ 'foo',
+ ),
).toMatchObject({ value: 'foo' })
expect(
ssrGetDynamicModelProps(
{
- type: 'email'
+ type: 'email',
},
- 'foo'
- )
+ 'foo',
+ ),
).toMatchObject({ value: 'foo' })
expect(
ssrGetDynamicModelProps(
{
- type: 'checkbox'
+ type: 'checkbox',
},
- true
- )
+ true,
+ ),
).toMatchObject({ checked: true })
expect(
ssrGetDynamicModelProps(
{
- type: 'checkbox'
+ type: 'checkbox',
},
- false
- )
+ false,
+ ),
).toBe(null)
expect(
ssrGetDynamicModelProps(
{
type: 'checkbox',
- value: '1'
+ value: '1',
},
- [1]
- )
+ [1],
+ ),
).toMatchObject({ checked: true })
expect(
ssrGetDynamicModelProps(
{
type: 'checkbox',
- value: 1
+ value: 1,
},
- [1]
- )
+ [1],
+ ),
).toMatchObject({ checked: true })
expect(
ssrGetDynamicModelProps(
{
type: 'checkbox',
- value: 0
+ value: 0,
},
- [1]
- )
+ [1],
+ ),
).toBe(null)
expect(
ssrGetDynamicModelProps(
{
type: 'radio',
- value: 'foo'
+ value: 'foo',
},
- 'foo'
- )
+ 'foo',
+ ),
).toMatchObject({ checked: true })
expect(
ssrGetDynamicModelProps(
{
type: 'radio',
- value: '1'
+ value: '1',
},
- 1
- )
+ 1,
+ ),
).toMatchObject({ checked: true })
expect(
ssrGetDynamicModelProps(
{
type: 'radio',
- value: 0
+ value: 0,
},
- 1
- )
+ 1,
+ ),
).toBe(null)
})
})
-import { createSSRApp, defineComponent, h, watch, ref } from 'vue'
-import { SSRContext, renderToString } from '../src'
+import { createSSRApp, defineComponent, h, ref, watch } from 'vue'
+import { type SSRContext, renderToString } from '../src'
describe('ssr: watch', () => {
// #6013
() => {
msg = 'hello world'
},
- { flush: 'sync' }
+ { flush: 'sync' },
)
count.value = 1
expect(msg).toBe('hello world')
-import { createApp, h, defineAsyncComponent } from 'vue'
-import { ReadableStream, TransformStream } from 'stream/web'
+import { createApp, defineAsyncComponent, h } from 'vue'
+import { ReadableStream, TransformStream } from 'node:stream/web'
import { pipeToWebWritable, renderToWebStream } from '../src'
beforeEach(() => {
- // @ts-ignore
+ // @ts-expect-error
global.ReadableStream = ReadableStream
})
afterEach(() => {
- // @ts-ignore
+ // @ts-expect-error
delete global.ReadableStream
})
test('renderToWebStream', async () => {
const Async = defineAsyncComponent(() =>
Promise.resolve({
- render: () => h('div', 'async')
- })
+ render: () => h('div', 'async'),
+ }),
)
const App = {
- render: () => [h('div', 'parent'), h(Async)]
+ render: () => [h('div', 'parent'), h(Async)],
}
const stream = renderToWebStream(createApp(App))
test('pipeToWebWritable', async () => {
const Async = defineAsyncComponent(() =>
Promise.resolve({
- render: () => h('div', 'async')
- })
+ render: () => h('div', 'async'),
+ }),
)
const App = {
- render: () => [h('div', 'parent'), h(Async)]
+ render: () => [h('div', 'parent'), h(Async)],
}
const { readable, writable } = new TransformStream()
-import { ComponentInternalInstance, ComponentOptions, warn } from 'vue'
+import {
+ type ComponentInternalInstance,
+ type ComponentOptions,
+ warn,
+} from 'vue'
import { compile } from '@vue/compiler-ssr'
-import { extend, generateCodeFrame, isFunction, NO } from '@vue/shared'
-import { CompilerError, CompilerOptions } from '@vue/compiler-core'
-import { PushFn } from '../render'
+import { NO, extend, generateCodeFrame, isFunction } from '@vue/shared'
+import type { CompilerError, CompilerOptions } from '@vue/compiler-core'
+import type { PushFn } from '../render'
import * as Vue from 'vue'
import * as helpers from '../internal'
type SSRRenderFunction = (
context: any,
push: PushFn,
- parentInstance: ComponentInternalInstance
+ parentInstance: ComponentInternalInstance,
) => void
const compileCache: Record<string, SSRRenderFunction> = Object.create(null)
export function ssrCompile(
template: string,
- instance: ComponentInternalInstance
+ instance: ComponentInternalInstance,
): SSRRenderFunction {
// TODO: this branch should now work in ESM builds, enable it in a minor
if (!__NODE_JS__) {
throw new Error(
`On-the-fly template compilation is not supported in the ESM build of ` +
`@vue/server-renderer. All templates must be pre-compiled into ` +
- `render functions.`
+ `render functions.`,
)
}
extend(
{
isCustomElement,
- delimiters
+ delimiters,
},
- compilerOptions
+ compilerOptions,
),
- componentCompilerOptions
+ componentCompilerOptions,
)
finalCompilerOptions.isCustomElement =
const cacheKey = JSON.stringify(
{
template,
- compilerOptions: finalCompilerOptions
+ compilerOptions: finalCompilerOptions,
},
(key, value) => {
return isFunction(value) ? value.toString() : value
- }
+ },
)
const cached = compileCache[cacheKey]
generateCodeFrame(
template as string,
err.loc.start.offset,
- err.loc.end.offset
+ err.loc.end.offset,
)
warn(codeFrame ? `${message}\n${codeFrame}` : message)
} else {
const { code } = compile(template, finalCompilerOptions)
const requireMap = {
vue: Vue,
- 'vue/server-renderer': helpers
+ 'vue/server-renderer': helpers,
}
const fakeRequire = (id: 'vue' | 'vue/server-renderer') => requireMap[id]
return (compileCache[cacheKey] = Function('require', code)(fakeRequire))
-import { ComponentPublicInstance, Directive } from '@vue/runtime-core'
+import type { ComponentPublicInstance, Directive } from '@vue/runtime-core'
export function ssrGetDirectiveProps(
instance: ComponentPublicInstance,
dir: Directive,
value?: any,
arg?: string,
- modifiers: Record<string, boolean> = {}
+ modifiers: Record<string, boolean> = {},
): Record<string, any> {
if (typeof dir !== 'function' && dir.getSSRProps) {
return (
value,
oldValue: undefined,
arg,
- modifiers
+ modifiers,
},
- null as any
+ null as any,
) || {}
)
}
import { escapeHtml, isSVGTag, stringifyStyle } from '@vue/shared'
import {
+ includeBooleanAttr,
+ isBooleanAttr,
+ isOn,
+ isSSRSafeAttrName,
+ isString,
+ makeMap,
normalizeClass,
normalizeStyle,
propsToAttrMap,
- isString,
- isOn,
- isSSRSafeAttrName,
- isBooleanAttr,
- includeBooleanAttr,
- makeMap
} from '@vue/shared'
// leading comma for empty string ""
const shouldIgnoreProp = makeMap(
- `,key,ref,innerHTML,textContent,ref_key,ref_for`
+ `,key,ref,innerHTML,textContent,ref_key,ref_for`,
)
export function ssrRenderAttrs(
props: Record<string, unknown>,
- tag?: string
+ tag?: string,
): string {
let ret = ''
for (const key in props) {
export function ssrRenderDynamicAttr(
key: string,
value: unknown,
- tag?: string
+ tag?: string,
): string {
if (!isRenderableValue(value)) {
return ``
return value === '' ? ` ${attrKey}` : ` ${attrKey}="${escapeHtml(value)}"`
} else {
console.warn(
- `[@vue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`
+ `[@vue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`,
)
return ``
}
-import { Component, ComponentInternalInstance, createVNode, Slots } from 'vue'
-import { Props, renderComponentVNode, SSRBuffer } from '../render'
-import { SSRSlots } from './ssrRenderSlot'
+import {
+ type Component,
+ type ComponentInternalInstance,
+ type Slots,
+ createVNode,
+} from 'vue'
+import { type Props, type SSRBuffer, renderComponentVNode } from '../render'
+import type { SSRSlots } from './ssrRenderSlot'
export function ssrRenderComponent(
comp: Component,
props: Props | null = null,
children: Slots | SSRSlots | null = null,
parentComponent: ComponentInternalInstance | null = null,
- slotScopeId?: string
+ slotScopeId?: string,
): SSRBuffer | Promise<SSRBuffer> {
return renderComponentVNode(
createVNode(comp, props, children),
parentComponent,
- slotScopeId
+ slotScopeId,
)
}
-import { isArray, isString, isObject } from '@vue/shared'
+import { isArray, isObject, isString } from '@vue/shared'
import { warn } from '@vue/runtime-core'
export function ssrRenderList(
source: unknown,
- renderItem: (value: unknown, key: string | number, index?: number) => void
+ renderItem: (value: unknown, key: string | number, index?: number) => void,
) {
if (isArray(source) || isString(source)) {
for (let i = 0, l = source.length; i < l; i++) {
-import { ComponentInternalInstance, Slots } from 'vue'
-import { Props, PushFn, renderVNodeChildren, SSRBufferItem } from '../render'
+import type { ComponentInternalInstance, Slots } from 'vue'
+import {
+ type Props,
+ type PushFn,
+ type SSRBufferItem,
+ renderVNodeChildren,
+} from '../render'
import { isArray } from '@vue/shared'
export type SSRSlots = Record<string, SSRSlot>
props: Props,
push: PushFn,
parentComponent: ComponentInternalInstance | null,
- scopeId: string | null
+ scopeId: string | null,
) => void
export function ssrRenderSlot(
fallbackRenderFn: (() => void) | null,
push: PushFn,
parentComponent: ComponentInternalInstance,
- slotScopeId?: string
+ slotScopeId?: string,
) {
// template-compiled slots are always rendered as fragments
push(`<!--[-->`)
fallbackRenderFn,
push,
parentComponent,
- slotScopeId
+ slotScopeId,
)
push(`<!--]-->`)
}
push: PushFn,
parentComponent: ComponentInternalInstance,
slotScopeId?: string,
- transition?: boolean
+ transition?: boolean,
) {
const slotFn = slots[slotName]
if (slotFn) {
slotProps,
bufferedPush,
parentComponent,
- slotScopeId ? ' ' + slotScopeId : ''
+ slotScopeId ? ' ' + slotScopeId : '',
)
if (isArray(ret)) {
// normal slot
-import { PushFn } from '../render'
+import type { PushFn } from '../render'
export async function ssrRenderSuspense(
push: PushFn,
- { default: renderContent }: Record<string, (() => void) | undefined>
+ { default: renderContent }: Record<string, (() => void) | undefined>,
) {
if (renderContent) {
renderContent()
-import { ComponentInternalInstance, ssrContextKey } from 'vue'
-import { createBuffer, PushFn, SSRBufferItem, SSRContext } from '../render'
+import { type ComponentInternalInstance, ssrContextKey } from 'vue'
+import {
+ type PushFn,
+ type SSRBufferItem,
+ type SSRContext,
+ createBuffer,
+} from '../render'
export function ssrRenderTeleport(
parentPush: PushFn,
contentRenderFn: (push: PushFn) => void,
target: string,
disabled: boolean,
- parentComponent: ComponentInternalInstance
+ parentComponent: ComponentInternalInstance,
) {
parentPush('<!--teleport start-->')
-import { looseEqual, looseIndexOf, isArray } from '@vue/shared'
+import { isArray, looseEqual, looseIndexOf } from '@vue/shared'
import { ssrRenderAttr } from './ssrRenderAttrs'
export const ssrLooseEqual = looseEqual as (a: unknown, b: unknown) => boolean
export function ssrRenderDynamicModel(
type: unknown,
model: unknown,
- value: unknown
+ value: unknown,
) {
switch (type) {
case 'radio':
// for <input v-bind="obj" v-model="model">
export function ssrGetDynamicModelProps(
existingProps: any = {},
- model: unknown
+ model: unknown,
) {
const { type, value } = existingProps
switch (type) {
pipeToWebWritable,
type SimpleReadable,
// deprecated
- renderToStream
+ renderToStream,
} from './renderToStream'
// internal runtime helpers
ssrRenderStyle,
ssrRenderAttrs,
ssrRenderAttr,
- ssrRenderDynamicAttr
+ ssrRenderDynamicAttr,
} from './helpers/ssrRenderAttrs'
export { ssrInterpolate } from './helpers/ssrInterpolate'
export { ssrRenderList } from './helpers/ssrRenderList'
ssrLooseEqual,
ssrLooseContain,
ssrRenderDynamicModel,
- ssrGetDynamicModelProps
+ ssrGetDynamicModelProps,
} from './helpers/ssrVModelHelpers'
import {
Comment,
- Component,
- ComponentInternalInstance,
- DirectiveBinding,
+ type Component,
+ type ComponentInternalInstance,
+ type DirectiveBinding,
Fragment,
- FunctionalComponent,
- mergeProps,
- ssrUtils,
+ type FunctionalComponent,
Static,
Text,
- VNode,
- VNodeArrayChildren,
- VNodeProps,
- warn
+ type VNode,
+ type VNodeArrayChildren,
+ type VNodeProps,
+ mergeProps,
+ ssrUtils,
+ warn,
} from 'vue'
import {
+ NOOP,
+ ShapeFlags,
escapeHtml,
escapeHtmlComment,
+ isArray,
isFunction,
isPromise,
isString,
isVoidTag,
- ShapeFlags,
- isArray,
- NOOP
} from '@vue/shared'
import { ssrRenderAttrs } from './helpers/ssrRenderAttrs'
import { ssrCompile } from './helpers/ssrCompile'
setCurrentRenderingInstance,
setupComponent,
renderComponentRoot,
- normalizeVNode
+ normalizeVNode,
} = ssrUtils
export type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean }
// this allows skipping unnecessary await ticks during unroll stage
buffer.hasAsync = true
}
- }
+ },
}
}
export function renderComponentVNode(
vnode: VNode,
parentComponent: ComponentInternalInstance | null = null,
- slotScopeId?: string
+ slotScopeId?: string,
): SSRBuffer | Promise<SSRBuffer> {
const instance = createComponentInstance(vnode, parentComponent, null)
const res = setupComponent(instance, true /* isSSR */)
if (prefetches) {
p = p
.then(() =>
- Promise.all(prefetches.map(prefetch => prefetch.call(instance.proxy)))
+ Promise.all(
+ prefetches.map(prefetch => prefetch.call(instance.proxy)),
+ ),
)
// Note: error display is already done by the wrapped lifecycle hook function.
.catch(() => {})
function renderComponentSubTree(
instance: ComponentInternalInstance,
- slotScopeId?: string
+ slotScopeId?: string,
): SSRBuffer | Promise<SSRBuffer> {
const comp = instance.type as Component
const { getBuffer, push } = createBuffer()
instance.props,
instance.setupState,
instance.data,
- instance.ctx
+ instance.ctx,
)
} finally {
setCurrentRenderingInstance(prev)
push,
(instance.subTree = renderComponentRoot(instance)),
instance,
- slotScopeId
+ slotScopeId,
)
} else {
const componentName = comp.name || comp.__file || `<Anonymous>`
push: PushFn,
vnode: VNode,
parentComponent: ComponentInternalInstance,
- slotScopeId?: string
+ slotScopeId?: string,
) {
const { type, shapeFlag, children } = vnode
switch (type) {
break
case Comment:
push(
- children ? `<!--${escapeHtmlComment(children as string)}-->` : `<!---->`
+ children
+ ? `<!--${escapeHtmlComment(children as string)}-->`
+ : `<!---->`,
)
break
case Static:
push,
children as VNodeArrayChildren,
parentComponent,
- slotScopeId
+ slotScopeId,
)
push(`<!--]-->`) // close
break
warn(
'[@vue/server-renderer] Invalid VNode type:',
type,
- `(${typeof type})`
+ `(${typeof type})`,
)
}
}
push: PushFn,
children: VNodeArrayChildren,
parentComponent: ComponentInternalInstance,
- slotScopeId: string | undefined
+ slotScopeId: string | undefined,
) {
for (let i = 0; i < children.length; i++) {
renderVNode(push, normalizeVNode(children[i]), parentComponent, slotScopeId)
push: PushFn,
vnode: VNode,
parentComponent: ComponentInternalInstance,
- slotScopeId: string | undefined
+ slotScopeId: string | undefined,
) {
const tag = vnode.type as string
let { props, children, shapeFlag, scopeId, dirs } = vnode
push,
children as VNodeArrayChildren,
parentComponent,
- slotScopeId
+ slotScopeId,
)
}
}
function applySSRDirectives(
vnode: VNode,
rawProps: VNodeProps | null,
- dirs: DirectiveBinding[]
+ dirs: DirectiveBinding[],
): VNodeProps {
const toMerge: VNodeProps[] = []
for (let i = 0; i < dirs.length; i++) {
const binding = dirs[i]
const {
- dir: { getSSRProps }
+ dir: { getSSRProps },
} = binding
if (getSSRProps) {
const props = getSSRProps(binding, vnode)
push: PushFn,
vnode: VNode,
parentComponent: ComponentInternalInstance,
- slotScopeId: string | undefined
+ slotScopeId: string | undefined,
) {
const target = vnode.props && vnode.props.to
const disabled = vnode.props && vnode.props.disabled
}
if (!isString(target)) {
warn(
- `[@vue/server-renderer] Teleport target must be a query selector string.`
+ `[@vue/server-renderer] Teleport target must be a query selector string.`,
)
return []
}
push,
vnode.children as VNodeArrayChildren,
parentComponent,
- slotScopeId
+ slotScopeId,
)
},
target,
disabled || disabled === '',
- parentComponent
+ parentComponent,
)
}
import {
- App,
- VNode,
+ type App,
+ type VNode,
+ createApp,
createVNode,
+ ssrContextKey,
ssrUtils,
- createApp,
- ssrContextKey
} from 'vue'
-import { isString, isPromise } from '@vue/shared'
-import { renderComponentVNode, SSRBuffer, SSRContext } from './render'
-import { Readable, Writable } from 'stream'
+import { isPromise, isString } from '@vue/shared'
+import { type SSRBuffer, type SSRContext, renderComponentVNode } from './render'
+import type { Readable, Writable } from 'node:stream'
import { resolveTeleports } from './renderToString'
const { isVNode } = ssrUtils
async function unrollBuffer(
buffer: SSRBuffer,
- stream: SimpleReadable
+ stream: SimpleReadable,
): Promise<void> {
if (buffer.hasAsync) {
for (let i = 0; i < buffer.length; i++) {
export function renderToSimpleStream<T extends SimpleReadable>(
input: App | VNode,
context: SSRContext,
- stream: T
+ stream: T,
): T {
if (isVNode(input)) {
// raw vnode, wrap with app (for context)
return renderToSimpleStream(
createApp({ render: () => input }),
context,
- stream
+ stream,
)
}
*/
export function renderToStream(
input: App | VNode,
- context: SSRContext = {}
+ context: SSRContext = {},
): Readable {
console.warn(
- `[@vue/server-renderer] renderToStream is deprecated - use renderToNodeStream instead.`
+ `[@vue/server-renderer] renderToStream is deprecated - use renderToNodeStream instead.`,
)
return renderToNodeStream(input, context)
}
export function renderToNodeStream(
input: App | VNode,
- context: SSRContext = {}
+ context: SSRContext = {},
): Readable {
const stream: Readable = __NODE_JS__
- ? new (require('stream').Readable)({ read() {} })
+ ? new (require('node:stream').Readable)({ read() {} })
: null
if (!stream) {
throw new Error(
`ESM build of renderToStream() does not support renderToNodeStream(). ` +
`Use pipeToNodeWritable() with an existing Node.js Writable stream ` +
- `instance instead.`
+ `instance instead.`,
)
}
export function pipeToNodeWritable(
input: App | VNode,
context: SSRContext = {},
- writable: Writable
+ writable: Writable,
) {
renderToSimpleStream(input, context, {
push(content) {
},
destroy(err) {
writable.destroy(err)
- }
+ },
})
}
export function renderToWebStream(
input: App | VNode,
- context: SSRContext = {}
+ context: SSRContext = {},
): ReadableStream {
if (typeof ReadableStream !== 'function') {
throw new Error(
`ReadableStream constructor is not available in the global scope. ` +
`If the target environment does support web streams, consider using ` +
- `pipeToWebWritable() with an existing WritableStream instance instead.`
+ `pipeToWebWritable() with an existing WritableStream instance instead.`,
)
}
},
destroy(err) {
controller.error(err)
- }
+ },
})
},
cancel() {
cancelled = true
- }
+ },
})
}
export function pipeToWebWritable(
input: App | VNode,
context: SSRContext = {},
- writable: WritableStream
+ writable: WritableStream,
): void {
const writer = writable.getWriter()
const encoder = new TextEncoder()
// TODO better error handling?
console.log(err)
writer.close()
- }
+ },
})
}
import {
- App,
+ type App,
+ type VNode,
createApp,
createVNode,
ssrContextKey,
ssrUtils,
- VNode
} from 'vue'
import { isPromise, isString } from '@vue/shared'
-import { SSRContext, renderComponentVNode, SSRBuffer } from './render'
+import { type SSRBuffer, type SSRContext, renderComponentVNode } from './render'
const { isVNode } = ssrUtils
export async function renderToString(
input: App | VNode,
- context: SSRContext = {}
+ context: SSRContext = {},
): Promise<string> {
if (isVNode(input)) {
// raw vnode, wrap with app (for context)
// note: it's OK to await sequentially here because the Promises were
// created eagerly in parallel.
context.teleports[key] = await unrollBuffer(
- await Promise.all([context.__teleportBuffers[key]])
+ await Promise.all([context.__teleportBuffers[key]]),
)
}
}
if (import.meta.env.DEV) {
import('@vue/repl/codemirror-editor').then(
- mod => (EditorComponent.value = mod.default)
+ mod => (EditorComponent.value = mod.default),
)
} else {
import('@vue/repl/monaco-editor').then(
- mod => (EditorComponent.value = mod.default)
+ mod => (EditorComponent.value = mod.default),
)
}
: `${location.origin}/src/vue-dev-proxy-prod`,
defaultVueServerRendererURL: import.meta.env.PROD
? `${location.origin}/server-renderer.esm-browser.js`
- : `${location.origin}/src/vue-server-renderer-dev-proxy`
+ : `${location.origin}/src/vue-server-renderer-dev-proxy`,
})
// enable experimental features
script: {
inlineTemplate: useProdMode.value,
isProd: useProdMode.value,
- propsDestructure: true
+ propsDestructure: true,
},
style: {
- isProd: useProdMode.value
+ isProd: useProdMode.value,
},
template: {
- isProd: useProdMode.value
- }
+ isProd: useProdMode.value,
+ },
}
// persist state
'toggle-theme',
'toggle-ssr',
'toggle-prod',
- 'reload-page'
+ 'reload-page',
])
const { store } = props
cls.toggle('dark')
localStorage.setItem(
'vue-sfc-playground-prefer-dark',
- String(cls.contains('dark'))
+ String(cls.contains('dark')),
)
emit('toggle-theme', cls.contains('dark'))
}
async function fetchVersions(): Promise<string[]> {
const res = await fetch(
- `https://data.jsdelivr.com/v1/package/npm/${props.pkg}`
+ `https://data.jsdelivr.com/v1/package/npm/${props.pkg}`,
)
const { versions } = (await res.json()) as { versions: string[] }
import pkg from './template/package.json?raw'
import config from './template/vite.config.js?raw'
import readme from './template/README.md?raw'
-import { ReplStore } from '@vue/repl'
+import type { ReplStore } from '@vue/repl'
export async function downloadProject(store: ReplStore) {
if (!confirm('Download project files?')) {
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [vue()]
+ plugins: [vue()],
})
<template>
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24" fill="currentColor">
<path
- d="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.5,4.7,2.2,8.9,6.3,10.5C8.7,21.4,9,21.2,9,20.8v-1.6c0,0-0.4,0.1-0.9,0.1 c-1.4,0-2-1.2-2.1-1.9c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1 c0.4,0,0.7-0.1,0.9-0.2c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6 c0,0,1.4,0,2.8,1.3C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4 c0.7,0.8,1.2,1.8,1.2,3c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v2.6c0,0.3,0.3,0.6,0.7,0.5c3.7-1.5,6.3-5.1,6.3-9.3 C22,6.1,16.9,1.4,10.9,2.1z"/>
+ d="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.5,4.7,2.2,8.9,6.3,10.5C8.7,21.4,9,21.2,9,20.8v-1.6c0,0-0.4,0.1-0.9,0.1 c-1.4,0-2-1.2-2.1-1.9c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1 c0.4,0,0.7-0.1,0.9-0.2c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6 c0,0,1.4,0,2.8,1.3C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4 c0.7,0.8,1.2,1.8,1.2,3c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v2.6c0,0.3,0.3,0.6,0.7,0.5c3.7-1.5,6.3-5.1,6.3-9.3 C22,6.1,16.9,1.4,10.9,2.1z"
+ />
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
- <path fill="currentColor" d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z" />
+ <path
+ fill="currentColor"
+ d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"
+ />
</svg>
</template>
<template>
- <svg fill="currentColor" width="1.7em" height="1.7em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
- <path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
- <path d="M0 0h24v24H0z" fill="none"/>
+ <svg
+ fill="currentColor"
+ width="1.7em"
+ height="1.7em"
+ viewBox="0 0 24 24"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
+ />
+ <path d="M0 0h24v24H0z" fill="none" />
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
- <path fill="currentColor" d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z" />
- <path fill="currentColor" d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z" />
- <path fill="currentColor" d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z" />
- <path fill="currentColor" d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z" />
- <path fill="currentColor" d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z" />
- <path fill="currentColor" d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z" />
- <path fill="currentColor" d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z" />
- <path fill="currentColor" d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z" />
- <path fill="currentColor" d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z" />
+ <path
+ fill="currentColor"
+ d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"
+ />
+ <path
+ fill="currentColor"
+ d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"
+ />
+ <path
+ fill="currentColor"
+ d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"
+ />
+ <path
+ fill="currentColor"
+ d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"
+ />
+ <path
+ fill="currentColor"
+ d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"
+ />
+ <path
+ fill="currentColor"
+ d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"
+ />
+ <path
+ fill="currentColor"
+ d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"
+ />
+ <path
+ fill="currentColor"
+ d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"
+ />
+ <path
+ fill="currentColor"
+ d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"
+ />
</svg>
</template>
// @ts-expect-error Custom window property
window.VUE_DEVTOOLS_CONFIG = {
- defaultSelectedAppId: 'repl'
+ defaultSelectedAppId: 'repl',
}
createApp(App).mount('#app')
// serve vue to the iframe sandbox during dev.
-// @ts-ignore
+// @ts-expect-error
export * from 'vue/dist/vue.runtime.esm-browser.prod.js'
-import fs from 'fs'
-import path from 'path'
-import { defineConfig, Plugin } from 'vite'
+import fs from 'node:fs'
+import path from 'node:path'
+import { type Plugin, defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { execaSync } from 'execa'
script: {
fs: {
fileExists: fs.existsSync,
- readFile: file => fs.readFileSync(file, 'utf-8')
- }
- }
+ readFile: file => fs.readFileSync(file, 'utf-8'),
+ },
+ },
}),
- copyVuePlugin()
+ copyVuePlugin(),
],
define: {
__COMMIT__: JSON.stringify(commit),
- __VUE_PROD_DEVTOOLS__: JSON.stringify(true)
+ __VUE_PROD_DEVTOOLS__: JSON.stringify(true),
},
optimizeDeps: {
- exclude: ['@vue/repl']
- }
+ exclude: ['@vue/repl'],
+ },
})
function copyVuePlugin(): Plugin {
if (!fs.existsSync(filePath)) {
throw new Error(
`${basename} not built. ` +
- `Run "nr build vue -f esm-browser" first.`
+ `Run "nr build vue -f esm-browser" first.`,
)
}
this.emitFile({
type: 'asset',
fileName: basename,
- source: fs.readFileSync(filePath, 'utf-8')
+ source: fs.readFileSync(filePath, 'utf-8'),
})
}
copyFile(`../vue/dist/vue.runtime.esm-browser.js`)
copyFile(`../vue/dist/vue.runtime.esm-browser.prod.js`)
copyFile(`../server-renderer/dist/server-renderer.esm-browser.js`)
- }
+ },
}
}
const keyEnd =
windowsNewLineSource.indexOf(endToken, keyStart) + endToken.length
expect(
- generateCodeFrame(windowsNewLineSource, keyStart, keyEnd)
+ generateCodeFrame(windowsNewLineSource, keyStart, keyEnd),
).toMatchSnapshot()
})
const keyEnd =
unixNewlineSource.indexOf(endToken, keyStart) + endToken.length
expect(
- generateCodeFrame(unixNewlineSource, keyStart, keyEnd)
+ generateCodeFrame(unixNewlineSource, keyStart, keyEnd),
).toMatchSnapshot()
})
}
const date2 = new Date(2019, 1, 2, 3, 4, 5, 7)
const file1 = new File([''], 'filename.txt', {
type: 'text/plain',
- lastModified: date1.getTime()
+ lastModified: date1.getTime(),
})
const file2 = new File([''], 'filename.txt', {
type: 'text/plain',
- lastModified: date1.getTime()
+ lastModified: date1.getTime(),
})
const file3 = new File([''], 'filename.txt', {
type: 'text/plain',
- lastModified: date2.getTime()
+ lastModified: date2.getTime(),
})
const file4 = new File([''], 'filename.csv', {
type: 'text/csv',
- lastModified: date1.getTime()
+ lastModified: date1.getTime(),
})
const file5 = new File(['abcdef'], 'filename.txt', {
type: 'text/plain',
- lastModified: date1.getTime()
+ lastModified: date1.getTime(),
})
const file6 = new File(['12345'], 'filename.txt', {
type: 'text/plain',
- lastModified: date1.getTime()
+ lastModified: date1.getTime(),
})
// Identical file object references
const date1 = new Date(2019, 1, 2, 3, 4, 5, 6)
const file1 = new File([''], 'filename.txt', {
type: 'text/plain',
- lastModified: date1.getTime()
+ lastModified: date1.getTime(),
})
expect(looseEqual(123, '123')).toBe(true)
test('handles array correctly', () => {
expect(normalizeClass(['foo', undefined, true, false, 'bar'])).toEqual(
- 'foo bar'
+ 'foo bar',
)
})
test('handles object correctly', () => {
expect(normalizeClass({ foo: true, bar: false, baz: true })).toEqual(
- 'foo baz'
+ 'foo baz',
)
})
test('handles arrays and objects correctly', () => {
expect(
- normalizeClass(['foo', ['bar'], { baz: true }, [{ qux: true }]])
+ normalizeClass(['foo', ['bar'], { baz: true }, [{ qux: true }]]),
).toEqual('foo bar baz qux')
})
{ qux: '' },
{ quux: null },
{ corge: undefined },
- { grault: NaN }
- ])
+ { grault: NaN },
+ ]),
).toEqual('')
})
{ bar: 'not-empty' },
{ baz: 1 },
{ qux: {} },
- { quux: [] }
- ])
+ { quux: [] },
+ ]),
).toEqual('foo bar baz qux quux')
})
#ccc 0.5em,
white 0,
white 0.75em
- );`)
+ );`),
).toMatchInlineSnapshot(`
{
"background": "linear-gradient(white, white) padding-box,
foo: 555,
toString() {
return 'override'
- }
+ },
}
expect(toDisplayString(objWithToStringOverride)).toBe('override')
const objWithNonInvokableToString = {
foo: 555,
- toString: null
+ toString: null,
}
expect(toDisplayString(objWithNonInvokableToString)).toBe(
`{
"foo": 555,
"toString": null
-}`
+}`,
)
// object created from null does not have .toString in its prototype
expect(toDisplayString(nullObjectWithoutToString)).toBe(
`{
"bar": 1
-}`
+}`,
)
// array toString override is ignored
1,
2,
3
-]`
+]`,
)
})
expect(
toDisplayString({
n,
- np
- })
+ np,
+ }),
).toBe(JSON.stringify({ n: 1, np: 2 }, null, 2))
})
test('Map and Set', () => {
const m = new Map<any, any>([
[1, 'foo'],
- [{ baz: 1 }, { foo: 'bar', qux: 2 }]
+ [{ baz: 1 }, { foo: 'bar', qux: 2 }],
])
const s = new Set<any>([1, { foo: 'bar' }, m])
expect(
toDisplayString({
m,
- s
- })
+ s,
+ }),
).toMatchInlineSnapshot(`
"{
"m": {
const m = new Map<any, any>([
[Symbol(), 'foo'],
[Symbol(), 'bar'],
- [Symbol('baz'), 'baz']
+ [Symbol('baz'), 'baz'],
])
expect(toDisplayString(m)).toMatchInlineSnapshot(`
"{
`)
// confirming the symbol renders Symbol(foo)
expect(toDisplayString(new Map([[Symbol('foo'), 'foo']]))).toContain(
- String(Symbol('foo'))
+ String(Symbol('foo')),
)
})
export function generateCodeFrame(
source: string,
start = 0,
- end = source.length
+ end = source.length,
): string {
// Split the content into individual lines but capture the newline sequence
// that separated each line. This is important because the actual sequence is
res.push(
`${line}${' '.repeat(Math.max(3 - String(line).length, 0))}| ${
lines[j]
- }`
+ }`,
)
const lineLength = lines[j].length
const newLineSeqLength =
const pad = start - (count - (lineLength + newLineSeqLength))
const length = Math.max(
1,
- end > count ? lineLength - pad : end - start
+ end > count ? lineLength - pad : end - start,
)
res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length))
} else if (j > i) {
specialBooleanAttrs +
`,async,autofocus,autoplay,controls,default,defer,disabled,hidden,` +
`inert,loop,open,required,reversed,scoped,seamless,` +
- `checked,muted,multiple,selected`
+ `checked,muted,multiple,selected`,
)
/**
acceptCharset: 'accept-charset',
className: 'class',
htmlFor: 'for',
- httpEquiv: 'http-equiv'
+ httpEquiv: 'http-equiv',
}
/**
`referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,` +
`selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,` +
`start,step,style,summary,tabindex,target,title,translate,type,usemap,` +
- `value,width,wrap`
+ `value,width,wrap`,
)
/**
`vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,` +
`writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,` +
`xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,` +
- `xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`
+ `xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`,
)
const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
val: object,
- key: string | symbol
+ key: string | symbol,
): key is keyof typeof val => hasOwnProperty.call(val, key)
export const isArray = Array.isArray
',key,ref,ref_for,ref_key,' +
'onVnodeBeforeMount,onVnodeMounted,' +
'onVnodeBeforeUpdate,onVnodeUpdated,' +
- 'onVnodeBeforeUnmount,onVnodeUnmounted'
+ 'onVnodeBeforeUnmount,onVnodeUnmounted',
)
export const isBuiltInDirective = /*#__PURE__*/ makeMap(
- 'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo'
+ 'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo',
)
const cacheStringFunction = <T extends (str: string) => string>(fn: T): T => {
* @private
*/
export const hyphenate = cacheStringFunction((str: string) =>
- str.replace(hyphenateRE, '-$1').toLowerCase()
+ str.replace(hyphenateRE, '-$1').toLowerCase(),
)
/**
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
- value
+ value,
})
}
*/
export function makeMap(
str: string,
- expectsLowerCase?: boolean
+ expectsLowerCase?: boolean,
): (key: string) => boolean {
const set = new Set(str.split(','))
return expectsLowerCase
-import { isArray, isString, isObject, hyphenate } from './general'
+import { hyphenate, isArray, isObject, isString } from './general'
export type NormalizedStyle = Record<string, string | number>
export function normalizeStyle(
- value: unknown
+ value: unknown,
): NormalizedStyle | string | undefined {
if (isArray(value)) {
const res: NormalizedStyle = {}
}
export function stringifyStyle(
- styles: NormalizedStyle | string | undefined
+ styles: NormalizedStyle | string | undefined,
): string {
let ret = ''
if (!styles || isString(styles)) {
* render functions, which should always be fully diffed)
* OR manually cloneVNodes
*/
- BAIL = -2
+ BAIL = -2,
}
/**
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
[PatchFlags.DEV_ROOT_FRAGMENT]: `DEV_ROOT_FRAGMENT`,
[PatchFlags.HOISTED]: `HOISTED`,
- [PatchFlags.BAIL]: `BAIL`
+ [PatchFlags.BAIL]: `BAIL`,
}
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
- COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
+ COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
}
* received. This has to be refined at runtime, when the child's vnode
* is being created (in `normalizeChildren`)
*/
- FORWARDED = 3
+ FORWARDED = 3,
}
/**
export const slotFlagsText = {
[SlotFlags.STABLE]: 'STABLE',
[SlotFlags.DYNAMIC]: 'DYNAMIC',
- [SlotFlags.FORWARDED]: 'FORWARDED'
+ [SlotFlags.FORWARDED]: 'FORWARDED',
}
import {
isArray,
+ isFunction,
isMap,
isObject,
- isFunction,
isPlainObject,
isSet,
- objectToString,
isString,
- isSymbol
+ isSymbol,
+ objectToString,
} from './general'
/**
entries[stringifySymbol(key, i) + ' =>'] = val
return entries
},
- {} as Record<string, any>
- )
+ {} as Record<string, any>,
+ ),
}
} else if (isSet(val)) {
return {
- [`Set(${val.size})`]: [...val.values()].map(v => stringifySymbol(v))
+ [`Set(${val.size})`]: [...val.values()].map(v => stringifySymbol(v)),
}
} else if (isSymbol(val)) {
return stringifySymbol(val)
-import * as m from 'monaco-editor'
-import { compile, CompilerError, CompilerOptions } from '@vue/compiler-dom'
+import type * as m from 'monaco-editor'
+import {
+ type CompilerError,
+ type CompilerOptions,
+ compile,
+} from '@vue/compiler-dom'
import { compile as ssrCompile } from '@vue/compiler-ssr'
import {
- defaultOptions,
compilerOptions,
+ defaultOptions,
initOptions,
- ssrMode
+ ssrMode,
} from './options'
import { toRaw, watchEffect } from '@vue/runtime-dom'
import { SourceMapConsumer } from 'source-map-js'
scrollBeyondLastLine: false,
renderWhitespace: 'selection',
minimap: {
- enabled: false
- }
+ enabled: false,
+ },
}
window.init = () => {
hash = escape(atob(hash))
} catch (e) {}
persistedState = JSON.parse(
- decodeURIComponent(hash) || localStorage.getItem('state') || `{}`
+ decodeURIComponent(hash) || localStorage.getItem('state') || `{}`,
)
} catch (e: any) {
// bad stored state, clear it
console.warn(
'Persisted state in localStorage seems to be corrupted, please reload.\n' +
- e.message
+ e.message,
)
localStorage.clear()
}
sourceMap: true,
onError: err => {
errors.push(err)
- }
+ },
})
console.log(`Compiled in ${(performance.now() - start).toFixed(2)}ms.`)
monaco.editor.setModelMarkers(
editor.getModel()!,
`@vue/compiler-dom`,
- errors.filter(e => e.loc).map(formatError)
+ errors.filter(e => e.loc).map(formatError),
)
console.log(`AST: `, ast)
console.log(`Options: `, toRaw(compilerOptions))
endLineNumber: loc.end.line,
endColumn: loc.end.column,
message: `Vue template compilation error: ${err.message}`,
- code: String(err.code)
+ code: String(err.code),
}
}
for (key in compilerOptions) {
const val = compilerOptions[key]
if (typeof val !== 'object' && val !== defaultOptions[key]) {
- // @ts-ignore
+ // @ts-expect-error
optionsToSave[key] = val
}
}
const state = JSON.stringify({
src,
ssr: ssrMode.value,
- options: optionsToSave
+ options: optionsToSave,
} as PersistedState)
localStorage.setItem('state', state)
window.location.hash = btoa(unescape(encodeURIComponent(state)))
value: persistedState?.src || `<div>Hello World</div>`,
language: 'html',
...sharedEditorOptions,
- wordWrap: 'bounded'
+ wordWrap: 'bounded',
})
editor.getModel()!.updateOptions({
- tabSize: 2
+ tabSize: 2,
})
const output = monaco.editor.create(document.getElementById('output')!, {
value: '',
language: 'javascript',
readOnly: true,
- ...sharedEditorOptions
+ ...sharedEditorOptions,
})
output.getModel()!.updateOptions({
- tabSize: 2
+ tabSize: 2,
})
// handle resize
const pos = lastSuccessfulMap.generatedPositionFor({
source: 'ExampleTemplate.vue',
line: e.position.lineNumber,
- column: e.position.column - 1
+ column: e.position.column - 1,
})
if (pos.line != null && pos.column != null) {
prevOutputDecos = output.deltaDecorations(prevOutputDecos, [
pos.line,
pos.column + 1,
pos.line,
- pos.lastColumn ? pos.lastColumn + 2 : pos.column + 2
+ pos.lastColumn ? pos.lastColumn + 2 : pos.column + 2,
),
options: {
- inlineClassName: `highlight`
- }
- }
+ inlineClassName: `highlight`,
+ },
+ },
])
output.revealPositionInCenter({
lineNumber: pos.line,
- column: pos.column + 1
+ column: pos.column + 1,
})
} else {
clearOutputDecos()
}
}
- }, 100)
+ }, 100),
)
let previousEditorDecos: string[] = []
if (lastSuccessfulMap) {
const pos = lastSuccessfulMap.originalPositionFor({
line: e.position.lineNumber,
- column: e.position.column - 1
+ column: e.position.column - 1,
})
if (
pos.line != null &&
) {
const translatedPos = {
column: pos.column + 1,
- lineNumber: pos.line
+ lineNumber: pos.line,
}
previousEditorDecos = editor.deltaDecorations(previousEditorDecos, [
{
pos.line,
pos.column + 1,
pos.line,
- pos.column + 1
+ pos.column + 1,
),
options: {
isWholeLine: true,
- className: `highlight`
- }
- }
+ className: `highlight`,
+ },
+ },
])
editor.revealPositionInCenter(translatedPos)
} else {
clearEditorDecos()
}
}
- }, 100)
+ }, 100),
)
initOptions()
function debounce<T extends (...args: any[]) => any>(
fn: T,
- delay: number = 300
+ delay: number = 300,
): T {
let prevTimer: number | null = null
return ((...args: any[]) => {
-import { h, reactive, createApp, ref } from 'vue'
-import { CompilerOptions } from '@vue/compiler-dom'
+import { createApp, h, reactive, ref } from 'vue'
+import type { CompilerOptions } from '@vue/compiler-dom'
import { BindingTypes } from '@vue/compiler-core'
export const ssrMode = ref(false)
setupLet: BindingTypes.SETUP_LET,
setupMaybeRef: BindingTypes.SETUP_MAYBE_REF,
setupProp: BindingTypes.PROPS,
- vMySetupDir: BindingTypes.SETUP_CONST
- }
+ vMySetupDir: BindingTypes.SETUP_CONST,
+ },
}
export const compilerOptions: CompilerOptions = reactive(
- Object.assign({}, defaultOptions)
+ Object.assign({}, defaultOptions),
)
const App = {
'a',
{
href: `https://github.com/vuejs/core/tree/${__COMMIT__}`,
- target: `_blank`
+ target: `_blank`,
},
- `@${__COMMIT__}`
+ `@${__COMMIT__}`,
),
' | ',
h(
'a',
{
href: 'https://app.netlify.com/sites/vue-next-template-explorer/deploys',
- target: `_blank`
+ target: `_blank`,
},
- 'History'
+ 'History',
),
h('div', { id: 'options-wrapper' }, [
checked: isModule,
onChange() {
compilerOptions.mode = 'module'
- }
+ },
}),
h('label', { for: 'mode-module' }, 'module'),
' ',
checked: !isModule,
onChange() {
compilerOptions.mode = 'function'
- }
+ },
}),
- h('label', { for: 'mode-function' }, 'function')
+ h('label', { for: 'mode-function' }, 'function'),
]),
// whitespace handling
checked: compilerOptions.whitespace === 'condense',
onChange() {
compilerOptions.whitespace = 'condense'
- }
+ },
}),
h('label', { for: 'whitespace-condense' }, 'condense'),
' ',
checked: compilerOptions.whitespace === 'preserve',
onChange() {
compilerOptions.whitespace = 'preserve'
- }
+ },
}),
- h('label', { for: 'whitespace-preserve' }, 'preserve')
+ h('label', { for: 'whitespace-preserve' }, 'preserve'),
]),
// SSR
checked: ssrMode.value,
onChange(e: Event) {
ssrMode.value = (e.target as HTMLInputElement).checked
- }
+ },
}),
- h('label', { for: 'ssr' }, 'SSR')
+ h('label', { for: 'ssr' }, 'SSR'),
]),
// toggle prefixIdentifiers
onChange(e: Event) {
compilerOptions.prefixIdentifiers =
(e.target as HTMLInputElement).checked || isModule
- }
+ },
}),
- h('label', { for: 'prefix' }, 'prefixIdentifiers')
+ h('label', { for: 'prefix' }, 'prefixIdentifiers'),
]),
// toggle hoistStatic
compilerOptions.hoistStatic = (
e.target as HTMLInputElement
).checked
- }
+ },
}),
- h('label', { for: 'hoist' }, 'hoistStatic')
+ h('label', { for: 'hoist' }, 'hoistStatic'),
]),
// toggle cacheHandlers
compilerOptions.cacheHandlers = (
e.target as HTMLInputElement
).checked
- }
+ },
}),
- h('label', { for: 'cache' }, 'cacheHandlers')
+ h('label', { for: 'cache' }, 'cacheHandlers'),
]),
// toggle scopeId
isModule && (e.target as HTMLInputElement).checked
? 'scope-id'
: null
- }
+ },
}),
- h('label', { for: 'scope-id' }, 'scopeId')
+ h('label', { for: 'scope-id' }, 'scopeId'),
]),
// inline mode
compilerOptions.inline = (
e.target as HTMLInputElement
).checked
- }
+ },
}),
- h('label', { for: 'inline' }, 'inline')
+ h('label', { for: 'inline' }, 'inline'),
]),
// compat mode
).checked
? 2
: 3
- }
+ },
}),
- h('label', { for: 'compat' }, 'v2 compat mode')
- ])
- ])
- ])
+ h('label', { for: 'compat' }, 'v2 compat mode'),
+ ]),
+ ]),
+ ]),
]
}
- }
+ },
}
export function initOptions() {
rules: [
{
foreground: 'de935f',
- token: 'number'
+ token: 'number',
},
{
foreground: '969896',
- token: 'comment'
+ token: 'comment',
},
{
foreground: 'ced1cf',
- token: 'keyword.operator.class'
+ token: 'keyword.operator.class',
},
{
foreground: 'ced1cf',
- token: 'constant.other'
+ token: 'constant.other',
},
{
foreground: 'ced1cf',
- token: 'source.php.embedded.line'
+ token: 'source.php.embedded.line',
},
{
foreground: 'cc6666',
- token: 'variable'
+ token: 'variable',
},
{
foreground: 'cc6666',
- token: 'support.other.variable'
+ token: 'support.other.variable',
},
{
foreground: 'cc6666',
- token: 'string.other.link'
+ token: 'string.other.link',
},
{
foreground: 'cc6666',
- token: 'string.regexp'
+ token: 'string.regexp',
},
{
foreground: 'cc6666',
- token: 'entity.name.tag'
+ token: 'entity.name.tag',
},
{
foreground: 'cc6666',
- token: 'entity.other.attribute-name'
+ token: 'entity.other.attribute-name',
},
{
foreground: 'cc6666',
- token: 'meta.tag'
+ token: 'meta.tag',
},
{
foreground: 'cc6666',
- token: 'declaration.tag'
+ token: 'declaration.tag',
},
{
foreground: 'cc6666',
- token: 'markup.deleted.git_gutter'
+ token: 'markup.deleted.git_gutter',
},
{
foreground: 'de935f',
- token: 'constant.numeric'
+ token: 'constant.numeric',
},
{
foreground: 'de935f',
- token: 'constant.language'
+ token: 'constant.language',
},
{
foreground: 'de935f',
- token: 'support.constant'
+ token: 'support.constant',
},
{
foreground: 'de935f',
- token: 'constant.character'
+ token: 'constant.character',
},
{
foreground: 'de935f',
- token: 'variable.parameter'
+ token: 'variable.parameter',
},
{
foreground: 'de935f',
- token: 'punctuation.section.embedded'
+ token: 'punctuation.section.embedded',
},
{
foreground: 'de935f',
- token: 'keyword.other.unit'
+ token: 'keyword.other.unit',
},
{
foreground: 'f0c674',
- token: 'entity.name.class'
+ token: 'entity.name.class',
},
{
foreground: 'f0c674',
- token: 'entity.name.type.class'
+ token: 'entity.name.type.class',
},
{
foreground: 'f0c674',
- token: 'support.type'
+ token: 'support.type',
},
{
foreground: 'f0c674',
- token: 'support.class'
+ token: 'support.class',
},
{
foreground: 'b5bd68',
- token: 'string'
+ token: 'string',
},
{
foreground: 'b5bd68',
- token: 'constant.other.symbol'
+ token: 'constant.other.symbol',
},
{
foreground: 'b5bd68',
- token: 'entity.other.inherited-class'
+ token: 'entity.other.inherited-class',
},
{
foreground: 'b5bd68',
- token: 'markup.heading'
+ token: 'markup.heading',
},
{
foreground: 'b5bd68',
- token: 'markup.inserted.git_gutter'
+ token: 'markup.inserted.git_gutter',
},
{
foreground: '8abeb7',
- token: 'keyword.operator'
+ token: 'keyword.operator',
},
{
foreground: '8abeb7',
- token: 'constant.other.color'
+ token: 'constant.other.color',
},
{
foreground: '81a2be',
- token: 'entity.name.function'
+ token: 'entity.name.function',
},
{
foreground: '81a2be',
- token: 'meta.function-call'
+ token: 'meta.function-call',
},
{
foreground: '81a2be',
- token: 'support.function'
+ token: 'support.function',
},
{
foreground: '81a2be',
- token: 'keyword.other.special-method'
+ token: 'keyword.other.special-method',
},
{
foreground: '81a2be',
- token: 'meta.block-level'
+ token: 'meta.block-level',
},
{
foreground: '81a2be',
- token: 'markup.changed.git_gutter'
+ token: 'markup.changed.git_gutter',
},
{
foreground: 'b294bb',
- token: 'keyword'
+ token: 'keyword',
},
{
foreground: 'b294bb',
- token: 'storage'
+ token: 'storage',
},
{
foreground: 'b294bb',
- token: 'storage.type'
+ token: 'storage.type',
},
{
foreground: 'b294bb',
- token: 'entity.name.tag.css'
+ token: 'entity.name.tag.css',
},
{
foreground: 'ced2cf',
background: 'df5f5f',
- token: 'invalid'
+ token: 'invalid',
},
{
foreground: 'ced2cf',
background: '82a3bf',
- token: 'meta.separator'
+ token: 'meta.separator',
},
{
foreground: 'ced2cf',
background: 'b798bf',
- token: 'invalid.deprecated'
+ token: 'invalid.deprecated',
},
{
foreground: 'ffffff',
- token: 'markup.inserted.diff'
+ token: 'markup.inserted.diff',
},
{
foreground: 'ffffff',
- token: 'markup.deleted.diff'
+ token: 'markup.deleted.diff',
},
{
foreground: 'ffffff',
- token: 'meta.diff.header.to-file'
+ token: 'meta.diff.header.to-file',
},
{
foreground: 'ffffff',
- token: 'meta.diff.header.from-file'
+ token: 'meta.diff.header.from-file',
},
{
foreground: '718c00',
- token: 'markup.inserted.diff'
+ token: 'markup.inserted.diff',
},
{
foreground: '718c00',
- token: 'meta.diff.header.to-file'
+ token: 'meta.diff.header.to-file',
},
{
foreground: 'c82829',
- token: 'markup.deleted.diff'
+ token: 'markup.deleted.diff',
},
{
foreground: 'c82829',
- token: 'meta.diff.header.from-file'
+ token: 'meta.diff.header.from-file',
},
{
foreground: 'ffffff',
background: '4271ae',
- token: 'meta.diff.header.from-file'
+ token: 'meta.diff.header.from-file',
},
{
foreground: 'ffffff',
background: '4271ae',
- token: 'meta.diff.header.to-file'
+ token: 'meta.diff.header.to-file',
},
{
foreground: '3e999f',
fontStyle: 'italic',
- token: 'meta.diff.range'
- }
+ token: 'meta.diff.range',
+ },
],
colors: {
'editor.foreground': '#C5C8C6',
'editor.selectionBackground': '#373B41',
'editor.lineHighlightBackground': '#282A2E',
'editorCursor.foreground': '#AEAFAD',
- 'editorWhitespace.foreground': '#4B4E55'
- }
+ 'editorWhitespace.foreground': '#4B4E55',
+ },
}
body {
margin: 0;
overflow: hidden;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- --bg: #1D1F21;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
+ Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ --bg: #1d1f21;
--border: #333;
}
beforeEach(() => {
toggleDeprecationWarning(false)
Vue.configureCompat({
- MODE: 2
+ MODE: 2,
})
})
test('COMPILER_IS_ON_ELEMENT', () => {
const MyButton = {
- template: `<div><slot/></div>`
+ template: `<div><slot/></div>`,
}
const vm = new Vue({
template: `<button is="my-button">text</button>`,
components: {
- MyButton
- }
+ MyButton,
+ },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
test('COMPILER_IS_ON_ELEMENT (dynamic)', () => {
const MyButton = {
- template: `<div><slot/></div>`
+ template: `<div><slot/></div>`,
}
const vm = new Vue({
template: `<button :is="'MyButton'">text</button>`,
components: {
- MyButton
- }
+ MyButton,
+ },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
test('COMPILER_V_BIND_SYNC', async () => {
const MyButton = {
props: ['foo'],
- template: `<button @click="$emit('update:foo', 1)">{{ foo }}</button>`
+ template: `<button @click="$emit('update:foo', 1)">{{ foo }}</button>`,
}
const vm = new Vue({
data() {
return {
- foo: 0
+ foo: 0,
}
},
template: `<my-button :foo.sync="foo" />`,
components: {
- MyButton
- }
+ MyButton,
+ },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLButtonElement)
test('COMPILER_V_BIND_OBJECT_ORDER', () => {
const vm = new Vue({
- template: `<div id="foo" v-bind="{ id: 'bar', class: 'baz' }" />`
+ template: `<div id="foo" v-bind="{ id: 'bar', class: 'baz' }" />`,
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.id).toBe('foo')
expect(vm.$el.className).toBe('baz')
expect(
- CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER
+ CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
).toHaveBeenWarned()
})
template: `<child @click="spy" @click.native="spy" />`,
components: {
child: {
- template: `<button />`
- }
+ template: `<button />`,
+ },
},
methods: {
- spy
- }
+ spy,
+ },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLButtonElement)
test('COMPILER_V_IF_V_FOR_PRECEDENCE', () => {
new Vue({ template: `<div v-if="true" v-for="i in 1"/>` }).$mount()
expect(
- CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE
+ CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
).toHaveBeenWarned()
})
test('COMPILER_NATIVE_TEMPLATE', () => {
const vm = new Vue({
- template: `<div><template><div/></template></div>`
+ template: `<div><template><div/></template></div>`,
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
foo: {
data() {
return { n: 123 }
- }
- }
- }
+ },
+ },
+ },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
import {
DeprecationTypes,
deprecationData,
- toggleDeprecationWarning
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
beforeEach(() => {
toggleDeprecationWarning(true)
Vue.configureCompat({
MODE: 2,
- GLOBAL_MOUNT: 'suppress-warning'
+ GLOBAL_MOUNT: 'suppress-warning',
})
})
}
const vm = new Vue({
template: `<div><comp/></div>`,
- components: { comp }
+ components: { comp },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(
(deprecationData[DeprecationTypes.COMPONENT_ASYNC].message as Function)(
- comp
- )
+ comp,
+ ),
).toHaveBeenWarned()
})
const comp = () => Promise.resolve({ template: 'foo' })
const vm = new Vue({
template: `<div><comp/></div>`,
- components: { comp }
+ components: { comp },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.innerHTML).toBe(`<!---->`)
expect(
(deprecationData[DeprecationTypes.COMPONENT_ASYNC].message as Function)(
- comp
- )
+ comp,
+ ),
).toHaveBeenWarned()
})
test('object syntax', async () => {
const comp = () => ({
- component: Promise.resolve({ template: 'foo' })
+ component: Promise.resolve({ template: 'foo' }),
})
const vm = new Vue({
template: `<div><comp/></div>`,
- components: { comp }
+ components: { comp },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(
(deprecationData[DeprecationTypes.COMPONENT_ASYNC].message as Function)(
- comp
- )
+ comp,
+ ),
).toHaveBeenWarned()
})
})
import {
DeprecationTypes,
deprecationData,
- toggleDeprecationWarning
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
beforeEach(() => {
toggleDeprecationWarning(true)
Vue.configureCompat({
MODE: 2,
- GLOBAL_MOUNT: 'suppress-warning'
+ GLOBAL_MOUNT: 'suppress-warning',
})
})
name: 'Func',
functional: true,
props: {
- x: String
+ x: String,
},
inject: ['foo'],
render: (h: any, { data, props, injections, slots }: any) => {
return h('div', { id: props.x, class: data.class }, [
h('div', { class: 'inject' }, injections.foo),
- h('div', { class: 'slot' }, slots().default)
+ h('div', { class: 'slot' }, slots().default),
])
- }
+ },
}
const vm = new Vue({
provide() {
return {
- foo: 123
+ foo: 123,
}
},
components: {
- func
+ func,
},
- template: `<func class="foo" x="foo">hello</func>`
+ template: `<func class="foo" x="foo">hello</func>`,
}).$mount()
expect(vm.$el.id).toBe('foo')
expect(vm.$el.querySelector('.inject').textContent).toBe('123')
expect(vm.$el.querySelector('.slot').textContent).toBe('hello')
expect(vm.$el.outerHTML).toMatchInlineSnapshot(
- `"<div id="foo" class="foo"><div class="inject">123</div><div class="slot">hello</div></div>"`
+ `"<div id="foo" class="foo"><div class="inject">123</div><div class="slot">hello</div></div>"`,
)
expect(
(
deprecationData[DeprecationTypes.COMPONENT_FUNCTIONAL]
.message as Function
- )(func)
+ )(func),
).toHaveBeenWarned()
})
name: 'Func',
functional: true,
compatConfig: {
- ATTR_FALSE_VALUE: 'suppress-warning' as const
+ ATTR_FALSE_VALUE: 'suppress-warning' as const,
},
render: (h: any) => {
// should not render required: false due to compatConfig
return h('div', { 'data-some-attr': false })
- }
+ },
}
const vm = new Vue({
components: { func },
- template: `<func class="foo" x="foo">hello</func>`
+ template: `<func class="foo" x="foo">hello</func>`,
}).$mount()
expect(vm.$el.outerHTML).toMatchInlineSnapshot(`"<div></div>"`)
(
deprecationData[DeprecationTypes.COMPONENT_FUNCTIONAL]
.message as Function
- )(func)
+ )(func),
).toHaveBeenWarned()
expect(
(deprecationData[DeprecationTypes.ATTR_FALSE_VALUE].message as Function)(
- func
- )
+ func,
+ ),
).not.toHaveBeenWarned()
})
})
import Vue from '@vue/compat'
-import { ComponentOptions } from '../../runtime-core/src/component'
+import type { ComponentOptions } from '../../runtime-core/src/component'
import { nextTick } from '../../runtime-core/src/scheduler'
import {
DeprecationTypes,
deprecationData,
- toggleDeprecationWarning
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
import { triggerEvent } from './utils'
toggleDeprecationWarning(true)
Vue.configureCompat({
MODE: 2,
- GLOBAL_MOUNT: 'suppress-warning'
+ GLOBAL_MOUNT: 'suppress-warning',
})
})
const vm = new Vue({
data() {
return {
- text: 'foo'
+ text: 'foo',
}
},
components: { CustomInput },
<span>{{ text }}</span>
<custom-input v-model="text"></custom-input>
</div>
- `
+ `,
}).$mount() as any
const input = vm.$el.querySelector('input')
expect(
(deprecationData[DeprecationTypes.COMPONENT_V_MODEL].message as Function)(
- CustomInput
- )
+ CustomInput,
+ ),
).toHaveBeenWarned()
input.value = 'bar'
await runTest({
name: 'CustomInput',
props: ['value'],
- template: `<input :value="value" @input="$emit('input', $event.target.value)">`
+ template: `<input :value="value" @input="$emit('input', $event.target.value)">`,
})
})
props: ['input'],
model: {
prop: 'input',
- event: 'update'
+ event: 'update',
},
- template: `<input :value="input" @input="$emit('update', $event.target.value)">`
+ template: `<input :value="input" @input="$emit('update', $event.target.value)">`,
})
})
})
import Vue from '@vue/compat'
import { CompilerDeprecationTypes } from '../../compiler-core/src'
import {
- deprecationData,
DeprecationTypes,
- toggleDeprecationWarning
+ deprecationData,
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
beforeEach(() => {
const vm = new Vue({
template: '<div>{{ msg | globalUpper }}</div>',
data: () => ({
- msg: 'hi'
- })
+ msg: 'hi',
+ }),
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.textContent).toBe('HI')
const vm = new Vue({
template: '<div>{{ msg | upper }}</div>',
data: () => ({
- msg: 'hi'
+ msg: 'hi',
}),
filters: {
- upper
- }
+ upper,
+ },
}).$mount()
expect(vm.$el.textContent).toBe('HI')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
const vm = new Vue({
template: '<div>{{ msg | upper | reverse }}</div>',
data: () => ({
- msg: 'hi'
+ msg: 'hi',
}),
filters: {
upper,
- reverse
- }
+ reverse,
+ },
}).$mount()
expect(vm.$el.textContent).toBe('IH')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
filters: {
upper,
reverse,
- lower
+ lower,
},
data: () => ({
id: 'abc',
cls: 'foo',
- ref: 'BAR'
- })
+ ref: 'BAR',
+ }),
}).$mount()
expect(vm.$el.id).toBe('CBA')
expect(vm.$el.className).toBe('oof')
components: {
test: {
props: ['pattern'],
- template: '<div></div>'
- }
- }
+ template: '<div></div>',
+ },
+ },
}).$mount() as any
expect(vm.$refs.test.pattern).toBeInstanceOf(RegExp)
expect(vm.$refs.test.pattern.toString()).toBe('/a|b\\//')
const vm = new Vue({
data: () => ({ a: 2 }),
template: `<div>{{ 1/a / 4 | double }}</div>`,
- filters: { double }
+ filters: { double },
}).$mount()
expect(vm.$el.textContent).toBe(String(1 / 4))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
const vm = new Vue({
data: () => ({ a: 20 }),
template: `<div>{{ (a*2) / 5 | double }}</div>`,
- filters: { double }
+ filters: { double },
}).$mount()
expect(vm.$el.textContent).toBe(String(16))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
it('handle division with dot', () => {
const vm = new Vue({
template: `<div>{{ 20. / 5 | double }}</div>`,
- filters: { double }
+ filters: { double },
}).$mount()
expect(vm.$el.textContent).toBe(String(8))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
const vm = new Vue({
data: () => ({ a: [20] }),
template: `<div>{{ a[0] / 5 | double }}</div>`,
- filters: { double }
+ filters: { double },
}).$mount()
expect(vm.$el.textContent).toBe(String(8))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
const vm = new Vue({
data: () => ({ a: { n: 20 } }),
template: `<div>{{ a['n'] / 5 | double }}</div>`,
- filters: { double }
+ filters: { double },
}).$mount()
expect(vm.$el.textContent).toBe(String(8))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
const vm = new Vue({
data: () => ({ a_: 8 }),
template: `<div>{{ a_ / 2 | double }}</div>`,
- filters: { double }
+ filters: { double },
}).$mount()
expect(vm.$el.textContent).toBe(String(8))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
template: `<div>{{ msg | add(a, 3) }}</div>`,
data: () => ({
msg: 1,
- a: 2
+ a: 2,
}),
filters: {
- add: (v: number, arg1: number, arg2: number) => v + arg1 + arg2
- }
+ add: (v: number, arg1: number, arg2: number) => v + arg1 + arg2,
+ },
}).$mount()
expect(vm.$el.textContent).toBe('6')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
const vm = new Vue({
template: `<div>{{ msg + "b | c" + 'd' | upper }}</div>`,
data: () => ({
- msg: 'a'
+ msg: 'a',
}),
filters: {
- upper
- }
+ upper,
+ },
}).$mount()
expect(vm.$el.textContent).toBe('AB | CD')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
template: `<div>{{ b || msg | upper }}</div>`,
data: () => ({
b: false,
- msg: 'a'
+ msg: 'a',
}),
filters: {
- upper
- }
+ upper,
+ },
}).$mount()
expect(vm.$el.textContent).toBe('A')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
const vm = new Vue({
template: `<div>{{ { a: 123 } | pick('a') }}</div>`,
filters: {
- pick: (v: any, key: string) => v[key]
- }
+ pick: (v: any, key: string) => v[key],
+ },
}).$mount()
expect(vm.$el.textContent).toBe('123')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
const vm = new Vue({
template: `<div>{{ [1, 2, 3] | reverse }}</div>`,
filters: {
- reverse: (arr: any[]) => arr.reverse().join(',')
- }
+ reverse: (arr: any[]) => arr.reverse().join(','),
+ },
}).$mount()
expect(vm.$el.textContent).toBe('3,2,1')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
it('bigint support', () => {
const vm = new Vue({
- template: `<div>{{ BigInt(BigInt(10000000)) + BigInt(2000000000n) * 3000000n }}</div>`
+ template: `<div>{{ BigInt(BigInt(10000000)) + BigInt(2000000000n) * 3000000n }}</div>`,
}).$mount()
expect(vm.$el.textContent).toBe('6000000010000000')
})
import {
DeprecationTypes,
deprecationData,
- toggleDeprecationWarning
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
import { singletonApp } from '../../runtime-core/src/compat/global'
import { createApp } from '../src/esm-index'
compatConfig: { GLOBAL_MOUNT: true },
data() {
return {
- msg: 'hello'
+ msg: 'hello',
}
- }
+ },
})
expect(
- deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
+ deprecationData[DeprecationTypes.GLOBAL_MOUNT].message,
).toHaveBeenWarned()
expect(el.innerHTML).toBe('hello')
})
new Vue({
data() {
return {
- msg: 'hello'
+ msg: 'hello',
}
- }
+ },
}).$mount(el)
expect(el.innerHTML).toBe('hello')
})
new Vue().$mount(el)
// warning only
expect(
- deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
+ deprecationData[DeprecationTypes.GLOBAL_MOUNT].message,
).toHaveBeenWarned()
expect(
- deprecationData[DeprecationTypes.GLOBAL_MOUNT_CONTAINER].message
+ deprecationData[DeprecationTypes.GLOBAL_MOUNT_CONTAINER].message,
).toHaveBeenWarned()
})
})
const Test = Vue.extend({
name: 'test',
a: 1,
- b: 2
+ b: 2,
})
expect(Test.options.a).toBe(1)
expect(Test.options.b).toBe(2)
expect(Test.super).toBe(Vue)
const t = new Test({
- a: 2
+ a: 2,
})
expect(t.$options.a).toBe(2)
expect(t.$options.b).toBe(2)
// inheritance
const Test2 = Test.extend({
- a: 2
+ a: 2,
})
expect(Test2.options.a).toBe(2)
expect(Test2.options.b).toBe(2)
const t2 = new Test2({
- a: 3
+ a: 3,
})
expect(t2.$options.a).toBe(3)
expect(t2.$options.b).toBe(2)
expect(
- deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
+ deprecationData[DeprecationTypes.GLOBAL_MOUNT].message,
).toHaveBeenWarned()
expect(
- deprecationData[DeprecationTypes.GLOBAL_EXTEND].message
+ deprecationData[DeprecationTypes.GLOBAL_EXTEND].message,
).toHaveBeenWarned()
})
it('should work when used as components', () => {
const foo = Vue.extend({
- template: '<span>foo</span>'
+ template: '<span>foo</span>',
})
const bar = Vue.extend({
- template: '<span>bar</span>'
+ template: '<span>bar</span>',
})
const vm = new Vue({
template: '<div><foo></foo><bar></bar></div>',
- components: { foo, bar }
+ components: { foo, bar },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')
const A = Vue.extend({
created() {
calls.push(1)
- }
+ },
})
const B = A.extend({
created() {
calls.push(2)
- }
+ },
})
new B({
created() {
calls.push(3)
- }
+ },
})
expect(calls).toEqual([1, 2, 3])
})
const c = vi.fn()
const d = vi.fn()
const A = Vue.extend({
- created: a
+ created: a,
})
const B = Vue.extend({
mixins: [A],
- created: b
+ created: b,
})
const C = Vue.extend({
extends: B,
- created: c
+ created: c,
})
const D = Vue.extend({
mixins: [C],
created: d,
render() {
return null
- }
+ },
})
new D().$mount()
expect(a.mock.calls.length).toStrictEqual(1)
methods: {
a() {
return this.n
- }
- }
+ },
+ },
})
const B = A.extend({
methods: {
b() {
return this.n + 1
- }
- }
+ },
+ },
})
const b = new B({
data: () => ({ n: 0 }),
methods: {
c() {
return this.n + 2
- }
- }
+ },
+ },
}) as any
expect(b.a()).toBe(0)
expect(b.b()).toBe(1)
const A = Vue.extend({
components: {
aa: {
- template: '<div>A</div>'
- }
- }
+ template: '<div>A</div>',
+ },
+ },
})
const B = A.extend({
components: {
bb: {
- template: '<div>B</div>'
- }
- }
+ template: '<div>B</div>',
+ },
+ },
})
const b = new B({
- template: '<div><aa></aa><bb></bb></div>'
+ template: '<div><aa></aa><bb></bb></div>',
}).$mount()
expect(b.$el).toBeInstanceOf(HTMLDivElement)
expect(b.$el.innerHTML).toBe('<div>A</div><div>B</div>')
it('caching', () => {
const options = {
- template: '<div></div>'
+ template: '<div></div>',
}
const A = Vue.extend(options)
const B = Vue.extend(options)
expect(vm.$test).toBe(1)
delete Vue.prototype.$test
expect(
- deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
+ deprecationData[DeprecationTypes.GLOBAL_MOUNT].message,
).toHaveBeenWarned()
expect(
- deprecationData[DeprecationTypes.GLOBAL_PROTOTYPE].message
+ deprecationData[DeprecationTypes.GLOBAL_PROTOTYPE].message,
).toHaveBeenWarned()
})
const vm = new Vue({
data() {
return { msg: 'method' }
- }
+ },
}) as any
expect(vm.$test()).toBe('method')
delete Vue.prototype.$test
configurable: true,
get() {
return this.msg
- }
+ },
})
const vm = new Vue({
data() {
return { msg: 'getter' }
- }
+ },
}) as any
expect(vm.$test).toBe('getter')
delete Vue.prototype.$test
const vm = new Vue({
data() {
return {
- msg: 'test'
+ msg: 'test',
}
- }
+ },
}) as any
expect(typeof vm.$test).toBe('function')
expect(typeof vm.$test.additionalFn).toBe('function')
test('should affect apps created via createApp()', () => {
Vue.prototype.$test = 1
const vm = createApp({
- template: 'foo'
+ template: 'foo',
}).mount(document.createElement('div')) as any
expect(vm.$test).toBe(1)
delete Vue.prototype.$test
Vue.set(obj, 'foo', 1)
expect(obj.foo).toBe(1)
expect(
- deprecationData[DeprecationTypes.GLOBAL_SET].message
+ deprecationData[DeprecationTypes.GLOBAL_SET].message,
).toHaveBeenWarned()
})
Vue.delete(obj, 'foo')
expect('foo' in obj).toBe(false)
expect(
- deprecationData[DeprecationTypes.GLOBAL_DELETE].message
+ deprecationData[DeprecationTypes.GLOBAL_DELETE].message,
).toHaveBeenWarned()
})
})
const obj = Vue.observable({})
expect(isReactive(obj)).toBe(true)
expect(
- deprecationData[DeprecationTypes.GLOBAL_OBSERVABLE].message
+ deprecationData[DeprecationTypes.GLOBAL_OBSERVABLE].message,
).toHaveBeenWarned()
})
})
test('defineReactive', () => {
toggleDeprecationWarning(true)
const obj: any = {}
- // @ts-ignore
Vue.util.defineReactive(obj, 'test', 1)
let n
expect(n).toBe(2)
expect(
- deprecationData[DeprecationTypes.GLOBAL_PRIVATE_UTIL].message
+ deprecationData[DeprecationTypes.GLOBAL_PRIVATE_UTIL].message,
).toHaveBeenWarned()
})
test('defineReactive on instance', async () => {
const vm = new Vue({
beforeCreate() {
- // @ts-ignore
Vue.util.defineReactive(this, 'foo', 1)
},
- template: `<div>{{ foo }}</div>`
+ template: `<div>{{ foo }}</div>`,
}).$mount() as any
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.textContent).toBe('1')
test('defineReactive on instance with key that starts with $', async () => {
const vm = new Vue({
beforeCreate() {
- // @ts-ignore
Vue.util.defineReactive(this, '$foo', 1)
},
- template: `<div>{{ $foo }}</div>`
+ template: `<div>{{ $foo }}</div>`,
}).$mount() as any
expect(vm.$el.textContent).toBe('1')
vm.$foo = 2
test('defineReactive with object value', () => {
const obj: any = {}
const val = { a: 1 }
- // @ts-ignore
Vue.util.defineReactive(obj, 'foo', val)
let n
test('defineReactive with array value', () => {
const obj: any = {}
const val = [1]
- // @ts-ignore
Vue.util.defineReactive(obj, 'foo', val)
let n
test('global asset registration should affect apps created via createApp', () => {
Vue.component('foo', { template: 'foo' })
const vm = createApp({
- template: '<foo/>'
+ template: '<foo/>',
}).mount(document.createElement('div')) as any
expect(vm.$el.textContent).toBe('foo')
delete singletonApp._context.components.foo
test('post-facto global asset registration should affect apps created via createApp', () => {
const app = createApp({
- template: '<foo/>'
+ template: '<foo/>',
})
Vue.component('foo', { template: 'foo' })
const vm = app.mount(document.createElement('div'))
app2.component('foo', {})
expect(
- `Component "foo" has already been registered in target app`
+ `Component "foo" has already been registered in target app`,
).not.toHaveBeenWarned()
})
render: () => h('div'),
provide() {
return {
- test: 123
+ test: 123,
}
- }
+ },
})
app1.config.globalProperties.test = () => {}
app1.mount(document.createElement('div'))
import Vue from '@vue/compat'
import {
DeprecationTypes,
- toggleDeprecationWarning
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
import { createApp } from '../src/esm-index'
import { triggerEvent } from './utils'
test('GLOBAL_KEY_CODES', () => {
Vue.config.keyCodes = {
foo: 86,
- bar: [38, 87]
+ bar: [38, 87],
}
const onFoo = vi.fn()
template: `<input type="text" @keyup.foo="onFoo" @keyup.bar="onBar">`,
methods: {
onFoo,
- onBar
- }
+ onBar,
+ },
})
triggerEvent(el.children[0], 'keyup', e => {
const el = document.createElement('div')
new Vue({
el,
- template: `<v-foo/><foo/>`
+ template: `<v-foo/><foo/>`,
})
expect(el.innerHTML).toBe(`<v-foo></v-foo><foo></foo>`)
})
Vue.config.ignoredElements = [/^v-/, 'foo']
const el = document.createElement('div')
createApp({
- template: `<v-foo/><foo/>`
+ template: `<v-foo/><foo/>`,
}).mount(el)
expect(el.innerHTML).toBe(`<v-foo></v-foo><foo></foo>`)
})
-import { type Mock } from 'vitest'
+import type { Mock } from 'vitest'
import Vue from '@vue/compat'
-import { Slots } from '../../runtime-core/src/componentSlots'
+import type { Slots } from '../../runtime-core/src/componentSlots'
import { Text } from '../../runtime-core/src/vnode'
import {
DeprecationTypes,
deprecationData,
- toggleDeprecationWarning
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
-import { LegacyPublicInstance } from '../../runtime-core/src/compat/instance'
+import type { LegacyPublicInstance } from '../../runtime-core/src/compat/instance'
beforeEach(() => {
toggleDeprecationWarning(true)
Vue.configureCompat({
MODE: 2,
- GLOBAL_MOUNT: 'suppress-warning'
+ GLOBAL_MOUNT: 'suppress-warning',
})
})
new Vue().$set(obj, 'foo', 1)
expect(obj.foo).toBe(1)
expect(
- deprecationData[DeprecationTypes.INSTANCE_SET].message
+ deprecationData[DeprecationTypes.INSTANCE_SET].message,
).toHaveBeenWarned()
})
new Vue().$delete(obj, 'foo')
expect('foo' in obj).toBe(false)
expect(
- deprecationData[DeprecationTypes.INSTANCE_DELETE].message
+ deprecationData[DeprecationTypes.INSTANCE_DELETE].message,
).toHaveBeenWarned()
})
test('INSTANCE_DESTROY', () => {
new Vue({ template: 'foo' }).$mount().$destroy()
expect(
- deprecationData[DeprecationTypes.INSTANCE_DESTROY].message
+ deprecationData[DeprecationTypes.INSTANCE_DESTROY].message,
).toHaveBeenWarned()
})
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith(1, 2, 3, 4)
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
expect(spy).toHaveBeenCalledTimes(2)
expect(spy).toHaveBeenCalledWith(5, 6, 7, 8)
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
vm.$emit('test3', 1, 2, 3, 4)
expect(spy).toHaveBeenCalledTimes(1)
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
vm.$emit('test1')
expect(spy).not.toHaveBeenCalled()
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith(1, 2, 3)
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
vm.$emit('test', 1, 2, 3)
expect(spy).not.toHaveBeenCalled()
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
vm.$emit('test2')
expect(spy).not.toHaveBeenCalled()
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith(2)
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
expect(spy2).toHaveBeenCalledTimes(1)
expect(spy2).toHaveBeenCalledWith(1, 2, 3)
expect(
- deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+ deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
).toHaveBeenWarned()
})
})
(
deprecationData[DeprecationTypes.INSTANCE_EVENT_HOOKS]
.message as Function
- )('hook:mounted')
+ )('hook:mounted'),
).toHaveBeenWarned()
})
methods: { spy },
components: {
child: {
- template: 'foo'
- }
- }
+ template: 'foo',
+ },
+ },
}).$mount()
expect(spy).toHaveBeenCalled()
expect(
(
deprecationData[DeprecationTypes.INSTANCE_EVENT_HOOKS]
.message as Function
- )('hook:mounted')
+ )('hook:mounted'),
).toHaveBeenWarned()
})
})
template: 'foo',
data() {
return { n: 1 }
- }
- }
- }
+ },
+ },
+ },
}).$mount()
expect(vm.$children.length).toBe(4)
vm.$children.forEach((c: any) => {
expect(c.n).toBe(1)
})
expect(
- deprecationData[DeprecationTypes.INSTANCE_CHILDREN].message
+ deprecationData[DeprecationTypes.INSTANCE_CHILDREN].message,
).toHaveBeenWarned()
})
template: `<div/>`,
mounted() {
listeners = this.$listeners
- }
- }
- }
+ },
+ },
+ },
}).$mount()
expect(Object.keys(listeners!)).toMatchObject(['click', 'custom'])
expect(listeners!.custom()).toBe('bar')
expect(
- deprecationData[DeprecationTypes.INSTANCE_LISTENERS].message
+ deprecationData[DeprecationTypes.INSTANCE_LISTENERS].message,
).toHaveBeenWarned()
})
compatConfig: { RENDER_FUNCTION: false },
render() {
slots = this.$scopedSlots
- }
- }
- }
+ },
+ },
+ },
}).$mount()
expect(slots!.default!({ msg: 'hi' })).toMatchObject([
{
type: Text,
- children: 'hi'
- }
+ children: 'hi',
+ },
])
expect(
- deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
+ deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message,
).toHaveBeenWarned()
})
render() {
normalSlots = this.$slots
scopedSlots = this.$scopedSlots
- }
- }
- }
+ },
+ },
+ },
}).$mount()
expect('default' in normalSlots!).toBe(true)
expect('default' in scopedSlots!).toBe(false)
expect(
- deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
+ deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message,
).toHaveBeenWarned()
})
})
components: {
child: {
inheritAttrs: false,
- template: `<div><div v-bind="$attrs" /></div>`
- }
- }
+ template: `<div><div v-bind="$attrs" /></div>`,
+ },
+ },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.outerHTML).toBe(
- `<div class="foo" style="color: red;"><div id="ok"></div></div>`
+ `<div class="foo" style="color: red;"><div id="ok"></div></div>`,
)
expect(
(
deprecationData[DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE]
.message as Function
- )('Anonymous')
+ )('Anonymous'),
).toHaveBeenWarned()
})
import {
DeprecationTypes,
deprecationData,
- toggleDeprecationWarning
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
import { triggerEvent } from './utils'
import { h } from '@vue/runtime-core'
toggleDeprecationWarning(true)
Vue.configureCompat({
MODE: 2,
- GLOBAL_MOUNT: 'suppress-warning'
+ GLOBAL_MOUNT: 'suppress-warning',
})
})
test('mode as function', () => {
const Foo = {
name: 'Foo',
- render: (h: any) => h('div', 'foo')
+ render: (h: any) => h('div', 'foo'),
}
const Bar = {
name: 'Bar',
data: () => ({ msg: 'bar' }),
- render: (ctx: any) => h('div', ctx.msg)
+ render: (ctx: any) => h('div', ctx.msg),
}
toggleDeprecationWarning(false)
Vue.configureCompat({
- MODE: comp => (comp && comp.name === 'Bar' ? 3 : 2)
+ MODE: comp => (comp && comp.name === 'Bar' ? 3 : 2),
})
const vm = new Vue({
components: { Foo, Bar },
- template: `<div><foo/><bar/></div>`
+ template: `<div><foo/><bar/></div>`,
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
const vm = new Vue({
data() {
return {
- foo: []
+ foo: [],
}
},
watch: {
- foo: spy
- }
+ foo: spy,
+ },
}) as any
expect(
- deprecationData[DeprecationTypes.WATCH_ARRAY].message
+ deprecationData[DeprecationTypes.WATCH_ARRAY].message,
).toHaveBeenWarned()
expect(spy).not.toHaveBeenCalled()
thisCtx = {
foo: this.foo,
$options: this.$options,
- provided: this.provided
+ provided: this.provided,
}
return this.foo + 1
- }
- }
+ },
+ },
},
- template: `{{ bar }}`
+ template: `{{ bar }}`,
}
const vm = new Vue({
components: { Child },
provide: {
- provided: 2
+ provided: 2,
},
- template: `<child :foo="0" />`
+ template: `<child :foo="0" />`,
}).$mount()
expect(vm.$el.textContent).toBe('1')
expect(
(deprecationData[DeprecationTypes.PROPS_DEFAULT_THIS].message as Function)(
- 'bar'
- )
+ 'bar',
+ ),
).toHaveBeenWarned()
})
const spy = vi.fn()
const vm = new Vue({
template: `<input @keyup.1="spy">`,
- methods: { spy }
+ methods: { spy },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLInputElement)
triggerEvent(vm.$el, 'keyup', e => {
})
expect(spy).toHaveBeenCalled()
expect(
- deprecationData[DeprecationTypes.V_ON_KEYCODE_MODIFIER].message
+ deprecationData[DeprecationTypes.V_ON_KEYCODE_MODIFIER].message,
).toHaveBeenWarned()
})
inserted: vi.fn(),
update: vi.fn(),
componentUpdated: vi.fn(),
- unbind: vi.fn()
+ unbind: vi.fn(),
} as any
const getCalls = () =>
data() {
return {
ok: true,
- foo: 1
+ foo: 1,
}
},
template: `<div v-if="ok" v-my-dir="foo"/>`,
directives: {
- myDir
- }
+ myDir,
+ },
}).$mount() as any
expect(getCalls()).toMatchObject([1, 1, 0, 0, 0])
expect(
(deprecationData[DeprecationTypes.CUSTOM_DIR].message as Function)(
'bind',
- 'beforeMount'
- )
+ 'beforeMount',
+ ),
).toHaveBeenWarned()
expect(
(deprecationData[DeprecationTypes.CUSTOM_DIR].message as Function)(
'inserted',
- 'mounted'
- )
+ 'mounted',
+ ),
).toHaveBeenWarned()
vm.foo++
expect(
(deprecationData[DeprecationTypes.CUSTOM_DIR].message as Function)(
'update',
- 'updated'
- )
+ 'updated',
+ ),
).toHaveBeenWarned()
expect(
(deprecationData[DeprecationTypes.CUSTOM_DIR].message as Function)(
'componentUpdated',
- 'updated'
- )
+ 'updated',
+ ),
).toHaveBeenWarned()
})
test('ATTR_FALSE_VALUE', () => {
const vm = new Vue({
- template: `<div :id="false" :foo="false"/>`
+ template: `<div :id="false" :foo="false"/>`,
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.hasAttribute('id')).toBe(false)
expect(vm.$el.hasAttribute('foo')).toBe(false)
expect(
(deprecationData[DeprecationTypes.ATTR_FALSE_VALUE].message as Function)(
- 'id'
- )
+ 'id',
+ ),
).toHaveBeenWarned()
expect(
(deprecationData[DeprecationTypes.ATTR_FALSE_VALUE].message as Function)(
- 'foo'
- )
+ 'foo',
+ ),
).toHaveBeenWarned()
})
test('ATTR_ENUMERATED_COERCION', () => {
const vm = new Vue({
- template: `<div :draggable="null" :spellcheck="0" contenteditable="foo" />`
+ template: `<div :draggable="null" :spellcheck="0" contenteditable="foo" />`,
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
(
deprecationData[DeprecationTypes.ATTR_ENUMERATED_COERCION]
.message as Function
- )('draggable', null, 'false')
+ )('draggable', null, 'false'),
).toHaveBeenWarned()
expect(
(
deprecationData[DeprecationTypes.ATTR_ENUMERATED_COERCION]
.message as Function
- )('spellcheck', 0, 'true')
+ )('spellcheck', 0, 'true'),
).toHaveBeenWarned()
expect(
(
deprecationData[DeprecationTypes.ATTR_ENUMERATED_COERCION]
.message as Function
- )('contenteditable', 'foo', 'true')
+ )('contenteditable', 'foo', 'true'),
).toHaveBeenWarned()
})
import {
DeprecationTypes,
deprecationData,
- toggleDeprecationWarning
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
beforeEach(() => {
Vue.configureCompat({
MODE: 2,
GLOBAL_MOUNT: 'suppress-warning',
- GLOBAL_EXTEND: 'suppress-warning'
+ GLOBAL_EXTEND: 'suppress-warning',
})
})
test('root data plain object', () => {
const vm = new Vue({
data: { foo: 1 } as any,
- template: `{{ foo }}`
+ template: `{{ foo }}`,
}).$mount()
expect(vm.$el.textContent).toBe('1')
expect(
- deprecationData[DeprecationTypes.OPTIONS_DATA_FN].message
+ deprecationData[DeprecationTypes.OPTIONS_DATA_FN].message,
).toHaveBeenWarned()
})
data() {
return {
foo: {
- baz: 2
- }
+ baz: 2,
+ },
}
- }
+ },
}
const vm = new Vue({
mixins: [mixin],
data: () => ({
foo: {
- bar: 1
+ bar: 1,
},
- selfData: 3
+ selfData: 3,
}),
- template: `{{ { selfData, foo } }}`
+ template: `{{ { selfData, foo } }}`,
}).$mount()
expect(vm.$el.textContent).toBe(
- JSON.stringify({ selfData: 3, foo: { baz: 2, bar: 1 } }, null, 2)
+ JSON.stringify({ selfData: 3, foo: { baz: 2, bar: 1 } }, null, 2),
)
expect(
(deprecationData[DeprecationTypes.OPTIONS_DATA_MERGE].message as Function)(
- 'foo'
- )
+ 'foo',
+ ),
).toHaveBeenWarned()
})
const App = Vue.extend({
template: `<pre>{{ { mixinData, selfData } }}</pre>`,
mixins: [{ data: () => ({ mixinData: 'mixinData' }) }],
- data: () => ({ selfData: 'selfData' })
+ data: () => ({ selfData: 'selfData' }),
})
const vm = new App().$mount()
expect(vm.$el.textContent).toBe(
JSON.stringify(
{
mixinData: 'mixinData',
- selfData: 'selfData'
+ selfData: 'selfData',
},
null,
- 2
- )
+ 2,
+ ),
)
})
const child = {
template: `foo`,
beforeDestroy,
- destroyed
+ destroyed,
}
const vm = new Vue({
data() {
return { ok: true }
},
- components: { child }
+ components: { child },
}).$mount() as any
vm.ok = false
expect(destroyed).toHaveBeenCalled()
expect(
- deprecationData[DeprecationTypes.OPTIONS_BEFORE_DESTROY].message
+ deprecationData[DeprecationTypes.OPTIONS_BEFORE_DESTROY].message,
).toHaveBeenWarned()
expect(
- deprecationData[DeprecationTypes.OPTIONS_DESTROYED].message
+ deprecationData[DeprecationTypes.OPTIONS_DESTROYED].message,
).toHaveBeenWarned()
})
const child = Vue.extend({
template: `foo`,
beforeDestroy,
- destroyed
+ destroyed,
})
const vm = new Vue({
data() {
return { ok: true }
},
- components: { child }
+ components: { child },
}).$mount() as any
vm.ok = false
expect(destroyed).toHaveBeenCalled()
expect(
- deprecationData[DeprecationTypes.OPTIONS_BEFORE_DESTROY].message
+ deprecationData[DeprecationTypes.OPTIONS_BEFORE_DESTROY].message,
).toHaveBeenWarned()
expect(
- deprecationData[DeprecationTypes.OPTIONS_DESTROYED].message
+ deprecationData[DeprecationTypes.OPTIONS_DESTROYED].message,
).toHaveBeenWarned()
})
import Vue from '@vue/compat'
import { createComponentInstance } from '../../runtime-core/src/component'
import { setCurrentRenderingInstance } from '../../runtime-core/src/componentRenderContext'
-import { DirectiveBinding } from '../../runtime-core/src/directives'
+import type { DirectiveBinding } from '../../runtime-core/src/directives'
import { createVNode } from '../../runtime-core/src/vnode'
import {
- deprecationData,
DeprecationTypes,
- toggleDeprecationWarning
+ deprecationData,
+ toggleDeprecationWarning,
} from '../../runtime-core/src/compat/compatConfig'
import { compatH as h } from '../../runtime-core/src/compat/renderFn'
toggleDeprecationWarning(false)
Vue.configureCompat({
MODE: 2,
- GLOBAL_MOUNT: 'suppress-warning'
+ GLOBAL_MOUNT: 'suppress-warning',
})
})
const mockChildComp = {}
const mockComponent = {
directives: {
- mockDir
+ mockDir,
},
components: {
- foo: mockChildComp
- }
+ foo: mockChildComp,
+ },
}
const mockInstance = createComponentInstance(
createVNode(mockComponent),
null,
- null
+ null,
)
beforeEach(() => {
setCurrentRenderingInstance(mockInstance)
test('string component lookup', () => {
expect(h('foo')).toMatchObject({
- type: mockChildComp
+ type: mockChildComp,
})
})
class: 'foo',
style: { color: 'red' },
attrs: {
- id: 'foo'
+ id: 'foo',
},
domProps: {
- innerHTML: 'hi'
+ innerHTML: 'hi',
},
props: {
- myProp: 'foo'
- }
- })
+ myProp: 'foo',
+ },
+ }),
).toMatchObject({
props: {
class: 'foo',
style: { color: 'red' },
id: 'foo',
innerHTML: 'hi',
- myProp: 'foo'
- }
+ myProp: 'foo',
+ },
})
})
expect(
h('div', {
class: { foo: true },
- staticClass: 'bar'
- })
+ staticClass: 'bar',
+ }),
).toMatchObject({
props: {
- class: 'bar foo'
- }
+ class: 'bar foo',
+ },
})
})
expect(
h('div', {
style: { color: 'red' },
- staticStyle: { fontSize: '14px' }
- })
+ staticStyle: { fontSize: '14px' },
+ }),
).toMatchObject({
props: {
style: {
color: 'red',
- fontSize: '14px'
- }
- }
+ fontSize: '14px',
+ },
+ },
})
})
h('div', {
on: {
click: fn,
- fooBar: fn
+ fooBar: fn,
},
nativeOn: {
click: fn,
- 'bar-baz': fn
- }
- })
+ 'bar-baz': fn,
+ },
+ }),
).toMatchObject({
props: {
onClick: fn,
onClickNative: fn,
onFooBar: fn,
- 'onBar-bazNative': fn
- }
+ 'onBar-bazNative': fn,
+ },
})
})
on: {
'&click': fn,
'~keyup': fn,
- '!touchend': fn
- }
- })
+ '!touchend': fn,
+ },
+ }),
).toMatchObject({
props: {
onClickPassive: fn,
onKeyupOnce: fn,
- onTouchendCapture: fn
- }
+ onTouchendCapture: fn,
+ },
})
})
// expression: '1 + 1',
arg: 'foo',
modifiers: {
- bar: true
- }
- }
- ]
- })
+ bar: true,
+ },
+ },
+ ],
+ }),
).toMatchObject({
dirs: [
{
oldValue: void 0,
arg: 'foo',
modifiers: {
- bar: true
- }
- }
- ] as DirectiveBinding[]
+ bar: true,
+ },
+ },
+ ] as DirectiveBinding[],
})
})
test('scopedSlots', () => {
const scopedSlots = {
- default() {}
+ default() {},
}
const vnode = h(mockComponent, {
- scopedSlots
+ scopedSlots,
})
expect(vnode).toMatchObject({
- children: scopedSlots
+ children: scopedSlots,
})
expect('scopedSlots' in vnode.props!).toBe(false)
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
h('div', { slot: 'foo' }, 'one'),
h('div', { slot: 'bar' }, 'two'),
h('div', { slot: 'foo' }, 'three'),
- h('div', 'four')
+ h('div', 'four'),
])
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
const slots = vnode.children as any
expect(slots.default()).toMatchObject(['text', { children: 'four' }])
expect(slots.foo()).toMatchObject([
{ children: 'one' },
- { children: 'three' }
+ { children: 'three' },
])
expect(slots.bar()).toMatchObject([{ children: 'two' }])
})
'div',
{
class: 'foo',
- attrs: { id: 'bar' }
+ attrs: { id: 'bar' },
},
- 'hello'
+ 'hello',
)
- }
+ },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.outerHTML).toBe(`<div class="foo" id="bar">hello</div>`)
expect(
- deprecationData[DeprecationTypes.RENDER_FUNCTION].message
+ deprecationData[DeprecationTypes.RENDER_FUNCTION].message,
).toHaveBeenWarned()
})
const vm = new Vue({
data() {
return {
- a: 'hello'
+ a: 'hello',
}
},
// check is arg length based
render(c: any, _c: any) {
return createVNode('div', null, c.a)
- }
+ },
}).$mount()
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
expect(vm.$el.outerHTML).toBe(`<div>hello</div>`)
export function triggerEvent(
target: Element,
event: string,
- process?: (e: any) => any
+ process?: (e: any) => any,
) {
const e = new Event(event, {
bubbles: true,
- cancelable: true
+ cancelable: true,
})
if (process) process(e)
target.dispatchEvent(e)
// `dist/vue.esm-bundler.js` which is used by default for bundlers.
import { initDev } from './dev'
import {
- compatUtils,
- createApp,
+ DeprecationTypes,
+ KeepAlive,
Transition,
TransitionGroup,
- KeepAlive,
- DeprecationTypes,
+ compatUtils,
+ createApp,
+ vModelDynamic,
vShow,
- vModelDynamic
} from '@vue/runtime-dom'
import { extend } from '@vue/shared'
import * as runtimeDom from '@vue/runtime-dom'
function wrappedCreateApp(...args: any[]) {
- // @ts-ignore
+ // @ts-expect-error
const app = createApp(...args)
if (compatUtils.isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, null)) {
// register built-in components so that they can be resolved via strings
if (!__ESM_BUNDLER__) {
console.info(
`You are running a development build of Vue.\n` +
- `Make sure to use the production build (*.prod.js) when deploying for production.`
+ `Make sure to use the production build (*.prod.js) when deploying for production.`,
)
}
// This entry is the "full-build" that includes both the runtime
// and the compiler, and supports on-the-fly compilation of the template option.
import { createCompatVue } from './createCompatVue'
-import { compile, CompilerError, CompilerOptions } from '@vue/compiler-dom'
-import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
-import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
-import { InternalRenderFunction } from 'packages/runtime-core/src/component'
+import {
+ type CompilerError,
+ type CompilerOptions,
+ compile,
+} from '@vue/compiler-dom'
+import {
+ type RenderFunction,
+ registerRuntimeCompiler,
+ warn,
+} from '@vue/runtime-dom'
+import { NOOP, extend, generateCodeFrame, isString } from '@vue/shared'
+import type { InternalRenderFunction } from 'packages/runtime-core/src/component'
import * as runtimeDom from '@vue/runtime-dom'
import {
DeprecationTypes,
- warnDeprecation
+ warnDeprecation,
} from '../../runtime-core/src/compat/compatConfig'
const compileCache: Record<string, RenderFunction> = Object.create(null)
function compileToFunction(
template: string | HTMLElement,
- options?: CompilerOptions
+ options?: CompilerOptions,
): RenderFunction {
if (!isString(template)) {
if (template.nodeType) {
hoistStatic: true,
whitespace: 'preserve',
onError: __DEV__ ? onError : undefined,
- onWarn: __DEV__ ? e => onError(e, true) : NOOP
+ onWarn: __DEV__ ? e => onError(e, true) : NOOP,
} as CompilerOptions,
- options
- )
+ options,
+ ),
)
function onError(err: CompilerError, asWarning = false) {
generateCodeFrame(
template as string,
err.loc.start.offset,
- err.loc.end.offset
+ err.loc.end.offset,
)
warn(codeFrame ? `${message}\n${codeFrame}` : message)
}
? ` Use "vue.esm-browser.js" instead.`
: __GLOBAL__
? ` Use "vue.global.js" instead.`
- : ``) /* should not happen */
+ : ``) /* should not happen */,
)
}
}) as any
this.dispatchEvent(new Event('CAPScase'))
this.dispatchEvent(new Event('PascalCase'))
}
- }
+ },
)
const container = document.createElement('div')
}" />`,
methods: {
handler,
- handler2
- }
+ handler2,
+ },
}).mount(container)
expect(handler).toHaveBeenCalledTimes(3)
import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
-import path from 'path'
-import { h, createApp, Transition, ref, nextTick } from 'vue'
+import path from 'node:path'
+import { Transition, createApp, h, nextTick, ref } from 'vue'
describe('e2e: Transition', () => {
const { page, html, classList, isVisible, timeout, nextFrame, click } =
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-leave-from',
- 'v-leave-active'
+ 'v-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-leave-active',
- 'v-leave-to'
+ 'v-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-enter-from',
- 'v-enter-active'
+ 'v-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-enter-active',
- 'v-enter-to'
+ 'v-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'bye-from',
- 'bye-active'
+ 'bye-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'bye-active',
- 'bye-to'
+ 'bye-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'hello-from',
- 'hello-active'
+ 'hello-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'hello-active',
- 'hello-to'
+ 'hello-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const click = () => (toggle.value = !toggle.value)
const changeName = () => (name.value = 'changed')
return { toggle, click, name, changeName }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'changed-enter-from',
- 'changed-enter-active'
+ 'changed-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'changed-enter-active',
- 'changed-enter-to'
+ 'changed-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
} = window as any
const { createApp, ref } = (window as any).Vue
createApp({
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
}
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
expect(beforeLeaveSpy).toBeCalled()
expect(onLeaveSpy).toBeCalled()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
expect(afterLeaveSpy).not.toBeCalled()
await transitionFinish()
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
expect(beforeEnterSpy).toBeCalled()
expect(onEnterSpy).toBeCalled()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
expect(afterEnterSpy).not.toBeCalled()
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(afterEnterSpy).toBeCalled()
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
} = window as any
const { createApp, ref } = (window as any).Vue
createApp({
},
afterLeaveSpy: (el: Element) => {
afterLeaveSpy()
- }
+ },
}
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classList('.test')).toStrictEqual([
'test',
'before-leave',
- 'leave'
+ 'leave',
])
await timeout(200 + buffer)
expect(await classList('.test')).toStrictEqual([
'test',
'before-enter',
- 'enter'
+ 'enter',
])
await timeout(200 + buffer)
expect(afterEnterSpy).toBeCalled()
expect(await html('#container')).toBe(
- '<div class="test before-enter enter after-enter">content</div>'
+ '<div class="test before-enter enter after-enter">content</div>',
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test('onEnterCancelled', async () => {
return {
toggle,
click,
- enterCancelledSpy
+ enterCancelledSpy,
}
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
// cancel (leave)
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
expect(enterCancelledSpy).toBeCalled()
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
return Promise.resolve().then(() => {
return document.querySelector('.test')!.className.split(/\s+/g)
expect(appearClass).toStrictEqual([
'test',
'test-appear-from',
- 'test-appear-active'
+ 'test-appear-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-appear-active',
- 'test-appear-to'
+ 'test-appear-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
} = window as any
const { createApp, ref } = (window as any).Vue
createApp({
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
}
- }
+ },
}).mount('#app')
return Promise.resolve().then(() => {
return document.querySelector('.test')!.className.split(/\s+/g)
expect(appearClass).toStrictEqual([
'test',
'test-appear-from',
- 'test-appear-active'
+ 'test-appear-active',
])
expect(beforeAppearSpy).toBeCalled()
expect(onAppearSpy).toBeCalled()
expect(await classList('.test')).toStrictEqual([
'test',
'test-appear-active',
- 'test-appear-to'
+ 'test-appear-to',
])
expect(afterAppearSpy).not.toBeCalled()
await transitionFinish()
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
expect(beforeLeaveSpy).toBeCalled()
expect(onLeaveSpy).toBeCalled()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
expect(afterLeaveSpy).not.toBeCalled()
await transitionFinish()
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
expect(beforeEnterSpy).toBeCalled()
expect(onEnterSpy).toBeCalled()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
expect(afterEnterSpy).not.toBeCalled()
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(afterEnterSpy).toBeCalled()
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
onAfterEnterSpy,
onBeforeLeaveSpy,
onLeaveSpy,
- onAfterLeaveSpy
+ onAfterLeaveSpy,
} = window as any
const { createApp, ref } = (window as any).Vue
createApp({
onAfterEnterSpy,
onBeforeLeaveSpy,
onLeaveSpy,
- onAfterLeaveSpy
+ onAfterLeaveSpy,
}
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(onAfterEnterSpy).toBeCalled()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div>content</div>')
// leave
expect(await classWhenTransitionStart()).toStrictEqual([
'noop-leave-from',
- 'noop-leave-active'
+ 'noop-leave-active',
])
await nextFrame()
expect(await html('#container')).toBe('<!--v-if-->')
// enter
expect(await classWhenTransitionStart()).toStrictEqual([
'noop-enter-from',
- 'noop-enter-active'
+ 'noop-enter-active',
])
await nextFrame()
expect(await html('#container')).toBe('<div class="">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div>content</div>')
// leave
expect(await classWhenTransitionStart()).toStrictEqual([
'test-anim-leave-from',
- 'test-anim-leave-active'
+ 'test-anim-leave-active',
])
await nextFrame()
expect(await classList('#container div')).toStrictEqual([
'test-anim-leave-active',
- 'test-anim-leave-to'
+ 'test-anim-leave-to',
])
await transitionFinish(duration * 2)
expect(await html('#container')).toBe('<!--v-if-->')
// enter
expect(await classWhenTransitionStart()).toStrictEqual([
'test-anim-enter-from',
- 'test-anim-enter-active'
+ 'test-anim-enter-active',
])
await nextFrame()
expect(await classList('#container div')).toStrictEqual([
'test-anim-enter-active',
- 'test-anim-enter-to'
+ 'test-anim-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div>content</div>')
// leave
expect(await classWhenTransitionStart()).toStrictEqual([
'test-anim-long-leave-from',
- 'test-anim-long-leave-active'
+ 'test-anim-long-leave-active',
])
await nextFrame()
expect(await classList('#container div')).toStrictEqual([
'test-anim-long-leave-active',
- 'test-anim-long-leave-to'
+ 'test-anim-long-leave-to',
])
if (!process.env.CI) {
})
expect(await classList('#container div')).toStrictEqual([
'test-anim-long-leave-active',
- 'test-anim-long-leave-to'
+ 'test-anim-long-leave-to',
])
}
// enter
expect(await classWhenTransitionStart()).toStrictEqual([
'test-anim-long-enter-from',
- 'test-anim-long-enter-active'
+ 'test-anim-long-enter-active',
])
await nextFrame()
expect(await classList('#container div')).toStrictEqual([
'test-anim-long-enter-active',
- 'test-anim-long-enter-to'
+ 'test-anim-long-enter-to',
])
if (!process.env.CI) {
})
expect(await classList('#container div')).toStrictEqual([
'test-anim-long-enter-active',
- 'test-anim-long-enter-to'
+ 'test-anim-long-enter-to',
])
}
await transitionFinish(duration * 2)
expect(await html('#container')).toBe('<div class="">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe(
- '<circle cx="0" cy="0" r="10" class="test"></circle>'
+ '<circle cx="0" cy="0" r="10" class="test"></circle>',
)
const svgTransitionStart = () =>
expect(await svgTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await svgTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe(
- '<circle cx="0" cy="0" r="10" class="test"></circle>'
+ '<circle cx="0" cy="0" r="10" class="test"></circle>',
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
components: {
'my-transition': (props: any, { slots }: any) => {
return h(Transition, { name: 'test' }, slots)
- }
+ },
},
setup: () => {
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
`,
components: {
one: {
- template: '<div v-if="false">one</div>'
+ template: '<div v-if="false">one</div>',
},
two: {
- template: '<div>two</div>'
- }
+ template: '<div>two</div>',
+ },
},
setup: () => {
const toggle = ref(true)
const change = () =>
(view.value = view.value === 'one' ? 'two' : 'one')
return { toggle, click, change, view }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">two</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
Comp: {
async setup() {
return () => h('div', { class: 'test' }, 'content')
- }
- }
+ },
+ },
},
setup: () => {
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click, onEnterSpy, onLeaveSpy }
- }
+ },
}).mount('#app')
})
expect(onEnterSpy).toBeCalledTimes(1)
await nextFrame()
expect(await html('#container')).toBe(
- '<div class="test v-enter-active v-enter-to">content</div>'
+ '<div class="test v-enter-active v-enter-to">content</div>',
)
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-leave-from',
- 'v-leave-active'
+ 'v-leave-active',
])
expect(onLeaveSpy).toBeCalledTimes(1)
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-leave-active',
- 'v-leave-to'
+ 'v-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(enterClass).toStrictEqual([
'test',
'v-enter-from',
- 'v-enter-active'
+ 'v-enter-active',
])
expect(onEnterSpy).toBeCalledTimes(2)
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-enter-active',
- 'v-enter-to'
+ 'v-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
// #1689
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-leave-from',
- 'v-leave-active'
+ 'v-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-leave-active',
- 'v-leave-to'
+ 'v-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-enter-from',
- 'v-enter-active'
+ 'v-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-enter-active',
- 'v-enter-to'
+ 'v-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const One = {
async setup() {
return () => h('div', { class: 'test' }, 'one')
- }
+ },
}
const Two = {
async setup() {
return () => h('div', { class: 'test' }, 'two')
- }
+ },
}
createApp({
template: `
view.value = view.value === One ? Two : One
}
return { view, click }
- }
+ },
}).mount('#app')
})
await nextFrame()
expect(await html('#container')).toBe(
- '<div class="test v-enter-active v-enter-to">one</div>'
+ '<div class="test v-enter-active v-enter-to">one</div>',
)
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">one</div>')
await classWhenTransitionStart()
await nextFrame()
expect(await html('#container')).toBe(
- '<div class="test v-leave-active v-leave-to">one</div>'
+ '<div class="test v-leave-active v-leave-to">one</div>',
)
await transitionFinish()
await nextFrame()
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">two</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
// #3963
template: `<div>{{ msg }}</div>`,
setup() {
return new Promise(_resolve => {
- // @ts-ignore
+ // @ts-expect-error
window.resolve = () =>
_resolve({
- msg: 'success'
+ msg: 'success',
})
})
- }
+ },
}
createApp({
view.value = view.value ? null : h(One)
}
return { view, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="">Loading...</div>')
await page().evaluate(() => {
- // @ts-ignore
+ // @ts-expect-error
window.resolve()
})
await transitionFinish(duration * 2)
expect(await html('#container')).toBe('<div class="">success</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
// #5844
fooMountSpy(
!!el.value,
!!document.getElementById('foo'),
- !!document.getElementById('bar')
+ !!document.getElementById('bar'),
)
})
return () => h('div', { ref: el, id: 'foo' }, 'Foo')
- }
+ },
},
Bar: {
setup() {
barMountSpy(
!!el.value,
!!document.getElementById('foo'),
- !!document.getElementById('bar')
+ !!document.getElementById('bar'),
)
})
return () => h('div', { ref: el, id: 'bar' }, 'Bar')
- }
- }
+ },
+ },
},
setup: () => {
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
const One = {
async setup() {
return () => h('div', { class: 'test' }, 'one')
- }
+ },
}
const Two = {
async setup() {
return () => h('div', { class: 'test' }, 'two')
- }
+ },
}
createApp({
template: `
view.value = view.value === One ? Two : One
}
return { view, click }
- }
+ },
}).mount('#app')
}, duration)
await nextFrame()
expect(await html('#container')).toBe(
- '<div class="test test-enter-active test-enter-to">one</div>'
+ '<div class="test test-enter-active test-enter-to">one</div>',
)
await transitionFinish()
classWhenTransitionStart()
await nextFrame()
expect(await html('#container')).toBe(
- '<div class="test test-leave-active test-leave-to">one</div>'
+ '<div class="test test-leave-active test-leave-to">one</div>',
)
await transitionFinish()
await nextFrame()
expect(await html('#container')).toBe(
- '<div class="test test-enter-active test-enter-to">one</div>'
+ '<div class="test test-enter-active test-enter-to">one</div>',
)
await transitionFinish()
await nextFrame()
expect(await html('#container')).toBe('<div class="test">one</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await isVisible('.test')).toBe(false)
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe(
- '<div class="test" style="">content</div>'
+ '<div class="test" style="">content</div>',
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
} = window as any
const { createApp, ref } = (window as any).Vue
createApp({
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
}
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
expect(beforeLeaveSpy).toBeCalled()
expect(onLeaveSpy).toBeCalled()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
expect(afterLeaveSpy).not.toBeCalled()
await transitionFinish()
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
expect(beforeEnterSpy).toBeCalled()
expect(onEnterSpy).toBeCalled()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
expect(afterEnterSpy).not.toBeCalled()
await transitionFinish()
expect(await html('#container')).toBe(
- '<div class="test" style="">content</div>'
+ '<div class="test" style="">content</div>',
)
expect(afterEnterSpy).toBeCalled()
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click, onLeaveCancelledSpy }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
// cancel (enter)
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
expect(onLeaveCancelledSpy).toBeCalled()
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe(
- '<div class="test" style="">content</div>'
+ '<div class="test" style="">content</div>',
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
click,
beforeEnterSpy,
onEnterSpy,
- afterEnterSpy
+ afterEnterSpy,
}
- }
+ },
}).mount('#app')
return Promise.resolve().then(() => {
return document.querySelector('.test')!.className.split(/\s+/g)
expect(appearClass).toStrictEqual([
'test',
'test-appear-from',
- 'test-appear-active'
+ 'test-appear-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-appear-active',
- 'test-appear-to'
+ 'test-appear-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await isVisible('.test')).toBe(false)
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe(
- '<div class="test" style="">content</div>'
+ '<div class="test" style="">content</div>',
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
// #4845
click,
beforeEnterSpy,
onEnterSpy,
- afterEnterSpy
+ afterEnterSpy,
}
- }
+ },
}).mount('#app')
})
await nextTick()
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
expect(beforeEnterSpy).toBeCalledTimes(1)
expect(onEnterSpy).toBeCalledTimes(1)
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
expect(afterEnterSpy).not.toBeCalled()
await transitionFinish()
expect(await html('#container')).toBe(
- '<div class="test" style="">content</div>'
+ '<div class="test" style="">content</div>',
)
expect(afterEnterSpy).toBeCalled()
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
}, duration)
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish(duration * 2)
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish(duration * 2)
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
}, duration)
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish(duration * 2)
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
}, duration)
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish(duration * 2)
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
}, duration)
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-leave-from',
- 'test-leave-active'
+ 'test-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-leave-active',
- 'test-leave-to'
+ 'test-leave-to',
])
await transitionFinish(duration * 2)
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-from',
- 'test-enter-active'
+ 'test-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-active',
- 'test-enter-to'
+ 'test-enter-to',
])
await transitionFinish(duration * 4)
expect(await html('#container')).toBe('<div class="test">content</div>')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
<div class="test">content</div>
</transition>
</div>
- `
+ `,
}).mount(document.createElement('div'))
expect(
`[Vue warn]: <transition> explicit duration is NaN - ` +
- 'the duration expression might be incorrect.'
+ 'the duration expression might be incorrect.',
).toHaveBeenWarned()
createApp({
<div class="test">content</div>
</transition>
</div>
- `
+ `,
}).mount(document.createElement('div'))
expect(
`[Vue warn]: <transition> explicit duration is not a valid number - ` +
- `got ${JSON.stringify({})}`
+ `got ${JSON.stringify({})}`,
).toHaveBeenWarned()
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
createApp({
render() {
return h(Transition, null, {
- default: () => [h('div'), h('div')]
+ default: () => [h('div'), h('div')],
})
- }
+ },
}).mount(document.createElement('div'))
expect(
- '<transition> can only be used on a single element or component'
+ '<transition> can only be used on a single element or component',
).toHaveBeenWarned()
})
<div class="test">content</div>
</transition>
</div>
- `
+ `,
}).mount(document.createElement('div'))
expect(`invalid <transition> mode: none`).toHaveBeenWarned()
})
onLeave(el, end) {
innerSpy()
end()
- }
+ },
},
- this.$slots.default
+ this.$slots.default,
)
- }
+ },
}
const toggle = ref(true)
createApp({
render() {
return h(MyTransition, { onLeave: () => outerSpy() }, () =>
- toggle.value ? h('div') : null
+ toggle.value ? h('div') : null,
)
- }
+ },
}).mount(root)
expect(root.innerHTML).toBe(`<div></div>`)
template: `
<!-- Broken! -->
<div><slot /></div>
- `
- }
+ `,
+ },
},
template: `
<div id="container">
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe(
- '<!-- Broken! --><div class="test"><div>content</div></div>'
+ '<!-- Broken! --><div class="test"><div>content</div></div>',
)
// leave
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-leave-from',
- 'v-leave-active'
+ 'v-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-leave-active',
- 'v-leave-to'
+ 'v-leave-to',
])
await transitionFinish()
expect(await html('#container')).toBe('<!--v-if-->')
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-enter-from',
- 'v-enter-active'
+ 'v-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-enter-active',
- 'v-enter-to'
+ 'v-enter-to',
])
await transitionFinish()
expect(await html('#container')).toBe(
- '<!-- Broken! --><div class="test"><div>content</div></div>'
+ '<!-- Broken! --><div class="test"><div>content</div></div>',
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
-import path from 'path'
+import path from 'node:path'
import { createApp, ref } from 'vue'
describe('e2e: TransitionGroup', () => {
const items = ref(['a', 'b', 'c'])
const click = () => items.value.push('d', 'e')
return { click, items }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe(
`<div class="test">a</div>` +
`<div class="test">b</div>` +
- `<div class="test">c</div>`
+ `<div class="test">c</div>`,
)
expect(await htmlWhenTransitionStart()).toBe(
`<div class="test">b</div>` +
`<div class="test">c</div>` +
`<div class="test test-enter-from test-enter-active">d</div>` +
- `<div class="test test-enter-from test-enter-active">e</div>`
+ `<div class="test test-enter-from test-enter-active">e</div>`,
)
await nextFrame()
expect(await html('#container')).toBe(
`<div class="test">b</div>` +
`<div class="test">c</div>` +
`<div class="test test-enter-active test-enter-to">d</div>` +
- `<div class="test test-enter-active test-enter-to">e</div>`
+ `<div class="test test-enter-active test-enter-to">e</div>`,
)
await transitionFinish()
expect(await html('#container')).toBe(
`<div class="test">b</div>` +
`<div class="test">c</div>` +
`<div class="test">d</div>` +
- `<div class="test">e</div>`
+ `<div class="test">e</div>`,
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const items = ref(['a', 'b', 'c'])
const click = () => (items.value = ['b'])
return { click, items }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe(
`<div class="test">a</div>` +
`<div class="test">b</div>` +
- `<div class="test">c</div>`
+ `<div class="test">c</div>`,
)
expect(await htmlWhenTransitionStart()).toBe(
`<div class="test test-leave-from test-leave-active">a</div>` +
`<div class="test">b</div>` +
- `<div class="test test-leave-from test-leave-active">c</div>`
+ `<div class="test test-leave-from test-leave-active">c</div>`,
)
await nextFrame()
expect(await html('#container')).toBe(
`<div class="test test-leave-active test-leave-to">a</div>` +
`<div class="test">b</div>` +
- `<div class="test test-leave-active test-leave-to">c</div>`
+ `<div class="test test-leave-active test-leave-to">c</div>`,
)
await transitionFinish()
expect(await html('#container')).toBe(`<div class="test">b</div>`)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const items = ref(['a', 'b', 'c'])
const click = () => (items.value = ['b', 'c', 'd'])
return { click, items }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe(
`<div class="test">a</div>` +
`<div class="test">b</div>` +
- `<div class="test">c</div>`
+ `<div class="test">c</div>`,
)
expect(await htmlWhenTransitionStart()).toBe(
`<div class="test test-leave-from test-leave-active">a</div>` +
`<div class="test">b</div>` +
`<div class="test">c</div>` +
- `<div class="test test-enter-from test-enter-active">d</div>`
+ `<div class="test test-enter-from test-enter-active">d</div>`,
)
await nextFrame()
expect(await html('#container')).toBe(
`<div class="test test-leave-active test-leave-to">a</div>` +
`<div class="test">b</div>` +
`<div class="test">c</div>` +
- `<div class="test test-enter-active test-enter-to">d</div>`
+ `<div class="test test-enter-active test-enter-to">d</div>`,
)
await transitionFinish()
expect(await html('#container')).toBe(
`<div class="test">b</div>` +
`<div class="test">c</div>` +
- `<div class="test">d</div>`
+ `<div class="test">d</div>`,
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const items = ref(['a', 'b', 'c'])
const click = () => items.value.push('d', 'e')
return { click, items }
- }
+ },
}).mount('#app')
return Promise.resolve().then(() => {
return document.querySelector('#container')!.innerHTML
expect(appearHtml).toBe(
`<div class="test test-appear-from test-appear-active">a</div>` +
`<div class="test test-appear-from test-appear-active">b</div>` +
- `<div class="test test-appear-from test-appear-active">c</div>`
+ `<div class="test test-appear-from test-appear-active">c</div>`,
)
await nextFrame()
expect(await html('#container')).toBe(
`<div class="test test-appear-active test-appear-to">a</div>` +
`<div class="test test-appear-active test-appear-to">b</div>` +
- `<div class="test test-appear-active test-appear-to">c</div>`
+ `<div class="test test-appear-active test-appear-to">c</div>`,
)
await transitionFinish()
expect(await html('#container')).toBe(
`<div class="test">a</div>` +
`<div class="test">b</div>` +
- `<div class="test">c</div>`
+ `<div class="test">c</div>`,
)
// enter
`<div class="test">b</div>` +
`<div class="test">c</div>` +
`<div class="test test-enter-from test-enter-active">d</div>` +
- `<div class="test test-enter-from test-enter-active">e</div>`
+ `<div class="test test-enter-from test-enter-active">e</div>`,
)
await nextFrame()
expect(await html('#container')).toBe(
`<div class="test">b</div>` +
`<div class="test">c</div>` +
`<div class="test test-enter-active test-enter-to">d</div>` +
- `<div class="test test-enter-active test-enter-to">e</div>`
+ `<div class="test test-enter-active test-enter-to">e</div>`,
)
await transitionFinish()
expect(await html('#container')).toBe(
`<div class="test">b</div>` +
`<div class="test">c</div>` +
`<div class="test">d</div>` +
- `<div class="test">e</div>`
+ `<div class="test">e</div>`,
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
const items = ref(['a', 'b', 'c'])
const click = () => (items.value = ['d', 'b', 'a'])
return { click, items }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe(
`<div class="test">a</div>` +
`<div class="test">b</div>` +
- `<div class="test">c</div>`
+ `<div class="test">c</div>`,
)
expect(await htmlWhenTransitionStart()).toBe(
`<div class="test group-enter-from group-enter-active">d</div>` +
`<div class="test">b</div>` +
`<div class="test group-move" style="">a</div>` +
- `<div class="test group-leave-from group-leave-active group-move" style="">c</div>`
+ `<div class="test group-leave-from group-leave-active group-move" style="">c</div>`,
)
await nextFrame()
expect(await html('#container')).toBe(
`<div class="test group-enter-active group-enter-to">d</div>` +
`<div class="test">b</div>` +
`<div class="test group-move" style="">a</div>` +
- `<div class="test group-leave-active group-move group-leave-to" style="">c</div>`
+ `<div class="test group-leave-active group-move group-leave-to" style="">c</div>`,
)
await transitionFinish(duration * 2)
expect(await html('#container')).toBe(
`<div class="test">d</div>` +
`<div class="test">b</div>` +
- `<div class="test" style="">a</div>`
+ `<div class="test" style="">a</div>`,
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
items.value = ['a', 'b', 'c']
}
return { click, items, name, changeName }
- }
+ },
}).mount('#app')
})
expect(await html('#container')).toBe(
- `<div>a</div>` + `<div>b</div>` + `<div>c</div>`
+ `<div>a</div>` + `<div>b</div>` + `<div>c</div>`,
)
// invalid name
expect(await htmlWhenTransitionStart()).toBe(
- `<div>b</div>` + `<div>c</div>` + `<div>a</div>`
+ `<div>b</div>` + `<div>c</div>` + `<div>a</div>`,
)
// change name
const moveHtml = await page().evaluate(() => {
expect(moveHtml).toBe(
`<div class="group-move" style="">a</div>` +
`<div class="group-move" style="">b</div>` +
- `<div class="group-move" style="">c</div>`
+ `<div class="group-move" style="">c</div>`,
)
// not sure why but we just have to wait really long for this to
// pass consistently :/
expect(await html('#container')).toBe(
`<div class="" style="">a</div>` +
`<div class="" style="">b</div>` +
- `<div class="" style="">c</div>`
+ `<div class="" style="">c</div>`,
)
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
} = window as any
const { createApp, ref } = (window as any).Vue
createApp({
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
- afterLeaveSpy
+ afterLeaveSpy,
}
- }
+ },
}).mount('#app')
return Promise.resolve().then(() => {
return document.querySelector('#container')!.innerHTML
expect(appearHtml).toBe(
`<div class="test test-appear-from test-appear-active">a</div>` +
`<div class="test test-appear-from test-appear-active">b</div>` +
- `<div class="test test-appear-from test-appear-active">c</div>`
+ `<div class="test test-appear-from test-appear-active">c</div>`,
)
await nextFrame()
expect(afterAppearSpy).not.toBeCalled()
expect(await html('#container')).toBe(
`<div class="test test-appear-active test-appear-to">a</div>` +
`<div class="test test-appear-active test-appear-to">b</div>` +
- `<div class="test test-appear-active test-appear-to">c</div>`
+ `<div class="test test-appear-active test-appear-to">c</div>`,
)
await transitionFinish()
expect(afterAppearSpy).toBeCalled()
expect(await html('#container')).toBe(
`<div class="test">a</div>` +
`<div class="test">b</div>` +
- `<div class="test">c</div>`
+ `<div class="test">c</div>`,
)
// enter + leave
`<div class="test test-leave-from test-leave-active">a</div>` +
`<div class="test">b</div>` +
`<div class="test">c</div>` +
- `<div class="test test-enter-from test-enter-active">d</div>`
+ `<div class="test test-enter-from test-enter-active">d</div>`,
)
expect(beforeLeaveSpy).toBeCalled()
expect(onLeaveSpy).toBeCalled()
`<div class="test test-leave-active test-leave-to">a</div>` +
`<div class="test">b</div>` +
`<div class="test">c</div>` +
- `<div class="test test-enter-active test-enter-to">d</div>`
+ `<div class="test test-enter-active test-enter-to">d</div>`,
)
expect(afterLeaveSpy).not.toBeCalled()
expect(afterEnterSpy).not.toBeCalled()
expect(await html('#container')).toBe(
`<div class="test">b</div>` +
`<div class="test">c</div>` +
- `<div class="test">d</div>`
+ `<div class="test">d</div>`,
)
expect(afterLeaveSpy).toBeCalled()
expect(afterEnterSpy).toBeCalled()
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test('warn unkeyed children', () => {
setup: () => {
const items = ref(['a', 'b', 'c'])
return { items }
- }
+ },
}).mount(document.createElement('div'))
expect(`<TransitionGroup> children must be keyed`).toHaveBeenWarned()
author: {
name: 'Haoqun Jiang',
email: 'haoqunjiang@gmail.com',
- date: '2019-12-09T19:52:20Z'
+ date: '2019-12-09T19:52:20Z',
},
committer: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2019-12-09T19:52:20Z'
+ date: '2019-12-09T19:52:20Z',
},
message: 'test: add test for runtime-dom/modules/class (#75)',
tree: {
sha: 'f53f761827af281db86c31d113086c068c1d0789',
- url: 'https://api.github.com/repos/vuejs/core/git/trees/f53f761827af281db86c31d113086c068c1d0789'
+ url: 'https://api.github.com/repos/vuejs/core/git/trees/f53f761827af281db86c31d113086c068c1d0789',
},
url: 'https://api.github.com/repos/vuejs/core/git/commits/d1527fbee422c7170e56845e55b49c4fd6de72a7',
comment_count: 0,
verified: false,
reason: 'unsigned',
signature: null,
- payload: null
- }
+ payload: null,
+ },
},
url: 'https://api.github.com/repos/vuejs/core/commits/d1527fbee422c7170e56845e55b49c4fd6de72a7',
html_url:
received_events_url:
'https://api.github.com/users/sodatea/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
committer: {
login: 'yyx990803',
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
parents: [
{
sha: '2383b45e322272ddc102d6914c149b284a25d04f',
url: 'https://api.github.com/repos/vuejs/core/commits/2383b45e322272ddc102d6914c149b284a25d04f',
html_url:
- 'https://github.com/vuejs/core/commit/2383b45e322272ddc102d6914c149b284a25d04f'
- }
- ]
+ 'https://github.com/vuejs/core/commit/2383b45e322272ddc102d6914c149b284a25d04f',
+ },
+ ],
},
{
sha: '2383b45e322272ddc102d6914c149b284a25d04f',
author: {
name: 'GCA',
email: 'gcaaa31928@gmail.com',
- date: '2019-12-09T19:23:57Z'
+ date: '2019-12-09T19:23:57Z',
},
committer: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2019-12-09T19:23:57Z'
+ date: '2019-12-09T19:23:57Z',
},
message: 'chore: fix typo (#530) [ci skip]',
tree: {
sha: '2a5872ff8dc8ccb8121abd7e890ac3c0c9f1209f',
- url: 'https://api.github.com/repos/vuejs/core/git/trees/2a5872ff8dc8ccb8121abd7e890ac3c0c9f1209f'
+ url: 'https://api.github.com/repos/vuejs/core/git/trees/2a5872ff8dc8ccb8121abd7e890ac3c0c9f1209f',
},
url: 'https://api.github.com/repos/vuejs/core/git/commits/2383b45e322272ddc102d6914c149b284a25d04f',
comment_count: 0,
verified: false,
reason: 'unsigned',
signature: null,
- payload: null
- }
+ payload: null,
+ },
},
url: 'https://api.github.com/repos/vuejs/core/commits/2383b45e322272ddc102d6914c149b284a25d04f',
html_url:
received_events_url:
'https://api.github.com/users/gcaaa31928/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
committer: {
login: 'yyx990803',
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
parents: [
{
sha: 'e7e1314cccd1a66fcf8b8526ec21350ec16cc3ad',
url: 'https://api.github.com/repos/vuejs/core/commits/e7e1314cccd1a66fcf8b8526ec21350ec16cc3ad',
html_url:
- 'https://github.com/vuejs/core/commit/e7e1314cccd1a66fcf8b8526ec21350ec16cc3ad'
- }
- ]
+ 'https://github.com/vuejs/core/commit/e7e1314cccd1a66fcf8b8526ec21350ec16cc3ad',
+ },
+ ],
},
{
sha: 'e7e1314cccd1a66fcf8b8526ec21350ec16cc3ad',
author: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2019-12-09T19:23:01Z'
+ date: '2019-12-09T19:23:01Z',
},
committer: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2019-12-09T19:23:01Z'
+ date: '2019-12-09T19:23:01Z',
},
message: 'test: fix warning',
tree: {
sha: 'd942b17681e2e2fbbcd2ee04092390c7f2cf534d',
- url: 'https://api.github.com/repos/vuejs/core/git/trees/d942b17681e2e2fbbcd2ee04092390c7f2cf534d'
+ url: 'https://api.github.com/repos/vuejs/core/git/trees/d942b17681e2e2fbbcd2ee04092390c7f2cf534d',
},
url: 'https://api.github.com/repos/vuejs/core/git/commits/e7e1314cccd1a66fcf8b8526ec21350ec16cc3ad',
comment_count: 0,
verified: false,
reason: 'unsigned',
signature: null,
- payload: null
- }
+ payload: null,
+ },
},
url: 'https://api.github.com/repos/vuejs/core/commits/e7e1314cccd1a66fcf8b8526ec21350ec16cc3ad',
html_url:
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
committer: {
login: 'yyx990803',
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
parents: [
{
sha: '12ec62e6881f83dfa6c7f8a3c3650ec2567e6b1e',
url: 'https://api.github.com/repos/vuejs/core/commits/12ec62e6881f83dfa6c7f8a3c3650ec2567e6b1e',
html_url:
- 'https://github.com/vuejs/core/commit/12ec62e6881f83dfa6c7f8a3c3650ec2567e6b1e'
- }
- ]
- }
+ 'https://github.com/vuejs/core/commit/12ec62e6881f83dfa6c7f8a3c3650ec2567e6b1e',
+ },
+ ],
+ },
],
'v2-compat': [
{
author: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2018-11-13T03:42:34Z'
+ date: '2018-11-13T03:42:34Z',
},
committer: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2018-11-13T03:54:01Z'
+ date: '2018-11-13T03:54:01Z',
},
message: 'chore: fix tests',
tree: {
sha: '6ac7bd078a6eb0ad32b5102e0c5d2c29f2b20a48',
- url: 'https://api.github.com/repos/vuejs/core/git/trees/6ac7bd078a6eb0ad32b5102e0c5d2c29f2b20a48'
+ url: 'https://api.github.com/repos/vuejs/core/git/trees/6ac7bd078a6eb0ad32b5102e0c5d2c29f2b20a48',
},
url: 'https://api.github.com/repos/vuejs/core/git/commits/ecf4da822eea97f5db5fa769d39f994755384a4b',
comment_count: 0,
verified: false,
reason: 'unsigned',
signature: null,
- payload: null
- }
+ payload: null,
+ },
},
url: 'https://api.github.com/repos/vuejs/core/commits/ecf4da822eea97f5db5fa769d39f994755384a4b',
html_url:
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
committer: {
login: 'yyx990803',
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
parents: [
{
sha: 'ca296812d54aff123472d7147b83fddfb634d9bc',
url: 'https://api.github.com/repos/vuejs/core/commits/ca296812d54aff123472d7147b83fddfb634d9bc',
html_url:
- 'https://github.com/vuejs/core/commit/ca296812d54aff123472d7147b83fddfb634d9bc'
- }
- ]
+ 'https://github.com/vuejs/core/commit/ca296812d54aff123472d7147b83fddfb634d9bc',
+ },
+ ],
},
{
sha: 'ca296812d54aff123472d7147b83fddfb634d9bc',
author: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2018-11-13T03:21:56Z'
+ date: '2018-11-13T03:21:56Z',
},
committer: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2018-11-13T03:46:06Z'
+ date: '2018-11-13T03:46:06Z',
},
message: 'refactor: bring back clone for reused nodes',
tree: {
sha: '2cec32c97686e0ee9af1b87f0abdbbbdc18b6de6',
- url: 'https://api.github.com/repos/vuejs/core/git/trees/2cec32c97686e0ee9af1b87f0abdbbbdc18b6de6'
+ url: 'https://api.github.com/repos/vuejs/core/git/trees/2cec32c97686e0ee9af1b87f0abdbbbdc18b6de6',
},
url: 'https://api.github.com/repos/vuejs/core/git/commits/ca296812d54aff123472d7147b83fddfb634d9bc',
comment_count: 0,
verified: false,
reason: 'unsigned',
signature: null,
- payload: null
- }
+ payload: null,
+ },
},
url: 'https://api.github.com/repos/vuejs/core/commits/ca296812d54aff123472d7147b83fddfb634d9bc',
html_url:
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
committer: {
login: 'yyx990803',
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
parents: [
{
sha: 'e6be55a4989edb6f8750dbaa14eb693ec1f0d67b',
url: 'https://api.github.com/repos/vuejs/core/commits/e6be55a4989edb6f8750dbaa14eb693ec1f0d67b',
html_url:
- 'https://github.com/vuejs/core/commit/e6be55a4989edb6f8750dbaa14eb693ec1f0d67b'
- }
- ]
+ 'https://github.com/vuejs/core/commit/e6be55a4989edb6f8750dbaa14eb693ec1f0d67b',
+ },
+ ],
},
{
sha: 'e6be55a4989edb6f8750dbaa14eb693ec1f0d67b',
author: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2018-11-02T20:59:45Z'
+ date: '2018-11-02T20:59:45Z',
},
committer: {
name: 'Evan You',
email: 'yyx990803@gmail.com',
- date: '2018-11-02T20:59:45Z'
+ date: '2018-11-02T20:59:45Z',
},
message: 'chore: relax render type for tsx',
tree: {
sha: '7e2b3bb92ab91f755b2251e4a7903e6dd2042602',
- url: 'https://api.github.com/repos/vuejs/core/git/trees/7e2b3bb92ab91f755b2251e4a7903e6dd2042602'
+ url: 'https://api.github.com/repos/vuejs/core/git/trees/7e2b3bb92ab91f755b2251e4a7903e6dd2042602',
},
url: 'https://api.github.com/repos/vuejs/core/git/commits/e6be55a4989edb6f8750dbaa14eb693ec1f0d67b',
comment_count: 0,
verified: false,
reason: 'unsigned',
signature: null,
- payload: null
- }
+ payload: null,
+ },
},
url: 'https://api.github.com/repos/vuejs/core/commits/e6be55a4989edb6f8750dbaa14eb693ec1f0d67b',
html_url:
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
committer: {
login: 'yyx990803',
received_events_url:
'https://api.github.com/users/yyx990803/received_events',
type: 'User',
- site_admin: false
+ site_admin: false,
},
parents: [
{
sha: 'ccc835caff0344baad3c92ce786ad4f804bf667b',
url: 'https://api.github.com/repos/vuejs/core/commits/ccc835caff0344baad3c92ce786ad4f804bf667b',
html_url:
- 'https://github.com/vuejs/core/commit/ccc835caff0344baad3c92ce786ad4f804bf667b'
- }
- ]
- }
- ]
+ 'https://github.com/vuejs/core/commit/ccc835caff0344baad3c92ce786ad4f804bf667b',
+ },
+ ],
+ },
+ ],
}
-import path from 'path'
-import { setupPuppeteer, E2E_TIMEOUT } from './e2eUtils'
+import path from 'node:path'
+import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
import mocks from './commits.mock'
describe('e2e: commits', () => {
async function testCommits(apiType: 'classic' | 'composition') {
const baseUrl = `file://${path.resolve(
__dirname,
- `../../examples/${apiType}/commits.html`
+ `../../examples/${apiType}/commits.html`,
)}`
// intercept and mock the response to avoid hitting the actual API
status: 200,
contentType: 'application/json',
headers: { 'Access-Control-Allow-Origin': '*' },
- body: JSON.stringify(mocks[match[1] as 'main' | 'v2-compat'])
+ body: JSON.stringify(mocks[match[1] as 'main' | 'v2-compat']),
})
}
})
async () => {
await testCommits('classic')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
async () => {
await testCommits('composition')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
import puppeteer, {
- Browser,
- Page,
- ClickOptions,
- PuppeteerLaunchOptions
+ type Browser,
+ type ClickOptions,
+ type Page,
+ type PuppeteerLaunchOptions,
} from 'puppeteer'
export const E2E_TIMEOUT = 30 * 1000
const puppeteerOptions: PuppeteerLaunchOptions = {
args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
- headless: 'new'
+ headless: 'new',
}
const maxTries = 30
export async function expectByPolling(
poll: () => Promise<any>,
- expected: string
+ expected: string,
) {
for (let tries = 0; tries < maxTries; tries++) {
const actual = (await poll()) || ''
const err = e.args()[0]
console.error(
`Error from Puppeteer-loaded page:\n`,
- err.remoteObject().description
+ err.remoteObject().description,
)
}
})
async function isChecked(selector: string) {
return await page.$eval(
selector,
- node => (node as HTMLInputElement).checked
+ node => (node as HTMLInputElement).checked,
)
}
;(node as HTMLInputElement).value = value as string
node.dispatchEvent(new Event('input'))
},
- value
+ value,
)
}
async function clearValue(selector: string) {
return await page.$eval(
selector,
- node => ((node as HTMLInputElement).value = '')
+ node => ((node as HTMLInputElement).value = ''),
)
}
enterValue,
clearValue,
timeout,
- nextFrame
+ nextFrame,
}
}
-import path from 'path'
-import { setupPuppeteer, E2E_TIMEOUT } from './e2eUtils'
+import path from 'node:path'
+import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
interface TableData {
name: string
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < columns.length; j++) {
expect(
- await text(`tr:nth-child(${i + 1}) td:nth-child(${j + 1})`)
+ await text(`tr:nth-child(${i + 1}) td:nth-child(${j + 1})`),
).toContain(`${data[i][columns[j]]}`)
}
}
async function testGrid(apiType: 'classic' | 'composition') {
const baseUrl = `file://${path.resolve(
__dirname,
- `../../examples/${apiType}/grid.html`
+ `../../examples/${apiType}/grid.html`,
)}`
await page().goto(baseUrl)
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
- { name: 'Jet Li', power: 8000 }
+ { name: 'Jet Li', power: 8000 },
])
await click('th:nth-child(1)')
{ name: 'Jet Li', power: 8000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Chuck Norris', power: Infinity },
- { name: 'Bruce Lee', power: 9000 }
+ { name: 'Bruce Lee', power: 9000 },
])
await click('th:nth-child(2)')
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jet Li', power: 8000 },
- { name: 'Jackie Chan', power: 7000 }
+ { name: 'Jackie Chan', power: 7000 },
])
await click('th:nth-child(2)')
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 },
{ name: 'Bruce Lee', power: 9000 },
- { name: 'Chuck Norris', power: Infinity }
+ { name: 'Chuck Norris', power: Infinity },
])
await click('th:nth-child(1)')
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Jackie Chan', power: 7000 },
- { name: 'Jet Li', power: 8000 }
+ { name: 'Jet Li', power: 8000 },
])
await typeValue('input[name="query"]', 'j')
await assertTable([
{ name: 'Jackie Chan', power: 7000 },
- { name: 'Jet Li', power: 8000 }
+ { name: 'Jet Li', power: 8000 },
])
await typeValue('input[name="query"]', 'infinity')
async () => {
await testGrid('classic')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
async () => {
await testGrid('composition')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
-import path from 'path'
-import { setupPuppeteer, expectByPolling, E2E_TIMEOUT } from './e2eUtils'
+import path from 'node:path'
+import { E2E_TIMEOUT, expectByPolling, setupPuppeteer } from './e2eUtils'
describe('e2e: markdown', () => {
const { page, isVisible, value, html } = setupPuppeteer()
async function testMarkdown(apiType: 'classic' | 'composition') {
const baseUrl = `file://${path.resolve(
__dirname,
- `../../examples/${apiType}/markdown.html#test`
+ `../../examples/${apiType}/markdown.html#test`,
)}`
await page().goto(baseUrl)
() => html('#editor div'),
'<h1>hello</h1>\n' +
'<h2>foo</h2>\n' +
- '<ul>\n<li>bar</li>\n<li>baz</li>\n</ul>\n'
+ '<ul>\n<li>bar</li>\n<li>baz</li>\n</ul>\n',
)
}
async () => {
await testMarkdown('classic')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
async () => {
await testMarkdown('composition')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
-import path from 'path'
-import { setupPuppeteer, E2E_TIMEOUT } from './e2eUtils'
+import path from 'node:path'
+import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
declare const globalStats: {
label: string
declare function valueToPoint(
value: number,
index: number,
- total: number
+ total: number,
): {
x: number
y: number
document.querySelector('polygon')!.attributes[0].value === points
)
},
- [total]
- )
+ [total],
+ ),
).toBe(true)
}
return [point.x, point.y]
})
},
- [total]
+ [total],
)
for (let i = 0; i < total; i++) {
const textPosition = await page().$eval(
`text:nth-child(${i + 3})`,
- node => [+node.attributes[0].value, +node.attributes[1].value]
+ node => [+node.attributes[0].value, +node.attributes[1].value],
)
expect(textPosition).toEqual(positions[i])
}
async function testSvg(apiType: 'classic' | 'composition') {
const baseUrl = `file://${path.resolve(
__dirname,
- `../../examples/${apiType}/svg.html`
+ `../../examples/${apiType}/svg.html`,
)}`
await page().goto(baseUrl)
async () => {
await testSvg('classic')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
async () => {
await testSvg('composition')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
-import path from 'path'
-import { setupPuppeteer, E2E_TIMEOUT } from './e2eUtils'
+import path from 'node:path'
+import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
describe('e2e: todomvc', () => {
const {
isFocused,
classList,
enterValue,
- clearValue
+ clearValue,
} = setupPuppeteer()
async function removeItemAt(n: number) {
async function testTodomvc(apiType: 'classic' | 'composition') {
const baseUrl = `file://${path.resolve(
__dirname,
- `../../examples/${apiType}/todomvc.html`
+ `../../examples/${apiType}/todomvc.html`,
)}`
await page().goto(baseUrl)
async () => {
await testTodomvc('classic')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
async () => {
await testTodomvc('composition')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
-import path from 'path'
-import { setupPuppeteer, E2E_TIMEOUT } from './e2eUtils'
+import path from 'node:path'
+import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
describe('e2e: tree', () => {
const { page, click, count, text, children, isVisible } = setupPuppeteer()
async function testTree(apiType: 'classic' | 'composition') {
const baseUrl = `file://${path.resolve(
__dirname,
- `../../examples/${apiType}/tree.html`
+ `../../examples/${apiType}/tree.html`,
)}`
await page().goto(baseUrl)
expect((await children('#demo li ul')).length).toBe(4)
expect(await text('#demo li div span')).toContain('[-]')
expect(await text('#demo > .item > ul > .item:nth-child(1)')).toContain(
- 'hello'
+ 'hello',
)
expect(await text('#demo > .item > ul > .item:nth-child(2)')).toContain(
- 'wat'
+ 'wat',
)
expect(await text('#demo > .item > ul > .item:nth-child(3)')).toContain(
- 'child folder'
+ 'child folder',
)
expect(await text('#demo > .item > ul > .item:nth-child(3)')).toContain(
- '[+]'
+ '[+]',
)
// add items to root
await click('#demo > .item > ul > .add')
expect((await children('#demo li ul')).length).toBe(5)
expect(await text('#demo > .item > ul > .item:nth-child(1)')).toContain(
- 'hello'
+ 'hello',
)
expect(await text('#demo > .item > ul > .item:nth-child(2)')).toContain(
- 'wat'
+ 'wat',
)
expect(await text('#demo > .item > ul > .item:nth-child(3)')).toContain(
- 'child folder'
+ 'child folder',
)
expect(await text('#demo > .item > ul > .item:nth-child(3)')).toContain(
- '[+]'
+ '[+]',
)
expect(await text('#demo > .item > ul > .item:nth-child(4)')).toContain(
- 'new stuff'
+ 'new stuff',
)
// add another item
await click('#demo > .item > ul > .add')
expect((await children('#demo li ul')).length).toBe(6)
expect(await text('#demo > .item > ul > .item:nth-child(1)')).toContain(
- 'hello'
+ 'hello',
)
expect(await text('#demo > .item > ul > .item:nth-child(2)')).toContain(
- 'wat'
+ 'wat',
)
expect(await text('#demo > .item > ul > .item:nth-child(3)')).toContain(
- 'child folder'
+ 'child folder',
)
expect(await text('#demo > .item > ul > .item:nth-child(3)')).toContain(
- '[+]'
+ '[+]',
)
expect(await text('#demo > .item > ul > .item:nth-child(4)')).toContain(
- 'new stuff'
+ 'new stuff',
)
expect(await text('#demo > .item > ul > .item:nth-child(5)')).toContain(
- 'new stuff'
+ 'new stuff',
)
await click('#demo ul .bold')
expect(await count('.item > ul')).toBe(5)
expect(await text('#demo ul > .item:nth-child(1)')).toContain('[-]')
expect((await children('#demo ul > .item:nth-child(1) > ul')).length).toBe(
- 2
+ 2,
)
}
async () => {
await testTree('classic')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
test(
async () => {
await testTree('composition')
},
- E2E_TIMEOUT
+ E2E_TIMEOUT,
)
})
import { EMPTY_ARR } from '@vue/shared'
-import { createApp, ref, nextTick, reactive } from '../src'
+import { createApp, nextTick, reactive, ref } from '../src'
describe('compiler + runtime integration', () => {
it('should support runtime template compilation', () => {
template: `{{ count }}`,
data() {
return {
- count: 0
+ count: 0,
}
- }
+ },
}
createApp(App).mount(container)
expect(container.innerHTML).toBe(`0`)
mounted: vi.fn(),
activated: vi.fn(),
deactivated: vi.fn(),
- unmounted: vi.fn()
+ unmounted: vi.fn(),
}
const toggle = ref(true)
`,
data() {
return {
- toggle
+ toggle,
}
},
components: {
- One: one
- }
+ One: one,
+ },
}
createApp(App).mount(container)
expect(container.innerHTML).toBe(`one`)
template: `#template`,
data() {
return {
- count: 0
+ count: 0,
}
- }
+ },
}
createApp(App).mount(container)
expect(container.innerHTML).toBe(`0`)
template,
data() {
return {
- count: 0
+ count: 0,
}
- }
+ },
}
createApp(App).mount(container)
expect(container.innerHTML).toBe(`0`)
it('should warn template compilation errors with codeframe', () => {
const container = document.createElement('div')
const App = {
- template: `<div v-if>`
+ template: `<div v-if>`,
}
createApp(App).mount(container)
expect(
- `Template compilation error: Element is missing end tag`
+ `Template compilation error: Element is missing end tag`,
).toHaveBeenWarned()
expect(
`
1 | <div v-if>
- | ^`.trim()
+ | ^`.trim(),
).toHaveBeenWarned()
expect(`v-if/v-else-if is missing expression`).toHaveBeenWarned()
expect(
`
1 | <div v-if>
- | ^^^^`.trim()
+ | ^^^^`.trim(),
).toHaveBeenWarned()
})
it('should support custom element via config.isCustomElement (deprecated)', () => {
const app = createApp({
- template: '<custom></custom>'
+ template: '<custom></custom>',
})
const container = document.createElement('div')
app.config.isCustomElement = tag => tag === 'custom'
it('should support custom element via config.compilerOptions.isCustomElement', () => {
const app = createApp({
- template: '<custom></custom>'
+ template: '<custom></custom>',
})
const container = document.createElement('div')
app.config.compilerOptions.isCustomElement = tag => tag === 'custom'
it('should support using element innerHTML as template', () => {
const app = createApp({
data: () => ({
- msg: 'hello'
- })
+ msg: 'hello',
+ }),
})
const container = document.createElement('div')
container.innerHTML = '{{msg}}'
template: `{{ count }}`,
data() {
return {
- count: 0
+ count: 0,
}
- }
+ },
}
createApp(App).mount('#app')
expect(container.innerHTML).toBe(`0`)
it('should warn when template is not available', () => {
const app = createApp({
- template: {}
+ template: {},
})
const container = document.createElement('div')
app.mount(container)
it('should warn when template is is not found', () => {
const app = createApp({
- template: '#not-exist-id'
+ template: '#not-exist-id',
})
const container = document.createElement('div')
app.mount(container)
expect(
- '[Vue warn]: Template element not found or is empty: #not-exist-id'
+ '[Vue warn]: Template element not found or is empty: #not-exist-id',
).toHaveBeenWarned()
})
template: `{{ count }}`,
data() {
return {
- count: 0
+ count: 0,
}
- }
+ },
}
createApp(App).mount('#not-exist-id')
expect(
- '[Vue warn]: Failed to mount app: mount target selector "#not-exist-id" returned null.'
+ '[Vue warn]: Failed to mount app: mount target selector "#not-exist-id" returned null.',
).toHaveBeenWarned()
document.querySelector = origin
})
`,
data() {
return {
- count
+ count,
}
- }
+ },
}
createApp(App).mount(container)
expect(container.innerHTML).toBe(`<!--teleport start--><!--teleport end-->`)
setup() {
return { ok }
},
- template: `<div>{{ ok }}<div v-if="ok" v-once>{{ ok }}</div></div>`
+ template: `<div>{{ ok }}<div v-if="ok" v-once>{{ ok }}</div></div>`,
}
const container = document.createElement('div')
createApp(App).mount(container)
setup() {
return { list }
},
- template: `<div>{{ list.length }}<div v-for="i in list" v-once>{{ i }}</div></div>`
+ template: `<div>{{ list.length }}<div v-for="i in list" v-once>{{ i }}</div></div>`,
}
const container = document.createElement('div')
createApp(App).mount(container)
// #2413
it('EMPTY_ARR should not change', () => {
const App = {
- template: `<div v-for="v of ['a']">{{ v }}</div>`
+ template: `<div v-for="v of ['a']">{{ v }}</div>`,
}
const container = document.createElement('div')
createApp(App).mount(container)
test('BigInt support', () => {
const app = createApp({
- template: `<div>{{ BigInt(BigInt(100000111)) + BigInt(2000000000n) * 30000000n }}</div>`
+ template: `<div>{{ BigInt(BigInt(100000111)) + BigInt(2000000000n) * 30000000n }}</div>`,
})
const root = document.createElement('div')
app.mount(root)
// - compiler-core/src/transforms/transformElement.ts
import { vtcKey } from '../../runtime-dom/src/components/Transition'
-import { render, h, ref, nextTick } from '../src'
+import { h, nextTick, ref, render } from '../src'
describe('MathML support', () => {
afterEach(() => {
</annotation-xml>
</semantics>
</math>
- `
+ `,
}
render(h(App), root)
const e0 = document.getElementById('e0')!
</annotation>
</math>
</div>
- `
+ `,
}
render(h(App), root)
const f1 = document.querySelector('#f1')!
describe('config.compilerOptions', () => {
test('isCustomElement', () => {
const app = createApp({
- template: `<foo/>`
+ template: `<foo/>`,
})
app.config.compilerOptions.isCustomElement = (tag: string) => tag === 'foo'
const root = document.createElement('div')
test('comments', () => {
const app = createApp({
- template: `<div/><!--test--><div/>`
+ template: `<div/><!--test--><div/>`,
})
app.config.compilerOptions.comments = true
// the comments option is only relevant in production mode
test('whitespace', () => {
const app = createApp({
- template: `<div><span/>\n <span/></div>`
+ template: `<div><span/>\n <span/></div>`,
})
app.config.compilerOptions.whitespace = 'preserve'
const root = document.createElement('div')
test('delimiters', () => {
const app = createApp({
data: () => ({ foo: 'hi' }),
- template: `[[ foo ]]`
+ template: `[[ foo ]]`,
})
app.config.compilerOptions.delimiters = [`[[`, `]]`]
const root = document.createElement('div')
const app = createApp({
template: `<foo/>`,
compilerOptions: {
- isCustomElement: (tag: string) => tag === 'foo'
- }
+ isCustomElement: (tag: string) => tag === 'foo',
+ },
})
const root = document.createElement('div')
app.mount(root)
const app = createApp({
template: `<div/><!--test--><div/>`,
compilerOptions: {
- comments: true
- }
+ comments: true,
+ },
})
app.config.compilerOptions.comments = false
// the comments option is only relevant in production mode
const app = createApp({
template: `<div><span/>\n <span/></div>`,
compilerOptions: {
- whitespace: 'preserve'
- }
+ whitespace: 'preserve',
+ },
})
const root = document.createElement('div')
app.mount(root)
data: () => ({ foo: 'hi' }),
template: `[[ foo ]]`,
compilerOptions: {
- delimiters: [`[[`, `]]`]
- }
+ delimiters: [`[[`, `]]`],
+ },
})
const root = document.createElement('div')
app.mount(root)
// - compiler-core/src/transforms/transformElement.ts
import { vtcKey } from '../../runtime-dom/src/components/Transition'
-import { render, h, ref, nextTick } from '../src'
+import { h, nextTick, ref, render } from '../src'
describe('SVG support', () => {
afterEach(() => {
</foreignObject>
</svg>
</div>
- `
+ `,
}
render(h(App), root)
const e0 = document.getElementById('e0')!
</foreignObject>
</svg>
</div>
- `
+ `,
}
render(h(App), root)
const f1 = document.querySelector('#f1')!
-export * from './index.js'
\ No newline at end of file
+export * from './index.js'
-import type { VNode, ReservedProps, NativeElements } from '@vue/runtime-dom'
+/* eslint-disable @typescript-eslint/prefer-ts-expect-error */
+import type { NativeElements, ReservedProps, VNode } from '@vue/runtime-dom'
/**
* JSX namespace for usage with @jsxImportsSource directive
return h(type, props, children)
}
-export {
- Fragment,
- jsx,
- jsx as jsxs,
- jsx as jsxDEV
-}
+export { Fragment, jsx, jsx as jsxs, jsx as jsxDEV }
+/* eslint-disable @typescript-eslint/prefer-ts-expect-error */
// global JSX namespace registration
// somehow we have to copy=pase the jsx-runtime types here to make TypeScript happy
-import type { VNode, ReservedProps, NativeElements } from '@vue/runtime-dom'
+import type { NativeElements, ReservedProps, VNode } from '@vue/runtime-dom'
declare global {
namespace JSX {
-import {
+import type {
+ ComputedRef,
+ CustomRefFactory,
+ DebuggerOptions,
Ref,
UnwrapRef,
- ComputedRef,
WritableComputedOptions,
- DebuggerOptions,
WritableComputedRef,
- CustomRefFactory
} from '@vue/runtime-dom'
export declare const RefType: unique symbol
export declare enum RefTypes {
Ref = 1,
ComputedRef = 2,
- WritableComputedRef = 3
+ WritableComputedRef = 3,
}
type RefValue<T> = T extends null | undefined ? T : ReactiveVariable<T>
*/
export declare function $<T>(arg: ComputedRef<T>): ComputedRefValue<T>
export declare function $<T>(
- arg: WritableComputedRef<T>
+ arg: WritableComputedRef<T>,
): WritableComputedRefValue<T>
export declare function $<T>(arg: Ref<T>): RefValue<T>
export declare function $<T extends object>(arg?: T): DestructureRefs<T>
export declare function $$<T>(value: RefValue<T>): Ref<T>
export declare function $$<T>(value: ComputedRefValue<T>): ComputedRef<T>
export declare function $$<T>(
- value: WritableComputedRefValue<T>
+ value: WritableComputedRefValue<T>,
): WritableComputedRef<T>
type ToRawRefs<T extends object> = {
export declare function $toRef<T extends object, K extends keyof T>(
object: T,
- key: K
+ key: K,
): RefValue<T[K]>
export declare function $toRef<T extends object, K extends keyof T>(
object: T,
key: K,
- defaultValue: T[K]
+ defaultValue: T[K],
): RefValue<Exclude<T[K], undefined>>
export declare function $customRef<T>(factory: CustomRefFactory<T>): RefValue<T>
export declare function $computed<T>(
getter: () => T,
- debuggerOptions?: DebuggerOptions
+ debuggerOptions?: DebuggerOptions,
): ComputedRefValue<T>
export declare function $computed<T>(
options: WritableComputedOptions<T>,
- debuggerOptions?: DebuggerOptions
+ debuggerOptions?: DebuggerOptions,
): WritableComputedRefValue<T>
-export * from '@vue/server-renderer'
\ No newline at end of file
+export * from '@vue/server-renderer'
if (!__ESM_BUNDLER__) {
console.info(
`You are running a development build of Vue.\n` +
- `Make sure to use the production build (*.prod.js) when deploying for production.`
+ `Make sure to use the production build (*.prod.js) when deploying for production.`,
)
}
// This entry is the "full-build" that includes both the runtime
// and the compiler, and supports on-the-fly compilation of the template option.
import { initDev } from './dev'
-import { compile, CompilerOptions, CompilerError } from '@vue/compiler-dom'
-import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
+import {
+ type CompilerError,
+ type CompilerOptions,
+ compile,
+} from '@vue/compiler-dom'
+import {
+ type RenderFunction,
+ registerRuntimeCompiler,
+ warn,
+} from '@vue/runtime-dom'
import * as runtimeDom from '@vue/runtime-dom'
import {
- isString,
+ EMPTY_OBJ,
NOOP,
- generateCodeFrame,
extend,
- EMPTY_OBJ
+ generateCodeFrame,
+ isString,
} from '@vue/shared'
-import { InternalRenderFunction } from 'packages/runtime-core/src/component'
+import type { InternalRenderFunction } from 'packages/runtime-core/src/component'
if (__DEV__) {
initDev()
function compileToFunction(
template: string | HTMLElement,
- options?: CompilerOptions
+ options?: CompilerOptions,
): RenderFunction {
if (!isString(template)) {
if (template.nodeType) {
{
hoistStatic: true,
onError: __DEV__ ? onError : undefined,
- onWarn: __DEV__ ? e => onError(e, true) : NOOP
+ onWarn: __DEV__ ? e => onError(e, true) : NOOP,
} as CompilerOptions,
- options
+ options,
)
if (!opts.isCustomElement && typeof customElements !== 'undefined') {
generateCodeFrame(
template as string,
err.loc.start.offset,
- err.loc.end.offset
+ err.loc.end.offset,
)
warn(codeFrame ? `${message}\n${codeFrame}` : message)
}
? ` Use "vue.esm-browser.js" instead.`
: __GLOBAL__
? ` Use "vue.global.js" instead.`
- : ``) /* should not happen */
+ : ``) /* should not happen */,
)
}
}
'@types/semver':
specifier: ^7.5.5
version: 7.5.5
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^6.16.0
+ version: 6.16.0(@typescript-eslint/parser@6.15.0)(eslint@8.56.0)(typescript@5.2.2)
'@typescript-eslint/parser':
specifier: ^6.15.0
version: 6.15.0(eslint@8.56.0)(typescript@5.2.2)
eslint-define-config:
specifier: ^1.24.1
version: 1.24.1
+ eslint-plugin-import:
+ specifier: npm:eslint-plugin-i@^2.29.1
+ version: /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.15.0)(eslint@8.56.0)
eslint-plugin-jest:
specifier: ^27.6.0
- version: 27.6.0(eslint@8.56.0)(typescript@5.2.2)
+ version: 27.6.0(@typescript-eslint/eslint-plugin@6.16.0)(eslint@8.56.0)(typescript@5.2.2)
estree-walker:
specifier: ^2.0.2
version: 2.0.2
dev: true
optional: true
+ /@typescript-eslint/eslint-plugin@6.16.0(@typescript-eslint/parser@6.15.0)(eslint@8.56.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-O5f7Kv5o4dLWQtPX4ywPPa+v9G+1q1x8mz0Kr0pXUtKsevo+gIJHLkGc8RxaZWtP8RrhwhSNIWThnW42K9/0rQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
+ eslint: ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@eslint-community/regexpp': 4.9.1
+ '@typescript-eslint/parser': 6.15.0(eslint@8.56.0)(typescript@5.2.2)
+ '@typescript-eslint/scope-manager': 6.16.0
+ '@typescript-eslint/type-utils': 6.16.0(eslint@8.56.0)(typescript@5.2.2)
+ '@typescript-eslint/utils': 6.16.0(eslint@8.56.0)(typescript@5.2.2)
+ '@typescript-eslint/visitor-keys': 6.16.0
+ debug: 4.3.4
+ eslint: 8.56.0
+ graphemer: 1.4.0
+ ignore: 5.2.4
+ natural-compare: 1.4.0
+ semver: 7.5.4
+ ts-api-utils: 1.0.3(typescript@5.2.2)
+ typescript: 5.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/parser@6.15.0(eslint@8.56.0)(typescript@5.2.2):
resolution: {integrity: sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==}
engines: {node: ^16.0.0 || >=18.0.0}
'@typescript-eslint/visitor-keys': 6.15.0
dev: true
+ /@typescript-eslint/scope-manager@6.16.0:
+ resolution: {integrity: sha512-0N7Y9DSPdaBQ3sqSCwlrm9zJwkpOuc6HYm7LpzLAPqBL7dmzAUimr4M29dMkOP/tEwvOCC/Cxo//yOfJD3HUiw==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 6.16.0
+ '@typescript-eslint/visitor-keys': 6.16.0
+ dev: true
+
+ /@typescript-eslint/type-utils@6.16.0(eslint@8.56.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-ThmrEOcARmOnoyQfYkHw/DX2SEYBalVECmoldVuH6qagKROp/jMnfXpAU/pAIWub9c4YTxga+XwgAkoA0pxfmg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/typescript-estree': 6.16.0(typescript@5.2.2)
+ '@typescript-eslint/utils': 6.16.0(eslint@8.56.0)(typescript@5.2.2)
+ debug: 4.3.4
+ eslint: 8.56.0
+ ts-api-utils: 1.0.3(typescript@5.2.2)
+ typescript: 5.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/types@5.62.0:
resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
+ /@typescript-eslint/types@6.16.0:
+ resolution: {integrity: sha512-hvDFpLEvTJoHutVl87+MG/c5C8I6LOgEx05zExTSJDEVU7hhR3jhV8M5zuggbdFCw98+HhZWPHZeKS97kS3JoQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dev: true
+
/@typescript-eslint/typescript-estree@5.62.0(typescript@5.2.2):
resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- supports-color
dev: true
+ /@typescript-eslint/typescript-estree@6.16.0(typescript@5.2.2):
+ resolution: {integrity: sha512-VTWZuixh/vr7nih6CfrdpmFNLEnoVBF1skfjdyGnNwXOH1SLeHItGdZDHhhAIzd3ACazyY2Fg76zuzOVTaknGA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 6.16.0
+ '@typescript-eslint/visitor-keys': 6.16.0
+ debug: 4.3.4
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.3
+ semver: 7.5.4
+ ts-api-utils: 1.0.3(typescript@5.2.2)
+ typescript: 5.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/utils@5.62.0(eslint@8.56.0)(typescript@5.2.2):
resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- typescript
dev: true
+ /@typescript-eslint/utils@6.16.0(eslint@8.56.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-T83QPKrBm6n//q9mv7oiSvy/Xq/7Hyw9SzSEhMHJwznEmQayfBM87+oAlkNAMEO7/MjIwKyOHgBJbxB0s7gx2A==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
+ '@types/json-schema': 7.0.14
+ '@types/semver': 7.5.5
+ '@typescript-eslint/scope-manager': 6.16.0
+ '@typescript-eslint/types': 6.16.0
+ '@typescript-eslint/typescript-estree': 6.16.0(typescript@5.2.2)
+ eslint: 8.56.0
+ semver: 7.5.4
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
/@typescript-eslint/visitor-keys@5.62.0:
resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-visitor-keys: 3.4.3
dev: true
+ /@typescript-eslint/visitor-keys@6.16.0:
+ resolution: {integrity: sha512-QSFQLruk7fhs91a/Ep/LqRdbJCZ1Rq03rqBdKT5Ky17Sz8zRLUksqIe9DW0pKtg/Z35/ztbLQ6qpOCN6rOC11A==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 6.16.0
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
/@ungap/structured-clone@1.2.0:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
ms: 2.0.0
dev: true
+ /debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+ dev: true
+
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>= 8.6.0'}
dev: true
- /eslint-plugin-jest@27.6.0(eslint@8.56.0)(typescript@5.2.2):
+ /eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+ dependencies:
+ debug: 3.2.7
+ is-core-module: 2.13.1
+ resolve: 1.22.8
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.15.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 6.15.0(eslint@8.56.0)(typescript@5.2.2)
+ debug: 3.2.7
+ eslint: 8.56.0
+ eslint-import-resolver-node: 0.3.9
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.15.0)(eslint@8.56.0):
+ resolution: {integrity: sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ eslint: ^7.2.0 || ^8
+ dependencies:
+ debug: 4.3.4
+ doctrine: 3.0.0
+ eslint: 8.56.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.15.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0)
+ get-tsconfig: 4.7.2
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ semver: 7.5.4
+ transitivePeerDependencies:
+ - '@typescript-eslint/parser'
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
+ /eslint-plugin-jest@27.6.0(@typescript-eslint/eslint-plugin@6.16.0)(eslint@8.56.0)(typescript@5.2.2):
resolution: {integrity: sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
jest:
optional: true
dependencies:
+ '@typescript-eslint/eslint-plugin': 6.16.0(@typescript-eslint/parser@6.15.0)(eslint@8.56.0)(typescript@5.2.2)
'@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@5.2.2)
eslint: 8.56.0
transitivePeerDependencies:
resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==}
dev: true
+ /hasown@2.0.0:
+ resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ function-bind: 1.1.2
+ dev: true
+
/hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
has: 1.0.4
dev: true
+ /is-core-module@2.13.1:
+ resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
+ dependencies:
+ hasown: 2.0.0
+ dev: true
+
/is-date-object@1.0.5:
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
engines: {node: '>= 0.4'}
const outputConfigs = {
'esm-bundler': {
file: resolve(`dist/${name}.esm-bundler.js`),
- format: 'es'
+ format: 'es',
},
'esm-browser': {
file: resolve(`dist/${name}.esm-browser.js`),
- format: 'es'
+ format: 'es',
},
cjs: {
file: resolve(`dist/${name}.cjs.js`),
- format: 'cjs'
+ format: 'cjs',
},
global: {
file: resolve(`dist/${name}.global.js`),
- format: 'iife'
+ format: 'iife',
},
// runtime-only builds, for main "vue" package only
'esm-bundler-runtime': {
file: resolve(`dist/${name}.runtime.esm-bundler.js`),
- format: 'es'
+ format: 'es',
},
'esm-browser-runtime': {
file: resolve(`dist/${name}.runtime.esm-browser.js`),
- format: 'es'
+ format: 'es',
},
'global-runtime': {
file: resolve(`dist/${name}.runtime.global.js`),
- format: 'iife'
- }
+ format: 'iife',
+ },
}
/** @type {ReadonlyArray<PackageFormat>} */
: `false`,
__FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: isBundlerESMBuild
? `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__`
- : `false`
+ : `false`,
}
if (!isBundlerESMBuild) {
'context.onError(': `/*#__PURE__*/ context.onError(`,
'emitError(': `/*#__PURE__*/ emitError(`,
'createCompilerError(': `/*#__PURE__*/ createCompilerError(`,
- 'createDOMCompilerError(': `/*#__PURE__*/ createDOMCompilerError(`
+ 'createDOMCompilerError(': `/*#__PURE__*/ createDOMCompilerError(`,
})
}
if (isBundlerESMBuild) {
Object.assign(replacements, {
// preserve to be handled by bundlers
- __DEV__: `!!(process.env.NODE_ENV !== 'production')`
+ __DEV__: `!!(process.env.NODE_ENV !== 'production')`,
})
}
Object.assign(replacements, {
'process.env': '({})',
'process.platform': '""',
- 'process.stdout': 'null'
+ 'process.stdout': 'null',
})
}
'source-map-js',
'@babel/parser',
'estree-walker',
- 'entities/lib/decode.js'
+ 'entities/lib/decode.js',
]
if (isGlobalBuild || isBrowserESMBuild || isCompatPackage) {
// for @vue/compiler-sfc / server-renderer
...['path', 'url', 'stream'],
// somehow these throw warnings for runtime-* package builds
- ...treeShakenDeps
+ ...treeShakenDeps,
]
}
}
'teacup/lib/express',
'arc-templates/dist/es5',
'then-pug',
- 'then-jade'
+ 'then-jade',
]
}
? [
commonJS({
sourceMap: false,
- ignore: cjsIgnores
+ ignore: cjsIgnores,
}),
...(format === 'cjs' ? [] : [polyfillNode()]),
- nodeResolve()
+ nodeResolve(),
]
: []
external: resolveExternal(),
plugins: [
json({
- namedExports: false
+ namedExports: false,
}),
alias({
- entries
+ entries,
}),
enumPlugin,
...resolveReplace(),
sourceMap: output.sourcemap,
minify: false,
target: isServerRenderer || isNodeBuild ? 'es2019' : 'es2015',
- define: resolveDefine()
+ define: resolveDefine(),
}),
...resolveNodePlugins(),
- ...plugins
+ ...plugins,
],
output,
onwarn: (msg, warn) => {
}
},
treeshake: {
- moduleSideEffects: false
- }
+ moduleSideEffects: false,
+ },
}
}
function createProductionConfig(/** @type {PackageFormat} */ format) {
return createConfig(format, {
file: resolve(`dist/${name}.${format}.prod.js`),
- format: outputConfigs[format].format
+ format: outputConfigs[format].format,
})
}
format,
{
file: outputConfigs[format].file.replace(/\.js$/, '.prod.js'),
- format: outputConfigs[format].format
+ format: outputConfigs[format].format,
},
[
terser({
module: /^esm/.test(format),
compress: {
ecma: 2015,
- pure_getters: true
+ pure_getters: true,
},
- safari10: true
- })
- ]
+ safari10: true,
+ }),
+ ],
)
}
// @ts-check
import assert from 'node:assert/strict'
import { parse } from '@babel/parser'
-import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
+import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
import MagicString from 'magic-string'
import dts from 'rollup-plugin-dts'
if (!existsSync('temp/packages')) {
console.warn(
- 'no temp dts files found. run `tsc -p tsconfig.build.json` first.'
+ 'no temp dts files found. run `tsc -p tsconfig.build.json` first.',
)
process.exit(1)
}
input: `./temp/packages/${pkg}/src/index.d.ts`,
output: {
file: `packages/${pkg}/dist/${pkg}.d.ts`,
- format: 'es'
+ format: 'es',
},
plugins: [dts(), patchTypes(pkg), ...(pkg === 'vue' ? [copyMts()] : [])],
onwarn(warning, warn) {
return
}
warn(warning)
- }
+ },
}
- }
+ },
)
/**
const s = new MagicString(code)
const ast = parse(code, {
plugins: ['typescript'],
- sourceType: 'module'
+ sourceType: 'module',
})
/**
throw new Error(
`unhandled declare const with more than one declarators:\n${code.slice(
node.start,
- node.end
- )}`
+ node.end,
+ )}`,
)
}
} else if (
prev
? (assert(typeof prev.end === 'number'), prev.end)
: spec.start,
- spec.end
+ spec.end,
)
}
removed++
.join('\n')
}
return code
- }
+ },
}
}
writeBundle(_, bundle) {
assert('code' in bundle['vue.d.ts'])
writeFileSync('packages/vue/dist/vue.d.mts', bundle['vue.d.ts'].code)
- }
+ },
}
}
const resolveEntryForPkg = (/** @type {string} */ p) =>
path.resolve(
fileURLToPath(import.meta.url),
- `../../packages/${p}/src/index.ts`
+ `../../packages/${p}/src/index.ts`,
)
const dirs = readdirSync(new URL('../packages', import.meta.url))
vue: resolveEntryForPkg('vue'),
'vue/compiler-sfc': resolveEntryForPkg('compiler-sfc'),
'vue/server-renderer': resolveEntryForPkg('server-renderer'),
- '@vue/compat': resolveEntryForPkg('vue-compat')
+ '@vue/compat': resolveEntryForPkg('vue-compat'),
}
const nonSrcPackages = ['sfc-playground', 'template-explorer', 'dts-test']
import { existsSync } from 'node:fs'
import path from 'node:path'
import minimist from 'minimist'
-import { gzipSync, brotliCompressSync } from 'node:zlib'
+import { brotliCompressSync, gzipSync } from 'node:zlib'
import pico from 'picocolors'
import { execa, execaSync } from 'execa'
import { cpus } from 'node:os'
'build-dts',
...(targets.length
? ['--environment', `TARGETS:${resolvedTargets.join(',')}`]
- : [])
+ : []),
],
{
- stdio: 'inherit'
- }
+ stdio: 'inherit',
+ },
)
}
} finally {
`TARGET:${target}`,
formats ? `FORMATS:${formats}` : ``,
prodOnly ? `PROD_ONLY:true` : ``,
- sourceMap ? `SOURCE_MAP:true` : ``
+ sourceMap ? `SOURCE_MAP:true` : ``,
]
.filter(Boolean)
- .join(',')
+ .join(','),
],
- { stdio: 'inherit' }
+ { stdio: 'inherit' },
)
}
console.log(
`${pico.gray(pico.bold(fileName))} min:${prettyBytes(
- file.length
+ file.length,
)} / gzip:${prettyBytes(gzipped.length)} / brotli:${prettyBytes(
- brotli.length
- )}`
+ brotli.length,
+ )}`,
)
if (writeSize)
file: fileName,
size: file.length,
gzip: gzipped.length,
- brotli: brotli.length
+ brotli: brotli.length,
}),
- 'utf-8'
+ 'utf-8',
)
}
// smaller files and provides better tree-shaking.
import esbuild from 'esbuild'
-import { resolve, relative, dirname } from 'node:path'
+import { dirname, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import minimist from 'minimist'
__dirname,
`../packages/${target}/dist/${
target === 'vue-compat' ? `vue` : target
- }.${postfix}.${prod ? `prod.` : ``}js`
+ }.${postfix}.${prod ? `prod.` : ``}js`,
)
const relativeOutfile = relative(process.cwd(), outfile)
// for @vue/compiler-sfc / server-renderer
'path',
'url',
- 'stream'
+ 'stream',
]
}
const consolidatePkgPath = require.resolve(
'@vue/consolidate/package.json',
{
- paths: [resolve(__dirname, `../packages/${target}/`)]
- }
+ paths: [resolve(__dirname, `../packages/${target}/`)],
+ },
)
const consolidateDeps = Object.keys(
- require(consolidatePkgPath).devDependencies
+ require(consolidatePkgPath).devDependencies,
)
external = [
...external,
'teacup/lib/express',
'arc-templates/dist/es5',
'then-pug',
- 'then-jade'
+ 'then-jade',
]
}
}
build.onEnd(() => {
console.log(`built: ${relativeOutfile}`)
})
- }
- }
+ },
+ },
]
if (format !== 'cjs' && pkg.buildOptions?.enableNonBrowserBranches) {
__DEV__: prod ? `false` : `true`,
__TEST__: `false`,
__BROWSER__: String(
- format !== 'cjs' && !pkg.buildOptions?.enableNonBrowserBranches
+ format !== 'cjs' && !pkg.buildOptions?.enableNonBrowserBranches,
),
__GLOBAL__: String(format === 'global'),
__ESM_BUNDLER__: String(format.includes('esm-bundler')),
__FEATURE_SUSPENSE__: `true`,
__FEATURE_OPTIONS_API__: `true`,
__FEATURE_PROD_DEVTOOLS__: `false`,
- __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: `false`
- }
+ __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: `false`,
+ },
})
.then(ctx => ctx.watch())
mkdirSync,
readFileSync,
rmSync,
- writeFileSync
+ writeFileSync,
} from 'node:fs'
import * as path from 'node:path'
import { parse } from '@babel/parser'
const content = readFileSync(file, 'utf-8')
const ast = parse(content, {
plugins: ['typescript'],
- sourceType: 'module'
+ sourceType: 'module',
})
/** @type {Set<string>} */
const id = decl.id.name
if (enumIds.has(id)) {
throw new Error(
- `not support declaration merging for enum ${id} in ${file}`
+ `not support declaration merging for enum ${id} in ${file}`,
)
}
enumIds.add(id)
}
members.push({
name: key,
- value
+ value,
})
defines[fullKey] = JSON.stringify(value)
}
// e.g. 1 << 2
else if (init.type === 'BinaryExpression') {
const resolveValue = (
- /** @type {import('@babel/types').Expression | import('@babel/types').PrivateName} */ node
+ /** @type {import('@babel/types').Expression | import('@babel/types').PrivateName} */ node,
) => {
assert.ok(typeof node.start === 'number')
assert.ok(typeof node.end === 'number')
)
if (!(exp in defines)) {
throw new Error(
- `unhandled enum initialization expression ${exp} in ${file}`
+ `unhandled enum initialization expression ${exp} in ${file}`,
)
}
return defines[exp]
} else {
throw new Error(
- `unhandled BinaryExpression operand type ${node.type} in ${file}`
+ `unhandled BinaryExpression operand type ${node.type} in ${file}`,
)
}
}
value = evaluate(exp)
} else {
throw new Error(
- `unhandled UnaryExpression argument type ${init.argument.type} in ${file}`
+ `unhandled UnaryExpression argument type ${init.argument.type} in ${file}`,
)
}
} else {
throw new Error(
- `unhandled initializer type ${init.type} for ${fullKey} in ${file}`
+ `unhandled initializer type ${init.type} for ${fullKey} in ${file}`,
)
}
lastInitialized = value
declarations[file].push({
id,
range: [node.start, node.end],
- members
+ members,
})
}
}
/** @type {EnumData} */
const enumData = {
declarations,
- defines
+ defines,
}
writeFileSync(ENUM_CACHE_PATH, JSON.stringify(enumData))
const {
range: [start, end],
id,
- members
+ members,
} = declaration
s.update(
start,
// see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
return typeof value === 'string'
? [
- forwardMapping
+ forwardMapping,
// string enum members do not get a reverse mapping generated at all
]
: [
forwardMapping,
// other enum members should support enum reverse mapping
- reverseMapping
+ reverseMapping,
]
})
- .join(',\n')}}`
+ .join(',\n')}}`,
)
}
}
if (s) {
return {
code: s.toString(),
- map: s.generateMap()
+ map: s.generateMap(),
}
}
- }
+ },
}
return [plugin, enumData.defines]
'compiler-core',
'compiler-dom',
'compiler-ssr',
- 'shared'
+ 'shared',
]
let allFilesPresent = true
for (const pkg of packagesToCheck) {
if (
!fs.existsSync(
- new URL(`../packages/${pkg}/dist/${pkg}.cjs.js`, import.meta.url)
+ new URL(`../packages/${pkg}/dist/${pkg}.cjs.js`, import.meta.url),
)
) {
allFilesPresent = false
skipBuild: 'skip-build',
skipTests: 'skip-tests',
skipGit: 'skip-git',
- skipPrompts: 'skip-prompts'
- }
+ skipPrompts: 'skip-prompts',
+ },
})
const preId = args.preid || semver.prerelease(currentVersion)?.[0]
const pkgRoot = path.resolve(__dirname, '../packages', p)
if (fs.statSync(pkgRoot).isDirectory()) {
const pkg = JSON.parse(
- fs.readFileSync(path.resolve(pkgRoot, 'package.json'), 'utf-8')
+ fs.readFileSync(path.resolve(pkgRoot, 'package.json'), 'utf-8'),
)
return !pkg.private
}
'major',
...(preId
? /** @type {const} */ (['prepatch', 'preminor', 'premajor', 'prerelease'])
- : [])
+ : []),
]
const inc = (/** @type {import('semver').ReleaseType} */ i) =>
const run = async (
/** @type {string} */ bin,
/** @type {ReadonlyArray<string>} */ args,
- /** @type {import('execa').Options} */ opts = {}
+ /** @type {import('execa').Options} */ opts = {},
) => execa(bin, args, { stdio: 'inherit', ...opts })
const dryRun = async (
/** @type {string} */ bin,
/** @type {ReadonlyArray<string>} */ args,
- /** @type {import('execa').Options} */ opts = {}
+ /** @type {import('execa').Options} */ opts = {},
) => console.log(pico.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
const runIfNotDry = isDryRun ? dryRun : run
const getPkgRoot = (/** @type {string} */ pkg) =>
const { stdout } = await run(
'pnpm',
['view', `${pkgName}@~${canaryVersion}`, 'version', '--json'],
- { stdio: 'pipe' }
+ { stdio: 'pipe' },
)
let versions = JSON.parse(stdout)
versions = Array.isArray(versions) ? versions : [versions]
type: 'select',
name: 'release',
message: 'Select release type',
- choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
+ choices: versionIncrements
+ .map(i => `${i} (${inc(i)})`)
+ .concat(['custom']),
})
if (release === 'custom') {
type: 'input',
name: 'version',
message: 'Input custom version',
- initial: currentVersion
+ initial: currentVersion,
})
targetVersion = result.version
} else {
step(
isCanary
? `Releasing canary version v${targetVersion}...`
- : `Releasing v${targetVersion}...`
+ : `Releasing v${targetVersion}...`,
)
} else {
/** @type {{ yes: boolean }} */
const { yes: confirmRelease } = await prompt({
type: 'confirm',
name: 'yes',
- message: `Releasing v${targetVersion}. Confirm?`
+ message: `Releasing v${targetVersion}. Confirm?`,
})
if (!confirmRelease) {
const { yes: promptSkipTests } = await prompt({
type: 'confirm',
name: 'yes',
- message: `CI for this commit passed. Skip local tests?`
+ message: `CI for this commit passed. Skip local tests?`,
})
skipTests = promptSkipTests
step('\nUpdating cross dependencies...')
updateVersions(
targetVersion,
- isCanary ? renamePackageToCanary : keepThePackageName
+ isCanary ? renamePackageToCanary : keepThePackageName,
)
versionUpdated = true
const { yes: changelogOk } = await prompt({
type: 'confirm',
name: 'yes',
- message: `Changelog generated. Does it look good?`
+ message: `Changelog generated. Does it look good?`,
})
if (!changelogOk) {
console.log(
pico.yellow(
`The following packages are skipped and NOT published:\n- ${skippedPackages.join(
- '\n- '
- )}`
- )
+ '\n- ',
+ )}`,
+ ),
)
}
console.log()
const sha = await getSha()
const res = await fetch(
`https://api.github.com/repos/vuejs/core/actions/runs?head_sha=${sha}` +
- `&status=success&exclude_pull_requests=true`
+ `&status=success&exclude_pull_requests=true`,
)
const data = await res.json()
return data.workflow_runs.length > 0
try {
const branch = await getBranch()
const res = await fetch(
- `https://api.github.com/repos/vuejs/core/commits/${branch}?per_page=1`
+ `https://api.github.com/repos/vuejs/core/commits/${branch}?per_page=1`,
)
const data = await res.json()
if (data.sha === (await getSha())) {
type: 'confirm',
name: 'yes',
message: pico.red(
- `Local HEAD is not up-to-date with remote. Are you sure you want to continue?`
- )
+ `Local HEAD is not up-to-date with remote. Are you sure you want to continue?`,
+ ),
})
return yes
}
} catch (e) {
console.error(
- pico.red('Failed to check whether local HEAD is up-to-date with remote.')
+ pico.red('Failed to check whether local HEAD is up-to-date with remote.'),
)
return false
}
updatePackage(path.resolve(__dirname, '..'), version, getNewPackageName)
// 2. update all packages
packages.forEach(p =>
- updatePackage(getPkgRoot(p), version, getNewPackageName)
+ updatePackage(getPkgRoot(p), version, getNewPackageName),
)
}
const newName = getNewPackageName(dep)
const newVersion = newName === dep ? version : `npm:${newName}@${version}`
console.log(
- pico.yellow(`${pkg.name} -> ${depType} -> ${dep}@${newVersion}`)
+ pico.yellow(`${pkg.name} -> ${depType} -> ${dep}@${newVersion}`),
)
deps[dep] = newVersion
}
...(releaseTag ? ['--tag', releaseTag] : []),
'--access',
'public',
- ...additionalFlags
+ ...additionalFlags,
],
{
cwd: getPkgRoot(pkgName),
- stdio: 'pipe'
- }
+ stdio: 'pipe',
+ },
)
console.log(pico.green(`Successfully published ${pkgName}@${version}`))
} catch (/** @type {any} */ e) {
-import { type MockInstance } from 'vitest'
+import type { MockInstance } from 'vitest'
vi.stubGlobal('MathMLElement', class MathMLElement {})
if (passed) {
return {
pass: true,
- message: () => `expected "${received}" not to have been warned.`
+ message: () => `expected "${received}" not to have been warned.`,
}
} else {
const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
`expected "${received}" to have been warned` +
(msgs.length
? `.\n\nActual messages:\n\n - ${msgs}`
- : ` but no warning was recorded.`)
+ : ` but no warning was recorded.`),
}
}
},
if (passed) {
return {
pass: true,
- message: () => `expected "${received}" not to have been warned last.`
+ message: () => `expected "${received}" not to have been warned last.`,
}
} else {
const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
return {
pass: false,
message: () =>
- `expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`
+ `expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`,
}
}
},
if (found === n) {
return {
pass: true,
- message: () => `expected "${received}" to have been warned ${n} times.`
+ message: () => `expected "${received}" to have been warned ${n} times.`,
}
} else {
return {
pass: false,
message: () =>
- `expected "${received}" to have been warned ${n} times but got ${found}.`
+ `expected "${received}" to have been warned ${n} times but got ${found}.`,
}
}
- }
+ },
})
let warn: MockInstance
if (nonAssertedWarnings.length) {
throw new Error(
`test case threw unexpected warnings:\n - ${nonAssertedWarnings.join(
- '\n - '
- )}`
+ '\n - ',
+ )}`,
)
}
})
fileName,
`${prettyBytes(curr.size)}${getDiff(curr.size, prev?.size)}`,
`${prettyBytes(curr.gzip)}${getDiff(curr.gzip, prev?.gzip)}`,
- `${prettyBytes(curr.brotli)}${getDiff(curr.brotli, prev?.brotli)}`
+ `${prettyBytes(curr.brotli)}${getDiff(curr.brotli, prev?.brotli)}`,
])
}
async function renderUsages() {
const curr = (await importJSON<UsageResult>(
- path.resolve(currDir, '_usages.json')
+ path.resolve(currDir, '_usages.json'),
))!
const prev = await importJSON<UsageResult>(
- path.resolve(prevDir, '_usages.json')
+ path.resolve(prevDir, '_usages.json'),
)
output += '\n### Usages\n\n'
usage.name,
`${prettyBytes(usage.size)}${diffSize}`,
`${prettyBytes(usage.gzip)}${diffGzipped}`,
- `${prettyBytes(usage.brotli)}${diffBrotli}`
+ `${prettyBytes(usage.brotli)}${diffBrotli}`,
]
})
.filter((usage): usage is string[] => !!usage)
-import { mkdir, writeFile } from 'fs/promises'
+import { mkdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { rollup } from 'rollup'
import nodeResolve from '@rollup/plugin-node-resolve'
'watch',
'Transition',
'KeepAlive',
- 'Suspense'
- ]
- }
+ 'Suspense',
+ ],
+ },
]
main()
}
const results = Object.fromEntries(
- (await Promise.all(tasks)).map(r => [r.name, r])
+ (await Promise.all(tasks)).map(r => [r.name, r]),
)
await mkdir(sizeDir, { recursive: true })
await writeFile(
path.resolve(sizeDir, '_usages.json'),
JSON.stringify(results),
- 'utf-8'
+ 'utf-8',
)
}
},
load(_id) {
if (_id === id) return content
- }
+ },
},
nodeResolve(),
replace({
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
__VUE_OPTIONS_API__: 'true',
- preventAssignment: true
- })
- ]
+ preventAssignment: true,
+ }),
+ ],
})
const generated = await result.generate({})
const minified = (
await minify(bundled, {
module: true,
- toplevel: true
+ toplevel: true,
})
).code!
name: preset.name,
size,
gzip,
- brotli
+ brotli,
}
}
console.log()
console.error(
` ${pico.white(pico.bgRed(' ERROR '))} ${pico.red(
- `Target ${pico.underline(partialTargets.toString())} not found!`
- )}`
+ `Target ${pico.underline(partialTargets.toString())} not found!`,
+ )}`,
)
console.log()
// @ts-check
import pico from 'picocolors'
-import { readFileSync } from 'fs'
-import path from 'path'
+import { readFileSync } from 'node:fs'
+import path from 'node:path'
const msgPath = path.resolve('.git/COMMIT_EDITMSG')
const msg = readFileSync(msgPath, 'utf-8').trim()
console.log()
console.error(
` ${pico.white(pico.bgRed(' ERROR '))} ${pico.red(
- `invalid commit message format.`
+ `invalid commit message format.`,
)}\n\n` +
pico.red(
- ` Proper commit message format is required for automated changelog generation. Examples:\n\n`
+ ` Proper commit message format is required for automated changelog generation. Examples:\n\n`,
) +
` ${pico.green(`feat(compiler): add 'comments' option`)}\n` +
` ${pico.green(
- `fix(v-model): handle events on blur (close #28)`
+ `fix(v-model): handle events on blur (close #28)`,
)}\n\n` +
- pico.red(` See .github/commit-convention.md for more details.\n`)
+ pico.red(` See .github/commit-convention.md for more details.\n`),
)
process.exit(1)
}
const devBuild = fs.readFileSync(
'packages/vue/dist/vue.runtime.global.js',
- 'utf-8'
+ 'utf-8',
)
if (devBuild.includes('__spreadValues')) {
errors.push(
'dev build contains unexpected esbuild object spread helper.\n' +
'This means { ...obj } syntax is used in runtime code. This should be ' +
- 'refactoed to use the `extend` helper to avoid the extra code.'
+ 'refactoed to use the `extend` helper to avoid the extra code.',
)
}
const prodBuild = fs.readFileSync(
'packages/vue/dist/vue.runtime.global.prod.js',
- 'utf-8'
+ 'utf-8',
)
if (prodBuild.includes('Vue warn')) {
errors.push(
'prod build contains unexpected warning-related code.\n' +
- 'This means there are calls of warn() that are not guarded by the __DEV__ condition.'
+ 'This means there are calls of warn() that are not guarded by the __DEV__ condition.',
)
}
) {
errors.push(
'prod build contains unexpected domTagConifg lists.\n' +
- 'This means helpers like isHTMLTag() is used in runtime code paths when it should be compiler-only.'
+ 'This means helpers like isHTMLTag() is used in runtime code paths when it should be compiler-only.',
)
}
if (errors.length) {
throw new Error(
- `Found the following treeshaking errors:\n\n- ${errors.join('\n\n- ')}`
+ `Found the following treeshaking errors:\n\n- ${errors.join('\n\n- ')}`,
)
}
})
__FEATURE_SUSPENSE__: true,
__FEATURE_PROD_DEVTOOLS__: false,
__FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
- __COMPAT__: true
+ __COMPAT__: true,
},
resolve: {
- alias: entries
+ alias: entries,
},
plugins: [codspeedPlugin()],
test: {
globals: true,
setupFiles: 'scripts/setup-vitest.ts',
environmentMatchGlobs: [
- ['packages/{vue,vue-compat,runtime-dom}/**', 'jsdom']
+ ['packages/{vue,vue-compat,runtime-dom}/**', 'jsdom'],
],
sequence: {
- hooks: 'list'
+ hooks: 'list',
},
coverage: {
provider: 'istanbul',
// DOM transitions are tested via e2e so no coverage is collected
'packages/runtime-dom/src/components/Transition*',
// mostly entries
- 'packages/vue-compat/**'
- ]
- }
- }
+ 'packages/vue-compat/**',
+ ],
+ },
+ },
})
export default mergeConfig(config, {
test: {
- include: ['packages/vue/__tests__/e2e/*.spec.ts']
- }
+ include: ['packages/vue/__tests__/e2e/*.spec.ts'],
+ },
})
export default mergeConfig(config, {
test: {
- exclude: [...configDefaults.exclude, '**/e2e/**']
- }
+ exclude: [...configDefaults.exclude, '**/e2e/**'],
+ },
})