mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/minor'
This commit is contained in:
commit
377723d8b2
|
|
@ -3,6 +3,15 @@
|
||||||
const DOMGlobals = ['window', 'document']
|
const DOMGlobals = ['window', 'document']
|
||||||
const NodeGlobals = ['module', 'require']
|
const NodeGlobals = ['module', 'require']
|
||||||
|
|
||||||
|
const banConstEnum = {
|
||||||
|
selector: 'TSEnumDeclaration[const=true]',
|
||||||
|
message:
|
||||||
|
'Please use non-const enums. This project automatically inlines enums.'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('eslint-define-config').ESLintConfig}
|
||||||
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
|
@ -16,6 +25,7 @@ module.exports = {
|
||||||
|
|
||||||
'no-restricted-syntax': [
|
'no-restricted-syntax': [
|
||||||
'error',
|
'error',
|
||||||
|
banConstEnum,
|
||||||
// since we target ES2015 for baseline support, we need to forbid object
|
// since we target ES2015 for baseline support, we need to forbid object
|
||||||
// rest spread usage in destructure as it compiles into a verbose helper.
|
// rest spread usage in destructure as it compiles into a verbose helper.
|
||||||
'ObjectPattern > RestElement',
|
'ObjectPattern > RestElement',
|
||||||
|
|
@ -52,12 +62,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
// Packages targeting Node
|
// Packages targeting Node
|
||||||
{
|
{
|
||||||
files: [
|
files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'],
|
||||||
'packages/{compiler-sfc,compiler-ssr,server-renderer,reactivity-transform}/**'
|
|
||||||
],
|
|
||||||
rules: {
|
rules: {
|
||||||
'no-restricted-globals': ['error', ...DOMGlobals],
|
'no-restricted-globals': ['error', ...DOMGlobals],
|
||||||
'no-restricted-syntax': 'off'
|
'no-restricted-syntax': ['error', banConstEnum]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Private package, browser only + no syntax restrictions
|
// Private package, browser only + no syntax restrictions
|
||||||
|
|
@ -65,7 +73,7 @@ module.exports = {
|
||||||
files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
|
files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-restricted-globals': ['error', ...NodeGlobals],
|
'no-restricted-globals': ['error', ...NodeGlobals],
|
||||||
'no-restricted-syntax': 'off'
|
'no-restricted-syntax': ['error', banConstEnum]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// JavaScript files
|
// JavaScript files
|
||||||
|
|
@ -81,7 +89,7 @@ module.exports = {
|
||||||
files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'],
|
files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-restricted-globals': 'off',
|
'no-restricted-globals': 'off',
|
||||||
'no-restricted-syntax': 'off'
|
'no-restricted-syntax': ['error', banConstEnum]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
69
CHANGELOG.md
69
CHANGELOG.md
|
|
@ -1,3 +1,59 @@
|
||||||
|
# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** update compiler to ^7.23.4 ([#9681](https://github.com/vuejs/core/issues/9681)) ([31f6ebc](https://github.com/vuejs/core/commit/31f6ebc4df84490ed29fb75e7bf4259200eb51f0))
|
||||||
|
* **parser:** directive arg should be undefined on shorthands with no arg ([e49dffc](https://github.com/vuejs/core/commit/e49dffc9ece86bddf094b9ad4ad15eb4856d6277))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **dx:** link errors to docs in prod build ([#9165](https://github.com/vuejs/core/issues/9165)) ([9f8ba98](https://github.com/vuejs/core/commit/9f8ba9821fe166f77e63fa940e9e7e13ec3344fa))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.4.0-alpha.2](https://github.com/vuejs/core/compare/v3.3.9...v3.4.0-alpha.2) (2023-11-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* avoid confusing breakage in @vitejs/plugin-vue ([ceec69c](https://github.com/vuejs/core/commit/ceec69c8ccb96c433a4a506ad2e85e276998bade))
|
||||||
|
* **compiler-core:** fix line/column tracking when fast forwarding ([2e65ea4](https://github.com/vuejs/core/commit/2e65ea481f74db8649df8110a031cbdc98f98c84))
|
||||||
|
* **compiler-sfc:** fix ast reuse for ssr ([fb619cf](https://github.com/vuejs/core/commit/fb619cf9a440239f0ba88e327d10001a6a3c8171))
|
||||||
|
* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([c6083dc](https://github.com/vuejs/core/commit/c6083dcad31f3e9292c687fada9e32f287e2317f))
|
||||||
|
* **compiler-sfc:** use correct compiler when re-parsing in ssr mode ([678378a](https://github.com/vuejs/core/commit/678378afd559481badb486b243722b6287862e09))
|
||||||
|
|
||||||
|
|
||||||
|
* feat!: remove reactivity transform (#9321) ([79b8a09](https://github.com/vuejs/core/commit/79b8a0905bf363bf82edd2096fef10c3db6d9c3c)), closes [#9321](https://github.com/vuejs/core/issues/9321)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler-core:** support specifying root namespace when parsing ([40f72d5](https://github.com/vuejs/core/commit/40f72d5e50b389cb11b7ca13461aa2a75ddacdb4))
|
||||||
|
* **compiler-core:** support v-bind shorthand for key and value with the same name ([#9451](https://github.com/vuejs/core/issues/9451)) ([26399aa](https://github.com/vuejs/core/commit/26399aa6fac1596b294ffeba06bb498d86f5508c))
|
||||||
|
* **compiler:** improve parsing tolerance for language-tools ([41ff68e](https://github.com/vuejs/core/commit/41ff68ea579d933333392146625560359acb728a))
|
||||||
|
* **reactivity:** expose last result for computed getter ([#9497](https://github.com/vuejs/core/issues/9497)) ([48b47a1](https://github.com/vuejs/core/commit/48b47a1ab63577e2dbd91947eea544e3ef185b85))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* avoid sfc source map unnecessary serialization and parsing ([f15d2f6](https://github.com/vuejs/core/commit/f15d2f6cf69c0c39f8dfb5c33122790c68bf92e2))
|
||||||
|
* **codegen:** optimize line / column calculation during codegen ([3be53d9](https://github.com/vuejs/core/commit/3be53d9b974dae1a10eb795cade71ae765e17574))
|
||||||
|
* **codegen:** optimize source map generation ([c11002f](https://github.com/vuejs/core/commit/c11002f16afd243a2b15b546816e73882eea9e4d))
|
||||||
|
* **compiler-sfc:** remove magic-string trim on script ([e8e3ec6](https://github.com/vuejs/core/commit/e8e3ec6ca7392e43975c75b56eaaa711d5ea9410))
|
||||||
|
* **compiler-sfc:** use faster source map addMapping ([50cde7c](https://github.com/vuejs/core/commit/50cde7cfbcc49022ba88f5f69fa9b930b483c282))
|
||||||
|
* optimize away isBuiltInType ([66c0ed0](https://github.com/vuejs/core/commit/66c0ed0a3c1c6f37dafc6b1c52b75c6bf60e3136))
|
||||||
|
* optimize makeMap ([ae6fba9](https://github.com/vuejs/core/commit/ae6fba94954bac6430902f77b0d1113a98a75b18))
|
||||||
|
* optimize position cloning ([2073236](https://github.com/vuejs/core/commit/20732366b9b3530d33b842cf1fc985919afb9317))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* Reactivity Transform was marked deprecated in 3.3 and is now removed in 3.4. This change does not require a major due to the feature being experimental. Users who wish to continue using the feature can do so via the external plugin at https://vue-macros.dev/features/reactivity-transform.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.9](https://github.com/vuejs/core/compare/v3.3.8...v3.3.9) (2023-11-25)
|
## [3.3.9](https://github.com/vuejs/core/compare/v3.3.8...v3.3.9) (2023-11-25)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,6 +103,19 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler-core:** export error message ([#8729](https://github.com/vuejs/core/issues/8729)) ([f7e80ee](https://github.com/vuejs/core/commit/f7e80ee4a065a9eaba98720abf415d9e87756cbd))
|
||||||
|
* **compiler-sfc:** expose resolve type-based props and emits ([#8874](https://github.com/vuejs/core/issues/8874)) ([9e77580](https://github.com/vuejs/core/commit/9e77580c0c2f0d977bd0031a1d43cc334769d433))
|
||||||
|
* export runtime error strings ([#9301](https://github.com/vuejs/core/issues/9301)) ([feb2f2e](https://github.com/vuejs/core/commit/feb2f2edce2d91218a5e9a52c81e322e4033296b))
|
||||||
|
* **reactivity:** more efficient reactivity system ([#5912](https://github.com/vuejs/core/issues/5912)) ([16e06ca](https://github.com/vuejs/core/commit/16e06ca08f5a1e2af3fc7fb35de153dbe0c3087d)), closes [#311](https://github.com/vuejs/core/issues/311) [#1811](https://github.com/vuejs/core/issues/1811) [#6018](https://github.com/vuejs/core/issues/6018) [#7160](https://github.com/vuejs/core/issues/7160) [#8714](https://github.com/vuejs/core/issues/8714) [#9149](https://github.com/vuejs/core/issues/9149) [#9419](https://github.com/vuejs/core/issues/9419) [#9464](https://github.com/vuejs/core/issues/9464)
|
||||||
|
* **runtime-core:** add `once` option to watch ([#9034](https://github.com/vuejs/core/issues/9034)) ([a645e7a](https://github.com/vuejs/core/commit/a645e7aa51006516ba668b3a4365d296eb92ee7d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.7](https://github.com/vuejs/core/compare/v3.3.6...v3.3.7) (2023-10-24)
|
## [3.3.7](https://github.com/vuejs/core/compare/v3.3.6...v3.3.7) (2023-10-24)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
17
package.json
17
package.json
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0-vapor",
|
"version": "0.0.0-vapor",
|
||||||
"packageManager": "pnpm@8.10.5",
|
"packageManager": "pnpm@8.11.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js vue vue-vapor",
|
"dev": "node scripts/dev.js vue vue-vapor",
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
"serve": "serve",
|
"serve": "serve",
|
||||||
"open": "open http://localhost:3000/packages/template-explorer/local.html",
|
"open": "open http://localhost:3000/packages/template-explorer/local.html",
|
||||||
"build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
|
"build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
|
||||||
"build-all-cjs": "node scripts/build.js vue runtime compiler reactivity reactivity-transform shared -af cjs",
|
"build-all-cjs": "node scripts/build.js vue runtime compiler reactivity shared -af cjs",
|
||||||
"build-runtime-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js vue -f esm-browser-runtime",
|
"build-runtime-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js vue -f esm-browser-runtime",
|
||||||
"build-ssr-esm": "node scripts/build.js compiler-sfc server-renderer -f esm-browser",
|
"build-ssr-esm": "node scripts/build.js compiler-sfc server-renderer -f esm-browser",
|
||||||
"build-sfc-playground-self": "cd packages/sfc-playground && npm run build",
|
"build-sfc-playground-self": "cd packages/sfc-playground && npm run build",
|
||||||
|
|
@ -59,8 +59,8 @@
|
||||||
"node": ">=18.12.0"
|
"node": ">=18.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/parser": "^7.23.3",
|
"@babel/parser": "^7.23.4",
|
||||||
"@babel/types": "^7.23.3",
|
"@babel/types": "^7.23.4",
|
||||||
"@rollup/plugin-alias": "^5.0.1",
|
"@rollup/plugin-alias": "^5.0.1",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-json": "^6.0.1",
|
"@rollup/plugin-json": "^6.0.1",
|
||||||
|
|
@ -68,8 +68,10 @@
|
||||||
"@rollup/plugin-replace": "^5.0.4",
|
"@rollup/plugin-replace": "^5.0.4",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^20.9.2",
|
"@types/minimist": "^1.2.5",
|
||||||
"@typescript-eslint/parser": "^6.11.0",
|
"@types/node": "^20.10.0",
|
||||||
|
"@types/semver": "^7.5.5",
|
||||||
|
"@typescript-eslint/parser": "^6.13.0",
|
||||||
"@vitest/coverage-istanbul": "^0.34.6",
|
"@vitest/coverage-istanbul": "^0.34.6",
|
||||||
"@vue/consolidate": "0.17.3",
|
"@vue/consolidate": "0.17.3",
|
||||||
"conventional-changelog-cli": "^4.1.0",
|
"conventional-changelog-cli": "^4.1.0",
|
||||||
|
|
@ -77,6 +79,7 @@
|
||||||
"esbuild": "^0.19.5",
|
"esbuild": "^0.19.5",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^8.54.0",
|
||||||
|
"eslint-define-config": "^1.24.1",
|
||||||
"eslint-plugin-jest": "^27.6.0",
|
"eslint-plugin-jest": "^27.6.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
|
|
@ -104,7 +107,7 @@
|
||||||
"terser": "^5.22.0",
|
"terser": "^5.22.0",
|
||||||
"todomvc-app-css": "^2.4.3",
|
"todomvc-app-css": "^2.4.3",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"tsx": "^4.1.4",
|
"tsx": "^4.5.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vitest": "^0.34.6"
|
"vitest": "^0.34.6"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -40,6 +40,7 @@ import { PatchFlags } from '@vue/shared'
|
||||||
function createRoot(options: Partial<RootNode> = {}): RootNode {
|
function createRoot(options: Partial<RootNode> = {}): RootNode {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.ROOT,
|
type: NodeTypes.ROOT,
|
||||||
|
source: '',
|
||||||
children: [],
|
children: [],
|
||||||
helpers: new Set(),
|
helpers: new Set(),
|
||||||
components: [],
|
components: [],
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -56,7 +56,6 @@ export function createElementWithCodegen(
|
||||||
ns: Namespaces.HTML,
|
ns: Namespaces.HTML,
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
tagType: ElementTypes.ELEMENT,
|
tagType: ElementTypes.ELEMENT,
|
||||||
isSelfClosing: false,
|
|
||||||
props: [],
|
props: [],
|
||||||
children: [],
|
children: [],
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { baseParse } from '../src/parse'
|
import { baseParse } from '../src/parser'
|
||||||
import { transform, NodeTransform } from '../src/transform'
|
import { transform, NodeTransform } from '../src/transform'
|
||||||
import {
|
import {
|
||||||
ElementNode,
|
ElementNode,
|
||||||
|
|
|
||||||
|
|
@ -1195,25 +1195,13 @@ describe('compiler: element transform', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO remove in 3.4
|
test('is casting', () => {
|
||||||
test('v-is', () => {
|
const { node, root } = parseWithBind(`<div is="vue:foo" />`)
|
||||||
const { node, root } = parseWithBind(`<div v-is="'foo'" />`)
|
expect(root.helpers).toContain(RESOLVE_COMPONENT)
|
||||||
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
|
|
||||||
expect(node).toMatchObject({
|
expect(node).toMatchObject({
|
||||||
tag: {
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: RESOLVE_DYNAMIC_COMPONENT,
|
tag: '_component_foo'
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
|
||||||
content: `'foo'`,
|
|
||||||
isStatic: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// should skip v-is runtime check
|
|
||||||
directives: undefined
|
|
||||||
})
|
})
|
||||||
expect('v-is="component-name" has been deprecated').toHaveBeenWarned()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// #3934
|
// #3934
|
||||||
|
|
|
||||||
|
|
@ -128,51 +128,24 @@ describe('compiler: expression transform', () => {
|
||||||
{
|
{
|
||||||
content: `_ctx.foo`,
|
content: `_ctx.foo`,
|
||||||
loc: {
|
loc: {
|
||||||
source: `foo`,
|
start: { offset: 3, line: 1, column: 4 },
|
||||||
start: {
|
end: { offset: 6, line: 1, column: 7 }
|
||||||
offset: 3,
|
|
||||||
line: 1,
|
|
||||||
column: 4
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
offset: 6,
|
|
||||||
line: 1,
|
|
||||||
column: 7
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
`(`,
|
`(`,
|
||||||
{
|
{
|
||||||
content: `_ctx.baz`,
|
content: `_ctx.baz`,
|
||||||
loc: {
|
loc: {
|
||||||
source: `baz`,
|
start: { offset: 7, line: 1, column: 8 },
|
||||||
start: {
|
end: { offset: 10, line: 1, column: 11 }
|
||||||
offset: 7,
|
|
||||||
line: 1,
|
|
||||||
column: 8
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
offset: 10,
|
|
||||||
line: 1,
|
|
||||||
column: 11
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
` + 1, { key: `,
|
` + 1, { key: `,
|
||||||
{
|
{
|
||||||
content: `_ctx.kuz`,
|
content: `_ctx.kuz`,
|
||||||
loc: {
|
loc: {
|
||||||
source: `kuz`,
|
start: { offset: 23, line: 1, column: 24 },
|
||||||
start: {
|
end: { offset: 26, line: 1, column: 27 }
|
||||||
offset: 23,
|
|
||||||
line: 1,
|
|
||||||
column: 24
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
offset: 26,
|
|
||||||
line: 1,
|
|
||||||
column: 27
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
` })`
|
` })`
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,6 @@ describe('compiler: transform <slot> outlets', () => {
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
code: ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
loc: {
|
loc: {
|
||||||
source: `v-foo`,
|
|
||||||
start: {
|
start: {
|
||||||
offset: index,
|
offset: index,
|
||||||
line: 1,
|
line: 1,
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,44 @@ describe('compiler: transform v-bind', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('no expression', () => {
|
||||||
|
const node = parseWithVBind(`<div v-bind:id />`)
|
||||||
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
|
expect(props.properties[0]).toMatchObject({
|
||||||
|
key: {
|
||||||
|
content: `id`,
|
||||||
|
isStatic: true,
|
||||||
|
loc: {
|
||||||
|
start: { line: 1, column: 13, offset: 12 },
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('no expression (shorthand)', () => {
|
||||||
|
const node = parseWithVBind(`<div :id />`)
|
||||||
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
|
expect(props.properties[0]).toMatchObject({
|
||||||
|
key: {
|
||||||
|
content: `id`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `id`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('dynamic arg', () => {
|
test('dynamic arg', () => {
|
||||||
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
|
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
|
||||||
const props = (node.codegenNode as VNodeCall).props as CallExpression
|
const props = (node.codegenNode as VNodeCall).props as CallExpression
|
||||||
|
|
@ -98,9 +136,9 @@ describe('compiler: transform v-bind', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should error if no expression', () => {
|
test('should error if empty expression', () => {
|
||||||
const onError = vi.fn()
|
const onError = vi.fn()
|
||||||
const node = parseWithVBind(`<div v-bind:arg />`, { onError })
|
const node = parseWithVBind(`<div v-bind:arg="" />`, { onError })
|
||||||
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
|
code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
|
||||||
|
|
@ -111,7 +149,7 @@ describe('compiler: transform v-bind', () => {
|
||||||
},
|
},
|
||||||
end: {
|
end: {
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 16
|
column: 19
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -142,6 +180,21 @@ describe('compiler: transform v-bind', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('.camel modifier w/ no expression', () => {
|
||||||
|
const node = parseWithVBind(`<div v-bind:foo-bar.camel />`)
|
||||||
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
|
expect(props.properties[0]).toMatchObject({
|
||||||
|
key: {
|
||||||
|
content: `fooBar`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `fooBar`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('.camel modifier w/ dynamic arg', () => {
|
test('.camel modifier w/ dynamic arg', () => {
|
||||||
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
|
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
|
||||||
const props = (node.codegenNode as VNodeCall).props as CallExpression
|
const props = (node.codegenNode as VNodeCall).props as CallExpression
|
||||||
|
|
@ -219,6 +272,21 @@ describe('compiler: transform v-bind', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('.prop modifier w/ no expression', () => {
|
||||||
|
const node = parseWithVBind(`<div v-bind:fooBar.prop />`)
|
||||||
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
|
expect(props.properties[0]).toMatchObject({
|
||||||
|
key: {
|
||||||
|
content: `.fooBar`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `fooBar`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('.prop modifier w/ dynamic arg', () => {
|
test('.prop modifier w/ dynamic arg', () => {
|
||||||
const node = parseWithVBind(`<div v-bind:[fooBar].prop="id"/>`)
|
const node = parseWithVBind(`<div v-bind:[fooBar].prop="id"/>`)
|
||||||
const props = (node.codegenNode as VNodeCall).props as CallExpression
|
const props = (node.codegenNode as VNodeCall).props as CallExpression
|
||||||
|
|
@ -296,6 +364,21 @@ describe('compiler: transform v-bind', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('.prop modifier (shortband) w/ no expression', () => {
|
||||||
|
const node = parseWithVBind(`<div .fooBar />`)
|
||||||
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
|
expect(props.properties[0]).toMatchObject({
|
||||||
|
key: {
|
||||||
|
content: `.fooBar`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `fooBar`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('.attr modifier', () => {
|
test('.attr modifier', () => {
|
||||||
const node = parseWithVBind(`<div v-bind:foo-bar.attr="id"/>`)
|
const node = parseWithVBind(`<div v-bind:foo-bar.attr="id"/>`)
|
||||||
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
|
|
@ -310,4 +393,19 @@ describe('compiler: transform v-bind', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('.attr modifier w/ no expression', () => {
|
||||||
|
const node = parseWithVBind(`<div v-bind:foo-bar.attr />`)
|
||||||
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
|
expect(props.properties[0]).toMatchObject({
|
||||||
|
key: {
|
||||||
|
content: `^foo-bar`,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `fooBar`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { baseParse as parse } from '../../src/parse'
|
import { baseParse as parse } from '../../src/parser'
|
||||||
import { transform } from '../../src/transform'
|
import { transform } from '../../src/transform'
|
||||||
import { transformIf } from '../../src/transforms/vIf'
|
import { transformIf } from '../../src/transforms/vIf'
|
||||||
import { transformFor } from '../../src/transforms/vFor'
|
import { transformFor } from '../../src/transforms/vFor'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { baseParse as parse } from '../../src/parse'
|
import { baseParse as parse } from '../../src/parser'
|
||||||
import { transform } from '../../src/transform'
|
import { transform } from '../../src/transform'
|
||||||
import { transformIf } from '../../src/transforms/vIf'
|
import { transformIf } from '../../src/transforms/vIf'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
|
|
|
||||||
|
|
@ -850,7 +850,6 @@ describe('compiler: transform component slots', () => {
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
||||||
loc: {
|
loc: {
|
||||||
source: `bar`,
|
|
||||||
start: {
|
start: {
|
||||||
offset: index,
|
offset: index,
|
||||||
line: 1,
|
line: 1,
|
||||||
|
|
@ -873,7 +872,6 @@ describe('compiler: transform component slots', () => {
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
|
code: ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
|
||||||
loc: {
|
loc: {
|
||||||
source: `#foo`,
|
|
||||||
start: {
|
start: {
|
||||||
offset: index,
|
offset: index,
|
||||||
line: 1,
|
line: 1,
|
||||||
|
|
@ -896,7 +894,6 @@ describe('compiler: transform component slots', () => {
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE,
|
code: ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE,
|
||||||
loc: {
|
loc: {
|
||||||
source: `#foo`,
|
|
||||||
start: {
|
start: {
|
||||||
offset: index,
|
offset: index,
|
||||||
line: 1,
|
line: 1,
|
||||||
|
|
@ -919,7 +916,6 @@ describe('compiler: transform component slots', () => {
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.X_V_SLOT_MISPLACED,
|
code: ErrorCodes.X_V_SLOT_MISPLACED,
|
||||||
loc: {
|
loc: {
|
||||||
source: `v-slot`,
|
|
||||||
start: {
|
start: {
|
||||||
offset: index,
|
offset: index,
|
||||||
line: 1,
|
line: 1,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { TransformContext } from '../src'
|
import { TransformContext } from '../src'
|
||||||
import { Position } from '../src/ast'
|
import { Position } from '../src/ast'
|
||||||
import {
|
import {
|
||||||
getInnerRange,
|
|
||||||
advancePositionWithClone,
|
advancePositionWithClone,
|
||||||
isMemberExpressionNode,
|
isMemberExpressionNode,
|
||||||
isMemberExpressionBrowser,
|
isMemberExpressionBrowser,
|
||||||
|
|
@ -41,32 +40,6 @@ describe('advancePositionWithClone', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getInnerRange', () => {
|
|
||||||
const loc1 = {
|
|
||||||
source: 'foo\nbar\nbaz',
|
|
||||||
start: p(1, 1, 0),
|
|
||||||
end: p(3, 3, 11)
|
|
||||||
}
|
|
||||||
|
|
||||||
test('at start', () => {
|
|
||||||
const loc2 = getInnerRange(loc1, 0, 4)
|
|
||||||
expect(loc2.start).toEqual(loc1.start)
|
|
||||||
expect(loc2.end.column).toBe(1)
|
|
||||||
expect(loc2.end.line).toBe(2)
|
|
||||||
expect(loc2.end.offset).toBe(4)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('in between', () => {
|
|
||||||
const loc2 = getInnerRange(loc1, 4, 3)
|
|
||||||
expect(loc2.start.column).toBe(1)
|
|
||||||
expect(loc2.start.line).toBe(2)
|
|
||||||
expect(loc2.start.offset).toBe(4)
|
|
||||||
expect(loc2.end.column).toBe(4)
|
|
||||||
expect(loc2.end.line).toBe(2)
|
|
||||||
expect(loc2.end.offset).toBe(7)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('isMemberExpression', () => {
|
describe('isMemberExpression', () => {
|
||||||
function commonAssertions(fn: (str: string) => boolean) {
|
function commonAssertions(fn: (str: string) => boolean) {
|
||||||
// should work
|
// should work
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-core",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.3.9",
|
"version": "3.4.0-alpha.3",
|
||||||
"description": "@vue/compiler-core",
|
"description": "@vue/compiler-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-core.esm-bundler.js",
|
"module": "dist/compiler-core.esm-bundler.js",
|
||||||
|
|
@ -32,12 +32,13 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-core#readme",
|
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.3",
|
"@babel/parser": "^7.23.4",
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
|
"entities": "^4.5.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.23.3"
|
"@babel/types": "^7.23.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { isString } from '@vue/shared'
|
import { isString } from '@vue/shared'
|
||||||
import { ForParseResult } from './transforms/vFor'
|
|
||||||
import {
|
import {
|
||||||
RENDER_SLOT,
|
RENDER_SLOT,
|
||||||
CREATE_SLOTS,
|
CREATE_SLOTS,
|
||||||
|
|
@ -17,15 +16,16 @@ import { PropsExpression } from './transforms/transformElement'
|
||||||
import { ImportItem, TransformContext } from './transform'
|
import { ImportItem, TransformContext } from './transform'
|
||||||
|
|
||||||
// Vue template is a platform-agnostic superset of HTML (syntax only).
|
// Vue template is a platform-agnostic superset of HTML (syntax only).
|
||||||
// More namespaces like SVG and MathML are declared by platform specific
|
// More namespaces can be declared by platform specific compilers.
|
||||||
// compilers.
|
|
||||||
export type Namespace = number
|
export type Namespace = number
|
||||||
|
|
||||||
export const enum Namespaces {
|
export enum Namespaces {
|
||||||
HTML
|
HTML,
|
||||||
|
SVG,
|
||||||
|
MATH_ML
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum NodeTypes {
|
export enum NodeTypes {
|
||||||
ROOT,
|
ROOT,
|
||||||
ELEMENT,
|
ELEMENT,
|
||||||
TEXT,
|
TEXT,
|
||||||
|
|
@ -59,7 +59,7 @@ export const enum NodeTypes {
|
||||||
JS_RETURN_STATEMENT
|
JS_RETURN_STATEMENT
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ElementTypes {
|
export enum ElementTypes {
|
||||||
ELEMENT,
|
ELEMENT,
|
||||||
COMPONENT,
|
COMPONENT,
|
||||||
SLOT,
|
SLOT,
|
||||||
|
|
@ -102,6 +102,7 @@ export type TemplateChildNode =
|
||||||
|
|
||||||
export interface RootNode extends Node {
|
export interface RootNode extends Node {
|
||||||
type: NodeTypes.ROOT
|
type: NodeTypes.ROOT
|
||||||
|
source: string
|
||||||
children: TemplateChildNode[]
|
children: TemplateChildNode[]
|
||||||
helpers: Set<symbol>
|
helpers: Set<symbol>
|
||||||
components: string[]
|
components: string[]
|
||||||
|
|
@ -112,6 +113,7 @@ export interface RootNode extends Node {
|
||||||
temps: number
|
temps: number
|
||||||
ssrHelpers?: symbol[]
|
ssrHelpers?: symbol[]
|
||||||
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
|
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
|
||||||
|
transformed?: boolean
|
||||||
|
|
||||||
// v2 compat only
|
// v2 compat only
|
||||||
filters?: string[]
|
filters?: string[]
|
||||||
|
|
@ -128,9 +130,10 @@ export interface BaseElementNode extends Node {
|
||||||
ns: Namespace
|
ns: Namespace
|
||||||
tag: string
|
tag: string
|
||||||
tagType: ElementTypes
|
tagType: ElementTypes
|
||||||
isSelfClosing: boolean
|
|
||||||
props: Array<AttributeNode | DirectiveNode>
|
props: Array<AttributeNode | DirectiveNode>
|
||||||
children: TemplateChildNode[]
|
children: TemplateChildNode[]
|
||||||
|
isSelfClosing?: boolean
|
||||||
|
innerLoc?: SourceLocation // only for SFC root level elements
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlainElementNode extends BaseElementNode {
|
export interface PlainElementNode extends BaseElementNode {
|
||||||
|
|
@ -182,19 +185,28 @@ export interface CommentNode extends Node {
|
||||||
export interface AttributeNode extends Node {
|
export interface AttributeNode extends Node {
|
||||||
type: NodeTypes.ATTRIBUTE
|
type: NodeTypes.ATTRIBUTE
|
||||||
name: string
|
name: string
|
||||||
|
nameLoc: SourceLocation
|
||||||
value: TextNode | undefined
|
value: TextNode | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DirectiveNode extends Node {
|
export interface DirectiveNode extends Node {
|
||||||
type: NodeTypes.DIRECTIVE
|
type: NodeTypes.DIRECTIVE
|
||||||
|
/**
|
||||||
|
* the normalized name without prefix or shorthands, e.g. "bind", "on"
|
||||||
|
*/
|
||||||
name: string
|
name: string
|
||||||
|
/**
|
||||||
|
* the raw attribute name, preserving shorthand, and including arg & modifiers
|
||||||
|
* this is only used during parse.
|
||||||
|
*/
|
||||||
|
rawName?: string
|
||||||
exp: ExpressionNode | undefined
|
exp: ExpressionNode | undefined
|
||||||
arg: ExpressionNode | undefined
|
arg: ExpressionNode | undefined
|
||||||
modifiers: string[]
|
modifiers: string[]
|
||||||
/**
|
/**
|
||||||
* optional property to cache the expression parse result for v-for
|
* optional property to cache the expression parse result for v-for
|
||||||
*/
|
*/
|
||||||
parseResult?: ForParseResult
|
forParseResult?: ForParseResult
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -202,7 +214,7 @@ export interface DirectiveNode extends Node {
|
||||||
* Higher levels implies lower levels. e.g. a node that can be stringified
|
* Higher levels implies lower levels. e.g. a node that can be stringified
|
||||||
* can always be hoisted and skipped for patch.
|
* can always be hoisted and skipped for patch.
|
||||||
*/
|
*/
|
||||||
export const enum ConstantTypes {
|
export enum ConstantTypes {
|
||||||
NOT_CONSTANT = 0,
|
NOT_CONSTANT = 0,
|
||||||
CAN_SKIP_PATCH,
|
CAN_SKIP_PATCH,
|
||||||
CAN_HOIST,
|
CAN_HOIST,
|
||||||
|
|
@ -276,6 +288,14 @@ export interface ForNode extends Node {
|
||||||
codegenNode?: ForCodegenNode
|
codegenNode?: ForCodegenNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ForParseResult {
|
||||||
|
source: ExpressionNode
|
||||||
|
value: ExpressionNode | undefined
|
||||||
|
key: ExpressionNode | undefined
|
||||||
|
index: ExpressionNode | undefined
|
||||||
|
finalized: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface TextCallNode extends Node {
|
export interface TextCallNode extends Node {
|
||||||
type: NodeTypes.TEXT_CALL
|
type: NodeTypes.TEXT_CALL
|
||||||
content: TextNode | InterpolationNode | CompoundExpressionNode
|
content: TextNode | InterpolationNode | CompoundExpressionNode
|
||||||
|
|
@ -547,17 +567,18 @@ export interface ForIteratorExpression extends FunctionExpression {
|
||||||
// associated with template nodes, so their source locations are just a stub.
|
// associated with template nodes, so their source locations are just a stub.
|
||||||
// Container types like CompoundExpression also don't need a real location.
|
// Container types like CompoundExpression also don't need a real location.
|
||||||
export const locStub: SourceLocation = {
|
export const locStub: SourceLocation = {
|
||||||
source: '',
|
|
||||||
start: { line: 1, column: 1, offset: 0 },
|
start: { line: 1, column: 1, offset: 0 },
|
||||||
end: { line: 1, column: 1, offset: 0 }
|
end: { line: 1, column: 1, offset: 0 },
|
||||||
|
source: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRoot(
|
export function createRoot(
|
||||||
children: TemplateChildNode[],
|
children: TemplateChildNode[],
|
||||||
loc = locStub
|
source = ''
|
||||||
): RootNode {
|
): RootNode {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.ROOT,
|
type: NodeTypes.ROOT,
|
||||||
|
source,
|
||||||
children,
|
children,
|
||||||
helpers: new Set(),
|
helpers: new Set(),
|
||||||
components: [],
|
components: [],
|
||||||
|
|
@ -567,7 +588,7 @@ export function createRoot(
|
||||||
cached: 0,
|
cached: 0,
|
||||||
temps: 0,
|
temps: 0,
|
||||||
codegenNode: undefined,
|
codegenNode: undefined,
|
||||||
loc
|
loc: locStub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,13 @@ export interface CodegenResult {
|
||||||
map?: RawSourceMap
|
map?: RawSourceMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NewlineType {
|
||||||
|
Start = 0,
|
||||||
|
End = -1,
|
||||||
|
None = -2,
|
||||||
|
Unknown = -3
|
||||||
|
}
|
||||||
|
|
||||||
export interface CodegenContext
|
export interface CodegenContext
|
||||||
extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
|
extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
|
||||||
source: string
|
source: string
|
||||||
|
|
@ -80,7 +87,7 @@ export interface CodegenContext
|
||||||
pure: boolean
|
pure: boolean
|
||||||
map?: SourceMapGenerator
|
map?: SourceMapGenerator
|
||||||
helper(key: symbol): string
|
helper(key: symbol): string
|
||||||
push(code: string, node?: CodegenNode): void
|
push(code: string, newlineIndex?: number, node?: CodegenNode): void
|
||||||
indent(): void
|
indent(): void
|
||||||
deindent(withoutNewLine?: boolean): void
|
deindent(withoutNewLine?: boolean): void
|
||||||
newline(): void
|
newline(): void
|
||||||
|
|
@ -116,7 +123,7 @@ function createCodegenContext(
|
||||||
ssr,
|
ssr,
|
||||||
isTS,
|
isTS,
|
||||||
inSSR,
|
inSSR,
|
||||||
source: ast.loc.source,
|
source: ast.source,
|
||||||
code: ``,
|
code: ``,
|
||||||
column: 1,
|
column: 1,
|
||||||
line: 1,
|
line: 1,
|
||||||
|
|
@ -127,7 +134,7 @@ function createCodegenContext(
|
||||||
helper(key) {
|
helper(key) {
|
||||||
return `_${helperNameMap[key]}`
|
return `_${helperNameMap[key]}`
|
||||||
},
|
},
|
||||||
push(code, node) {
|
push(code, newlineIndex = NewlineType.None, node) {
|
||||||
context.code += code
|
context.code += code
|
||||||
if (!__BROWSER__ && context.map) {
|
if (!__BROWSER__ && context.map) {
|
||||||
if (node) {
|
if (node) {
|
||||||
|
|
@ -140,7 +147,41 @@ function createCodegenContext(
|
||||||
}
|
}
|
||||||
addMapping(node.loc.start, name)
|
addMapping(node.loc.start, name)
|
||||||
}
|
}
|
||||||
advancePositionWithMutation(context, code)
|
if (newlineIndex === NewlineType.Unknown) {
|
||||||
|
// multiple newlines, full iteration
|
||||||
|
advancePositionWithMutation(context, code)
|
||||||
|
} else {
|
||||||
|
// fast paths
|
||||||
|
context.offset += code.length
|
||||||
|
if (newlineIndex === NewlineType.None) {
|
||||||
|
// no newlines; fast path to avoid newline detection
|
||||||
|
if (__TEST__ && code.includes('\n')) {
|
||||||
|
throw new Error(
|
||||||
|
`CodegenContext.push() called newlineIndex: none, but contains` +
|
||||||
|
`newlines: ${code.replace(/\n/g, '\\n')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
context.column += code.length
|
||||||
|
} else {
|
||||||
|
// single newline at known index
|
||||||
|
if (newlineIndex === NewlineType.End) {
|
||||||
|
newlineIndex = code.length - 1
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
__TEST__ &&
|
||||||
|
(code.charAt(newlineIndex) !== '\n' ||
|
||||||
|
code.slice(0, newlineIndex).includes('\n') ||
|
||||||
|
code.slice(newlineIndex + 1).includes('\n'))
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
|
||||||
|
`but does not conform: ${code.replace(/\n/g, '\\n')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
context.line++
|
||||||
|
context.column = code.length - newlineIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
if (node && node.loc !== locStub) {
|
if (node && node.loc !== locStub) {
|
||||||
addMapping(node.loc.end)
|
addMapping(node.loc.end)
|
||||||
}
|
}
|
||||||
|
|
@ -162,28 +203,31 @@ function createCodegenContext(
|
||||||
}
|
}
|
||||||
|
|
||||||
function newline(n: number) {
|
function newline(n: number) {
|
||||||
context.push('\n' + ` `.repeat(n))
|
context.push('\n' + ` `.repeat(n), NewlineType.Start)
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMapping(loc: Position, name?: string) {
|
function addMapping(loc: Position, name: string | null = null) {
|
||||||
context.map!.addMapping({
|
// we use the private property to directly add the mapping
|
||||||
name,
|
// because the addMapping() implementation in source-map-js has a bunch of
|
||||||
source: context.filename,
|
// unnecessary arg and validation checks that are pure overhead in our case.
|
||||||
original: {
|
const { _names, _mappings } = context.map!
|
||||||
line: loc.line,
|
if (name !== null && !_names.has(name)) _names.add(name)
|
||||||
column: loc.column - 1 // source-map column is 0 based
|
_mappings.add({
|
||||||
},
|
originalLine: loc.line,
|
||||||
generated: {
|
originalColumn: loc.column - 1, // source-map column is 0 based
|
||||||
line: context.line,
|
generatedLine: context.line,
|
||||||
column: context.column - 1
|
generatedColumn: context.column - 1,
|
||||||
}
|
source: filename,
|
||||||
|
// @ts-ignore it is possible to be null
|
||||||
|
name
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!__BROWSER__ && sourceMap) {
|
if (!__BROWSER__ && sourceMap) {
|
||||||
// lazy require source-map implementation, only in non-browser builds
|
// lazy require source-map implementation, only in non-browser builds
|
||||||
context.map = new SourceMapGenerator()
|
context.map = new SourceMapGenerator()
|
||||||
context.map!.setSourceContent(filename, context.source)
|
context.map.setSourceContent(filename, context.source)
|
||||||
|
context.map._sources.add(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
@ -250,8 +294,10 @@ export function generate(
|
||||||
// function mode const declarations should be inside with block
|
// function mode const declarations should be inside with block
|
||||||
// also they should be renamed to avoid collision with user properties
|
// also they should be renamed to avoid collision with user properties
|
||||||
if (hasHelpers) {
|
if (hasHelpers) {
|
||||||
push(`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue`)
|
push(
|
||||||
push(`\n`)
|
`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
|
||||||
|
NewlineType.End
|
||||||
|
)
|
||||||
newline()
|
newline()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +328,7 @@ export function generate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ast.components.length || ast.directives.length || ast.temps) {
|
if (ast.components.length || ast.directives.length || ast.temps) {
|
||||||
push(`\n`)
|
push(`\n`, NewlineType.Start)
|
||||||
newline()
|
newline()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,8 +354,7 @@ export function generate(
|
||||||
ast,
|
ast,
|
||||||
code: context.code,
|
code: context.code,
|
||||||
preamble: isSetupInlined ? preambleContext.code : ``,
|
preamble: isSetupInlined ? preambleContext.code : ``,
|
||||||
// SourceMapGenerator does have toJSON() method but it's not in the types
|
map: context.map ? context.map.toJSON() : undefined
|
||||||
map: context.map ? (context.map as any).toJSON() : undefined
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -334,11 +379,14 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
||||||
const helpers = Array.from(ast.helpers)
|
const helpers = Array.from(ast.helpers)
|
||||||
if (helpers.length > 0) {
|
if (helpers.length > 0) {
|
||||||
if (!__BROWSER__ && prefixIdentifiers) {
|
if (!__BROWSER__ && prefixIdentifiers) {
|
||||||
push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`)
|
push(
|
||||||
|
`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`,
|
||||||
|
NewlineType.End
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// "with" mode.
|
// "with" mode.
|
||||||
// save Vue in a separate variable to avoid collision
|
// save Vue in a separate variable to avoid collision
|
||||||
push(`const _Vue = ${VueBinding}\n`)
|
push(`const _Vue = ${VueBinding}\n`, NewlineType.End)
|
||||||
// in "with" mode, helpers are declared inside the with block to avoid
|
// in "with" mode, helpers are declared inside the with block to avoid
|
||||||
// has check cost, but hoists are lifted out of the function - we need
|
// has check cost, but hoists are lifted out of the function - we need
|
||||||
// to provide the helper here.
|
// to provide the helper here.
|
||||||
|
|
@ -353,7 +401,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
||||||
.filter(helper => helpers.includes(helper))
|
.filter(helper => helpers.includes(helper))
|
||||||
.map(aliasHelper)
|
.map(aliasHelper)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
push(`const { ${staticHelpers} } = _Vue\n`)
|
push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -363,7 +411,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
||||||
push(
|
push(
|
||||||
`const { ${ast.ssrHelpers
|
`const { ${ast.ssrHelpers
|
||||||
.map(aliasHelper)
|
.map(aliasHelper)
|
||||||
.join(', ')} } = require("${ssrRuntimeModuleName}")\n`
|
.join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
|
||||||
|
NewlineType.End
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
genHoists(ast.hoists, context)
|
genHoists(ast.hoists, context)
|
||||||
|
|
@ -402,18 +451,21 @@ function genModulePreamble(
|
||||||
push(
|
push(
|
||||||
`import { ${helpers
|
`import { ${helpers
|
||||||
.map(s => helperNameMap[s])
|
.map(s => helperNameMap[s])
|
||||||
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
|
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
|
||||||
|
NewlineType.End
|
||||||
)
|
)
|
||||||
push(
|
push(
|
||||||
`\n// Binding optimization for webpack code-split\nconst ${helpers
|
`\n// Binding optimization for webpack code-split\nconst ${helpers
|
||||||
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
|
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
|
||||||
.join(', ')}\n`
|
.join(', ')}\n`,
|
||||||
|
NewlineType.End
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
push(
|
push(
|
||||||
`import { ${helpers
|
`import { ${helpers
|
||||||
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
|
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
|
||||||
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
|
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
|
||||||
|
NewlineType.End
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -422,7 +474,8 @@ function genModulePreamble(
|
||||||
push(
|
push(
|
||||||
`import { ${ast.ssrHelpers
|
`import { ${ast.ssrHelpers
|
||||||
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
|
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
|
||||||
.join(', ')} } from "${ssrRuntimeModuleName}"\n`
|
.join(', ')} } from "${ssrRuntimeModuleName}"\n`,
|
||||||
|
NewlineType.End
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -554,7 +607,7 @@ function genNodeList(
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const node = nodes[i]
|
const node = nodes[i]
|
||||||
if (isString(node)) {
|
if (isString(node)) {
|
||||||
push(node)
|
push(node, NewlineType.Unknown)
|
||||||
} else if (isArray(node)) {
|
} else if (isArray(node)) {
|
||||||
genNodeListAsArray(node, context)
|
genNodeListAsArray(node, context)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -573,7 +626,7 @@ function genNodeList(
|
||||||
|
|
||||||
function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
|
function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
|
||||||
if (isString(node)) {
|
if (isString(node)) {
|
||||||
context.push(node)
|
context.push(node, NewlineType.Unknown)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (isSymbol(node)) {
|
if (isSymbol(node)) {
|
||||||
|
|
@ -671,12 +724,16 @@ function genText(
|
||||||
node: TextNode | SimpleExpressionNode,
|
node: TextNode | SimpleExpressionNode,
|
||||||
context: CodegenContext
|
context: CodegenContext
|
||||||
) {
|
) {
|
||||||
context.push(JSON.stringify(node.content), node)
|
context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
|
function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
|
||||||
const { content, isStatic } = node
|
const { content, isStatic } = node
|
||||||
context.push(isStatic ? JSON.stringify(content) : content, node)
|
context.push(
|
||||||
|
isStatic ? JSON.stringify(content) : content,
|
||||||
|
NewlineType.Unknown,
|
||||||
|
node
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
|
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
|
||||||
|
|
@ -694,7 +751,7 @@ function genCompoundExpression(
|
||||||
for (let i = 0; i < node.children!.length; i++) {
|
for (let i = 0; i < node.children!.length; i++) {
|
||||||
const child = node.children![i]
|
const child = node.children![i]
|
||||||
if (isString(child)) {
|
if (isString(child)) {
|
||||||
context.push(child)
|
context.push(child, NewlineType.Unknown)
|
||||||
} else {
|
} else {
|
||||||
genNode(child, context)
|
genNode(child, context)
|
||||||
}
|
}
|
||||||
|
|
@ -715,9 +772,9 @@ function genExpressionAsPropertyKey(
|
||||||
const text = isSimpleIdentifier(node.content)
|
const text = isSimpleIdentifier(node.content)
|
||||||
? node.content
|
? node.content
|
||||||
: JSON.stringify(node.content)
|
: JSON.stringify(node.content)
|
||||||
push(text, node)
|
push(text, NewlineType.None, node)
|
||||||
} else {
|
} else {
|
||||||
push(`[${node.content}]`, node)
|
push(`[${node.content}]`, NewlineType.Unknown, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,7 +783,11 @@ function genComment(node: CommentNode, context: CodegenContext) {
|
||||||
if (pure) {
|
if (pure) {
|
||||||
push(PURE_ANNOTATION)
|
push(PURE_ANNOTATION)
|
||||||
}
|
}
|
||||||
push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
|
push(
|
||||||
|
`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
|
||||||
|
NewlineType.Unknown,
|
||||||
|
node
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
||||||
|
|
@ -754,7 +815,7 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
||||||
const callHelper: symbol = isBlock
|
const callHelper: symbol = isBlock
|
||||||
? getVNodeBlockHelper(context.inSSR, isComponent)
|
? getVNodeBlockHelper(context.inSSR, isComponent)
|
||||||
: getVNodeHelper(context.inSSR, isComponent)
|
: getVNodeHelper(context.inSSR, isComponent)
|
||||||
push(helper(callHelper) + `(`, node)
|
push(helper(callHelper) + `(`, NewlineType.None, node)
|
||||||
genNodeList(
|
genNodeList(
|
||||||
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
|
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
|
||||||
context
|
context
|
||||||
|
|
@ -785,7 +846,7 @@ function genCallExpression(node: CallExpression, context: CodegenContext) {
|
||||||
if (pure) {
|
if (pure) {
|
||||||
push(PURE_ANNOTATION)
|
push(PURE_ANNOTATION)
|
||||||
}
|
}
|
||||||
push(callee + `(`, node)
|
push(callee + `(`, NewlineType.None, node)
|
||||||
genNodeList(node.arguments, context)
|
genNodeList(node.arguments, context)
|
||||||
push(`)`)
|
push(`)`)
|
||||||
}
|
}
|
||||||
|
|
@ -794,7 +855,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
|
||||||
const { push, indent, deindent, newline } = context
|
const { push, indent, deindent, newline } = context
|
||||||
const { properties } = node
|
const { properties } = node
|
||||||
if (!properties.length) {
|
if (!properties.length) {
|
||||||
push(`{}`, node)
|
push(`{}`, NewlineType.None, node)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const multilines =
|
const multilines =
|
||||||
|
|
@ -834,7 +895,7 @@ function genFunctionExpression(
|
||||||
// wrap slot functions with owner context
|
// wrap slot functions with owner context
|
||||||
push(`_${helperNameMap[WITH_CTX]}(`)
|
push(`_${helperNameMap[WITH_CTX]}(`)
|
||||||
}
|
}
|
||||||
push(`(`, node)
|
push(`(`, NewlineType.None, node)
|
||||||
if (isArray(params)) {
|
if (isArray(params)) {
|
||||||
genNodeList(params, context)
|
genNodeList(params, context)
|
||||||
} else if (params) {
|
} else if (params) {
|
||||||
|
|
@ -934,7 +995,7 @@ function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
|
||||||
for (let i = 0; i < l; i++) {
|
for (let i = 0; i < l; i++) {
|
||||||
const e = node.elements[i]
|
const e = node.elements[i]
|
||||||
if (isString(e)) {
|
if (isString(e)) {
|
||||||
push(e.replace(/(`|\$|\\)/g, '\\$1'))
|
push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown)
|
||||||
} else {
|
} else {
|
||||||
push('${')
|
push('${')
|
||||||
if (multilines) indent()
|
if (multilines) indent()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { SourceLocation } from '../ast'
|
import { SourceLocation } from '../ast'
|
||||||
import { CompilerError } from '../errors'
|
import { CompilerError } from '../errors'
|
||||||
import { ParserContext } from '../parse'
|
import { MergedParserOptions } from '../parser'
|
||||||
import { TransformContext } from '../transform'
|
import { TransformContext } from '../transform'
|
||||||
|
|
||||||
export type CompilerCompatConfig = Partial<
|
export type CompilerCompatConfig = Partial<
|
||||||
|
|
@ -13,10 +13,9 @@ export interface CompilerCompatOptions {
|
||||||
compatConfig?: CompilerCompatConfig
|
compatConfig?: CompilerCompatConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum CompilerDeprecationTypes {
|
export enum CompilerDeprecationTypes {
|
||||||
COMPILER_IS_ON_ELEMENT = 'COMPILER_IS_ON_ELEMENT',
|
COMPILER_IS_ON_ELEMENT = 'COMPILER_IS_ON_ELEMENT',
|
||||||
COMPILER_V_BIND_SYNC = 'COMPILER_V_BIND_SYNC',
|
COMPILER_V_BIND_SYNC = 'COMPILER_V_BIND_SYNC',
|
||||||
COMPILER_V_BIND_PROP = 'COMPILER_V_BIND_PROP',
|
|
||||||
COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',
|
COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',
|
||||||
COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE',
|
COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE',
|
||||||
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
|
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
|
||||||
|
|
@ -47,12 +46,6 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
|
||||||
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_PROP]: {
|
|
||||||
message:
|
|
||||||
`.prop modifier for v-bind has been removed and no longer necessary. ` +
|
|
||||||
`Vue 3 will automatically set a binding as DOM property when appropriate.`
|
|
||||||
},
|
|
||||||
|
|
||||||
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
|
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
|
||||||
message:
|
message:
|
||||||
`v-bind="obj" usage is now order sensitive and behaves like JavaScript ` +
|
`v-bind="obj" usage is now order sensitive and behaves like JavaScript ` +
|
||||||
|
|
@ -100,12 +93,9 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
|
||||||
|
|
||||||
function getCompatValue(
|
function getCompatValue(
|
||||||
key: CompilerDeprecationTypes | 'MODE',
|
key: CompilerDeprecationTypes | 'MODE',
|
||||||
context: ParserContext | TransformContext
|
{ compatConfig }: MergedParserOptions | TransformContext
|
||||||
) {
|
) {
|
||||||
const config = (context as ParserContext).options
|
const value = compatConfig && compatConfig[key]
|
||||||
? (context as ParserContext).options.compatConfig
|
|
||||||
: (context as TransformContext).compatConfig
|
|
||||||
const value = config && config[key]
|
|
||||||
if (key === 'MODE') {
|
if (key === 'MODE') {
|
||||||
return value || 3 // compiler defaults to v3 behavior
|
return value || 3 // compiler defaults to v3 behavior
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -115,7 +105,7 @@ function getCompatValue(
|
||||||
|
|
||||||
export function isCompatEnabled(
|
export function isCompatEnabled(
|
||||||
key: CompilerDeprecationTypes,
|
key: CompilerDeprecationTypes,
|
||||||
context: ParserContext | TransformContext
|
context: MergedParserOptions | TransformContext
|
||||||
) {
|
) {
|
||||||
const mode = getCompatValue('MODE', context)
|
const mode = getCompatValue('MODE', context)
|
||||||
const value = getCompatValue(key, context)
|
const value = getCompatValue(key, context)
|
||||||
|
|
@ -126,7 +116,7 @@ export function isCompatEnabled(
|
||||||
|
|
||||||
export function checkCompatEnabled(
|
export function checkCompatEnabled(
|
||||||
key: CompilerDeprecationTypes,
|
key: CompilerDeprecationTypes,
|
||||||
context: ParserContext | TransformContext,
|
context: MergedParserOptions | TransformContext,
|
||||||
loc: SourceLocation | null,
|
loc: SourceLocation | null,
|
||||||
...args: any[]
|
...args: any[]
|
||||||
): boolean {
|
): boolean {
|
||||||
|
|
@ -139,7 +129,7 @@ export function checkCompatEnabled(
|
||||||
|
|
||||||
export function warnDeprecation(
|
export function warnDeprecation(
|
||||||
key: CompilerDeprecationTypes,
|
key: CompilerDeprecationTypes,
|
||||||
context: ParserContext | TransformContext,
|
context: MergedParserOptions | TransformContext,
|
||||||
loc: SourceLocation | null,
|
loc: SourceLocation | null,
|
||||||
...args: any[]
|
...args: any[]
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { CompilerOptions } from './options'
|
import { CompilerOptions } from './options'
|
||||||
import { baseParse } from './parse'
|
import { baseParse } from './parser'
|
||||||
import { transform, NodeTransform, DirectiveTransform } from './transform'
|
import { transform, NodeTransform, DirectiveTransform } from './transform'
|
||||||
import { generate, CodegenResult } from './codegen'
|
import { generate, CodegenResult } from './codegen'
|
||||||
import { RootNode } from './ast'
|
import { RootNode } from './ast'
|
||||||
|
|
@ -59,7 +59,7 @@ export function getBaseTransformPreset(
|
||||||
// we name it `baseCompile` so that higher order compilers like
|
// we name it `baseCompile` so that higher order compilers like
|
||||||
// @vue/compiler-dom can export `compile` while re-exporting everything else.
|
// @vue/compiler-dom can export `compile` while re-exporting everything else.
|
||||||
export function baseCompile(
|
export function baseCompile(
|
||||||
template: string | RootNode,
|
source: string | RootNode,
|
||||||
options: CompilerOptions = {}
|
options: CompilerOptions = {}
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
const onError = options.onError || defaultOnError
|
const onError = options.onError || defaultOnError
|
||||||
|
|
@ -82,7 +82,7 @@ export function baseCompile(
|
||||||
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
|
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
|
||||||
}
|
}
|
||||||
|
|
||||||
const ast = isString(template) ? baseParse(template, options) : template
|
const ast = isString(source) ? baseParse(source, options) : source
|
||||||
const [nodeTransforms, directiveTransforms] =
|
const [nodeTransforms, directiveTransforms] =
|
||||||
getBaseTransformPreset(prefixIdentifiers)
|
getBaseTransformPreset(prefixIdentifiers)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,14 @@ export function createCompilerError<T extends number>(
|
||||||
const msg =
|
const msg =
|
||||||
__DEV__ || !__BROWSER__
|
__DEV__ || !__BROWSER__
|
||||||
? (messages || errorMessages)[code] + (additionalMessage || ``)
|
? (messages || errorMessages)[code] + (additionalMessage || ``)
|
||||||
: code
|
: `https://vuejs.org/errors/#compiler-${code}`
|
||||||
const error = new SyntaxError(String(msg)) as InferCompilerError<T>
|
const error = new SyntaxError(String(msg)) as InferCompilerError<T>
|
||||||
error.code = code
|
error.code = code
|
||||||
error.loc = loc
|
error.loc = loc
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ErrorCodes {
|
export enum ErrorCodes {
|
||||||
// parse errors
|
// parse errors
|
||||||
ABRUPT_CLOSING_OF_EMPTY_COMMENT,
|
ABRUPT_CLOSING_OF_EMPTY_COMMENT,
|
||||||
CDATA_IN_HTML_CONTENT,
|
CDATA_IN_HTML_CONTENT,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export {
|
||||||
type BindingMetadata,
|
type BindingMetadata,
|
||||||
BindingTypes
|
BindingTypes
|
||||||
} from './options'
|
} from './options'
|
||||||
export { baseParse, TextModes } from './parse'
|
export { baseParse } from './parser'
|
||||||
export {
|
export {
|
||||||
transform,
|
transform,
|
||||||
type TransformContext,
|
type TransformContext,
|
||||||
|
|
@ -24,6 +24,7 @@ export {
|
||||||
export { generate, type CodegenContext, type CodegenResult } from './codegen'
|
export { generate, type CodegenContext, type CodegenResult } from './codegen'
|
||||||
export {
|
export {
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
|
errorMessages,
|
||||||
createCompilerError,
|
createCompilerError,
|
||||||
type CoreCompilerError,
|
type CoreCompilerError,
|
||||||
type CompilerError
|
type CompilerError
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import { ElementNode, Namespace, TemplateChildNode, ParentNode } from './ast'
|
import {
|
||||||
import { TextModes } from './parse'
|
ElementNode,
|
||||||
|
Namespace,
|
||||||
|
TemplateChildNode,
|
||||||
|
ParentNode,
|
||||||
|
Namespaces
|
||||||
|
} from './ast'
|
||||||
import { CompilerError } from './errors'
|
import { CompilerError } from './errors'
|
||||||
import {
|
import {
|
||||||
NodeTransform,
|
NodeTransform,
|
||||||
|
|
@ -17,6 +22,24 @@ export interface ErrorHandlingOptions {
|
||||||
export interface ParserOptions
|
export interface ParserOptions
|
||||||
extends ErrorHandlingOptions,
|
extends ErrorHandlingOptions,
|
||||||
CompilerCompatOptions {
|
CompilerCompatOptions {
|
||||||
|
/**
|
||||||
|
* Base mode is platform agnostic and only parses HTML-like template syntax,
|
||||||
|
* treating all tags the same way. Specific tag parsing behavior can be
|
||||||
|
* configured by higher-level compilers.
|
||||||
|
*
|
||||||
|
* HTML mode adds additional logic for handling special parsing behavior in
|
||||||
|
* `<script>`, `<style>`,`<title>` and `<textarea>`.
|
||||||
|
* The logic is handled inside compiler-core for efficiency.
|
||||||
|
*
|
||||||
|
* SFC mode treats content of all root-level tags except `<template>` as plain
|
||||||
|
* text.
|
||||||
|
*/
|
||||||
|
parseMode?: 'base' | 'html' | 'sfc'
|
||||||
|
/**
|
||||||
|
* Specify the root namespace to use when parsing a template.
|
||||||
|
* Defaults to `Namespaces.HTML` (0).
|
||||||
|
*/
|
||||||
|
ns?: Namespaces
|
||||||
/**
|
/**
|
||||||
* e.g. platform native elements, e.g. `<div>` for browsers
|
* e.g. platform native elements, e.g. `<div>` for browsers
|
||||||
*/
|
*/
|
||||||
|
|
@ -40,14 +63,11 @@ export interface ParserOptions
|
||||||
/**
|
/**
|
||||||
* Get tag namespace
|
* Get tag namespace
|
||||||
*/
|
*/
|
||||||
getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
|
getNamespace?: (
|
||||||
/**
|
tag: string,
|
||||||
* Get text parsing mode for this element
|
parent: ElementNode | undefined,
|
||||||
*/
|
rootNamespace: Namespace
|
||||||
getTextMode?: (
|
) => Namespace
|
||||||
node: ElementNode,
|
|
||||||
parent: ElementNode | undefined
|
|
||||||
) => TextModes
|
|
||||||
/**
|
/**
|
||||||
* @default ['{{', '}}']
|
* @default ['{{', '}}']
|
||||||
*/
|
*/
|
||||||
|
|
@ -57,7 +77,8 @@ export interface ParserOptions
|
||||||
*/
|
*/
|
||||||
whitespace?: 'preserve' | 'condense'
|
whitespace?: 'preserve' | 'condense'
|
||||||
/**
|
/**
|
||||||
* Only needed for DOM compilers
|
* Only used for DOM compilers that runs in the browser.
|
||||||
|
* In non-browser builds, this option is ignored.
|
||||||
*/
|
*/
|
||||||
decodeEntities?: (rawText: string, asAttr: boolean) => string
|
decodeEntities?: (rawText: string, asAttr: boolean) => string
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,7 +94,7 @@ export type HoistTransform = (
|
||||||
parent: ParentNode
|
parent: ParentNode
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
export const enum BindingTypes {
|
export enum BindingTypes {
|
||||||
/**
|
/**
|
||||||
* returned from data()
|
* returned from data()
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,997 @@
|
||||||
|
import {
|
||||||
|
AttributeNode,
|
||||||
|
ConstantTypes,
|
||||||
|
DirectiveNode,
|
||||||
|
ElementNode,
|
||||||
|
ElementTypes,
|
||||||
|
ForParseResult,
|
||||||
|
Namespaces,
|
||||||
|
NodeTypes,
|
||||||
|
RootNode,
|
||||||
|
SimpleExpressionNode,
|
||||||
|
SourceLocation,
|
||||||
|
TemplateChildNode,
|
||||||
|
createRoot,
|
||||||
|
createSimpleExpression
|
||||||
|
} from './ast'
|
||||||
|
import { ParserOptions } from './options'
|
||||||
|
import Tokenizer, {
|
||||||
|
CharCodes,
|
||||||
|
ParseMode,
|
||||||
|
QuoteType,
|
||||||
|
Sequences,
|
||||||
|
State,
|
||||||
|
isWhitespace,
|
||||||
|
toCharCodes
|
||||||
|
} from './tokenizer'
|
||||||
|
import {
|
||||||
|
CompilerCompatOptions,
|
||||||
|
CompilerDeprecationTypes,
|
||||||
|
checkCompatEnabled,
|
||||||
|
isCompatEnabled,
|
||||||
|
warnDeprecation
|
||||||
|
} from './compat/compatConfig'
|
||||||
|
import { NO, extend } from '@vue/shared'
|
||||||
|
import {
|
||||||
|
ErrorCodes,
|
||||||
|
createCompilerError,
|
||||||
|
defaultOnError,
|
||||||
|
defaultOnWarn
|
||||||
|
} from './errors'
|
||||||
|
import { forAliasRE, isCoreComponent, isStaticArgOf } from './utils'
|
||||||
|
import { decodeHTML } from 'entities/lib/decode.js'
|
||||||
|
|
||||||
|
type OptionalOptions =
|
||||||
|
| 'decodeEntities'
|
||||||
|
| 'whitespace'
|
||||||
|
| 'isNativeTag'
|
||||||
|
| 'isBuiltInComponent'
|
||||||
|
| keyof CompilerCompatOptions
|
||||||
|
|
||||||
|
export type MergedParserOptions = Omit<
|
||||||
|
Required<ParserOptions>,
|
||||||
|
OptionalOptions
|
||||||
|
> &
|
||||||
|
Pick<ParserOptions, OptionalOptions>
|
||||||
|
|
||||||
|
export const defaultParserOptions: MergedParserOptions = {
|
||||||
|
parseMode: 'base',
|
||||||
|
ns: Namespaces.HTML,
|
||||||
|
delimiters: [`{{`, `}}`],
|
||||||
|
getNamespace: () => Namespaces.HTML,
|
||||||
|
isVoidTag: NO,
|
||||||
|
isPreTag: NO,
|
||||||
|
isCustomElement: NO,
|
||||||
|
onError: defaultOnError,
|
||||||
|
onWarn: defaultOnWarn,
|
||||||
|
comments: __DEV__
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentOptions: MergedParserOptions = defaultParserOptions
|
||||||
|
let currentRoot: RootNode | null = null
|
||||||
|
|
||||||
|
// parser state
|
||||||
|
let currentInput = ''
|
||||||
|
let currentOpenTag: ElementNode | null = null
|
||||||
|
let currentProp: AttributeNode | DirectiveNode | null = null
|
||||||
|
let currentAttrValue = ''
|
||||||
|
let currentAttrStartIndex = -1
|
||||||
|
let currentAttrEndIndex = -1
|
||||||
|
let inPre = 0
|
||||||
|
let inVPre = false
|
||||||
|
let currentVPreBoundary: ElementNode | null = null
|
||||||
|
const stack: ElementNode[] = []
|
||||||
|
|
||||||
|
const tokenizer = new Tokenizer(stack, {
|
||||||
|
onerr: emitError,
|
||||||
|
|
||||||
|
ontext(start, end) {
|
||||||
|
onText(getSlice(start, end), start, end)
|
||||||
|
},
|
||||||
|
|
||||||
|
ontextentity(char, start, end) {
|
||||||
|
onText(char, start, end)
|
||||||
|
},
|
||||||
|
|
||||||
|
oninterpolation(start, end) {
|
||||||
|
if (inVPre) {
|
||||||
|
return onText(getSlice(start, end), start, end)
|
||||||
|
}
|
||||||
|
let innerStart = start + tokenizer.delimiterOpen.length
|
||||||
|
let innerEnd = end - tokenizer.delimiterClose.length
|
||||||
|
while (isWhitespace(currentInput.charCodeAt(innerStart))) {
|
||||||
|
innerStart++
|
||||||
|
}
|
||||||
|
while (isWhitespace(currentInput.charCodeAt(innerEnd - 1))) {
|
||||||
|
innerEnd--
|
||||||
|
}
|
||||||
|
let exp = getSlice(innerStart, innerEnd)
|
||||||
|
// decode entities for backwards compat
|
||||||
|
if (exp.includes('&')) {
|
||||||
|
if (__BROWSER__) {
|
||||||
|
exp = currentOptions.decodeEntities!(exp, false)
|
||||||
|
} else {
|
||||||
|
exp = decodeHTML(exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addNode({
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: createSimpleExpression(exp, false, getLoc(innerStart, innerEnd)),
|
||||||
|
loc: getLoc(start, end)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onopentagname(start, end) {
|
||||||
|
const name = getSlice(start, end)
|
||||||
|
currentOpenTag = {
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: name,
|
||||||
|
ns: currentOptions.getNamespace(name, stack[0], currentOptions.ns),
|
||||||
|
tagType: ElementTypes.ELEMENT, // will be refined on tag close
|
||||||
|
props: [],
|
||||||
|
children: [],
|
||||||
|
loc: getLoc(start - 1, end),
|
||||||
|
codegenNode: undefined
|
||||||
|
}
|
||||||
|
if (tokenizer.inSFCRoot) {
|
||||||
|
// in SFC mode, generate locations for root-level tags' inner content.
|
||||||
|
currentOpenTag.innerLoc = getLoc(
|
||||||
|
end + fastForward(end, CharCodes.Gt) + 1,
|
||||||
|
end
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onopentagend(end) {
|
||||||
|
endOpenTag(end)
|
||||||
|
},
|
||||||
|
|
||||||
|
onclosetag(start, end) {
|
||||||
|
const name = getSlice(start, end)
|
||||||
|
if (!currentOptions.isVoidTag(name)) {
|
||||||
|
let found = false
|
||||||
|
for (let i = 0; i < stack.length; i++) {
|
||||||
|
const e = stack[i]
|
||||||
|
if (e.tag.toLowerCase() === name.toLowerCase()) {
|
||||||
|
found = true
|
||||||
|
if (i > 0) {
|
||||||
|
emitError(ErrorCodes.X_MISSING_END_TAG, stack[0].loc.start.offset)
|
||||||
|
}
|
||||||
|
for (let j = 0; j <= i; j++) {
|
||||||
|
const el = stack.shift()!
|
||||||
|
onCloseTag(el, end, j < i)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
emitError(ErrorCodes.X_INVALID_END_TAG, backTrack(start, CharCodes.Lt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onselfclosingtag(end) {
|
||||||
|
const name = currentOpenTag!.tag
|
||||||
|
currentOpenTag!.isSelfClosing = true
|
||||||
|
endOpenTag(end)
|
||||||
|
if (stack[0]?.tag === name) {
|
||||||
|
onCloseTag(stack.shift()!, end)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onattribname(start, end) {
|
||||||
|
// plain attribute
|
||||||
|
currentProp = {
|
||||||
|
type: NodeTypes.ATTRIBUTE,
|
||||||
|
name: getSlice(start, end),
|
||||||
|
nameLoc: getLoc(start, end),
|
||||||
|
value: undefined,
|
||||||
|
loc: getLoc(start)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ondirname(start, end) {
|
||||||
|
const raw = getSlice(start, end)
|
||||||
|
const name =
|
||||||
|
raw === '.' || raw === ':'
|
||||||
|
? 'bind'
|
||||||
|
: raw === '@'
|
||||||
|
? 'on'
|
||||||
|
: raw === '#'
|
||||||
|
? 'slot'
|
||||||
|
: raw.slice(2)
|
||||||
|
|
||||||
|
if (!inVPre && name === '') {
|
||||||
|
emitError(ErrorCodes.X_MISSING_DIRECTIVE_NAME, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inVPre || name === '') {
|
||||||
|
currentProp = {
|
||||||
|
type: NodeTypes.ATTRIBUTE,
|
||||||
|
name: raw,
|
||||||
|
nameLoc: getLoc(start, end),
|
||||||
|
value: undefined,
|
||||||
|
loc: getLoc(start)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentProp = {
|
||||||
|
type: NodeTypes.DIRECTIVE,
|
||||||
|
name,
|
||||||
|
rawName: raw,
|
||||||
|
exp: undefined,
|
||||||
|
arg: undefined,
|
||||||
|
modifiers: raw === '.' ? ['prop'] : [],
|
||||||
|
loc: getLoc(start)
|
||||||
|
}
|
||||||
|
if (name === 'pre') {
|
||||||
|
inVPre = true
|
||||||
|
currentVPreBoundary = currentOpenTag
|
||||||
|
// convert dirs before this one to attributes
|
||||||
|
const props = currentOpenTag!.props
|
||||||
|
for (let i = 0; i < props.length; i++) {
|
||||||
|
if (props[i].type === NodeTypes.DIRECTIVE) {
|
||||||
|
props[i] = dirToAttr(props[i] as DirectiveNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ondirarg(start, end) {
|
||||||
|
if (start === end) return
|
||||||
|
const arg = getSlice(start, end)
|
||||||
|
if (inVPre) {
|
||||||
|
;(currentProp as AttributeNode).name += arg
|
||||||
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||||
|
} else {
|
||||||
|
const isStatic = arg[0] !== `[`
|
||||||
|
;(currentProp as DirectiveNode).arg = createSimpleExpression(
|
||||||
|
isStatic ? arg : arg.slice(1, -1),
|
||||||
|
isStatic,
|
||||||
|
getLoc(start, end),
|
||||||
|
isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ondirmodifier(start, end) {
|
||||||
|
const mod = getSlice(start, end)
|
||||||
|
if (inVPre) {
|
||||||
|
;(currentProp as AttributeNode).name += '.' + mod
|
||||||
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||||
|
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
||||||
|
// slot has no modifiers, special case for edge cases like
|
||||||
|
// https://github.com/vuejs/language-tools/issues/2710
|
||||||
|
const arg = (currentProp as DirectiveNode).arg
|
||||||
|
if (arg) {
|
||||||
|
;(arg as SimpleExpressionNode).content += '.' + mod
|
||||||
|
setLocEnd(arg.loc, end)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
;(currentProp as DirectiveNode).modifiers.push(mod)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onattribdata(start, end) {
|
||||||
|
currentAttrValue += getSlice(start, end)
|
||||||
|
if (currentAttrStartIndex < 0) currentAttrStartIndex = start
|
||||||
|
currentAttrEndIndex = end
|
||||||
|
},
|
||||||
|
|
||||||
|
onattribentity(char, start, end) {
|
||||||
|
currentAttrValue += char
|
||||||
|
if (currentAttrStartIndex < 0) currentAttrStartIndex = start
|
||||||
|
currentAttrEndIndex = end
|
||||||
|
},
|
||||||
|
|
||||||
|
onattribnameend(end) {
|
||||||
|
const start = currentProp!.loc.start.offset
|
||||||
|
const name = getSlice(start, end)
|
||||||
|
if (currentProp!.type === NodeTypes.DIRECTIVE) {
|
||||||
|
currentProp!.rawName = name
|
||||||
|
}
|
||||||
|
// check duplicate attrs
|
||||||
|
if (
|
||||||
|
currentOpenTag!.props.some(
|
||||||
|
p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
emitError(ErrorCodes.DUPLICATE_ATTRIBUTE, start)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onattribend(quote, end) {
|
||||||
|
if (currentOpenTag && currentProp) {
|
||||||
|
// finalize end pos
|
||||||
|
setLocEnd(currentProp.loc, end)
|
||||||
|
|
||||||
|
if (quote !== QuoteType.NoValue) {
|
||||||
|
if (__BROWSER__ && currentAttrValue.includes('&')) {
|
||||||
|
currentAttrValue = currentOptions.decodeEntities!(
|
||||||
|
currentAttrValue,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentProp.type === NodeTypes.ATTRIBUTE) {
|
||||||
|
// assign value
|
||||||
|
|
||||||
|
// condense whitespaces in class
|
||||||
|
if (currentProp!.name === 'class') {
|
||||||
|
currentAttrValue = condense(currentAttrValue).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quote === QuoteType.Unquoted && !currentAttrValue) {
|
||||||
|
emitError(ErrorCodes.MISSING_ATTRIBUTE_VALUE, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentProp!.value = {
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: currentAttrValue,
|
||||||
|
loc:
|
||||||
|
quote === QuoteType.Unquoted
|
||||||
|
? getLoc(currentAttrStartIndex, currentAttrEndIndex)
|
||||||
|
: getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
tokenizer.inSFCRoot &&
|
||||||
|
currentOpenTag.tag === 'template' &&
|
||||||
|
currentProp.name === 'lang' &&
|
||||||
|
currentAttrValue &&
|
||||||
|
currentAttrValue !== 'html'
|
||||||
|
) {
|
||||||
|
// SFC root template with preprocessor lang, force tokenizer to
|
||||||
|
// RCDATA mode
|
||||||
|
tokenizer.enterRCDATA(toCharCodes(`</template`), 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// directive
|
||||||
|
currentProp.exp = createSimpleExpression(
|
||||||
|
currentAttrValue,
|
||||||
|
false,
|
||||||
|
getLoc(currentAttrStartIndex, currentAttrEndIndex)
|
||||||
|
)
|
||||||
|
if (currentProp.name === 'for') {
|
||||||
|
currentProp.forParseResult = parseForExpression(currentProp.exp)
|
||||||
|
}
|
||||||
|
// 2.x compat v-bind:foo.sync -> v-model:foo
|
||||||
|
let syncIndex = -1
|
||||||
|
if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
currentProp.name === 'bind' &&
|
||||||
|
(syncIndex = currentProp.modifiers.indexOf('sync')) > -1 &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
||||||
|
currentOptions,
|
||||||
|
currentProp.loc,
|
||||||
|
currentProp.rawName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
currentProp.name = 'model'
|
||||||
|
currentProp.modifiers.splice(syncIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
currentProp.type !== NodeTypes.DIRECTIVE ||
|
||||||
|
currentProp.name !== 'pre'
|
||||||
|
) {
|
||||||
|
currentOpenTag.props.push(currentProp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentAttrValue = ''
|
||||||
|
currentAttrStartIndex = currentAttrEndIndex = -1
|
||||||
|
},
|
||||||
|
|
||||||
|
oncomment(start, end) {
|
||||||
|
if (currentOptions.comments) {
|
||||||
|
addNode({
|
||||||
|
type: NodeTypes.COMMENT,
|
||||||
|
content: getSlice(start, end),
|
||||||
|
loc: getLoc(start - 4, end + 3)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onend() {
|
||||||
|
const end = currentInput.length
|
||||||
|
// EOF ERRORS
|
||||||
|
if ((__DEV__ || !__BROWSER__) && tokenizer.state !== State.Text) {
|
||||||
|
switch (tokenizer.state) {
|
||||||
|
case State.BeforeTagName:
|
||||||
|
case State.BeforeClosingTagName:
|
||||||
|
emitError(ErrorCodes.EOF_BEFORE_TAG_NAME, end)
|
||||||
|
break
|
||||||
|
case State.Interpolation:
|
||||||
|
case State.InterpolationClose:
|
||||||
|
emitError(
|
||||||
|
ErrorCodes.X_MISSING_INTERPOLATION_END,
|
||||||
|
tokenizer.sectionStart
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case State.InCommentLike:
|
||||||
|
if (tokenizer.currentSequence === Sequences.CdataEnd) {
|
||||||
|
emitError(ErrorCodes.EOF_IN_CDATA, end)
|
||||||
|
} else {
|
||||||
|
emitError(ErrorCodes.EOF_IN_COMMENT, end)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case State.InTagName:
|
||||||
|
case State.InSelfClosingTag:
|
||||||
|
case State.InClosingTagName:
|
||||||
|
case State.BeforeAttrName:
|
||||||
|
case State.InAttrName:
|
||||||
|
case State.InDirName:
|
||||||
|
case State.InDirArg:
|
||||||
|
case State.InDirDynamicArg:
|
||||||
|
case State.InDirModifier:
|
||||||
|
case State.AfterAttrName:
|
||||||
|
case State.BeforeAttrValue:
|
||||||
|
case State.InAttrValueDq: // "
|
||||||
|
case State.InAttrValueSq: // '
|
||||||
|
case State.InAttrValueNq:
|
||||||
|
emitError(ErrorCodes.EOF_IN_TAG, end)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// console.log(tokenizer.state)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let index = 0; index < stack.length; index++) {
|
||||||
|
onCloseTag(stack[index], end - 1)
|
||||||
|
emitError(ErrorCodes.X_MISSING_END_TAG, stack[index].loc.start.offset)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
oncdata(start, end) {
|
||||||
|
if (stack[0].ns !== Namespaces.HTML) {
|
||||||
|
onText(getSlice(start, end), start, end)
|
||||||
|
} else {
|
||||||
|
emitError(ErrorCodes.CDATA_IN_HTML_CONTENT, start - 9)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onprocessinginstruction(start) {
|
||||||
|
// ignore as we do not have runtime handling for this, only check error
|
||||||
|
if ((stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML) {
|
||||||
|
emitError(
|
||||||
|
ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
|
||||||
|
start - 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// This regex doesn't cover the case if key or index aliases have destructuring,
|
||||||
|
// but those do not make sense in the first place, so this works in practice.
|
||||||
|
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||||
|
const stripParensRE = /^\(|\)$/g
|
||||||
|
|
||||||
|
function parseForExpression(
|
||||||
|
input: SimpleExpressionNode
|
||||||
|
): ForParseResult | undefined {
|
||||||
|
const loc = input.loc
|
||||||
|
const exp = input.content
|
||||||
|
const inMatch = exp.match(forAliasRE)
|
||||||
|
if (!inMatch) return
|
||||||
|
|
||||||
|
const [, LHS, RHS] = inMatch
|
||||||
|
|
||||||
|
const createAliasExpression = (content: string, offset: number) => {
|
||||||
|
const start = loc.start.offset + offset
|
||||||
|
const end = start + content.length
|
||||||
|
return createSimpleExpression(content, false, getLoc(start, end))
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: ForParseResult = {
|
||||||
|
source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),
|
||||||
|
value: undefined,
|
||||||
|
key: undefined,
|
||||||
|
index: undefined,
|
||||||
|
finalized: false
|
||||||
|
}
|
||||||
|
|
||||||
|
let valueContent = LHS.trim().replace(stripParensRE, '').trim()
|
||||||
|
const trimmedOffset = LHS.indexOf(valueContent)
|
||||||
|
|
||||||
|
const iteratorMatch = valueContent.match(forIteratorRE)
|
||||||
|
if (iteratorMatch) {
|
||||||
|
valueContent = valueContent.replace(forIteratorRE, '').trim()
|
||||||
|
|
||||||
|
const keyContent = iteratorMatch[1].trim()
|
||||||
|
let keyOffset: number | undefined
|
||||||
|
if (keyContent) {
|
||||||
|
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
|
||||||
|
result.key = createAliasExpression(keyContent, keyOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iteratorMatch[2]) {
|
||||||
|
const indexContent = iteratorMatch[2].trim()
|
||||||
|
|
||||||
|
if (indexContent) {
|
||||||
|
result.index = createAliasExpression(
|
||||||
|
indexContent,
|
||||||
|
exp.indexOf(
|
||||||
|
indexContent,
|
||||||
|
result.key
|
||||||
|
? keyOffset! + keyContent.length
|
||||||
|
: trimmedOffset + valueContent.length
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueContent) {
|
||||||
|
result.value = createAliasExpression(valueContent, trimmedOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSlice(start: number, end: number) {
|
||||||
|
return currentInput.slice(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
function endOpenTag(end: number) {
|
||||||
|
addNode(currentOpenTag!)
|
||||||
|
const { tag, ns } = currentOpenTag!
|
||||||
|
if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
|
||||||
|
inPre++
|
||||||
|
}
|
||||||
|
if (currentOptions.isVoidTag(tag)) {
|
||||||
|
onCloseTag(currentOpenTag!, end)
|
||||||
|
} else {
|
||||||
|
stack.unshift(currentOpenTag!)
|
||||||
|
if (ns === Namespaces.SVG || ns === Namespaces.MATH_ML) {
|
||||||
|
tokenizer.inXML = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentOpenTag = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function onText(content: string, start: number, end: number) {
|
||||||
|
if (__BROWSER__) {
|
||||||
|
const tag = stack[0]?.tag
|
||||||
|
if (tag !== 'script' && tag !== 'style' && content.includes('&')) {
|
||||||
|
content = currentOptions.decodeEntities!(content, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parent = stack[0] || currentRoot
|
||||||
|
const lastNode = parent.children[parent.children.length - 1]
|
||||||
|
if (lastNode?.type === NodeTypes.TEXT) {
|
||||||
|
// merge
|
||||||
|
lastNode.content += content
|
||||||
|
setLocEnd(lastNode.loc, end)
|
||||||
|
} else {
|
||||||
|
parent.children.push({
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content,
|
||||||
|
loc: getLoc(start, end)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCloseTag(el: ElementNode, end: number, isImplied = false) {
|
||||||
|
// attach end position
|
||||||
|
if (isImplied) {
|
||||||
|
// implied close, end should be backtracked to close
|
||||||
|
setLocEnd(el.loc, backTrack(end, CharCodes.Lt))
|
||||||
|
} else {
|
||||||
|
setLocEnd(el.loc, end + fastForward(end, CharCodes.Gt) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenizer.inSFCRoot) {
|
||||||
|
// SFC root tag, resolve inner end
|
||||||
|
if (el.children.length) {
|
||||||
|
el.innerLoc!.end = extend({}, el.children[el.children.length - 1].loc.end)
|
||||||
|
} else {
|
||||||
|
el.innerLoc!.end = extend({}, el.innerLoc!.start)
|
||||||
|
}
|
||||||
|
el.innerLoc!.source = getSlice(
|
||||||
|
el.innerLoc!.start.offset,
|
||||||
|
el.innerLoc!.end.offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// refine element type
|
||||||
|
const { tag, ns } = el
|
||||||
|
if (!inVPre) {
|
||||||
|
if (tag === 'slot') {
|
||||||
|
el.tagType = ElementTypes.SLOT
|
||||||
|
} else if (isFragmentTemplate(el)) {
|
||||||
|
el.tagType = ElementTypes.TEMPLATE
|
||||||
|
} else if (isComponent(el)) {
|
||||||
|
el.tagType = ElementTypes.COMPONENT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// whitespace management
|
||||||
|
if (!tokenizer.inRCDATA) {
|
||||||
|
el.children = condenseWhitespace(el.children, el.tag)
|
||||||
|
}
|
||||||
|
if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
|
||||||
|
inPre--
|
||||||
|
}
|
||||||
|
if (currentVPreBoundary === el) {
|
||||||
|
inVPre = false
|
||||||
|
currentVPreBoundary = null
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
tokenizer.inXML &&
|
||||||
|
(stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML
|
||||||
|
) {
|
||||||
|
tokenizer.inXML = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.x compat / deprecation checks
|
||||||
|
if (__COMPAT__) {
|
||||||
|
const props = el.props
|
||||||
|
if (
|
||||||
|
__DEV__ &&
|
||||||
|
isCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
|
||||||
|
currentOptions
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
let hasIf = false
|
||||||
|
let hasFor = false
|
||||||
|
for (let i = 0; i < props.length; i++) {
|
||||||
|
const p = props[i]
|
||||||
|
if (p.type === NodeTypes.DIRECTIVE) {
|
||||||
|
if (p.name === 'if') {
|
||||||
|
hasIf = true
|
||||||
|
} else if (p.name === 'for') {
|
||||||
|
hasFor = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasIf && hasFor) {
|
||||||
|
warnDeprecation(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
|
||||||
|
currentOptions,
|
||||||
|
el.loc
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
|
||||||
|
currentOptions
|
||||||
|
) &&
|
||||||
|
el.tag === 'template' &&
|
||||||
|
!isFragmentTemplate(el)
|
||||||
|
) {
|
||||||
|
__DEV__ &&
|
||||||
|
warnDeprecation(
|
||||||
|
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
|
||||||
|
currentOptions,
|
||||||
|
el.loc
|
||||||
|
)
|
||||||
|
// unwrap
|
||||||
|
const parent = stack[0] || currentRoot
|
||||||
|
const index = parent.children.indexOf(el)
|
||||||
|
parent.children.splice(index, 1, ...el.children)
|
||||||
|
}
|
||||||
|
|
||||||
|
const inlineTemplateProp = props.find(
|
||||||
|
p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template'
|
||||||
|
) as AttributeNode
|
||||||
|
if (
|
||||||
|
inlineTemplateProp &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
|
||||||
|
currentOptions,
|
||||||
|
inlineTemplateProp.loc
|
||||||
|
) &&
|
||||||
|
el.children.length
|
||||||
|
) {
|
||||||
|
inlineTemplateProp.value = {
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: getSlice(
|
||||||
|
el.children[0].loc.start.offset,
|
||||||
|
el.children[el.children.length - 1].loc.end.offset
|
||||||
|
),
|
||||||
|
loc: inlineTemplateProp.loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fastForward(start: number, c: number) {
|
||||||
|
let offset = 0
|
||||||
|
while (
|
||||||
|
currentInput.charCodeAt(start + offset) !== CharCodes.Gt &&
|
||||||
|
start + offset < currentInput.length
|
||||||
|
) {
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
function backTrack(index: number, c: number) {
|
||||||
|
let i = index
|
||||||
|
while (currentInput.charCodeAt(i) !== c && i >= 0) i--
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
|
||||||
|
function isFragmentTemplate({ tag, props }: ElementNode): boolean {
|
||||||
|
if (tag === 'template') {
|
||||||
|
for (let i = 0; i < props.length; i++) {
|
||||||
|
if (
|
||||||
|
props[i].type === NodeTypes.DIRECTIVE &&
|
||||||
|
specialTemplateDir.has((props[i] as DirectiveNode).name)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function isComponent({ tag, props }: ElementNode): boolean {
|
||||||
|
if (currentOptions.isCustomElement(tag)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
tag === 'component' ||
|
||||||
|
isUpperCase(tag.charCodeAt(0)) ||
|
||||||
|
isCoreComponent(tag) ||
|
||||||
|
currentOptions.isBuiltInComponent?.(tag) ||
|
||||||
|
(currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// at this point the tag should be a native tag, but check for potential "is"
|
||||||
|
// casting
|
||||||
|
for (let i = 0; i < props.length; i++) {
|
||||||
|
const p = props[i]
|
||||||
|
if (p.type === NodeTypes.ATTRIBUTE) {
|
||||||
|
if (p.name === 'is' && p.value) {
|
||||||
|
if (p.value.content.startsWith('vue:')) {
|
||||||
|
return true
|
||||||
|
} else if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
||||||
|
currentOptions,
|
||||||
|
p.loc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
// :is on plain element - only treat as component in compat mode
|
||||||
|
p.name === 'bind' &&
|
||||||
|
isStaticArgOf(p.arg, 'is') &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
||||||
|
currentOptions,
|
||||||
|
p.loc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUpperCase(c: number) {
|
||||||
|
return c > 64 && c < 91
|
||||||
|
}
|
||||||
|
|
||||||
|
const windowsNewlineRE = /\r\n/g
|
||||||
|
function condenseWhitespace(
|
||||||
|
nodes: TemplateChildNode[],
|
||||||
|
tag?: string
|
||||||
|
): TemplateChildNode[] {
|
||||||
|
const shouldCondense = currentOptions.whitespace !== 'preserve'
|
||||||
|
let removedWhitespace = false
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
const node = nodes[i]
|
||||||
|
if (node.type === NodeTypes.TEXT) {
|
||||||
|
if (!inPre) {
|
||||||
|
if (isAllWhitespace(node.content)) {
|
||||||
|
const prev = nodes[i - 1]?.type
|
||||||
|
const next = nodes[i + 1]?.type
|
||||||
|
// Remove if:
|
||||||
|
// - the whitespace is the first or last node, or:
|
||||||
|
// - (condense mode) the whitespace is between two comments, or:
|
||||||
|
// - (condense mode) the whitespace is between comment and element, or:
|
||||||
|
// - (condense mode) the whitespace is between two elements AND contains newline
|
||||||
|
if (
|
||||||
|
!prev ||
|
||||||
|
!next ||
|
||||||
|
(shouldCondense &&
|
||||||
|
((prev === NodeTypes.COMMENT &&
|
||||||
|
(next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||
|
||||||
|
(prev === NodeTypes.ELEMENT &&
|
||||||
|
(next === NodeTypes.COMMENT ||
|
||||||
|
(next === NodeTypes.ELEMENT &&
|
||||||
|
hasNewlineChar(node.content))))))
|
||||||
|
) {
|
||||||
|
removedWhitespace = true
|
||||||
|
nodes[i] = null as any
|
||||||
|
} else {
|
||||||
|
// Otherwise, the whitespace is condensed into a single space
|
||||||
|
node.content = ' '
|
||||||
|
}
|
||||||
|
} else if (shouldCondense) {
|
||||||
|
// in condense mode, consecutive whitespaces in text are condensed
|
||||||
|
// down to a single space.
|
||||||
|
node.content = condense(node.content)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// #6410 normalize windows newlines in <pre>:
|
||||||
|
// in SSR, browsers normalize server-rendered \r\n into a single \n
|
||||||
|
// in the DOM
|
||||||
|
node.content = node.content.replace(windowsNewlineRE, '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inPre && tag && currentOptions.isPreTag(tag)) {
|
||||||
|
// remove leading newline per html spec
|
||||||
|
// https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
|
||||||
|
const first = nodes[0]
|
||||||
|
if (first && first.type === NodeTypes.TEXT) {
|
||||||
|
first.content = first.content.replace(/^\r?\n/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removedWhitespace ? nodes.filter(Boolean) : nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAllWhitespace(str: string) {
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
if (!isWhitespace(str.charCodeAt(i))) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasNewlineChar(str: string) {
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const c = str.charCodeAt(i)
|
||||||
|
if (c === CharCodes.NewLine || c === CharCodes.CarriageReturn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function condense(str: string) {
|
||||||
|
let ret = ''
|
||||||
|
let prevCharIsWhitespace = false
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
if (isWhitespace(str.charCodeAt(i))) {
|
||||||
|
if (!prevCharIsWhitespace) {
|
||||||
|
ret += ' '
|
||||||
|
prevCharIsWhitespace = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret += str[i]
|
||||||
|
prevCharIsWhitespace = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNode(node: TemplateChildNode) {
|
||||||
|
;(stack[0] || currentRoot).children.push(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoc(start: number, end?: number): SourceLocation {
|
||||||
|
return {
|
||||||
|
start: tokenizer.getPos(start),
|
||||||
|
// @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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLocEnd(loc: SourceLocation, end: number) {
|
||||||
|
loc.end = tokenizer.getPos(end)
|
||||||
|
loc.source = getSlice(loc.start.offset, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dirToAttr(dir: DirectiveNode): AttributeNode {
|
||||||
|
const attr: AttributeNode = {
|
||||||
|
type: NodeTypes.ATTRIBUTE,
|
||||||
|
name: dir.rawName!,
|
||||||
|
nameLoc: getLoc(
|
||||||
|
dir.loc.start.offset,
|
||||||
|
dir.loc.start.offset + dir.rawName!.length
|
||||||
|
),
|
||||||
|
value: undefined,
|
||||||
|
loc: dir.loc
|
||||||
|
}
|
||||||
|
if (dir.exp) {
|
||||||
|
// account for quotes
|
||||||
|
const loc = dir.exp.loc
|
||||||
|
if (loc.end.offset < dir.loc.end.offset) {
|
||||||
|
loc.start.offset--
|
||||||
|
loc.start.column--
|
||||||
|
loc.end.offset++
|
||||||
|
loc.end.column++
|
||||||
|
}
|
||||||
|
attr.value = {
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: (dir.exp as SimpleExpressionNode).content,
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attr
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitError(code: ErrorCodes, index: number) {
|
||||||
|
currentOptions.onError(createCompilerError(code, getLoc(index, index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
tokenizer.reset()
|
||||||
|
currentOpenTag = null
|
||||||
|
currentProp = null
|
||||||
|
currentAttrValue = ''
|
||||||
|
currentAttrStartIndex = -1
|
||||||
|
currentAttrEndIndex = -1
|
||||||
|
stack.length = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function baseParse(input: string, options?: ParserOptions): RootNode {
|
||||||
|
reset()
|
||||||
|
currentInput = input
|
||||||
|
currentOptions = extend({}, defaultParserOptions)
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
let key: keyof ParserOptions
|
||||||
|
for (key in options) {
|
||||||
|
if (options[key] != null) {
|
||||||
|
// @ts-ignore
|
||||||
|
currentOptions[key] = options[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
if (!__BROWSER__ && currentOptions.decodeEntities) {
|
||||||
|
console.warn(
|
||||||
|
`[@vue/compiler-core] decodeEntities option is passed but will be ` +
|
||||||
|
`ignored in non-browser builds.`
|
||||||
|
)
|
||||||
|
} else if (__BROWSER__ && !currentOptions.decodeEntities) {
|
||||||
|
throw new Error(
|
||||||
|
`[@vue/compiler-core] decodeEntities option is required in browser builds.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenizer.mode =
|
||||||
|
currentOptions.parseMode === 'html'
|
||||||
|
? ParseMode.HTML
|
||||||
|
: currentOptions.parseMode === 'sfc'
|
||||||
|
? ParseMode.SFC
|
||||||
|
: ParseMode.BASE
|
||||||
|
|
||||||
|
tokenizer.inXML =
|
||||||
|
currentOptions.ns === Namespaces.SVG ||
|
||||||
|
currentOptions.ns === Namespaces.MATH_ML
|
||||||
|
|
||||||
|
const delimiters = options?.delimiters
|
||||||
|
if (delimiters) {
|
||||||
|
tokenizer.delimiterOpen = toCharCodes(delimiters[0])
|
||||||
|
tokenizer.delimiterClose = toCharCodes(delimiters[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = (currentRoot = createRoot([], input))
|
||||||
|
tokenizer.parse(currentInput)
|
||||||
|
root.loc = getLoc(0, input.length)
|
||||||
|
root.children = condenseWhitespace(root.children)
|
||||||
|
currentRoot = null
|
||||||
|
return root
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -334,6 +334,7 @@ export function transform(root: RootNode, options: TransformOptions) {
|
||||||
root.hoists = context.hoists
|
root.hoists = context.hoists
|
||||||
root.temps = context.temps
|
root.temps = context.temps
|
||||||
root.cached = context.cached
|
root.cached = context.cached
|
||||||
|
root.transformed = true
|
||||||
|
|
||||||
if (__COMPAT__) {
|
if (__COMPAT__) {
|
||||||
root.filters = [...context.filters!]
|
root.filters = [...context.filters!]
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,10 @@ import {
|
||||||
GUARD_REACTIVE_PROPS
|
GUARD_REACTIVE_PROPS
|
||||||
} from '../runtimeHelpers'
|
} from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
getInnerRange,
|
|
||||||
toValidAssetId,
|
toValidAssetId,
|
||||||
findProp,
|
findProp,
|
||||||
isCoreComponent,
|
isCoreComponent,
|
||||||
isStaticArgOf,
|
isStaticArgOf,
|
||||||
findDir,
|
|
||||||
isStaticExp
|
isStaticExp
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { buildSlots } from './vSlot'
|
import { buildSlots } from './vSlot'
|
||||||
|
|
@ -285,19 +283,6 @@ export function resolveComponentType(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.5 v-is (TODO: remove in 3.4)
|
|
||||||
const isDir = !isExplicitDynamic && findDir(node, 'is')
|
|
||||||
if (isDir && isDir.exp) {
|
|
||||||
if (__DEV__) {
|
|
||||||
context.onWarn(
|
|
||||||
createCompilerError(ErrorCodes.DEPRECATION_V_IS, isDir.loc)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
|
|
||||||
isDir.exp
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
|
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
|
||||||
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
||||||
if (builtIn) {
|
if (builtIn) {
|
||||||
|
|
@ -489,7 +474,7 @@ export function buildProps(
|
||||||
// static attribute
|
// static attribute
|
||||||
const prop = props[i]
|
const prop = props[i]
|
||||||
if (prop.type === NodeTypes.ATTRIBUTE) {
|
if (prop.type === NodeTypes.ATTRIBUTE) {
|
||||||
const { loc, name, value } = prop
|
const { loc, name, nameLoc, value } = prop
|
||||||
let isStatic = true
|
let isStatic = true
|
||||||
if (name === 'ref') {
|
if (name === 'ref') {
|
||||||
hasRef = true
|
hasRef = true
|
||||||
|
|
@ -536,11 +521,7 @@ export function buildProps(
|
||||||
}
|
}
|
||||||
properties.push(
|
properties.push(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
createSimpleExpression(
|
createSimpleExpression(name, true, nameLoc),
|
||||||
name,
|
|
||||||
true,
|
|
||||||
getInnerRange(loc, 0, name.length)
|
|
||||||
),
|
|
||||||
createSimpleExpression(
|
createSimpleExpression(
|
||||||
value ? value.content : '',
|
value ? value.content : '',
|
||||||
isStatic,
|
isStatic,
|
||||||
|
|
|
||||||
|
|
@ -336,9 +336,9 @@ export function processExpression(
|
||||||
id.name,
|
id.name,
|
||||||
false,
|
false,
|
||||||
{
|
{
|
||||||
source,
|
|
||||||
start: advancePositionWithClone(node.loc.start, source, start),
|
start: advancePositionWithClone(node.loc.start, source, start),
|
||||||
end: advancePositionWithClone(node.loc.start, source, end)
|
end: advancePositionWithClone(node.loc.start, source, end),
|
||||||
|
source
|
||||||
},
|
},
|
||||||
id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
|
id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,25 @@ import {
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { camelize } from '@vue/shared'
|
import { camelize } from '@vue/shared'
|
||||||
import { CAMELIZE } from '../runtimeHelpers'
|
import { CAMELIZE } from '../runtimeHelpers'
|
||||||
|
import { processExpression } from './transformExpression'
|
||||||
|
|
||||||
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
|
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
|
||||||
// codegen for the entire props object. This transform here is only for v-bind
|
// codegen for the entire props object. This transform here is only for v-bind
|
||||||
// *with* args.
|
// *with* args.
|
||||||
export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
const { exp, modifiers, loc } = dir
|
const { modifiers, loc } = dir
|
||||||
const arg = dir.arg!
|
const arg = dir.arg!
|
||||||
|
|
||||||
|
// :arg is replaced by :arg="arg"
|
||||||
|
let { exp } = dir
|
||||||
|
if (!exp && arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
|
const propName = camelize(arg.content)
|
||||||
|
exp = dir.exp = createSimpleExpression(propName, false, arg.loc)
|
||||||
|
if (!__BROWSER__) {
|
||||||
|
exp = dir.exp = processExpression(exp, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
arg.children.unshift(`(`)
|
arg.children.unshift(`(`)
|
||||||
arg.children.push(`) || ""`)
|
arg.children.push(`) || ""`)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
SourceLocation,
|
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createFunctionExpression,
|
createFunctionExpression,
|
||||||
|
|
@ -28,17 +27,16 @@ import {
|
||||||
createBlockStatement,
|
createBlockStatement,
|
||||||
createCompoundExpression,
|
createCompoundExpression,
|
||||||
getVNodeBlockHelper,
|
getVNodeBlockHelper,
|
||||||
getVNodeHelper
|
getVNodeHelper,
|
||||||
|
ForParseResult
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
getInnerRange,
|
|
||||||
findProp,
|
findProp,
|
||||||
isTemplateNode,
|
isTemplateNode,
|
||||||
isSlotOutlet,
|
isSlotOutlet,
|
||||||
injectProp,
|
injectProp,
|
||||||
findDir,
|
findDir
|
||||||
forAliasRE
|
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import {
|
import {
|
||||||
RENDER_LIST,
|
RENDER_LIST,
|
||||||
|
|
@ -256,12 +254,7 @@ export function processFor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseResult = parseForExpression(
|
const parseResult = dir.forParseResult
|
||||||
// can only be simple expression because vFor transform is applied
|
|
||||||
// before expression transform.
|
|
||||||
dir.exp as SimpleExpressionNode,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!parseResult) {
|
if (!parseResult) {
|
||||||
context.onError(
|
context.onError(
|
||||||
|
|
@ -270,6 +263,8 @@ export function processFor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalizeForParseResult(parseResult, context)
|
||||||
|
|
||||||
const { addIdentifiers, removeIdentifiers, scopes } = context
|
const { addIdentifiers, removeIdentifiers, scopes } = context
|
||||||
const { source, value, key, index } = parseResult
|
const { source, value, key, index } = parseResult
|
||||||
|
|
||||||
|
|
@ -309,107 +304,56 @@ export function processFor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This regex doesn't cover the case if key or index aliases have destructuring,
|
export function finalizeForParseResult(
|
||||||
// but those do not make sense in the first place, so this works in practice.
|
result: ForParseResult,
|
||||||
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
|
||||||
const stripParensRE = /^\(|\)$/g
|
|
||||||
|
|
||||||
export interface ForParseResult {
|
|
||||||
source: ExpressionNode
|
|
||||||
value: ExpressionNode | undefined
|
|
||||||
key: ExpressionNode | undefined
|
|
||||||
index: ExpressionNode | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseForExpression(
|
|
||||||
input: SimpleExpressionNode,
|
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): ForParseResult | undefined {
|
) {
|
||||||
const loc = input.loc
|
if (result.finalized) return
|
||||||
const exp = input.content
|
|
||||||
const inMatch = exp.match(forAliasRE)
|
|
||||||
if (!inMatch) return
|
|
||||||
|
|
||||||
const [, LHS, RHS] = inMatch
|
|
||||||
|
|
||||||
const result: ForParseResult = {
|
|
||||||
source: createAliasExpression(
|
|
||||||
loc,
|
|
||||||
RHS.trim(),
|
|
||||||
exp.indexOf(RHS, LHS.length)
|
|
||||||
),
|
|
||||||
value: undefined,
|
|
||||||
key: undefined,
|
|
||||||
index: undefined
|
|
||||||
}
|
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||||
result.source = processExpression(
|
result.source = processExpression(
|
||||||
result.source as SimpleExpressionNode,
|
result.source as SimpleExpressionNode,
|
||||||
context
|
context
|
||||||
)
|
)
|
||||||
|
if (result.key) {
|
||||||
|
result.key = processExpression(
|
||||||
|
result.key as SimpleExpressionNode,
|
||||||
|
context,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (result.index) {
|
||||||
|
result.index = processExpression(
|
||||||
|
result.index as SimpleExpressionNode,
|
||||||
|
context,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (result.value) {
|
||||||
|
result.value = processExpression(
|
||||||
|
result.value as SimpleExpressionNode,
|
||||||
|
context,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (__DEV__ && __BROWSER__) {
|
if (__DEV__ && __BROWSER__) {
|
||||||
validateBrowserExpression(result.source as SimpleExpressionNode, context)
|
validateBrowserExpression(result.source as SimpleExpressionNode, context)
|
||||||
}
|
if (result.key) {
|
||||||
|
validateBrowserExpression(
|
||||||
let valueContent = LHS.trim().replace(stripParensRE, '').trim()
|
result.key as SimpleExpressionNode,
|
||||||
const trimmedOffset = LHS.indexOf(valueContent)
|
context,
|
||||||
|
true
|
||||||
const iteratorMatch = valueContent.match(forIteratorRE)
|
)
|
||||||
if (iteratorMatch) {
|
|
||||||
valueContent = valueContent.replace(forIteratorRE, '').trim()
|
|
||||||
|
|
||||||
const keyContent = iteratorMatch[1].trim()
|
|
||||||
let keyOffset: number | undefined
|
|
||||||
if (keyContent) {
|
|
||||||
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
|
|
||||||
result.key = createAliasExpression(loc, keyContent, keyOffset)
|
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
|
||||||
result.key = processExpression(result.key, context, true)
|
|
||||||
}
|
|
||||||
if (__DEV__ && __BROWSER__) {
|
|
||||||
validateBrowserExpression(
|
|
||||||
result.key as SimpleExpressionNode,
|
|
||||||
context,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (result.index) {
|
||||||
if (iteratorMatch[2]) {
|
validateBrowserExpression(
|
||||||
const indexContent = iteratorMatch[2].trim()
|
result.index as SimpleExpressionNode,
|
||||||
|
context,
|
||||||
if (indexContent) {
|
true
|
||||||
result.index = createAliasExpression(
|
)
|
||||||
loc,
|
|
||||||
indexContent,
|
|
||||||
exp.indexOf(
|
|
||||||
indexContent,
|
|
||||||
result.key
|
|
||||||
? keyOffset! + keyContent.length
|
|
||||||
: trimmedOffset + valueContent.length
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
|
||||||
result.index = processExpression(result.index, context, true)
|
|
||||||
}
|
|
||||||
if (__DEV__ && __BROWSER__) {
|
|
||||||
validateBrowserExpression(
|
|
||||||
result.index as SimpleExpressionNode,
|
|
||||||
context,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
if (result.value) {
|
||||||
|
|
||||||
if (valueContent) {
|
|
||||||
result.value = createAliasExpression(loc, valueContent, trimmedOffset)
|
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
|
||||||
result.value = processExpression(result.value, context, true)
|
|
||||||
}
|
|
||||||
if (__DEV__ && __BROWSER__) {
|
|
||||||
validateBrowserExpression(
|
validateBrowserExpression(
|
||||||
result.value as SimpleExpressionNode,
|
result.value as SimpleExpressionNode,
|
||||||
context,
|
context,
|
||||||
|
|
@ -417,20 +361,7 @@ export function parseForExpression(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result.finalized = true
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAliasExpression(
|
|
||||||
range: SourceLocation,
|
|
||||||
content: string,
|
|
||||||
offset: number
|
|
||||||
): SimpleExpressionNode {
|
|
||||||
return createSimpleExpression(
|
|
||||||
content,
|
|
||||||
false,
|
|
||||||
getInnerRange(range, offset, content.length)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createForLoopParams(
|
export function createForLoopParams(
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,7 @@ import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
import { validateBrowserExpression } from '../validateExpression'
|
import { validateBrowserExpression } from '../validateExpression'
|
||||||
import { FRAGMENT, CREATE_COMMENT } from '../runtimeHelpers'
|
import { FRAGMENT, CREATE_COMMENT } from '../runtimeHelpers'
|
||||||
import {
|
import { injectProp, findDir, findProp, getMemoedVNodeCall } from '../utils'
|
||||||
injectProp,
|
|
||||||
findDir,
|
|
||||||
findProp,
|
|
||||||
isBuiltInType,
|
|
||||||
getMemoedVNodeCall
|
|
||||||
} from '../utils'
|
|
||||||
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||||
|
|
||||||
export const transformIf = createStructuralDirectiveTransform(
|
export const transformIf = createStructuralDirectiveTransform(
|
||||||
|
|
@ -165,7 +159,8 @@ export function processIf(
|
||||||
!(
|
!(
|
||||||
context.parent &&
|
context.parent &&
|
||||||
context.parent.type === NodeTypes.ELEMENT &&
|
context.parent.type === NodeTypes.ELEMENT &&
|
||||||
isBuiltInType(context.parent.tag, 'transition')
|
(context.parent.tag === 'transition' ||
|
||||||
|
context.parent.tag === 'Transition')
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
branch.children = [...comments, ...branch.children]
|
branch.children = [...comments, ...branch.children]
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
return createTransformProps()
|
return createTransformProps()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we assume v-model directives are always parsed
|
||||||
|
// (not artificially created by a transform)
|
||||||
const rawExp = exp.loc.source
|
const rawExp = exp.loc.source
|
||||||
const expString =
|
const expString =
|
||||||
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
|
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import {
|
||||||
SourceLocation,
|
SourceLocation,
|
||||||
createConditionalExpression,
|
createConditionalExpression,
|
||||||
ConditionalExpression,
|
ConditionalExpression,
|
||||||
SimpleExpressionNode,
|
|
||||||
FunctionExpression,
|
FunctionExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
|
|
@ -32,7 +31,7 @@ import {
|
||||||
isStaticExp
|
isStaticExp
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
|
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
|
||||||
import { parseForExpression, createForLoopParams } from './vFor'
|
import { createForLoopParams, finalizeForParseResult } from './vFor'
|
||||||
import { SlotFlags, slotFlagsText } from '@vue/shared'
|
import { SlotFlags, slotFlagsText } from '@vue/shared'
|
||||||
|
|
||||||
const defaultFallback = createSimpleExpression(`undefined`, false)
|
const defaultFallback = createSimpleExpression(`undefined`, false)
|
||||||
|
|
@ -78,11 +77,9 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
|
||||||
node.props.some(isVSlot) &&
|
node.props.some(isVSlot) &&
|
||||||
(vFor = findDir(node, 'for'))
|
(vFor = findDir(node, 'for'))
|
||||||
) {
|
) {
|
||||||
const result = (vFor.parseResult = parseForExpression(
|
const result = vFor.forParseResult
|
||||||
vFor.exp as SimpleExpressionNode,
|
|
||||||
context
|
|
||||||
))
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
finalizeForParseResult(result, context)
|
||||||
const { value, key, index } = result
|
const { value, key, index } = result
|
||||||
const { addIdentifiers, removeIdentifiers } = context
|
const { addIdentifiers, removeIdentifiers } = context
|
||||||
value && addIdentifiers(value)
|
value && addIdentifiers(value)
|
||||||
|
|
@ -100,7 +97,7 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
|
||||||
|
|
||||||
export type SlotFnBuilder = (
|
export type SlotFnBuilder = (
|
||||||
slotProps: ExpressionNode | undefined,
|
slotProps: ExpressionNode | undefined,
|
||||||
vForExp: ExpressionNode | undefined,
|
vFor: DirectiveNode | undefined,
|
||||||
slotChildren: TemplateChildNode[],
|
slotChildren: TemplateChildNode[],
|
||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
) => FunctionExpression
|
) => FunctionExpression
|
||||||
|
|
@ -203,12 +200,7 @@ export function buildSlots(
|
||||||
}
|
}
|
||||||
|
|
||||||
const vFor = findDir(slotElement, 'for')
|
const vFor = findDir(slotElement, 'for')
|
||||||
const slotFunction = buildSlotFn(
|
const slotFunction = buildSlotFn(slotProps, vFor, slotChildren, slotLoc)
|
||||||
slotProps,
|
|
||||||
vFor?.exp,
|
|
||||||
slotChildren,
|
|
||||||
slotLoc
|
|
||||||
)
|
|
||||||
|
|
||||||
// check if this slot is conditional (v-if/v-for)
|
// check if this slot is conditional (v-if/v-for)
|
||||||
let vIf: DirectiveNode | undefined
|
let vIf: DirectiveNode | undefined
|
||||||
|
|
@ -266,10 +258,9 @@ export function buildSlots(
|
||||||
}
|
}
|
||||||
} else if (vFor) {
|
} else if (vFor) {
|
||||||
hasDynamicSlots = true
|
hasDynamicSlots = true
|
||||||
const parseResult =
|
const parseResult = vFor.forParseResult
|
||||||
vFor.parseResult ||
|
|
||||||
parseForExpression(vFor.exp as SimpleExpressionNode, context)
|
|
||||||
if (parseResult) {
|
if (parseResult) {
|
||||||
|
finalizeForParseResult(parseResult, context)
|
||||||
// Render the dynamic slots as an array and add it to the createSlot()
|
// Render the dynamic slots as an array and add it to the createSlot()
|
||||||
// args. The runtime knows how to handle it appropriately.
|
// args. The runtime knows how to handle it appropriately.
|
||||||
dynamicSlots.push(
|
dynamicSlots.push(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
SourceLocation,
|
|
||||||
Position,
|
Position,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
|
@ -37,7 +36,7 @@ import {
|
||||||
GUARD_REACTIVE_PROPS,
|
GUARD_REACTIVE_PROPS,
|
||||||
WITH_MEMO
|
WITH_MEMO
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { isString, isObject, hyphenate, extend, NOOP } from '@vue/shared'
|
import { isString, isObject, NOOP } from '@vue/shared'
|
||||||
import { PropsExpression } from './transforms/transformElement'
|
import { PropsExpression } from './transforms/transformElement'
|
||||||
import { parseExpression } from '@babel/parser'
|
import { parseExpression } from '@babel/parser'
|
||||||
import { Expression } from '@babel/types'
|
import { Expression } from '@babel/types'
|
||||||
|
|
@ -45,18 +44,20 @@ import { Expression } from '@babel/types'
|
||||||
export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
||||||
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
|
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
|
||||||
|
|
||||||
export const isBuiltInType = (tag: string, expected: string): boolean =>
|
|
||||||
tag === expected || tag === hyphenate(expected)
|
|
||||||
|
|
||||||
export function isCoreComponent(tag: string): symbol | void {
|
export function isCoreComponent(tag: string): symbol | void {
|
||||||
if (isBuiltInType(tag, 'Teleport')) {
|
switch (tag) {
|
||||||
return TELEPORT
|
case 'Teleport':
|
||||||
} else if (isBuiltInType(tag, 'Suspense')) {
|
case 'teleport':
|
||||||
return SUSPENSE
|
return TELEPORT
|
||||||
} else if (isBuiltInType(tag, 'KeepAlive')) {
|
case 'Suspense':
|
||||||
return KEEP_ALIVE
|
case 'suspense':
|
||||||
} else if (isBuiltInType(tag, 'BaseTransition')) {
|
return SUSPENSE
|
||||||
return BASE_TRANSITION
|
case 'KeepAlive':
|
||||||
|
case 'keep-alive':
|
||||||
|
return KEEP_ALIVE
|
||||||
|
case 'BaseTransition':
|
||||||
|
case 'base-transition':
|
||||||
|
return BASE_TRANSITION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +65,7 @@ const nonIdentifierRE = /^\d|[^\$\w]/
|
||||||
export const isSimpleIdentifier = (name: string): boolean =>
|
export const isSimpleIdentifier = (name: string): boolean =>
|
||||||
!nonIdentifierRE.test(name)
|
!nonIdentifierRE.test(name)
|
||||||
|
|
||||||
const enum MemberExpLexState {
|
enum MemberExpLexState {
|
||||||
inMemberExp,
|
inMemberExp,
|
||||||
inBrackets,
|
inBrackets,
|
||||||
inParens,
|
inParens,
|
||||||
|
|
@ -174,38 +175,17 @@ export const isMemberExpression = __BROWSER__
|
||||||
? isMemberExpressionBrowser
|
? isMemberExpressionBrowser
|
||||||
: isMemberExpressionNode
|
: isMemberExpressionNode
|
||||||
|
|
||||||
export function getInnerRange(
|
|
||||||
loc: SourceLocation,
|
|
||||||
offset: number,
|
|
||||||
length: number
|
|
||||||
): SourceLocation {
|
|
||||||
__TEST__ && assert(offset <= loc.source.length)
|
|
||||||
const source = loc.source.slice(offset, offset + length)
|
|
||||||
const newLoc: SourceLocation = {
|
|
||||||
source,
|
|
||||||
start: advancePositionWithClone(loc.start, loc.source, offset),
|
|
||||||
end: loc.end
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length != null) {
|
|
||||||
__TEST__ && assert(offset + length <= loc.source.length)
|
|
||||||
newLoc.end = advancePositionWithClone(
|
|
||||||
loc.start,
|
|
||||||
loc.source,
|
|
||||||
offset + length
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return newLoc
|
|
||||||
}
|
|
||||||
|
|
||||||
export function advancePositionWithClone(
|
export function advancePositionWithClone(
|
||||||
pos: Position,
|
pos: Position,
|
||||||
source: string,
|
source: string,
|
||||||
numberOfCharacters: number = source.length
|
numberOfCharacters: number = source.length
|
||||||
): Position {
|
): Position {
|
||||||
return advancePositionWithMutation(
|
return advancePositionWithMutation(
|
||||||
extend({}, pos),
|
{
|
||||||
|
offset: pos.offset,
|
||||||
|
line: pos.line,
|
||||||
|
column: pos.column
|
||||||
|
},
|
||||||
source,
|
source,
|
||||||
numberOfCharacters
|
numberOfCharacters
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
TextNode,
|
TextNode,
|
||||||
ErrorCodes,
|
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
InterpolationNode,
|
InterpolationNode,
|
||||||
AttributeNode,
|
AttributeNode,
|
||||||
ConstantTypes
|
ConstantTypes,
|
||||||
|
Namespaces
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { parserOptions, DOMNamespaces } from '../src/parserOptions'
|
import { parserOptions } from '../src/parserOptions'
|
||||||
|
|
||||||
describe('DOM parser', () => {
|
describe('DOM parser', () => {
|
||||||
describe('Text', () => {
|
describe('Text', () => {
|
||||||
|
|
@ -32,7 +32,7 @@ describe('DOM parser', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('textarea handles character references', () => {
|
test('textarea handles entities', () => {
|
||||||
const ast = parse('<textarea>&</textarea>', parserOptions)
|
const ast = parse('<textarea>&</textarea>', parserOptions)
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
const text = element.children[0] as TextNode
|
const text = element.children[0] as TextNode
|
||||||
|
|
@ -277,11 +277,10 @@ describe('DOM parser', () => {
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
ns: DOMNamespaces.HTML,
|
ns: Namespaces.HTML,
|
||||||
tag: 'img',
|
tag: 'img',
|
||||||
tagType: ElementTypes.ELEMENT,
|
tagType: ElementTypes.ELEMENT,
|
||||||
props: [],
|
props: [],
|
||||||
isSelfClosing: false,
|
|
||||||
children: [],
|
children: [],
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
|
|
@ -316,15 +315,8 @@ describe('DOM parser', () => {
|
||||||
|
|
||||||
test('Strict end tag detection for textarea.', () => {
|
test('Strict end tag detection for textarea.', () => {
|
||||||
const ast = parse(
|
const ast = parse(
|
||||||
'<textarea>hello</textarea</textarea0></texTArea a="<>">',
|
'<textarea>hello</textarea</textarea0></texTArea>',
|
||||||
{
|
parserOptions
|
||||||
...parserOptions,
|
|
||||||
onError: err => {
|
|
||||||
if (err.code !== ErrorCodes.END_TAG_WITH_ATTRIBUTES) {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
const text = element.children[0] as TextNode
|
const text = element.children[0] as TextNode
|
||||||
|
|
@ -347,21 +339,21 @@ describe('DOM parser', () => {
|
||||||
const ast = parse('<html>test</html>', parserOptions)
|
const ast = parse('<html>test</html>', parserOptions)
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element.ns).toBe(DOMNamespaces.HTML)
|
expect(element.ns).toBe(Namespaces.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('SVG namespace', () => {
|
test('SVG namespace', () => {
|
||||||
const ast = parse('<svg>test</svg>', parserOptions)
|
const ast = parse('<svg>test</svg>', parserOptions)
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element.ns).toBe(DOMNamespaces.SVG)
|
expect(element.ns).toBe(Namespaces.SVG)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('MATH_ML namespace', () => {
|
test('MATH_ML namespace', () => {
|
||||||
const ast = parse('<math>test</math>', parserOptions)
|
const ast = parse('<math>test</math>', parserOptions)
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element.ns).toBe(DOMNamespaces.MATH_ML)
|
expect(element.ns).toBe(Namespaces.MATH_ML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('SVG in MATH_ML namespace', () => {
|
test('SVG in MATH_ML namespace', () => {
|
||||||
|
|
@ -373,8 +365,8 @@ describe('DOM parser', () => {
|
||||||
const elementAnnotation = elementMath.children[0] as ElementNode
|
const elementAnnotation = elementMath.children[0] as ElementNode
|
||||||
const elementSvg = elementAnnotation.children[0] as ElementNode
|
const elementSvg = elementAnnotation.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
|
expect(elementMath.ns).toBe(Namespaces.MATH_ML)
|
||||||
expect(elementSvg.ns).toBe(DOMNamespaces.SVG)
|
expect(elementSvg.ns).toBe(Namespaces.SVG)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('html text/html in MATH_ML namespace', () => {
|
test('html text/html in MATH_ML namespace', () => {
|
||||||
|
|
@ -387,8 +379,8 @@ describe('DOM parser', () => {
|
||||||
const elementAnnotation = elementMath.children[0] as ElementNode
|
const elementAnnotation = elementMath.children[0] as ElementNode
|
||||||
const element = elementAnnotation.children[0] as ElementNode
|
const element = elementAnnotation.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
|
expect(elementMath.ns).toBe(Namespaces.MATH_ML)
|
||||||
expect(element.ns).toBe(DOMNamespaces.HTML)
|
expect(element.ns).toBe(Namespaces.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('html application/xhtml+xml in MATH_ML namespace', () => {
|
test('html application/xhtml+xml in MATH_ML namespace', () => {
|
||||||
|
|
@ -400,8 +392,8 @@ describe('DOM parser', () => {
|
||||||
const elementAnnotation = elementMath.children[0] as ElementNode
|
const elementAnnotation = elementMath.children[0] as ElementNode
|
||||||
const element = elementAnnotation.children[0] as ElementNode
|
const element = elementAnnotation.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
|
expect(elementMath.ns).toBe(Namespaces.MATH_ML)
|
||||||
expect(element.ns).toBe(DOMNamespaces.HTML)
|
expect(element.ns).toBe(Namespaces.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('mtext malignmark in MATH_ML namespace', () => {
|
test('mtext malignmark in MATH_ML namespace', () => {
|
||||||
|
|
@ -413,8 +405,8 @@ describe('DOM parser', () => {
|
||||||
const elementText = elementMath.children[0] as ElementNode
|
const elementText = elementMath.children[0] as ElementNode
|
||||||
const element = elementText.children[0] as ElementNode
|
const element = elementText.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
|
expect(elementMath.ns).toBe(Namespaces.MATH_ML)
|
||||||
expect(element.ns).toBe(DOMNamespaces.MATH_ML)
|
expect(element.ns).toBe(Namespaces.MATH_ML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('mtext and not malignmark tag in MATH_ML namespace', () => {
|
test('mtext and not malignmark tag in MATH_ML namespace', () => {
|
||||||
|
|
@ -423,8 +415,8 @@ describe('DOM parser', () => {
|
||||||
const elementText = elementMath.children[0] as ElementNode
|
const elementText = elementMath.children[0] as ElementNode
|
||||||
const element = elementText.children[0] as ElementNode
|
const element = elementText.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
|
expect(elementMath.ns).toBe(Namespaces.MATH_ML)
|
||||||
expect(element.ns).toBe(DOMNamespaces.HTML)
|
expect(element.ns).toBe(Namespaces.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('foreignObject tag in SVG namespace', () => {
|
test('foreignObject tag in SVG namespace', () => {
|
||||||
|
|
@ -436,8 +428,8 @@ describe('DOM parser', () => {
|
||||||
const elementForeignObject = elementSvg.children[0] as ElementNode
|
const elementForeignObject = elementSvg.children[0] as ElementNode
|
||||||
const element = elementForeignObject.children[0] as ElementNode
|
const element = elementForeignObject.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementSvg.ns).toBe(DOMNamespaces.SVG)
|
expect(elementSvg.ns).toBe(Namespaces.SVG)
|
||||||
expect(element.ns).toBe(DOMNamespaces.HTML)
|
expect(element.ns).toBe(Namespaces.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('desc tag in SVG namespace', () => {
|
test('desc tag in SVG namespace', () => {
|
||||||
|
|
@ -446,8 +438,8 @@ describe('DOM parser', () => {
|
||||||
const elementDesc = elementSvg.children[0] as ElementNode
|
const elementDesc = elementSvg.children[0] as ElementNode
|
||||||
const element = elementDesc.children[0] as ElementNode
|
const element = elementDesc.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementSvg.ns).toBe(DOMNamespaces.SVG)
|
expect(elementSvg.ns).toBe(Namespaces.SVG)
|
||||||
expect(element.ns).toBe(DOMNamespaces.HTML)
|
expect(element.ns).toBe(Namespaces.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('title tag in SVG namespace', () => {
|
test('title tag in SVG namespace', () => {
|
||||||
|
|
@ -456,8 +448,8 @@ describe('DOM parser', () => {
|
||||||
const elementTitle = elementSvg.children[0] as ElementNode
|
const elementTitle = elementSvg.children[0] as ElementNode
|
||||||
const element = elementTitle.children[0] as ElementNode
|
const element = elementTitle.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementSvg.ns).toBe(DOMNamespaces.SVG)
|
expect(elementSvg.ns).toBe(Namespaces.SVG)
|
||||||
expect(element.ns).toBe(DOMNamespaces.HTML)
|
expect(element.ns).toBe(Namespaces.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('SVG in HTML namespace', () => {
|
test('SVG in HTML namespace', () => {
|
||||||
|
|
@ -465,8 +457,8 @@ describe('DOM parser', () => {
|
||||||
const elementHtml = ast.children[0] as ElementNode
|
const elementHtml = ast.children[0] as ElementNode
|
||||||
const element = elementHtml.children[0] as ElementNode
|
const element = elementHtml.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementHtml.ns).toBe(DOMNamespaces.HTML)
|
expect(elementHtml.ns).toBe(Namespaces.HTML)
|
||||||
expect(element.ns).toBe(DOMNamespaces.SVG)
|
expect(element.ns).toBe(Namespaces.SVG)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('MATH in HTML namespace', () => {
|
test('MATH in HTML namespace', () => {
|
||||||
|
|
@ -474,8 +466,35 @@ describe('DOM parser', () => {
|
||||||
const elementHtml = ast.children[0] as ElementNode
|
const elementHtml = ast.children[0] as ElementNode
|
||||||
const element = elementHtml.children[0] as ElementNode
|
const element = elementHtml.children[0] as ElementNode
|
||||||
|
|
||||||
expect(elementHtml.ns).toBe(DOMNamespaces.HTML)
|
expect(elementHtml.ns).toBe(Namespaces.HTML)
|
||||||
expect(element.ns).toBe(DOMNamespaces.MATH_ML)
|
expect(element.ns).toBe(Namespaces.MATH_ML)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('root ns', () => {
|
||||||
|
const ast = parse('<foreignObject><test/></foreignObject>', {
|
||||||
|
...parserOptions,
|
||||||
|
ns: Namespaces.SVG
|
||||||
|
})
|
||||||
|
const elementForieng = ast.children[0] as ElementNode
|
||||||
|
const element = elementForieng.children[0] as ElementNode
|
||||||
|
|
||||||
|
expect(elementForieng.ns).toBe(Namespaces.SVG)
|
||||||
|
expect(element.ns).toBe(Namespaces.HTML)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('correct XML handling with root ns', () => {
|
||||||
|
// when root ns is an XML namespace, there should be no special content
|
||||||
|
// treatment for <script>, <style>, <textarea> etc.
|
||||||
|
const ast = parse('<script><g/><g/></script>', {
|
||||||
|
...parserOptions,
|
||||||
|
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' }
|
||||||
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-dom",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.3.9",
|
"version": "3.4.0-alpha.3",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
import { ParserOptions } from '@vue/compiler-core'
|
|
||||||
import namedCharacterReferences from './namedChars.json'
|
|
||||||
|
|
||||||
// lazy compute this to make this file tree-shakable for browser
|
|
||||||
let maxCRNameLength: number
|
|
||||||
|
|
||||||
export const decodeHtml: ParserOptions['decodeEntities'] = (
|
|
||||||
rawText,
|
|
||||||
asAttr
|
|
||||||
) => {
|
|
||||||
let offset = 0
|
|
||||||
const end = rawText.length
|
|
||||||
let decodedText = ''
|
|
||||||
|
|
||||||
function advance(length: number) {
|
|
||||||
offset += length
|
|
||||||
rawText = rawText.slice(length)
|
|
||||||
}
|
|
||||||
|
|
||||||
while (offset < end) {
|
|
||||||
const head = /&(?:#x?)?/i.exec(rawText)
|
|
||||||
if (!head || offset + head.index >= end) {
|
|
||||||
const remaining = end - offset
|
|
||||||
decodedText += rawText.slice(0, remaining)
|
|
||||||
advance(remaining)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance to the "&".
|
|
||||||
decodedText += rawText.slice(0, head.index)
|
|
||||||
advance(head.index)
|
|
||||||
|
|
||||||
if (head[0] === '&') {
|
|
||||||
// Named character reference.
|
|
||||||
let name = ''
|
|
||||||
let value: string | undefined = undefined
|
|
||||||
if (/[0-9a-z]/i.test(rawText[1])) {
|
|
||||||
if (!maxCRNameLength) {
|
|
||||||
maxCRNameLength = Object.keys(namedCharacterReferences).reduce(
|
|
||||||
(max, name) => Math.max(max, name.length),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for (let length = maxCRNameLength; !value && length > 0; --length) {
|
|
||||||
name = rawText.slice(1, 1 + length)
|
|
||||||
value = (namedCharacterReferences as Record<string, string>)[name]
|
|
||||||
}
|
|
||||||
if (value) {
|
|
||||||
const semi = name.endsWith(';')
|
|
||||||
if (
|
|
||||||
asAttr &&
|
|
||||||
!semi &&
|
|
||||||
/[=a-z0-9]/i.test(rawText[name.length + 1] || '')
|
|
||||||
) {
|
|
||||||
decodedText += '&' + name
|
|
||||||
advance(1 + name.length)
|
|
||||||
} else {
|
|
||||||
decodedText += value
|
|
||||||
advance(1 + name.length)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
decodedText += '&' + name
|
|
||||||
advance(1 + name.length)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
decodedText += '&'
|
|
||||||
advance(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Numeric character reference.
|
|
||||||
const hex = head[0] === '&#x'
|
|
||||||
const pattern = hex ? /^&#x([0-9a-f]+);?/i : /^&#([0-9]+);?/
|
|
||||||
const body = pattern.exec(rawText)
|
|
||||||
if (!body) {
|
|
||||||
decodedText += head[0]
|
|
||||||
advance(head[0].length)
|
|
||||||
} else {
|
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state
|
|
||||||
let cp = Number.parseInt(body[1], hex ? 16 : 10)
|
|
||||||
if (cp === 0) {
|
|
||||||
cp = 0xfffd
|
|
||||||
} else if (cp > 0x10ffff) {
|
|
||||||
cp = 0xfffd
|
|
||||||
} else if (cp >= 0xd800 && cp <= 0xdfff) {
|
|
||||||
cp = 0xfffd
|
|
||||||
} else if ((cp >= 0xfdd0 && cp <= 0xfdef) || (cp & 0xfffe) === 0xfffe) {
|
|
||||||
// noop
|
|
||||||
} else if (
|
|
||||||
(cp >= 0x01 && cp <= 0x08) ||
|
|
||||||
cp === 0x0b ||
|
|
||||||
(cp >= 0x0d && cp <= 0x1f) ||
|
|
||||||
(cp >= 0x7f && cp <= 0x9f)
|
|
||||||
) {
|
|
||||||
cp = CCR_REPLACEMENTS[cp] || cp
|
|
||||||
}
|
|
||||||
decodedText += String.fromCodePoint(cp)
|
|
||||||
advance(body[0].length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return decodedText
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state
|
|
||||||
const CCR_REPLACEMENTS: Record<number, number | undefined> = {
|
|
||||||
0x80: 0x20ac,
|
|
||||||
0x82: 0x201a,
|
|
||||||
0x83: 0x0192,
|
|
||||||
0x84: 0x201e,
|
|
||||||
0x85: 0x2026,
|
|
||||||
0x86: 0x2020,
|
|
||||||
0x87: 0x2021,
|
|
||||||
0x88: 0x02c6,
|
|
||||||
0x89: 0x2030,
|
|
||||||
0x8a: 0x0160,
|
|
||||||
0x8b: 0x2039,
|
|
||||||
0x8c: 0x0152,
|
|
||||||
0x8e: 0x017d,
|
|
||||||
0x91: 0x2018,
|
|
||||||
0x92: 0x2019,
|
|
||||||
0x93: 0x201c,
|
|
||||||
0x94: 0x201d,
|
|
||||||
0x95: 0x2022,
|
|
||||||
0x96: 0x2013,
|
|
||||||
0x97: 0x2014,
|
|
||||||
0x98: 0x02dc,
|
|
||||||
0x99: 0x2122,
|
|
||||||
0x9a: 0x0161,
|
|
||||||
0x9b: 0x203a,
|
|
||||||
0x9c: 0x0153,
|
|
||||||
0x9e: 0x017e,
|
|
||||||
0x9f: 0x0178
|
|
||||||
}
|
|
||||||
|
|
@ -20,7 +20,7 @@ export function createDOMCompilerError(
|
||||||
) as DOMCompilerError
|
) as DOMCompilerError
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum DOMErrorCodes {
|
export enum DOMErrorCodes {
|
||||||
X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */,
|
X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */,
|
||||||
X_V_HTML_WITH_CHILDREN,
|
X_V_HTML_WITH_CHILDREN,
|
||||||
X_V_TEXT_NO_EXPRESSION,
|
X_V_TEXT_NO_EXPRESSION,
|
||||||
|
|
@ -36,7 +36,7 @@ export const enum DOMErrorCodes {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__TEST__) {
|
if (__TEST__) {
|
||||||
// esbuild cannot infer const enum increments if first value is from another
|
// esbuild cannot infer enum increments if first value is from another
|
||||||
// file, so we have to manually keep them in sync. this check ensures it
|
// file, so we have to manually keep them in sync. this check ensures it
|
||||||
// errors out if there are collisions.
|
// errors out if there are collisions.
|
||||||
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@ export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compile(
|
export function compile(
|
||||||
template: string,
|
src: string | RootNode,
|
||||||
options: CompilerOptions = {}
|
options: CompilerOptions = {}
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
return baseCompile(
|
return baseCompile(
|
||||||
template,
|
src,
|
||||||
extend({}, parserOptions, options, {
|
extend({}, parserOptions, options, {
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
// ignore <script> and <tag>
|
// ignore <script> and <tag>
|
||||||
|
|
@ -68,5 +68,9 @@ export function parse(template: string, options: ParserOptions = {}): RootNode {
|
||||||
|
|
||||||
export * from './runtimeHelpers'
|
export * from './runtimeHelpers'
|
||||||
export { transformStyle } from './transforms/transformStyle'
|
export { transformStyle } from './transforms/transformStyle'
|
||||||
export { createDOMCompilerError, DOMErrorCodes } from './errors'
|
export {
|
||||||
|
createDOMCompilerError,
|
||||||
|
DOMErrorCodes,
|
||||||
|
DOMErrorMessages
|
||||||
|
} from './errors'
|
||||||
export * from '@vue/compiler-core'
|
export * from '@vue/compiler-core'
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,48 +1,30 @@
|
||||||
import {
|
import { ParserOptions, NodeTypes, Namespaces } from '@vue/compiler-core'
|
||||||
TextModes,
|
import { isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
||||||
ParserOptions,
|
|
||||||
ElementNode,
|
|
||||||
NodeTypes,
|
|
||||||
isBuiltInType
|
|
||||||
} from '@vue/compiler-core'
|
|
||||||
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
|
||||||
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
||||||
import { decodeHtml } from './decodeHtml'
|
|
||||||
import { decodeHtmlBrowser } from './decodeHtmlBrowser'
|
import { decodeHtmlBrowser } from './decodeHtmlBrowser'
|
||||||
|
|
||||||
const isRawTextContainer = /*#__PURE__*/ makeMap(
|
|
||||||
'style,iframe,script,noscript',
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
export const enum DOMNamespaces {
|
|
||||||
HTML = 0 /* Namespaces.HTML */,
|
|
||||||
SVG,
|
|
||||||
MATH_ML
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parserOptions: ParserOptions = {
|
export const parserOptions: ParserOptions = {
|
||||||
|
parseMode: 'html',
|
||||||
isVoidTag,
|
isVoidTag,
|
||||||
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
||||||
isPreTag: tag => tag === 'pre',
|
isPreTag: tag => tag === 'pre',
|
||||||
decodeEntities: __BROWSER__ ? decodeHtmlBrowser : decodeHtml,
|
decodeEntities: __BROWSER__ ? decodeHtmlBrowser : undefined,
|
||||||
|
|
||||||
isBuiltInComponent: (tag: string): symbol | undefined => {
|
isBuiltInComponent: tag => {
|
||||||
if (isBuiltInType(tag, `Transition`)) {
|
if (tag === 'Transition' || tag === 'transition') {
|
||||||
return TRANSITION
|
return TRANSITION
|
||||||
} else if (isBuiltInType(tag, `TransitionGroup`)) {
|
} else if (tag === 'TransitionGroup' || tag === 'transition-group') {
|
||||||
return TRANSITION_GROUP
|
return TRANSITION_GROUP
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
|
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
|
||||||
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
|
getNamespace(tag, parent, rootNamespace) {
|
||||||
let ns = parent ? parent.ns : DOMNamespaces.HTML
|
let ns = parent ? parent.ns : rootNamespace
|
||||||
|
if (parent && ns === Namespaces.MATH_ML) {
|
||||||
if (parent && ns === DOMNamespaces.MATH_ML) {
|
|
||||||
if (parent.tag === 'annotation-xml') {
|
if (parent.tag === 'annotation-xml') {
|
||||||
if (tag === 'svg') {
|
if (tag === 'svg') {
|
||||||
return DOMNamespaces.SVG
|
return Namespaces.SVG
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
parent.props.some(
|
parent.props.some(
|
||||||
|
|
@ -54,46 +36,33 @@ export const parserOptions: ParserOptions = {
|
||||||
a.value.content === 'application/xhtml+xml')
|
a.value.content === 'application/xhtml+xml')
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
ns = DOMNamespaces.HTML
|
ns = Namespaces.HTML
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
/^m(?:[ions]|text)$/.test(parent.tag) &&
|
/^m(?:[ions]|text)$/.test(parent.tag) &&
|
||||||
tag !== 'mglyph' &&
|
tag !== 'mglyph' &&
|
||||||
tag !== 'malignmark'
|
tag !== 'malignmark'
|
||||||
) {
|
) {
|
||||||
ns = DOMNamespaces.HTML
|
ns = Namespaces.HTML
|
||||||
}
|
}
|
||||||
} else if (parent && ns === DOMNamespaces.SVG) {
|
} else if (parent && ns === Namespaces.SVG) {
|
||||||
if (
|
if (
|
||||||
parent.tag === 'foreignObject' ||
|
parent.tag === 'foreignObject' ||
|
||||||
parent.tag === 'desc' ||
|
parent.tag === 'desc' ||
|
||||||
parent.tag === 'title'
|
parent.tag === 'title'
|
||||||
) {
|
) {
|
||||||
ns = DOMNamespaces.HTML
|
ns = Namespaces.HTML
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ns === DOMNamespaces.HTML) {
|
if (ns === Namespaces.HTML) {
|
||||||
if (tag === 'svg') {
|
if (tag === 'svg') {
|
||||||
return DOMNamespaces.SVG
|
return Namespaces.SVG
|
||||||
}
|
}
|
||||||
if (tag === 'math') {
|
if (tag === 'math') {
|
||||||
return DOMNamespaces.MATH_ML
|
return Namespaces.MATH_ML
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ns
|
return ns
|
||||||
},
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments
|
|
||||||
getTextMode({ tag, ns }: ElementNode): TextModes {
|
|
||||||
if (ns === DOMNamespaces.HTML) {
|
|
||||||
if (tag === 'textarea' || tag === 'title') {
|
|
||||||
return TextModes.RCDATA
|
|
||||||
}
|
|
||||||
if (isRawTextContainer(tag)) {
|
|
||||||
return TextModes.RAWTEXT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TextModes.DATA
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export const transformTransition: NodeTransform = (node, context) => {
|
||||||
node.props.push({
|
node.props.push({
|
||||||
type: NodeTypes.ATTRIBUTE,
|
type: NodeTypes.ATTRIBUTE,
|
||||||
name: 'persisted',
|
name: 'persisted',
|
||||||
|
nameLoc: node.loc,
|
||||||
value: undefined,
|
value: undefined,
|
||||||
loc: node.loc
|
loc: node.loc
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ import {
|
||||||
PlainElementNode,
|
PlainElementNode,
|
||||||
JSChildNode,
|
JSChildNode,
|
||||||
TextCallNode,
|
TextCallNode,
|
||||||
ConstantTypes
|
ConstantTypes,
|
||||||
|
Namespaces
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import {
|
import {
|
||||||
isVoidTag,
|
isVoidTag,
|
||||||
|
|
@ -31,9 +32,8 @@ import {
|
||||||
isKnownSvgAttr,
|
isKnownSvgAttr,
|
||||||
isBooleanAttr
|
isBooleanAttr
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { DOMNamespaces } from '../parserOptions'
|
|
||||||
|
|
||||||
export const enum StringifyThresholds {
|
export enum StringifyThresholds {
|
||||||
ELEMENT_WITH_BINDING_COUNT = 5,
|
ELEMENT_WITH_BINDING_COUNT = 5,
|
||||||
NODE_COUNT = 20
|
NODE_COUNT = 20
|
||||||
}
|
}
|
||||||
|
|
@ -148,11 +148,11 @@ const getHoistedNode = (node: TemplateChildNode) =>
|
||||||
node.codegenNode.hoisted
|
node.codegenNode.hoisted
|
||||||
|
|
||||||
const dataAriaRE = /^(data|aria)-/
|
const dataAriaRE = /^(data|aria)-/
|
||||||
const isStringifiableAttr = (name: string, ns: DOMNamespaces) => {
|
const isStringifiableAttr = (name: string, ns: Namespaces) => {
|
||||||
return (
|
return (
|
||||||
(ns === DOMNamespaces.HTML
|
(ns === Namespaces.HTML
|
||||||
? isKnownHtmlAttr(name)
|
? isKnownHtmlAttr(name)
|
||||||
: ns === DOMNamespaces.SVG
|
: ns === Namespaces.SVG
|
||||||
? isKnownSvgAttr(name)
|
? isKnownSvgAttr(name)
|
||||||
: false) || dataAriaRE.test(name)
|
: false) || dataAriaRE.test(name)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ return { a }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC analyze <script> bindings > auto name inference > do not overwrite manual name (call) 1`] = `
|
exports[`SFC analyze <script> bindings > auto name inference > do not overwrite manual name (call) 1`] = `
|
||||||
"import { defineComponent } from 'vue'
|
"
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
const __default__ = defineComponent({
|
const __default__ = defineComponent({
|
||||||
name: 'Baz'
|
name: 'Baz'
|
||||||
})
|
})
|
||||||
|
|
@ -30,7 +31,8 @@ return { a, defineComponent }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC analyze <script> bindings > auto name inference > do not overwrite manual name (object) 1`] = `
|
exports[`SFC analyze <script> bindings > auto name inference > do not overwrite manual name (object) 1`] = `
|
||||||
"const __default__ = {
|
"
|
||||||
|
const __default__ = {
|
||||||
name: 'Baz'
|
name: 'Baz'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +47,8 @@ return { a }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > export call expression as default 1`] = `
|
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > export call expression as default 1`] = `
|
||||||
"function fn() {
|
"
|
||||||
|
function fn() {
|
||||||
return \\"hello, world\\";
|
return \\"hello, world\\";
|
||||||
}
|
}
|
||||||
const __default__ = fn();
|
const __default__ = fn();
|
||||||
|
|
@ -63,7 +66,8 @@ return { fn }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > keep original semi style 1`] = `
|
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > keep original semi style 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
props: ['item'],
|
props: ['item'],
|
||||||
emits: ['change'],
|
emits: ['change'],
|
||||||
setup(__props, { expose: __expose, emit: __emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
|
|
@ -515,27 +519,28 @@ return { }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > async/await detection > ref 1`] = `
|
exports[`SFC compile <script setup> > async/await detection > ref 1`] = `
|
||||||
"import { withAsyncContext as _withAsyncContext, ref as _ref } from 'vue'
|
"import { withAsyncContext as _withAsyncContext } from 'vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async setup(__props, { expose: __expose }) {
|
async setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
let __temp, __restore
|
let __temp, __restore
|
||||||
let a = _ref(1 + ((
|
let a = ref(1 + ((
|
||||||
([__temp,__restore] = _withAsyncContext(() => foo)),
|
([__temp,__restore] = _withAsyncContext(() => foo)),
|
||||||
__temp = await __temp,
|
__temp = await __temp,
|
||||||
__restore(),
|
__restore(),
|
||||||
__temp
|
__temp
|
||||||
)))
|
)))
|
||||||
return { a }
|
return { get a() { return a }, set a(v) { a = v } }
|
||||||
}
|
}
|
||||||
|
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 1`] = `
|
exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
async function foo() { await bar }
|
async function foo() { await bar }
|
||||||
|
|
@ -546,7 +551,8 @@ return { foo }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 2`] = `
|
exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 2`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
const foo = async () => { await bar }
|
const foo = async () => { await bar }
|
||||||
|
|
@ -557,7 +563,8 @@ return { foo }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 3`] = `
|
exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 3`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
const obj = { async method() { await bar }}
|
const obj = { async method() { await bar }}
|
||||||
|
|
@ -568,7 +575,8 @@ return { obj }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 4`] = `
|
exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 4`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
const cls = class Foo { async method() { await bar }}
|
const cls = class Foo { async method() { await bar }}
|
||||||
|
|
@ -618,7 +626,8 @@ return { a }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > binding analysis for destructure 1`] = `
|
exports[`SFC compile <script setup> > binding analysis for destructure 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
|
@ -818,23 +827,29 @@ return { bar }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > imports > dedupe between user & helper 1`] = `
|
exports[`SFC compile <script setup> > imports > dedupe between user & helper 1`] = `
|
||||||
"import { ref as _ref } from 'vue'
|
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
|
||||||
import { ref } from 'vue'
|
import { useCssVars, ref } from 'vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
let foo = _ref(1)
|
_useCssVars(_ctx => ({
|
||||||
|
\\"xxxxxxxx-msg\\": (msg.value)
|
||||||
|
}))
|
||||||
|
|
||||||
return { foo, ref }
|
const msg = ref()
|
||||||
|
|
||||||
|
return { msg, useCssVars, ref }
|
||||||
}
|
}
|
||||||
|
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > imports > import dedupe between <script> and <script setup> 1`] = `
|
exports[`SFC compile <script setup> > imports > import dedupe between <script> and <script setup> 1`] = `
|
||||||
"import { x } from './x'
|
"
|
||||||
|
|
||||||
|
import { x } from './x'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
|
|
@ -898,7 +913,9 @@ return { ref }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > imports > should support module string names syntax 1`] = `
|
exports[`SFC compile <script setup> > imports > should support module string names syntax 1`] = `
|
||||||
"import { \\"😏\\" as foo } from './foo'
|
"
|
||||||
|
|
||||||
|
import { \\"😏\\" as foo } from './foo'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
|
|
@ -1185,7 +1202,8 @@ return (_ctx, _cache) => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > inlineTemplate mode > with defineExpose() 1`] = `
|
exports[`SFC compile <script setup> > inlineTemplate mode > with defineExpose() 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
|
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
|
@ -1348,7 +1366,8 @@ return { a }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC genDefaultAs > <script> + <script setup> 1`] = `
|
exports[`SFC genDefaultAs > <script> + <script setup> 1`] = `
|
||||||
"const __default__ = {}
|
"
|
||||||
|
const __default__ = {}
|
||||||
|
|
||||||
const _sfc_ = /*#__PURE__*/Object.assign(__default__, {
|
const _sfc_ = /*#__PURE__*/Object.assign(__default__, {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
|
|
@ -1363,7 +1382,8 @@ return { a }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC genDefaultAs > <script> + <script setup> 2`] = `
|
exports[`SFC genDefaultAs > <script> + <script setup> 2`] = `
|
||||||
"const __default__ = {}
|
"
|
||||||
|
const __default__ = {}
|
||||||
|
|
||||||
const _sfc_ = /*#__PURE__*/Object.assign(__default__, {
|
const _sfc_ = /*#__PURE__*/Object.assign(__default__, {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
|
|
|
||||||
|
|
@ -58,28 +58,9 @@ export function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`source map 1`] = `
|
|
||||||
{
|
|
||||||
"mappings": ";;;wBACE,oBAA8B;IAAzB,oBAAmB,4BAAbA,WAAM",
|
|
||||||
"names": [
|
|
||||||
"render",
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
"example.vue",
|
|
||||||
],
|
|
||||||
"sourcesContent": [
|
|
||||||
"
|
|
||||||
<div><p>{{ render }}</p></div>
|
|
||||||
",
|
|
||||||
],
|
|
||||||
"version": 3,
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`template errors 1`] = `
|
exports[`template errors 1`] = `
|
||||||
[
|
[
|
||||||
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
|
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
|
||||||
[SyntaxError: v-bind is missing expression.],
|
|
||||||
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.],
|
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.],
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -240,14 +240,22 @@ describe('SFC compile <script setup>', () => {
|
||||||
const { content } = compile(
|
const { content } = compile(
|
||||||
`
|
`
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { useCssVars, ref } from 'vue'
|
||||||
let foo = $ref(1)
|
const msg = ref()
|
||||||
</script>
|
</script>
|
||||||
`,
|
|
||||||
{ reactivityTransform: true }
|
<style>
|
||||||
|
.foo {
|
||||||
|
color: v-bind(msg)
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`
|
||||||
)
|
)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content).toMatch(`import { ref } from 'vue'`)
|
expect(content).toMatch(
|
||||||
|
`import { useCssVars as _useCssVars, unref as _unref } from 'vue'`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`import { useCssVars, ref } from 'vue'`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('import dedupe between <script> and <script setup>', () => {
|
test('import dedupe between <script> and <script setup>', () => {
|
||||||
|
|
@ -891,9 +899,7 @@ describe('SFC compile <script setup>', () => {
|
||||||
|
|
||||||
describe('async/await detection', () => {
|
describe('async/await detection', () => {
|
||||||
function assertAwaitDetection(code: string, shouldAsync = true) {
|
function assertAwaitDetection(code: string, shouldAsync = true) {
|
||||||
const { content } = compile(`<script setup>${code}</script>`, {
|
const { content } = compile(`<script setup>${code}</script>`)
|
||||||
reactivityTransform: true
|
|
||||||
})
|
|
||||||
if (shouldAsync) {
|
if (shouldAsync) {
|
||||||
expect(content).toMatch(`let __temp, __restore`)
|
expect(content).toMatch(`let __temp, __restore`)
|
||||||
}
|
}
|
||||||
|
|
@ -911,7 +917,7 @@ describe('SFC compile <script setup>', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ref', () => {
|
test('ref', () => {
|
||||||
assertAwaitDetection(`let a = $ref(1 + (await foo))`)
|
assertAwaitDetection(`let a = ref(1 + (await foo))`)
|
||||||
})
|
})
|
||||||
|
|
||||||
// #4448
|
// #4448
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`defineEmits > basic usage 1`] = `
|
exports[`defineEmits > basic usage 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
emits: ['foo', 'bar'],
|
emits: ['foo', 'bar'],
|
||||||
setup(__props, { expose: __expose, emit: __emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ return { n, get x() { return x } }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`defineExpose() 1`] = `
|
exports[`defineExpose() 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
|
|
||||||
__expose({ foo: 123 })
|
__expose({ foo: 123 })
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`defineOptions() > basic usage 1`] = `
|
exports[`defineOptions() > basic usage 1`] = `
|
||||||
"export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, {
|
"
|
||||||
|
export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
|
@ -14,7 +15,8 @@ return { }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`defineOptions() > empty argument 1`] = `
|
exports[`defineOptions() > empty argument 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,8 @@ return () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc reactive props destructure > defineProps/defineEmits in multi-variable declaration (full removal) 1`] = `
|
exports[`sfc reactive props destructure > defineProps/defineEmits in multi-variable declaration (full removal) 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
props: ['item'],
|
props: ['item'],
|
||||||
emits: ['a'],
|
emits: ['a'],
|
||||||
setup(__props, { emit: __emit }) {
|
setup(__props, { emit: __emit }) {
|
||||||
|
|
@ -192,7 +193,8 @@ return () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
|
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
props: ['item'],
|
props: ['item'],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
|
|
@ -205,7 +207,8 @@ return () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc reactive props destructure > multi-variable declaration fix #6757 1`] = `
|
exports[`sfc reactive props destructure > multi-variable declaration fix #6757 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
props: ['item'],
|
props: ['item'],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
|
|
@ -218,7 +221,8 @@ return () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc reactive props destructure > multi-variable declaration fix #7422 1`] = `
|
exports[`sfc reactive props destructure > multi-variable declaration fix #7422 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
props: ['item'],
|
props: ['item'],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
|
|
@ -250,7 +254,8 @@ return (_ctx, _cache) => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc reactive props destructure > nested scope 1`] = `
|
exports[`sfc reactive props destructure > nested scope 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
props: ['foo', 'bar'],
|
props: ['foo', 'bar'],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`sfc hoist static > should enable when only script setup 1`] = `
|
exports[`sfc hoist static > should enable when only script setup 1`] = `
|
||||||
"const foo = 'bar'
|
"
|
||||||
|
const foo = 'bar'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
@ -91,7 +92,8 @@ return () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc hoist static > should not hoist a function or class 1`] = `
|
exports[`sfc hoist static > should not hoist a function or class 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
const fn = () => {}
|
const fn = () => {}
|
||||||
|
|
@ -105,7 +107,8 @@ return () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc hoist static > should not hoist a object or array 1`] = `
|
exports[`sfc hoist static > should not hoist a object or array 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
const obj = { foo: 'bar' }
|
const obj = { foo: 'bar' }
|
||||||
|
|
@ -118,7 +121,8 @@ return () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc hoist static > should not hoist a variable 1`] = `
|
exports[`sfc hoist static > should not hoist a variable 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
let KEY1 = 'default value'
|
let KEY1 = 'default value'
|
||||||
|
|
@ -133,7 +137,8 @@ return () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sfc hoist static > should not hoist when disabled 1`] = `
|
exports[`sfc hoist static > should not hoist when disabled 1`] = `
|
||||||
"export default {
|
"
|
||||||
|
export default {
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
const foo = 'bar'
|
const foo = 'bar'
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`sfc ref transform > $ unwrapping 1`] = `
|
|
||||||
"import { ref, shallowRef } from 'vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup(__props, { expose: __expose }) {
|
|
||||||
__expose();
|
|
||||||
|
|
||||||
let foo = (ref())
|
|
||||||
let a = (ref(1))
|
|
||||||
let b = (shallowRef({
|
|
||||||
count: 0
|
|
||||||
}))
|
|
||||||
let c = () => {}
|
|
||||||
let d
|
|
||||||
|
|
||||||
return { foo, a, b, get c() { return c }, set c(v) { c = v }, get d() { return d }, set d(v) { d = v }, ref, shallowRef }
|
|
||||||
}
|
|
||||||
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`sfc ref transform > $ref & $shallowRef declarations 1`] = `
|
|
||||||
"import { ref as _ref, shallowRef as _shallowRef } from 'vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup(__props, { expose: __expose }) {
|
|
||||||
__expose();
|
|
||||||
|
|
||||||
let foo = _ref()
|
|
||||||
let a = _ref(1)
|
|
||||||
let b = _shallowRef({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
let c = () => {}
|
|
||||||
let d
|
|
||||||
|
|
||||||
return { foo, a, b, get c() { return c }, set c(v) { c = v }, get d() { return d }, set d(v) { d = v } }
|
|
||||||
}
|
|
||||||
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`sfc ref transform > usage /w typescript 1`] = `
|
|
||||||
"import { ref as _ref, defineComponent as _defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
|
||||||
setup(__props, { expose: __expose }) {
|
|
||||||
__expose();
|
|
||||||
|
|
||||||
let msg = _ref<string | number>('foo');
|
|
||||||
let bar = _ref <string | number>('bar');
|
|
||||||
|
|
||||||
return { msg, bar }
|
|
||||||
}
|
|
||||||
|
|
||||||
})"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`sfc ref transform > usage in normal <script> 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
let count = _ref(0)
|
|
||||||
const inc = () => count.value++
|
|
||||||
return ({ count })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`sfc ref transform > usage with normal <script> (has macro usage) + <script setup> (no macro usage) 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let data = _ref()
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup(__props, { expose: __expose }) {
|
|
||||||
__expose();
|
|
||||||
|
|
||||||
console.log(data.value)
|
|
||||||
|
|
||||||
return { data }
|
|
||||||
}
|
|
||||||
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`sfc ref transform > usage with normal <script> + <script setup> 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(0)
|
|
||||||
let c = _ref(0)
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup(__props, { expose: __expose }) {
|
|
||||||
__expose();
|
|
||||||
|
|
||||||
let b = _ref(0)
|
|
||||||
let c = 0
|
|
||||||
function change() {
|
|
||||||
a.value++
|
|
||||||
b.value++
|
|
||||||
c++
|
|
||||||
}
|
|
||||||
|
|
||||||
return { a, c, b, change }
|
|
||||||
}
|
|
||||||
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
// TODO remove in 3.4
|
|
||||||
import { BindingTypes } from '@vue/compiler-core'
|
|
||||||
import { compileSFCScript as compile, assertCode } from '../utils'
|
|
||||||
|
|
||||||
// this file only tests integration with SFC - main test case for the ref
|
|
||||||
// transform can be found in <root>/packages/reactivity-transform/__tests__
|
|
||||||
describe('sfc ref transform', () => {
|
|
||||||
function compileWithReactivityTransform(src: string) {
|
|
||||||
return compile(src, { reactivityTransform: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
test('$ unwrapping', () => {
|
|
||||||
const { content, bindings } = compileWithReactivityTransform(`<script setup>
|
|
||||||
import { ref, shallowRef } from 'vue'
|
|
||||||
let foo = $(ref())
|
|
||||||
let a = $(ref(1))
|
|
||||||
let b = $(shallowRef({
|
|
||||||
count: 0
|
|
||||||
}))
|
|
||||||
let c = () => {}
|
|
||||||
let d
|
|
||||||
</script>`)
|
|
||||||
expect(content).not.toMatch(`$(ref())`)
|
|
||||||
expect(content).not.toMatch(`$(ref(1))`)
|
|
||||||
expect(content).not.toMatch(`$(shallowRef({`)
|
|
||||||
expect(content).toMatch(`let foo = (ref())`)
|
|
||||||
expect(content).toMatch(`let a = (ref(1))`)
|
|
||||||
expect(content).toMatch(`
|
|
||||||
let b = (shallowRef({
|
|
||||||
count: 0
|
|
||||||
}))
|
|
||||||
`)
|
|
||||||
// normal declarations left untouched
|
|
||||||
expect(content).toMatch(`let c = () => {}`)
|
|
||||||
expect(content).toMatch(`let d`)
|
|
||||||
expect(content).toMatch(
|
|
||||||
`return { foo, a, b, get c() { return c }, set c(v) { c = v }, ` +
|
|
||||||
`get d() { return d }, set d(v) { d = v }, ref, shallowRef }`
|
|
||||||
)
|
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
foo: BindingTypes.SETUP_REF,
|
|
||||||
a: BindingTypes.SETUP_REF,
|
|
||||||
b: BindingTypes.SETUP_REF,
|
|
||||||
c: BindingTypes.SETUP_LET,
|
|
||||||
d: BindingTypes.SETUP_LET,
|
|
||||||
ref: BindingTypes.SETUP_CONST,
|
|
||||||
shallowRef: BindingTypes.SETUP_CONST
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('$ref & $shallowRef declarations', () => {
|
|
||||||
const { content, bindings } = compileWithReactivityTransform(`<script setup>
|
|
||||||
let foo = $ref()
|
|
||||||
let a = $ref(1)
|
|
||||||
let b = $shallowRef({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
let c = () => {}
|
|
||||||
let d
|
|
||||||
</script>`)
|
|
||||||
expect(content).toMatch(
|
|
||||||
`import { ref as _ref, shallowRef as _shallowRef } from 'vue'`
|
|
||||||
)
|
|
||||||
expect(content).not.toMatch(`$ref()`)
|
|
||||||
expect(content).not.toMatch(`$ref(1)`)
|
|
||||||
expect(content).not.toMatch(`$shallowRef({`)
|
|
||||||
expect(content).toMatch(`let foo = _ref()`)
|
|
||||||
expect(content).toMatch(`let a = _ref(1)`)
|
|
||||||
expect(content).toMatch(`
|
|
||||||
let b = _shallowRef({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
`)
|
|
||||||
// normal declarations left untouched
|
|
||||||
expect(content).toMatch(`let c = () => {}`)
|
|
||||||
expect(content).toMatch(`let d`)
|
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
foo: BindingTypes.SETUP_REF,
|
|
||||||
a: BindingTypes.SETUP_REF,
|
|
||||||
b: BindingTypes.SETUP_REF,
|
|
||||||
c: BindingTypes.SETUP_LET,
|
|
||||||
d: BindingTypes.SETUP_LET
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('usage in normal <script>', () => {
|
|
||||||
const { content } = compileWithReactivityTransform(`<script>
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
let count = $ref(0)
|
|
||||||
const inc = () => count++
|
|
||||||
return $$({ count })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>`)
|
|
||||||
expect(content).not.toMatch(`$ref(0)`)
|
|
||||||
expect(content).toMatch(`import { ref as _ref } from 'vue'`)
|
|
||||||
expect(content).toMatch(`let count = _ref(0)`)
|
|
||||||
expect(content).toMatch(`count.value++`)
|
|
||||||
expect(content).toMatch(`return ({ count })`)
|
|
||||||
assertCode(content)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('usage /w typescript', () => {
|
|
||||||
const { content } = compileWithReactivityTransform(`
|
|
||||||
<script setup lang="ts">
|
|
||||||
let msg = $ref<string | number>('foo');
|
|
||||||
let bar = $ref <string | number>('bar');
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
expect(content).toMatch(`import { ref as _ref`)
|
|
||||||
expect(content).toMatch(`let msg = _ref<string | number>('foo')`)
|
|
||||||
expect(content).toMatch(`let bar = _ref <string | number>('bar')`)
|
|
||||||
assertCode(content)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('usage with normal <script> + <script setup>', () => {
|
|
||||||
const { content, bindings } = compileWithReactivityTransform(`<script>
|
|
||||||
let a = $ref(0)
|
|
||||||
let c = $ref(0)
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
let b = $ref(0)
|
|
||||||
let c = 0
|
|
||||||
function change() {
|
|
||||||
a++
|
|
||||||
b++
|
|
||||||
c++
|
|
||||||
}
|
|
||||||
</script>`)
|
|
||||||
// should dedupe helper imports
|
|
||||||
expect(content).toMatch(`import { ref as _ref } from 'vue'`)
|
|
||||||
|
|
||||||
expect(content).toMatch(`let a = _ref(0)`)
|
|
||||||
expect(content).toMatch(`let b = _ref(0)`)
|
|
||||||
|
|
||||||
// root level ref binding declared in <script> should be inherited in <script setup>
|
|
||||||
expect(content).toMatch(`a.value++`)
|
|
||||||
expect(content).toMatch(`b.value++`)
|
|
||||||
// c shadowed
|
|
||||||
expect(content).toMatch(`c++`)
|
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
a: BindingTypes.SETUP_REF,
|
|
||||||
b: BindingTypes.SETUP_REF,
|
|
||||||
c: BindingTypes.SETUP_REF,
|
|
||||||
change: BindingTypes.SETUP_CONST
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('usage with normal <script> (has macro usage) + <script setup> (no macro usage)', () => {
|
|
||||||
const { content } = compileWithReactivityTransform(`
|
|
||||||
<script>
|
|
||||||
let data = $ref()
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
console.log(data)
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
expect(content).toMatch(`console.log(data.value)`)
|
|
||||||
assertCode(content)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('errors', () => {
|
|
||||||
test('defineProps/Emit() referencing ref declarations', () => {
|
|
||||||
expect(() =>
|
|
||||||
compile(
|
|
||||||
`<script setup>
|
|
||||||
let bar = $ref(1)
|
|
||||||
defineProps({
|
|
||||||
bar
|
|
||||||
})
|
|
||||||
</script>`,
|
|
||||||
{ reactivityTransform: true }
|
|
||||||
)
|
|
||||||
).toThrow(`cannot reference locally declared variables`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
compile(
|
|
||||||
`<script setup>
|
|
||||||
let bar = $ref(1)
|
|
||||||
defineEmits({
|
|
||||||
bar
|
|
||||||
})
|
|
||||||
</script>`,
|
|
||||||
{ reactivityTransform: true }
|
|
||||||
)
|
|
||||||
).toThrow(`cannot reference locally declared variables`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RawSourceMap, SourceMapConsumer } from 'source-map-js'
|
||||||
import {
|
import {
|
||||||
compileTemplate,
|
compileTemplate,
|
||||||
SFCTemplateCompileOptions
|
SFCTemplateCompileOptions
|
||||||
|
|
@ -107,24 +108,193 @@ test('source map', () => {
|
||||||
const template = parse(
|
const template = parse(
|
||||||
`
|
`
|
||||||
<template>
|
<template>
|
||||||
<div><p>{{ render }}</p></div>
|
<div><p>{{ foobar }}</p></div>
|
||||||
</template>
|
</template>
|
||||||
`,
|
`,
|
||||||
{ filename: 'example.vue', sourceMap: true }
|
{ filename: 'example.vue', sourceMap: true }
|
||||||
).descriptor.template as SFCTemplateBlock
|
).descriptor.template!
|
||||||
|
|
||||||
const result = compile({
|
const { code, map } = compile({
|
||||||
filename: 'example.vue',
|
filename: 'example.vue',
|
||||||
source: template.content
|
source: template.content
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.map).toMatchSnapshot()
|
expect(map!.sources).toEqual([`example.vue`])
|
||||||
|
expect(map!.sourcesContent).toEqual([template.content])
|
||||||
|
|
||||||
|
const consumer = new SourceMapConsumer(map as RawSourceMap)
|
||||||
|
expect(
|
||||||
|
consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
|
||||||
|
).toMatchObject(getPositionInCode(template.content, `foobar`))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work w/ AST from descriptor', () => {
|
||||||
|
const source = `
|
||||||
|
<template>
|
||||||
|
<div><p>{{ foobar }}</p></div>
|
||||||
|
</template>
|
||||||
|
`
|
||||||
|
const template = parse(source, {
|
||||||
|
filename: 'example.vue',
|
||||||
|
sourceMap: true
|
||||||
|
}).descriptor.template!
|
||||||
|
|
||||||
|
expect(template.ast!.source).toBe(source)
|
||||||
|
|
||||||
|
const { code, map } = compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content,
|
||||||
|
ast: template.ast
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map!.sources).toEqual([`example.vue`])
|
||||||
|
// when reusing AST from SFC parse for template compile,
|
||||||
|
// the source corresponds to the entire SFC
|
||||||
|
expect(map!.sourcesContent).toEqual([source])
|
||||||
|
|
||||||
|
const consumer = new SourceMapConsumer(map as RawSourceMap)
|
||||||
|
expect(
|
||||||
|
consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
|
||||||
|
).toMatchObject(getPositionInCode(source, `foobar`))
|
||||||
|
|
||||||
|
expect(code).toBe(
|
||||||
|
compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content
|
||||||
|
}).code
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work w/ AST from descriptor in SSR mode', () => {
|
||||||
|
const source = `
|
||||||
|
<template>
|
||||||
|
<div><p>{{ foobar }}</p></div>
|
||||||
|
</template>
|
||||||
|
`
|
||||||
|
const template = parse(source, {
|
||||||
|
filename: 'example.vue',
|
||||||
|
sourceMap: true
|
||||||
|
}).descriptor.template!
|
||||||
|
|
||||||
|
expect(template.ast!.source).toBe(source)
|
||||||
|
|
||||||
|
const { code, map } = compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: '', // make sure it's actually using the AST instead of source
|
||||||
|
ast: template.ast,
|
||||||
|
ssr: true
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map!.sources).toEqual([`example.vue`])
|
||||||
|
// when reusing AST from SFC parse for template compile,
|
||||||
|
// the source corresponds to the entire SFC
|
||||||
|
expect(map!.sourcesContent).toEqual([source])
|
||||||
|
|
||||||
|
const consumer = new SourceMapConsumer(map as RawSourceMap)
|
||||||
|
expect(
|
||||||
|
consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
|
||||||
|
).toMatchObject(getPositionInCode(source, `foobar`))
|
||||||
|
|
||||||
|
expect(code).toBe(
|
||||||
|
compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content,
|
||||||
|
ssr: true
|
||||||
|
}).code
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not reuse AST if using custom compiler', () => {
|
||||||
|
const source = `
|
||||||
|
<template>
|
||||||
|
<div><p>{{ foobar }}</p></div>
|
||||||
|
</template>
|
||||||
|
`
|
||||||
|
const template = parse(source, {
|
||||||
|
filename: 'example.vue',
|
||||||
|
sourceMap: true
|
||||||
|
}).descriptor.template!
|
||||||
|
|
||||||
|
const { code } = compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content,
|
||||||
|
ast: template.ast,
|
||||||
|
compiler: {
|
||||||
|
parse: () => null as any,
|
||||||
|
// @ts-ignore
|
||||||
|
compile: input => ({ code: input })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// what we really want to assert is that the `input` received by the custom
|
||||||
|
// compiler is the source string, not the AST.
|
||||||
|
expect(code).toBe(template.content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should force re-parse on already transformed AST', () => {
|
||||||
|
const source = `
|
||||||
|
<template>
|
||||||
|
<div><p>{{ foobar }}</p></div>
|
||||||
|
</template>
|
||||||
|
`
|
||||||
|
const template = parse(source, {
|
||||||
|
filename: 'example.vue',
|
||||||
|
sourceMap: true
|
||||||
|
}).descriptor.template!
|
||||||
|
|
||||||
|
// force set to empty, if this is reused then it won't generate proper code
|
||||||
|
template.ast!.children = []
|
||||||
|
template.ast!.transformed = true
|
||||||
|
|
||||||
|
const { code } = compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: '',
|
||||||
|
ast: template.ast
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(code).toBe(
|
||||||
|
compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content
|
||||||
|
}).code
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should force re-parse with correct compiler in SSR mode', () => {
|
||||||
|
const source = `
|
||||||
|
<template>
|
||||||
|
<div><p>{{ foobar }}</p></div>
|
||||||
|
</template>
|
||||||
|
`
|
||||||
|
const template = parse(source, {
|
||||||
|
filename: 'example.vue',
|
||||||
|
sourceMap: true
|
||||||
|
}).descriptor.template!
|
||||||
|
|
||||||
|
// force set to empty, if this is reused then it won't generate proper code
|
||||||
|
template.ast!.children = []
|
||||||
|
template.ast!.transformed = true
|
||||||
|
|
||||||
|
const { code } = compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: '',
|
||||||
|
ast: template.ast,
|
||||||
|
ssr: true
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(code).toBe(
|
||||||
|
compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content,
|
||||||
|
ssr: true
|
||||||
|
}).code
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('template errors', () => {
|
test('template errors', () => {
|
||||||
const result = compile({
|
const result = compile({
|
||||||
filename: 'example.vue',
|
filename: 'example.vue',
|
||||||
source: `<div :foo
|
source: `<div
|
||||||
:bar="a[" v-model="baz"/>`
|
:bar="a[" v-model="baz"/>`
|
||||||
})
|
})
|
||||||
expect(result.errors).toMatchSnapshot()
|
expect(result.errors).toMatchSnapshot()
|
||||||
|
|
@ -199,3 +369,36 @@ test('dynamic v-on + static v-on should merged', () => {
|
||||||
|
|
||||||
expect(result.code).toMatchSnapshot()
|
expect(result.code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
interface Pos {
|
||||||
|
line: number
|
||||||
|
column: number
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPositionInCode(
|
||||||
|
code: string,
|
||||||
|
token: string,
|
||||||
|
expectName: string | boolean = false
|
||||||
|
): Pos {
|
||||||
|
const generatedOffset = code.indexOf(token)
|
||||||
|
let line = 1
|
||||||
|
let lastNewLinePos = -1
|
||||||
|
for (let i = 0; i < generatedOffset; i++) {
|
||||||
|
if (code.charCodeAt(i) === 10 /* newline char code */) {
|
||||||
|
line++
|
||||||
|
lastNewLinePos = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res: Pos = {
|
||||||
|
line,
|
||||||
|
column:
|
||||||
|
lastNewLinePos === -1
|
||||||
|
? generatedOffset
|
||||||
|
: generatedOffset - lastNewLinePos - 1
|
||||||
|
}
|
||||||
|
if (expectName) {
|
||||||
|
res.name = typeof expectName === 'string' ? expectName : token
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { parse } from '../src'
|
import { parse } from '../src'
|
||||||
import { baseParse, baseCompile } from '@vue/compiler-core'
|
import { baseCompile, createRoot } from '@vue/compiler-core'
|
||||||
import { SourceMapConsumer } from 'source-map-js'
|
import { SourceMapConsumer } from 'source-map-js'
|
||||||
|
|
||||||
describe('compiler:sfc', () => {
|
describe('compiler:sfc', () => {
|
||||||
|
|
@ -7,15 +7,61 @@ describe('compiler:sfc', () => {
|
||||||
test('style block', () => {
|
test('style block', () => {
|
||||||
// Padding determines how many blank lines will there be before the style block
|
// Padding determines how many blank lines will there be before the style block
|
||||||
const padding = Math.round(Math.random() * 10)
|
const padding = Math.round(Math.random() * 10)
|
||||||
const style = parse(
|
const src =
|
||||||
`${'\n'.repeat(padding)}<style>\n.color {\n color: red;\n }\n</style>\n`
|
`${'\n'.repeat(padding)}` +
|
||||||
).descriptor.styles[0]
|
`<style>
|
||||||
|
.css {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
expect(style.map).not.toBeUndefined()
|
<style module>
|
||||||
|
.css-module {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
const consumer = new SourceMapConsumer(style.map!)
|
<style scoped>
|
||||||
|
.css-scoped {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.css-scoped-nested {
|
||||||
|
color: red;
|
||||||
|
.dummy {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>`
|
||||||
|
const {
|
||||||
|
descriptor: { styles }
|
||||||
|
} = parse(src)
|
||||||
|
|
||||||
|
expect(styles[0].map).not.toBeUndefined()
|
||||||
|
const consumer = new SourceMapConsumer(styles[0].map!)
|
||||||
|
const lineOffset =
|
||||||
|
src.slice(0, src.indexOf(`<style>`)).split('\n').length - 1
|
||||||
consumer.eachMapping(mapping => {
|
consumer.eachMapping(mapping => {
|
||||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
expect(mapping.generatedLine + lineOffset).toBe(mapping.originalLine)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(styles[1].map).not.toBeUndefined()
|
||||||
|
const consumer1 = new SourceMapConsumer(styles[1].map!)
|
||||||
|
const lineOffset1 =
|
||||||
|
src.slice(0, src.indexOf(`<style module>`)).split('\n').length - 1
|
||||||
|
consumer1.eachMapping(mapping => {
|
||||||
|
expect(mapping.generatedLine + lineOffset1).toBe(mapping.originalLine)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(styles[2].map).not.toBeUndefined()
|
||||||
|
const consumer2 = new SourceMapConsumer(styles[2].map!)
|
||||||
|
const lineOffset2 =
|
||||||
|
src.slice(0, src.indexOf(`<style scoped>`)).split('\n').length - 1
|
||||||
|
consumer2.eachMapping(mapping => {
|
||||||
|
expect(mapping.generatedLine + lineOffset2).toBe(mapping.originalLine)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -122,8 +168,7 @@ h1 { color: red }
|
||||||
line: 3,
|
line: 3,
|
||||||
column: 1,
|
column: 1,
|
||||||
offset: 10 + content.length
|
offset: 10 + content.length
|
||||||
},
|
}
|
||||||
source: content
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -132,9 +177,8 @@ h1 { color: red }
|
||||||
expect(descriptor.template).toBeTruthy()
|
expect(descriptor.template).toBeTruthy()
|
||||||
expect(descriptor.template!.content).toBeFalsy()
|
expect(descriptor.template!.content).toBeFalsy()
|
||||||
expect(descriptor.template!.loc).toMatchObject({
|
expect(descriptor.template!.loc).toMatchObject({
|
||||||
start: { line: 1, column: 1, offset: 0 },
|
start: { line: 1, column: 12, offset: 11 },
|
||||||
end: { line: 1, column: 1, offset: 0 },
|
end: { line: 1, column: 12, offset: 11 }
|
||||||
source: ''
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -144,8 +188,7 @@ h1 { color: red }
|
||||||
expect(descriptor.template!.content).toBeFalsy()
|
expect(descriptor.template!.content).toBeFalsy()
|
||||||
expect(descriptor.template!.loc).toMatchObject({
|
expect(descriptor.template!.loc).toMatchObject({
|
||||||
start: { line: 1, column: 11, offset: 10 },
|
start: { line: 1, column: 11, offset: 10 },
|
||||||
end: { line: 1, column: 11, offset: 10 },
|
end: { line: 1, column: 11, offset: 10 }
|
||||||
source: ''
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -167,6 +210,11 @@ h1 { color: red }
|
||||||
expect(descriptor.script!.attrs['src']).toBe('com')
|
expect(descriptor.script!.attrs['src']).toBe('com')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should not expose ast on template node if has src import', () => {
|
||||||
|
const { descriptor } = parse(`<template src="./foo.html"/>`)
|
||||||
|
expect(descriptor.template!.ast).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
test('ignoreEmpty: false', () => {
|
test('ignoreEmpty: false', () => {
|
||||||
const { descriptor } = parse(
|
const { descriptor } = parse(
|
||||||
`<script></script>\n<script setup>\n</script>`,
|
`<script></script>\n<script setup>\n</script>`,
|
||||||
|
|
@ -176,14 +224,12 @@ h1 { color: red }
|
||||||
)
|
)
|
||||||
expect(descriptor.script).toBeTruthy()
|
expect(descriptor.script).toBeTruthy()
|
||||||
expect(descriptor.script!.loc).toMatchObject({
|
expect(descriptor.script!.loc).toMatchObject({
|
||||||
source: '',
|
|
||||||
start: { line: 1, column: 9, offset: 8 },
|
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).toBeTruthy()
|
||||||
expect(descriptor.scriptSetup!.loc).toMatchObject({
|
expect(descriptor.scriptSetup!.loc).toMatchObject({
|
||||||
source: '\n',
|
|
||||||
start: { line: 2, column: 15, offset: 32 },
|
start: { line: 2, column: 15, offset: 32 },
|
||||||
end: { line: 3, column: 1, offset: 33 }
|
end: { line: 3, column: 1, offset: 33 }
|
||||||
})
|
})
|
||||||
|
|
@ -208,13 +254,15 @@ h1 { color: red }
|
||||||
})
|
})
|
||||||
|
|
||||||
// #1120
|
// #1120
|
||||||
test('alternative template lang should be treated as plain text', () => {
|
test('template with preprocessor lang should be treated as plain text', () => {
|
||||||
const content = `p(v-if="1 < 2") test`
|
const content = `p(v-if="1 < 2") test <div/>`
|
||||||
const { descriptor, errors } = parse(
|
const { descriptor, errors } = parse(
|
||||||
`<template lang="pug">` + content + `</template>`
|
`<template lang="pug">` + content + `</template>`
|
||||||
)
|
)
|
||||||
expect(errors.length).toBe(0)
|
expect(errors.length).toBe(0)
|
||||||
expect(descriptor.template!.content).toBe(content)
|
expect(descriptor.template!.content).toBe(content)
|
||||||
|
// should not attempt to parse the content
|
||||||
|
expect(descriptor.template!.ast!.children.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
//#2566
|
//#2566
|
||||||
|
|
@ -260,11 +308,18 @@ h1 { color: red }
|
||||||
test('custom compiler', () => {
|
test('custom compiler', () => {
|
||||||
const { errors } = parse(`<template><input></template>`, {
|
const { errors } = parse(`<template><input></template>`, {
|
||||||
compiler: {
|
compiler: {
|
||||||
parse: baseParse,
|
parse: (_, options) => {
|
||||||
|
options.onError!(new Error('foo') as any)
|
||||||
|
return createRoot([])
|
||||||
|
},
|
||||||
compile: baseCompile
|
compile: baseCompile
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(errors.length).toBe(1)
|
expect(errors.length).toBe(2)
|
||||||
|
// error thrown by the custom parse
|
||||||
|
expect(errors[0].message).toBe('foo')
|
||||||
|
// error thrown based on the returned root
|
||||||
|
expect(errors[1].message).toMatch('At least one')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('treat custom blocks as raw text', () => {
|
test('treat custom blocks as raw text', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.3.9",
|
"version": "3.4.0-alpha.3",
|
||||||
"description": "@vue/compiler-sfc",
|
"description": "@vue/compiler-sfc",
|
||||||
"main": "dist/compiler-sfc.cjs.js",
|
"main": "dist/compiler-sfc.cjs.js",
|
||||||
"module": "dist/compiler-sfc.esm-browser.js",
|
"module": "dist/compiler-sfc.esm-browser.js",
|
||||||
|
|
@ -32,11 +32,10 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-sfc#readme",
|
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-sfc#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.3",
|
"@babel/parser": "^7.23.4",
|
||||||
"@vue/compiler-core": "workspace:*",
|
"@vue/compiler-core": "workspace:*",
|
||||||
"@vue/compiler-dom": "workspace:*",
|
"@vue/compiler-dom": "workspace:*",
|
||||||
"@vue/compiler-ssr": "workspace:*",
|
"@vue/compiler-ssr": "workspace:*",
|
||||||
"@vue/reactivity-transform": "workspace:*",
|
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
|
|
@ -44,10 +43,10 @@
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.23.3",
|
"@babel/types": "^7.23.4",
|
||||||
"@vue/consolidate": "^0.17.3",
|
"@vue/consolidate": "^0.17.3",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.1.0",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"minimatch": "^9.0.3",
|
"minimatch": "^9.0.3",
|
||||||
"postcss-modules": "^4.3.1",
|
"postcss-modules": "^4.3.1",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import {
|
||||||
import { CSS_VARS_HELPER, genCssVarsCode } from './style/cssVars'
|
import { CSS_VARS_HELPER, genCssVarsCode } from './style/cssVars'
|
||||||
import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
|
import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
|
||||||
import { warnOnce } from './warn'
|
import { warnOnce } from './warn'
|
||||||
import { shouldTransform, transformAST } from '@vue/reactivity-transform'
|
|
||||||
import { transformDestructuredProps } from './script/definePropsDestructure'
|
import { transformDestructuredProps } from './script/definePropsDestructure'
|
||||||
import { ScriptCompileContext } from './script/context'
|
import { ScriptCompileContext } from './script/context'
|
||||||
import {
|
import {
|
||||||
|
|
@ -122,14 +121,6 @@ export interface SFCScriptCompileOptions {
|
||||||
fileExists(file: string): boolean
|
fileExists(file: string): boolean
|
||||||
readFile(file: string): string | undefined
|
readFile(file: string): string | undefined
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* (Experimental) Enable syntax transform for using refs without `.value` and
|
|
||||||
* using destructured props with reactivity
|
|
||||||
* @deprecated the Reactivity Transform proposal has been dropped. This
|
|
||||||
* feature will be removed from Vue core in 3.4. If you intend to continue
|
|
||||||
* using it, disable this and switch to the [Vue Macros implementation](https://vue-macros.sxzz.moe/features/reactivity-transform.html).
|
|
||||||
*/
|
|
||||||
reactivityTransform?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImportBinding {
|
export interface ImportBinding {
|
||||||
|
|
@ -165,8 +156,6 @@ export function compileScript(
|
||||||
const scriptLang = script && script.lang
|
const scriptLang = script && script.lang
|
||||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||||
|
|
||||||
// TODO remove in 3.4
|
|
||||||
const enableReactivityTransform = !!options.reactivityTransform
|
|
||||||
let refBindings: string[] | undefined
|
let refBindings: string[] | undefined
|
||||||
|
|
||||||
if (!scriptSetup) {
|
if (!scriptSetup) {
|
||||||
|
|
@ -478,20 +467,6 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply reactivity transform
|
|
||||||
// TODO remove in 3.4
|
|
||||||
if (enableReactivityTransform && shouldTransform(script.content)) {
|
|
||||||
const { rootRefs, importedHelpers } = transformAST(
|
|
||||||
scriptAst,
|
|
||||||
ctx.s,
|
|
||||||
scriptStartOffset!
|
|
||||||
)
|
|
||||||
refBindings = rootRefs
|
|
||||||
for (const h of importedHelpers) {
|
|
||||||
ctx.helperImports.add(h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// <script> after <script setup>
|
// <script> after <script setup>
|
||||||
// we need to move the block up so that `const __default__` is
|
// we need to move the block up so that `const __default__` is
|
||||||
// declared before being used in the actual component definition
|
// declared before being used in the actual component definition
|
||||||
|
|
@ -687,26 +662,7 @@ export function compileScript(
|
||||||
transformDestructuredProps(ctx, vueImportAliases)
|
transformDestructuredProps(ctx, vueImportAliases)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Apply reactivity transform
|
// 4. check macro args to make sure it doesn't reference setup scope
|
||||||
// TODO remove in 3.4
|
|
||||||
if (
|
|
||||||
enableReactivityTransform &&
|
|
||||||
// normal <script> had ref bindings that maybe used in <script setup>
|
|
||||||
(refBindings || shouldTransform(scriptSetup.content))
|
|
||||||
) {
|
|
||||||
const { rootRefs, importedHelpers } = transformAST(
|
|
||||||
scriptSetupAst,
|
|
||||||
ctx.s,
|
|
||||||
startOffset,
|
|
||||||
refBindings
|
|
||||||
)
|
|
||||||
refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs
|
|
||||||
for (const h of importedHelpers) {
|
|
||||||
ctx.helperImports.add(h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. check macro args to make sure it doesn't reference setup scope
|
|
||||||
// variables
|
// variables
|
||||||
checkInvalidScopeReference(ctx.propsRuntimeDecl, DEFINE_PROPS)
|
checkInvalidScopeReference(ctx.propsRuntimeDecl, DEFINE_PROPS)
|
||||||
checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
|
checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
|
||||||
|
|
@ -714,7 +670,7 @@ export function compileScript(
|
||||||
checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS)
|
checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS)
|
||||||
checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS)
|
checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS)
|
||||||
|
|
||||||
// 6. remove non-script content
|
// 5. remove non-script content
|
||||||
if (script) {
|
if (script) {
|
||||||
if (startOffset < scriptStartOffset!) {
|
if (startOffset < scriptStartOffset!) {
|
||||||
// <script setup> before <script>
|
// <script setup> before <script>
|
||||||
|
|
@ -733,7 +689,7 @@ export function compileScript(
|
||||||
ctx.s.remove(endOffset, source.length)
|
ctx.s.remove(endOffset, source.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. analyze binding metadata
|
// 6. analyze binding metadata
|
||||||
// `defineProps` & `defineModel` also register props bindings
|
// `defineProps` & `defineModel` also register props bindings
|
||||||
if (scriptAst) {
|
if (scriptAst) {
|
||||||
Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
|
Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
|
||||||
|
|
@ -762,7 +718,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. inject `useCssVars` calls
|
// 7. inject `useCssVars` calls
|
||||||
if (
|
if (
|
||||||
sfc.cssVars.length &&
|
sfc.cssVars.length &&
|
||||||
// no need to do this when targeting SSR
|
// no need to do this when targeting SSR
|
||||||
|
|
@ -781,7 +737,7 @@ export function compileScript(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. finalize setup() argument signature
|
// 8. finalize setup() argument signature
|
||||||
let args = `__props`
|
let args = `__props`
|
||||||
if (ctx.propsTypeDecl) {
|
if (ctx.propsTypeDecl) {
|
||||||
// mark as any and only cast on assignment
|
// mark as any and only cast on assignment
|
||||||
|
|
@ -832,7 +788,7 @@ export function compileScript(
|
||||||
args += `, { ${destructureElements.join(', ')} }`
|
args += `, { ${destructureElements.join(', ')} }`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. generate return statement
|
// 9. generate return statement
|
||||||
let returned
|
let returned
|
||||||
if (
|
if (
|
||||||
!options.inlineTemplate ||
|
!options.inlineTemplate ||
|
||||||
|
|
@ -948,7 +904,7 @@ export function compileScript(
|
||||||
ctx.s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
|
ctx.s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. finalize default export
|
// 10. finalize default export
|
||||||
const genDefaultAs = options.genDefaultAs
|
const genDefaultAs = options.genDefaultAs
|
||||||
? `const ${options.genDefaultAs} =`
|
? `const ${options.genDefaultAs} =`
|
||||||
: `export default`
|
: `export default`
|
||||||
|
|
@ -1022,7 +978,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12. finalize Vue helper imports
|
// 11. finalize Vue helper imports
|
||||||
if (ctx.helperImports.size > 0) {
|
if (ctx.helperImports.size > 0) {
|
||||||
ctx.s.prepend(
|
ctx.s.prepend(
|
||||||
`import { ${[...ctx.helperImports]
|
`import { ${[...ctx.helperImports]
|
||||||
|
|
@ -1031,8 +987,6 @@ export function compileScript(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.s.trim()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...scriptSetup,
|
...scriptSetup,
|
||||||
bindings: ctx.bindingMetadata,
|
bindings: ctx.bindingMetadata,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ import {
|
||||||
CompilerError,
|
CompilerError,
|
||||||
NodeTransform,
|
NodeTransform,
|
||||||
ParserOptions,
|
ParserOptions,
|
||||||
RootNode
|
RootNode,
|
||||||
|
NodeTypes,
|
||||||
|
ElementNode,
|
||||||
|
createRoot
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import {
|
import {
|
||||||
SourceMapConsumer,
|
SourceMapConsumer,
|
||||||
|
|
@ -30,7 +33,7 @@ import { warnOnce } from './warn'
|
||||||
import { genCssVarsFromList } from './style/cssVars'
|
import { genCssVarsFromList } from './style/cssVars'
|
||||||
|
|
||||||
export interface TemplateCompiler {
|
export interface TemplateCompiler {
|
||||||
compile(template: string, options: CompilerOptions): CodegenResult
|
compile(source: string | RootNode, options: CompilerOptions): CodegenResult
|
||||||
parse(template: string, options: ParserOptions): RootNode
|
parse(template: string, options: ParserOptions): RootNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,6 +49,7 @@ export interface SFCTemplateCompileResults {
|
||||||
|
|
||||||
export interface SFCTemplateCompileOptions {
|
export interface SFCTemplateCompileOptions {
|
||||||
source: string
|
source: string
|
||||||
|
ast?: RootNode
|
||||||
filename: string
|
filename: string
|
||||||
id: string
|
id: string
|
||||||
scoped?: boolean
|
scoped?: boolean
|
||||||
|
|
@ -131,7 +135,8 @@ export function compileTemplate(
|
||||||
try {
|
try {
|
||||||
return doCompileTemplate({
|
return doCompileTemplate({
|
||||||
...options,
|
...options,
|
||||||
source: preprocess(options, preprocessor)
|
source: preprocess(options, preprocessor),
|
||||||
|
ast: undefined // invalidate AST if template goes through preprocessor
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -164,10 +169,11 @@ function doCompileTemplate({
|
||||||
slotted,
|
slotted,
|
||||||
inMap,
|
inMap,
|
||||||
source,
|
source,
|
||||||
|
ast: inAST,
|
||||||
ssr = false,
|
ssr = false,
|
||||||
ssrCssVars,
|
ssrCssVars,
|
||||||
isProd = false,
|
isProd = false,
|
||||||
compiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM,
|
compiler,
|
||||||
compilerOptions = {},
|
compilerOptions = {},
|
||||||
transformAssetUrls
|
transformAssetUrls
|
||||||
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
|
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
|
||||||
|
|
@ -199,7 +205,30 @@ function doCompileTemplate({
|
||||||
const shortId = id.replace(/^data-v-/, '')
|
const shortId = id.replace(/^data-v-/, '')
|
||||||
const longId = `data-v-${shortId}`
|
const longId = `data-v-${shortId}`
|
||||||
|
|
||||||
let { code, ast, preamble, map } = compiler.compile(source, {
|
const defaultCompiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM
|
||||||
|
compiler = compiler || defaultCompiler
|
||||||
|
|
||||||
|
if (compiler !== defaultCompiler) {
|
||||||
|
// user using custom compiler, this means we cannot reuse the AST from
|
||||||
|
// the descriptor as they might be different.
|
||||||
|
inAST = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inAST?.transformed) {
|
||||||
|
// If input AST has already been transformed, then it cannot be reused.
|
||||||
|
// We need to parse a fresh one. Can't just use `source` here since we need
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
const template = newAST.children.find(
|
||||||
|
node => node.type === NodeTypes.ELEMENT && node.tag === 'template'
|
||||||
|
) as ElementNode
|
||||||
|
inAST = createRoot(template.children, inAST.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
let { code, ast, preamble, map } = compiler.compile(inAST || source, {
|
||||||
mode: 'module',
|
mode: 'module',
|
||||||
prefixIdentifiers: true,
|
prefixIdentifiers: true,
|
||||||
hoistStatic: true,
|
hoistStatic: true,
|
||||||
|
|
@ -222,7 +251,7 @@ function doCompileTemplate({
|
||||||
// inMap should be the map produced by ./parse.ts which is a simple line-only
|
// inMap should be the map produced by ./parse.ts which is a simple line-only
|
||||||
// mapping. If it is present, we need to adjust the final map and errors to
|
// mapping. If it is present, we need to adjust the final map and errors to
|
||||||
// reflect the original line numbers.
|
// reflect the original line numbers.
|
||||||
if (inMap) {
|
if (inMap && !inAST) {
|
||||||
if (map) {
|
if (map) {
|
||||||
map = mapLines(inMap, map)
|
map = mapLines(inMap, map)
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +264,7 @@ function doCompileTemplate({
|
||||||
let msg = w.message
|
let msg = w.message
|
||||||
if (w.loc) {
|
if (w.loc) {
|
||||||
msg += `\n${generateCodeFrame(
|
msg += `\n${generateCodeFrame(
|
||||||
source,
|
inAST?.source || source,
|
||||||
w.loc.start.offset,
|
w.loc.start.offset,
|
||||||
w.loc.end.offset
|
w.loc.end.offset
|
||||||
)}`
|
)}`
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,6 @@ import { SFCParseResult, parseCache as _parseCache } from './parse'
|
||||||
// #9521 export parseCache as a simple map to avoid exposing LRU types
|
// #9521 export parseCache as a simple map to avoid exposing LRU types
|
||||||
export const parseCache = _parseCache as Map<string, SFCParseResult>
|
export const parseCache = _parseCache as Map<string, SFCParseResult>
|
||||||
|
|
||||||
// TODO remove in 3.4
|
|
||||||
export {
|
|
||||||
shouldTransform as shouldTransformRef,
|
|
||||||
transform as transformRef,
|
|
||||||
transformAST as transformRefAST
|
|
||||||
} from '@vue/reactivity-transform'
|
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
export { parse as babelParse } from '@babel/parser'
|
export { parse as babelParse } from '@babel/parser'
|
||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
|
|
@ -37,6 +30,8 @@ export {
|
||||||
|
|
||||||
// Internals for type resolution
|
// Internals for type resolution
|
||||||
export { invalidateTypeCache, registerTS } from './script/resolveType'
|
export { invalidateTypeCache, registerTS } from './script/resolveType'
|
||||||
|
export { extractRuntimeProps } from './script/defineProps'
|
||||||
|
export { extractRuntimeEmits } from './script/defineEmits'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export type {
|
export type {
|
||||||
|
|
@ -62,6 +57,7 @@ export type { SFCScriptCompileOptions } from './compileScript'
|
||||||
export type { ScriptCompileContext } from './script/context'
|
export type { ScriptCompileContext } from './script/context'
|
||||||
export type {
|
export type {
|
||||||
TypeResolveContext,
|
TypeResolveContext,
|
||||||
|
SimpleTypeResolveOptions,
|
||||||
SimpleTypeResolveContext
|
SimpleTypeResolveContext
|
||||||
} from './script/resolveType'
|
} from './script/resolveType'
|
||||||
export type {
|
export type {
|
||||||
|
|
@ -73,3 +69,10 @@ export type {
|
||||||
CompilerError,
|
CompilerError,
|
||||||
BindingMetadata
|
BindingMetadata
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated this is preserved to avoid breaking vite-plugin-vue < 5.0
|
||||||
|
* with reactivityTransform: true. The desired behavior should be silently
|
||||||
|
* ignoring the option instead of breaking.
|
||||||
|
*/
|
||||||
|
export const shouldTransformRef = () => false
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ import {
|
||||||
ElementNode,
|
ElementNode,
|
||||||
SourceLocation,
|
SourceLocation,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
TextModes,
|
BindingMetadata,
|
||||||
BindingMetadata
|
RootNode,
|
||||||
|
createRoot
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import * as CompilerDOM from '@vue/compiler-dom'
|
import * as CompilerDOM from '@vue/compiler-dom'
|
||||||
import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
|
import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
|
||||||
|
|
@ -37,7 +38,7 @@ export interface SFCBlock {
|
||||||
|
|
||||||
export interface SFCTemplateBlock extends SFCBlock {
|
export interface SFCTemplateBlock extends SFCBlock {
|
||||||
type: 'template'
|
type: 'template'
|
||||||
ast: ElementNode
|
ast?: RootNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SFCScriptBlock extends SFCBlock {
|
export interface SFCScriptBlock extends SFCBlock {
|
||||||
|
|
@ -128,31 +129,7 @@ export function parse(
|
||||||
|
|
||||||
const errors: (CompilerError | SyntaxError)[] = []
|
const errors: (CompilerError | SyntaxError)[] = []
|
||||||
const ast = compiler.parse(source, {
|
const ast = compiler.parse(source, {
|
||||||
// there are no components at SFC parsing level
|
parseMode: 'sfc',
|
||||||
isNativeTag: () => true,
|
|
||||||
// preserve all whitespaces
|
|
||||||
isPreTag: () => true,
|
|
||||||
getTextMode: ({ tag, props }, parent) => {
|
|
||||||
// all top level elements except <template> are parsed as raw text
|
|
||||||
// containers
|
|
||||||
if (
|
|
||||||
(!parent && tag !== 'template') ||
|
|
||||||
// <template lang="xxx"> should also be treated as raw text
|
|
||||||
(tag === 'template' &&
|
|
||||||
props.some(
|
|
||||||
p =>
|
|
||||||
p.type === NodeTypes.ATTRIBUTE &&
|
|
||||||
p.name === 'lang' &&
|
|
||||||
p.value &&
|
|
||||||
p.value.content &&
|
|
||||||
p.value.content !== 'html'
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
return TextModes.RAWTEXT
|
|
||||||
} else {
|
|
||||||
return TextModes.DATA
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: e => {
|
onError: e => {
|
||||||
errors.push(e)
|
errors.push(e)
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +138,8 @@ export function parse(
|
||||||
if (node.type !== NodeTypes.ELEMENT) {
|
if (node.type !== NodeTypes.ELEMENT) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// we only want to keep the nodes that are not empty (when the tag is not a template)
|
// we only want to keep the nodes that are not empty
|
||||||
|
// (when the tag is not a template)
|
||||||
if (
|
if (
|
||||||
ignoreEmpty &&
|
ignoreEmpty &&
|
||||||
node.tag !== 'template' &&
|
node.tag !== 'template' &&
|
||||||
|
|
@ -178,7 +156,10 @@ export function parse(
|
||||||
source,
|
source,
|
||||||
false
|
false
|
||||||
) as SFCTemplateBlock)
|
) as SFCTemplateBlock)
|
||||||
templateBlock.ast = node
|
|
||||||
|
if (!templateBlock.attrs.src) {
|
||||||
|
templateBlock.ast = createRoot(node.children, source)
|
||||||
|
}
|
||||||
|
|
||||||
// warn against 2.x <template functional>
|
// warn against 2.x <template functional>
|
||||||
if (templateBlock.attrs.functional) {
|
if (templateBlock.attrs.functional) {
|
||||||
|
|
@ -188,7 +169,9 @@ export function parse(
|
||||||
`difference from stateful ones. Just use a normal <template> ` +
|
`difference from stateful ones. Just use a normal <template> ` +
|
||||||
`instead.`
|
`instead.`
|
||||||
) as CompilerError
|
) as CompilerError
|
||||||
err.loc = node.props.find(p => p.name === 'functional')!.loc
|
err.loc = node.props.find(
|
||||||
|
p => p.type === NodeTypes.ATTRIBUTE && p.name === 'functional'
|
||||||
|
)!.loc
|
||||||
errors.push(err)
|
errors.push(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -307,32 +290,11 @@ function createBlock(
|
||||||
pad: SFCParseOptions['pad']
|
pad: SFCParseOptions['pad']
|
||||||
): SFCBlock {
|
): SFCBlock {
|
||||||
const type = node.tag
|
const type = node.tag
|
||||||
let { start, end } = node.loc
|
const loc = node.innerLoc!
|
||||||
let content = ''
|
|
||||||
if (node.children.length) {
|
|
||||||
start = node.children[0].loc.start
|
|
||||||
end = node.children[node.children.length - 1].loc.end
|
|
||||||
content = source.slice(start.offset, end.offset)
|
|
||||||
} else {
|
|
||||||
const offset = node.loc.source.indexOf(`</`)
|
|
||||||
if (offset > -1) {
|
|
||||||
start = {
|
|
||||||
line: start.line,
|
|
||||||
column: start.column + offset,
|
|
||||||
offset: start.offset + offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end = { ...start }
|
|
||||||
}
|
|
||||||
const loc = {
|
|
||||||
source: content,
|
|
||||||
start,
|
|
||||||
end
|
|
||||||
}
|
|
||||||
const attrs: Record<string, string | true> = {}
|
const attrs: Record<string, string | true> = {}
|
||||||
const block: SFCBlock = {
|
const block: SFCBlock = {
|
||||||
type,
|
type,
|
||||||
content,
|
content: source.slice(loc.start.offset, loc.end.offset),
|
||||||
loc,
|
loc,
|
||||||
attrs
|
attrs
|
||||||
}
|
}
|
||||||
|
|
@ -341,18 +303,19 @@ function createBlock(
|
||||||
}
|
}
|
||||||
node.props.forEach(p => {
|
node.props.forEach(p => {
|
||||||
if (p.type === NodeTypes.ATTRIBUTE) {
|
if (p.type === NodeTypes.ATTRIBUTE) {
|
||||||
attrs[p.name] = p.value ? p.value.content || true : true
|
const name = p.name
|
||||||
if (p.name === 'lang') {
|
attrs[name] = p.value ? p.value.content || true : true
|
||||||
|
if (name === 'lang') {
|
||||||
block.lang = p.value && p.value.content
|
block.lang = p.value && p.value.content
|
||||||
} else if (p.name === 'src') {
|
} else if (name === 'src') {
|
||||||
block.src = p.value && p.value.content
|
block.src = p.value && p.value.content
|
||||||
} else if (type === 'style') {
|
} else if (type === 'style') {
|
||||||
if (p.name === 'scoped') {
|
if (name === 'scoped') {
|
||||||
;(block as SFCStyleBlock).scoped = true
|
;(block as SFCStyleBlock).scoped = true
|
||||||
} else if (p.name === 'module') {
|
} else if (name === 'module') {
|
||||||
;(block as SFCStyleBlock).module = attrs[p.name]
|
;(block as SFCStyleBlock).module = attrs[name]
|
||||||
}
|
}
|
||||||
} else if (type === 'script' && p.name === 'setup') {
|
} else if (type === 'script' && name === 'setup') {
|
||||||
;(block as SFCScriptBlock).setup = attrs.setup
|
;(block as SFCScriptBlock).setup = attrs.setup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -376,28 +339,27 @@ function generateSourceMap(
|
||||||
sourceRoot: sourceRoot.replace(/\\/g, '/')
|
sourceRoot: sourceRoot.replace(/\\/g, '/')
|
||||||
})
|
})
|
||||||
map.setSourceContent(filename, source)
|
map.setSourceContent(filename, source)
|
||||||
|
map._sources.add(filename)
|
||||||
generated.split(splitRE).forEach((line, index) => {
|
generated.split(splitRE).forEach((line, index) => {
|
||||||
if (!emptyRE.test(line)) {
|
if (!emptyRE.test(line)) {
|
||||||
const originalLine = index + 1 + lineOffset
|
const originalLine = index + 1 + lineOffset
|
||||||
const generatedLine = index + 1
|
const generatedLine = index + 1
|
||||||
for (let i = 0; i < line.length; i++) {
|
for (let i = 0; i < line.length; i++) {
|
||||||
if (!/\s/.test(line[i])) {
|
if (!/\s/.test(line[i])) {
|
||||||
map.addMapping({
|
map._mappings.add({
|
||||||
|
originalLine,
|
||||||
|
originalColumn: i,
|
||||||
|
generatedLine,
|
||||||
|
generatedColumn: i,
|
||||||
source: filename,
|
source: filename,
|
||||||
original: {
|
// @ts-ignore
|
||||||
line: originalLine,
|
name: null
|
||||||
column: i
|
|
||||||
},
|
|
||||||
generated: {
|
|
||||||
line: generatedLine,
|
|
||||||
column: i
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return JSON.parse(map.toString())
|
return map.toJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
function padContent(
|
function padContent(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ import {
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { isCallOf } from './utils'
|
import { isCallOf } from './utils'
|
||||||
import { ScriptCompileContext } from './context'
|
import { ScriptCompileContext } from './context'
|
||||||
import { resolveTypeElements, resolveUnionType } from './resolveType'
|
import {
|
||||||
|
TypeResolveContext,
|
||||||
|
resolveTypeElements,
|
||||||
|
resolveUnionType
|
||||||
|
} from './resolveType'
|
||||||
|
|
||||||
export const DEFINE_EMITS = 'defineEmits'
|
export const DEFINE_EMITS = 'defineEmits'
|
||||||
|
|
||||||
|
|
@ -64,7 +68,7 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined {
|
||||||
return emitsDecl
|
return emitsDecl
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractRuntimeEmits(ctx: ScriptCompileContext): Set<string> {
|
export function extractRuntimeEmits(ctx: TypeResolveContext): Set<string> {
|
||||||
const emits = new Set<string>()
|
const emits = new Set<string>()
|
||||||
const node = ctx.emitsTypeDecl!
|
const node = ctx.emitsTypeDecl!
|
||||||
|
|
||||||
|
|
@ -97,7 +101,7 @@ function extractRuntimeEmits(ctx: ScriptCompileContext): Set<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractEventNames(
|
function extractEventNames(
|
||||||
ctx: ScriptCompileContext,
|
ctx: TypeResolveContext,
|
||||||
eventName: ArrayPattern | Identifier | ObjectPattern | RestElement,
|
eventName: ArrayPattern | Identifier | ObjectPattern | RestElement,
|
||||||
emits: Set<string>
|
emits: Set<string>
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ import {
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { BindingTypes, isFunctionType } from '@vue/compiler-dom'
|
import { BindingTypes, isFunctionType } from '@vue/compiler-dom'
|
||||||
import { ScriptCompileContext } from './context'
|
import { ScriptCompileContext } from './context'
|
||||||
import { inferRuntimeType, resolveTypeElements } from './resolveType'
|
import {
|
||||||
|
TypeResolveContext,
|
||||||
|
inferRuntimeType,
|
||||||
|
resolveTypeElements
|
||||||
|
} from './resolveType'
|
||||||
import {
|
import {
|
||||||
resolveObjectKey,
|
resolveObjectKey,
|
||||||
UNKNOWN_TYPE,
|
UNKNOWN_TYPE,
|
||||||
|
|
@ -150,7 +154,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (ctx.propsTypeDecl) {
|
} else if (ctx.propsTypeDecl) {
|
||||||
propsDecls = genRuntimePropsFromTypes(ctx)
|
propsDecls = extractRuntimeProps(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelsDecls = genModelProps(ctx)
|
const modelsDecls = genModelProps(ctx)
|
||||||
|
|
@ -162,7 +166,9 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
|
export function extractRuntimeProps(
|
||||||
|
ctx: TypeResolveContext
|
||||||
|
): string | undefined {
|
||||||
// this is only called if propsTypeDecl exists
|
// this is only called if propsTypeDecl exists
|
||||||
const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!)
|
const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!)
|
||||||
if (!props.length) {
|
if (!props.length) {
|
||||||
|
|
@ -175,7 +181,7 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults))
|
propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults))
|
||||||
// register bindings
|
// register bindings
|
||||||
if (!(prop.key in ctx.bindingMetadata)) {
|
if ('bindingMetadata' in ctx && !(prop.key in ctx.bindingMetadata)) {
|
||||||
ctx.bindingMetadata[prop.key] = BindingTypes.PROPS
|
ctx.bindingMetadata[prop.key] = BindingTypes.PROPS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +199,7 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRuntimePropsFromType(
|
function resolveRuntimePropsFromType(
|
||||||
ctx: ScriptCompileContext,
|
ctx: TypeResolveContext,
|
||||||
node: Node
|
node: Node
|
||||||
): PropTypeData[] {
|
): PropTypeData[] {
|
||||||
const props: PropTypeData[] = []
|
const props: PropTypeData[] = []
|
||||||
|
|
@ -222,7 +228,7 @@ function resolveRuntimePropsFromType(
|
||||||
}
|
}
|
||||||
|
|
||||||
function genRuntimePropFromType(
|
function genRuntimePropFromType(
|
||||||
ctx: ScriptCompileContext,
|
ctx: TypeResolveContext,
|
||||||
{ key, required, type, skipCheck }: PropTypeData,
|
{ key, required, type, skipCheck }: PropTypeData,
|
||||||
hasStaticDefaults: boolean
|
hasStaticDefaults: boolean
|
||||||
): string {
|
): string {
|
||||||
|
|
@ -284,7 +290,7 @@ function genRuntimePropFromType(
|
||||||
* static properties, we can directly generate more optimized default
|
* static properties, we can directly generate more optimized default
|
||||||
* declarations. Otherwise we will have to fallback to runtime merging.
|
* declarations. Otherwise we will have to fallback to runtime merging.
|
||||||
*/
|
*/
|
||||||
function hasStaticWithDefaults(ctx: ScriptCompileContext) {
|
function hasStaticWithDefaults(ctx: TypeResolveContext) {
|
||||||
return !!(
|
return !!(
|
||||||
ctx.propsRuntimeDefaults &&
|
ctx.propsRuntimeDefaults &&
|
||||||
ctx.propsRuntimeDefaults.type === 'ObjectExpression' &&
|
ctx.propsRuntimeDefaults.type === 'ObjectExpression' &&
|
||||||
|
|
@ -297,7 +303,7 @@ function hasStaticWithDefaults(ctx: ScriptCompileContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function genDestructuredDefaultValue(
|
function genDestructuredDefaultValue(
|
||||||
ctx: ScriptCompileContext,
|
ctx: TypeResolveContext,
|
||||||
key: string,
|
key: string,
|
||||||
inferredType?: string[]
|
inferredType?: string[]
|
||||||
):
|
):
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function processPropsDestructure(
|
||||||
ctx: ScriptCompileContext,
|
ctx: ScriptCompileContext,
|
||||||
declId: ObjectPattern
|
declId: ObjectPattern
|
||||||
) {
|
) {
|
||||||
if (!ctx.options.propsDestructure && !ctx.options.reactivityTransform) {
|
if (!ctx.options.propsDestructure) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ export function transformDestructuredProps(
|
||||||
ctx: ScriptCompileContext,
|
ctx: ScriptCompileContext,
|
||||||
vueImportAliases: Record<string, string>
|
vueImportAliases: Record<string, string>
|
||||||
) {
|
) {
|
||||||
if (!ctx.options.propsDestructure && !ctx.options.reactivityTransform) {
|
if (!ctx.options.propsDestructure) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@ import { SFCDescriptor } from '../parse'
|
||||||
import {
|
import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
createRoot,
|
|
||||||
forAliasRE,
|
forAliasRE,
|
||||||
parserOptions,
|
parserOptions,
|
||||||
transform,
|
walkIdentifiers,
|
||||||
walkIdentifiers
|
TemplateChildNode
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { createCache } from '../cache'
|
import { createCache } from '../cache'
|
||||||
import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
|
import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
|
||||||
|
|
@ -35,53 +34,54 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let code = ''
|
let code = ''
|
||||||
transform(createRoot([ast]), {
|
|
||||||
nodeTransforms: [
|
|
||||||
node => {
|
|
||||||
if (node.type === NodeTypes.ELEMENT) {
|
|
||||||
if (
|
|
||||||
!parserOptions.isNativeTag!(node.tag) &&
|
|
||||||
!parserOptions.isBuiltInComponent!(node.tag)
|
|
||||||
) {
|
|
||||||
code += `,${camelize(node.tag)},${capitalize(camelize(node.tag))}`
|
|
||||||
}
|
|
||||||
for (let i = 0; i < node.props.length; i++) {
|
|
||||||
const prop = node.props[i]
|
|
||||||
if (prop.type === NodeTypes.DIRECTIVE) {
|
|
||||||
if (!isBuiltInDirective(prop.name)) {
|
|
||||||
code += `,v${capitalize(camelize(prop.name))}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// process dynamic directive arguments
|
ast!.children.forEach(walk)
|
||||||
if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) {
|
|
||||||
code += `,${stripStrings(
|
|
||||||
(prop.arg as SimpleExpressionNode).content
|
|
||||||
)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prop.exp) {
|
function walk(node: TemplateChildNode) {
|
||||||
code += `,${processExp(
|
switch (node.type) {
|
||||||
(prop.exp as SimpleExpressionNode).content,
|
case NodeTypes.ELEMENT:
|
||||||
prop.name
|
if (
|
||||||
)}`
|
!parserOptions.isNativeTag!(node.tag) &&
|
||||||
}
|
!parserOptions.isBuiltInComponent!(node.tag)
|
||||||
}
|
) {
|
||||||
if (
|
code += `,${camelize(node.tag)},${capitalize(camelize(node.tag))}`
|
||||||
prop.type === NodeTypes.ATTRIBUTE &&
|
|
||||||
prop.name === 'ref' &&
|
|
||||||
prop.value?.content
|
|
||||||
) {
|
|
||||||
code += `,${prop.value.content}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (node.type === NodeTypes.INTERPOLATION) {
|
|
||||||
code += `,${processExp(
|
|
||||||
(node.content as SimpleExpressionNode).content
|
|
||||||
)}`
|
|
||||||
}
|
}
|
||||||
}
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
]
|
const prop = node.props[i]
|
||||||
})
|
if (prop.type === NodeTypes.DIRECTIVE) {
|
||||||
|
if (!isBuiltInDirective(prop.name)) {
|
||||||
|
code += `,v${capitalize(camelize(prop.name))}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// process dynamic directive arguments
|
||||||
|
if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) {
|
||||||
|
code += `,${stripStrings(
|
||||||
|
(prop.arg as SimpleExpressionNode).content
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop.exp) {
|
||||||
|
code += `,${processExp(
|
||||||
|
(prop.exp as SimpleExpressionNode).content,
|
||||||
|
prop.name
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
prop.type === NodeTypes.ATTRIBUTE &&
|
||||||
|
prop.name === 'ref' &&
|
||||||
|
prop.value?.content
|
||||||
|
) {
|
||||||
|
code += `,${prop.value.content}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.children.forEach(walk)
|
||||||
|
break
|
||||||
|
case NodeTypes.INTERPOLATION:
|
||||||
|
code += `,${processExp((node.content as SimpleExpressionNode).content)}`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
code += ';'
|
code += ';'
|
||||||
templateUsageCheckCache.set(content, code)
|
templateUsageCheckCache.set(content, code)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { shouldTransform, transformAST } from '@vue/reactivity-transform'
|
|
||||||
import { analyzeScriptBindings } from './analyzeScriptBindings'
|
import { analyzeScriptBindings } from './analyzeScriptBindings'
|
||||||
import { ScriptCompileContext } from './context'
|
import { ScriptCompileContext } from './context'
|
||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import { RawSourceMap } from 'source-map-js'
|
|
||||||
import { rewriteDefaultAST } from '../rewriteDefault'
|
import { rewriteDefaultAST } from '../rewriteDefault'
|
||||||
import { genNormalScriptCssVarsCode } from '../style/cssVars'
|
import { genNormalScriptCssVarsCode } from '../style/cssVars'
|
||||||
|
|
||||||
|
|
@ -22,33 +20,8 @@ export function processNormalScript(
|
||||||
let map = script.map
|
let map = script.map
|
||||||
const scriptAst = ctx.scriptAst!
|
const scriptAst = ctx.scriptAst!
|
||||||
const bindings = analyzeScriptBindings(scriptAst.body)
|
const bindings = analyzeScriptBindings(scriptAst.body)
|
||||||
const { source, filename, cssVars } = ctx.descriptor
|
const { cssVars } = ctx.descriptor
|
||||||
const { sourceMap, genDefaultAs, isProd } = ctx.options
|
const { genDefaultAs, isProd } = ctx.options
|
||||||
|
|
||||||
// TODO remove in 3.4
|
|
||||||
if (ctx.options.reactivityTransform && shouldTransform(content)) {
|
|
||||||
const s = new MagicString(source)
|
|
||||||
const startOffset = script.loc.start.offset
|
|
||||||
const endOffset = script.loc.end.offset
|
|
||||||
const { importedHelpers } = transformAST(scriptAst, s, startOffset)
|
|
||||||
if (importedHelpers.length) {
|
|
||||||
s.prepend(
|
|
||||||
`import { ${importedHelpers
|
|
||||||
.map(h => `${h} as _${h}`)
|
|
||||||
.join(', ')} } from 'vue'\n`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
s.remove(0, startOffset)
|
|
||||||
s.remove(endOffset, source.length)
|
|
||||||
content = s.toString()
|
|
||||||
if (sourceMap !== false) {
|
|
||||||
map = s.generateMap({
|
|
||||||
source: filename,
|
|
||||||
hires: true,
|
|
||||||
includeContent: true
|
|
||||||
}) as unknown as RawSourceMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cssVars.length || genDefaultAs) {
|
if (cssVars.length || genDefaultAs) {
|
||||||
const defaultVar = genDefaultAs || normalScriptDefaultVar
|
const defaultVar = genDefaultAs || normalScriptDefaultVar
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,13 @@ import { extname, dirname, join } from 'path'
|
||||||
import { minimatch as isMatch } from 'minimatch'
|
import { minimatch as isMatch } from 'minimatch'
|
||||||
import * as process from 'process'
|
import * as process from 'process'
|
||||||
|
|
||||||
|
export type SimpleTypeResolveOptions = Partial<
|
||||||
|
Pick<
|
||||||
|
SFCScriptCompileOptions,
|
||||||
|
'globalTypeFiles' | 'fs' | 'babelParserPlugins' | 'isProd'
|
||||||
|
>
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TypeResolveContext is compatible with ScriptCompileContext
|
* TypeResolveContext is compatible with ScriptCompileContext
|
||||||
* but also allows a simpler version of it with minimal required properties
|
* but also allows a simpler version of it with minimal required properties
|
||||||
|
|
@ -60,13 +67,28 @@ import * as process from 'process'
|
||||||
*/
|
*/
|
||||||
export type SimpleTypeResolveContext = Pick<
|
export type SimpleTypeResolveContext = Pick<
|
||||||
ScriptCompileContext,
|
ScriptCompileContext,
|
||||||
// required
|
// file
|
||||||
'source' | 'filename' | 'error' | 'options'
|
| 'source'
|
||||||
|
| 'filename'
|
||||||
|
|
||||||
|
// utils
|
||||||
|
| 'error'
|
||||||
|
| 'helper'
|
||||||
|
| 'getString'
|
||||||
|
|
||||||
|
// props
|
||||||
|
| 'propsTypeDecl'
|
||||||
|
| 'propsRuntimeDefaults'
|
||||||
|
| 'propsDestructuredBindings'
|
||||||
|
|
||||||
|
// emits
|
||||||
|
| 'emitsTypeDecl'
|
||||||
> &
|
> &
|
||||||
Partial<
|
Partial<
|
||||||
Pick<ScriptCompileContext, 'scope' | 'globalScopes' | 'deps' | 'fs'>
|
Pick<ScriptCompileContext, 'scope' | 'globalScopes' | 'deps' | 'fs'>
|
||||||
> & {
|
> & {
|
||||||
ast: Statement[]
|
ast: Statement[]
|
||||||
|
options: SimpleTypeResolveOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext
|
export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export function parseCssVars(sfc: SFCDescriptor): string[] {
|
||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
|
|
||||||
const enum LexerState {
|
enum LexerState {
|
||||||
inParens,
|
inParens,
|
||||||
inSingleQuoteString,
|
inSingleQuoteString,
|
||||||
inDoubleQuoteString
|
inDoubleQuoteString
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,12 @@ const scss: StylePreprocessor = (source, map, options, load = require) => {
|
||||||
if (map) {
|
if (map) {
|
||||||
return {
|
return {
|
||||||
code: result.css.toString(),
|
code: result.css.toString(),
|
||||||
map: merge(map, JSON.parse(result.map.toString())),
|
map: merge(
|
||||||
|
map,
|
||||||
|
result.map.toJSON
|
||||||
|
? result.map.toJSON()
|
||||||
|
: JSON.parse(result.map.toString())
|
||||||
|
),
|
||||||
errors: [],
|
errors: [],
|
||||||
dependencies
|
dependencies
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-ssr",
|
"name": "@vue/compiler-ssr",
|
||||||
"version": "3.3.9",
|
"version": "3.4.0-alpha.3",
|
||||||
"description": "@vue/compiler-ssr",
|
"description": "@vue/compiler-ssr",
|
||||||
"main": "dist/compiler-ssr.cjs.js",
|
"main": "dist/compiler-ssr.cjs.js",
|
||||||
"types": "dist/compiler-ssr.d.ts",
|
"types": "dist/compiler-ssr.d.ts",
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@ export function createSSRCompilerError(
|
||||||
return createCompilerError(code, loc, SSRErrorMessages) as SSRCompilerError
|
return createCompilerError(code, loc, SSRErrorMessages) as SSRCompilerError
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum SSRErrorCodes {
|
export enum SSRErrorCodes {
|
||||||
X_SSR_UNSAFE_ATTR_NAME = 65 /* DOMErrorCodes.__EXTEND_POINT__ */,
|
X_SSR_UNSAFE_ATTR_NAME = 65 /* DOMErrorCodes.__EXTEND_POINT__ */,
|
||||||
X_SSR_NO_TELEPORT_TARGET,
|
X_SSR_NO_TELEPORT_TARGET,
|
||||||
X_SSR_INVALID_AST_NODE
|
X_SSR_INVALID_AST_NODE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__TEST__) {
|
if (__TEST__) {
|
||||||
// esbuild cannot infer const enum increments if first value is from another
|
// esbuild cannot infer enum increments if first value is from another
|
||||||
// file, so we have to manually keep them in sync. this check ensures it
|
// file, so we have to manually keep them in sync. this check ensures it
|
||||||
// errors out if there are collisions.
|
// errors out if there are collisions.
|
||||||
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import {
|
||||||
noopDirectiveTransform,
|
noopDirectiveTransform,
|
||||||
transformBind,
|
transformBind,
|
||||||
transformStyle,
|
transformStyle,
|
||||||
transformOn
|
transformOn,
|
||||||
|
RootNode
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { ssrCodegenTransform } from './ssrCodegenTransform'
|
import { ssrCodegenTransform } from './ssrCodegenTransform'
|
||||||
import { ssrTransformElement } from './transforms/ssrTransformElement'
|
import { ssrTransformElement } from './transforms/ssrTransformElement'
|
||||||
|
|
@ -28,12 +29,11 @@ import { ssrInjectFallthroughAttrs } from './transforms/ssrInjectFallthroughAttr
|
||||||
import { ssrInjectCssVars } from './transforms/ssrInjectCssVars'
|
import { ssrInjectCssVars } from './transforms/ssrInjectCssVars'
|
||||||
|
|
||||||
export function compile(
|
export function compile(
|
||||||
template: string,
|
source: string | RootNode,
|
||||||
options: CompilerOptions = {}
|
options: CompilerOptions = {}
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
options = {
|
options = {
|
||||||
...options,
|
...options,
|
||||||
// apply DOM-specific parsing options
|
|
||||||
...parserOptions,
|
...parserOptions,
|
||||||
ssr: true,
|
ssr: true,
|
||||||
inSSR: true,
|
inSSR: true,
|
||||||
|
|
@ -45,7 +45,7 @@ export function compile(
|
||||||
hoistStatic: false
|
hoistStatic: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const ast = baseParse(template, options)
|
const ast = typeof source === 'string' ? baseParse(source, options) : source
|
||||||
|
|
||||||
// Save raw options for AST. This is needed when performing sub-transforms
|
// Save raw options for AST. This is needed when performing sub-transforms
|
||||||
// on slot vnode branches.
|
// on slot vnode branches.
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ import {
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
RootNode,
|
RootNode,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
findDir,
|
findDir
|
||||||
isBuiltInType
|
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
|
|
||||||
export const ssrInjectCssVars: NodeTransform = (node, context) => {
|
export const ssrInjectCssVars: NodeTransform = (node, context) => {
|
||||||
|
|
@ -43,7 +42,7 @@ function injectCssVars(node: RootNode | TemplateChildNode) {
|
||||||
node.tagType === ElementTypes.COMPONENT) &&
|
node.tagType === ElementTypes.COMPONENT) &&
|
||||||
!findDir(node, 'for')
|
!findDir(node, 'for')
|
||||||
) {
|
) {
|
||||||
if (isBuiltInType(node.tag, 'Suspense')) {
|
if (node.tag === 'suspense' || node.tag === 'Suspense') {
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
if (
|
if (
|
||||||
child.type === NodeTypes.ELEMENT &&
|
child.type === NodeTypes.ELEMENT &&
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@ import {
|
||||||
RootNode,
|
RootNode,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
ParentNode,
|
ParentNode,
|
||||||
findDir,
|
findDir
|
||||||
isBuiltInType
|
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
|
|
||||||
const filterChild = (node: ParentNode) =>
|
const filterChild = (node: ParentNode) =>
|
||||||
|
|
@ -28,8 +27,10 @@ export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
node.type === NodeTypes.ELEMENT &&
|
node.type === NodeTypes.ELEMENT &&
|
||||||
node.tagType === ElementTypes.COMPONENT &&
|
node.tagType === ElementTypes.COMPONENT &&
|
||||||
(isBuiltInType(node.tag, 'Transition') ||
|
(node.tag === 'transition' ||
|
||||||
isBuiltInType(node.tag, 'KeepAlive'))
|
node.tag === 'Transition' ||
|
||||||
|
node.tag === 'KeepAlive' ||
|
||||||
|
node.tag === 'keep-alive')
|
||||||
) {
|
) {
|
||||||
const rootChildren = filterChild(context.root)
|
const rootChildren = filterChild(context.root)
|
||||||
if (rootChildren.length === 1 && rootChildren[0] === node) {
|
if (rootChildren.length === 1 && rootChildren[0] === node) {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ import {
|
||||||
JSChildNode,
|
JSChildNode,
|
||||||
RESOLVE_DYNAMIC_COMPONENT,
|
RESOLVE_DYNAMIC_COMPONENT,
|
||||||
TRANSITION,
|
TRANSITION,
|
||||||
stringifyExpression
|
stringifyExpression,
|
||||||
|
DirectiveNode
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
|
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
|
|
@ -54,7 +55,7 @@ import {
|
||||||
ssrProcessTransitionGroup,
|
ssrProcessTransitionGroup,
|
||||||
ssrTransformTransitionGroup
|
ssrTransformTransitionGroup
|
||||||
} from './ssrTransformTransitionGroup'
|
} from './ssrTransformTransitionGroup'
|
||||||
import { isSymbol, isObject, isArray } from '@vue/shared'
|
import { isSymbol, isObject, isArray, extend } from '@vue/shared'
|
||||||
import { buildSSRProps } from './ssrTransformElement'
|
import { buildSSRProps } from './ssrTransformElement'
|
||||||
import {
|
import {
|
||||||
ssrProcessTransition,
|
ssrProcessTransition,
|
||||||
|
|
@ -278,8 +279,8 @@ const vnodeDirectiveTransforms = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createVNodeSlotBranch(
|
function createVNodeSlotBranch(
|
||||||
props: ExpressionNode | undefined,
|
slotProps: ExpressionNode | undefined,
|
||||||
vForExp: ExpressionNode | undefined,
|
vFor: DirectiveNode | undefined,
|
||||||
children: TemplateChildNode[],
|
children: TemplateChildNode[],
|
||||||
parentContext: TransformContext
|
parentContext: TransformContext
|
||||||
): ReturnStatement {
|
): ReturnStatement {
|
||||||
|
|
@ -300,32 +301,28 @@ function createVNodeSlotBranch(
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap the children with a wrapper template for proper children treatment.
|
// wrap the children with a wrapper template for proper children treatment.
|
||||||
|
// important: provide v-slot="props" and v-for="exp" on the wrapper for
|
||||||
|
// proper scope analysis
|
||||||
|
const wrapperProps: TemplateNode['props'] = []
|
||||||
|
if (slotProps) {
|
||||||
|
wrapperProps.push({
|
||||||
|
type: NodeTypes.DIRECTIVE,
|
||||||
|
name: 'slot',
|
||||||
|
exp: slotProps,
|
||||||
|
arg: undefined,
|
||||||
|
modifiers: [],
|
||||||
|
loc: locStub
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (vFor) {
|
||||||
|
wrapperProps.push(extend({}, vFor))
|
||||||
|
}
|
||||||
const wrapperNode: TemplateNode = {
|
const wrapperNode: TemplateNode = {
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
ns: Namespaces.HTML,
|
ns: Namespaces.HTML,
|
||||||
tag: 'template',
|
tag: 'template',
|
||||||
tagType: ElementTypes.TEMPLATE,
|
tagType: ElementTypes.TEMPLATE,
|
||||||
isSelfClosing: false,
|
props: wrapperProps,
|
||||||
// important: provide v-slot="props" and v-for="exp" on the wrapper for
|
|
||||||
// proper scope analysis
|
|
||||||
props: [
|
|
||||||
{
|
|
||||||
type: NodeTypes.DIRECTIVE,
|
|
||||||
name: 'slot',
|
|
||||||
exp: props,
|
|
||||||
arg: undefined,
|
|
||||||
modifiers: [],
|
|
||||||
loc: locStub
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.DIRECTIVE,
|
|
||||||
name: 'for',
|
|
||||||
exp: vForExp,
|
|
||||||
arg: undefined,
|
|
||||||
modifiers: [],
|
|
||||||
loc: locStub
|
|
||||||
}
|
|
||||||
],
|
|
||||||
children,
|
children,
|
||||||
loc: locStub,
|
loc: locStub,
|
||||||
codegenNode: undefined
|
codegenNode: undefined
|
||||||
|
|
|
||||||
|
|
@ -292,14 +292,15 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// special case: value on <textarea>
|
// special case: value on <textarea>
|
||||||
if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
|
const name = prop.name
|
||||||
|
if (node.tag === 'textarea' && name === 'value' && prop.value) {
|
||||||
rawChildrenMap.set(node, escapeHtml(prop.value.content))
|
rawChildrenMap.set(node, escapeHtml(prop.value.content))
|
||||||
} else if (!needMergeProps) {
|
} else if (!needMergeProps) {
|
||||||
if (prop.name === 'key' || prop.name === 'ref') {
|
if (name === 'key' || name === 'ref') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// static prop
|
// static prop
|
||||||
if (prop.name === 'class' && prop.value) {
|
if (name === 'class' && prop.value) {
|
||||||
staticClassBinding = JSON.stringify(prop.value.content)
|
staticClassBinding = JSON.stringify(prop.value.content)
|
||||||
}
|
}
|
||||||
openTag.push(
|
openTag.push(
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
import { ref, computed, Ref, ComputedRef, WritableComputedRef } from 'vue'
|
|
||||||
import 'vue/macros-global'
|
|
||||||
import { RefType, RefTypes } from 'vue/macros'
|
|
||||||
import { expectType } from './utils'
|
|
||||||
|
|
||||||
// wrapping refs
|
|
||||||
|
|
||||||
// normal
|
|
||||||
let n = $(ref(1))
|
|
||||||
n = 2
|
|
||||||
// @ts-expect-error
|
|
||||||
n = 'foo'
|
|
||||||
|
|
||||||
// #4499 nullable
|
|
||||||
let msg = $(ref<string | null>(null))
|
|
||||||
msg = 'hello world'
|
|
||||||
msg = null
|
|
||||||
expectType<RefTypes.Ref | undefined>(msg![RefType])
|
|
||||||
|
|
||||||
// computed
|
|
||||||
let m = $(computed(() => n + 1))
|
|
||||||
m * 1
|
|
||||||
// @ts-expect-error
|
|
||||||
m.slice()
|
|
||||||
expectType<RefTypes.ComputedRef | undefined>(m[RefType])
|
|
||||||
|
|
||||||
// writable computed
|
|
||||||
let wc = $(
|
|
||||||
computed({
|
|
||||||
get: () => n + 1,
|
|
||||||
set: v => (n = v - 1)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
wc = 2
|
|
||||||
// @ts-expect-error
|
|
||||||
wc = 'foo'
|
|
||||||
expectType<RefTypes.WritableComputedRef | undefined>(wc[RefType])
|
|
||||||
|
|
||||||
// destructure
|
|
||||||
function useFoo() {
|
|
||||||
let x = $ref(1)
|
|
||||||
let y = $computed(() => 'hi')
|
|
||||||
|
|
||||||
return $$({
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
z: 123
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fooRes = useFoo()
|
|
||||||
const { x, y, z } = $(fooRes)
|
|
||||||
expectType<number>(x)
|
|
||||||
expectType<string>(y)
|
|
||||||
expectType<number>(z)
|
|
||||||
|
|
||||||
// $ref
|
|
||||||
expectType<number>($ref(1))
|
|
||||||
expectType<number>($ref(ref(1)))
|
|
||||||
expectType<{ foo: number }>($ref({ foo: ref(1) }))
|
|
||||||
|
|
||||||
// $shallowRef
|
|
||||||
expectType<number>($shallowRef(1))
|
|
||||||
expectType<{ foo: Ref<number> }>($shallowRef({ foo: ref(1) }))
|
|
||||||
|
|
||||||
// $computed
|
|
||||||
expectType<number>($computed(() => 1))
|
|
||||||
let b = $ref(1)
|
|
||||||
expectType<number>(
|
|
||||||
$computed(() => b, {
|
|
||||||
onTrack() {}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// writable computed
|
|
||||||
expectType<number>(
|
|
||||||
$computed({
|
|
||||||
get: () => 1,
|
|
||||||
set: () => {}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// $$
|
|
||||||
const xRef = $$(x)
|
|
||||||
expectType<Ref<number>>(xRef)
|
|
||||||
|
|
||||||
const yRef = $$(y)
|
|
||||||
expectType<ComputedRef<string>>(yRef)
|
|
||||||
|
|
||||||
const c = $computed(() => 1)
|
|
||||||
const cRef = $$(c)
|
|
||||||
expectType<ComputedRef<number>>(cRef)
|
|
||||||
|
|
||||||
const c2 = $computed({
|
|
||||||
get: () => 1,
|
|
||||||
set: () => {}
|
|
||||||
})
|
|
||||||
const c2Ref = $$(c2)
|
|
||||||
expectType<WritableComputedRef<number>>(c2Ref)
|
|
||||||
|
|
||||||
// $$ on object
|
|
||||||
const obj = $$({
|
|
||||||
n,
|
|
||||||
m,
|
|
||||||
wc
|
|
||||||
})
|
|
||||||
|
|
||||||
expectType<Ref<number>>(obj.n)
|
|
||||||
expectType<ComputedRef<number>>(obj.m)
|
|
||||||
expectType<WritableComputedRef<number>>(obj.wc)
|
|
||||||
|
|
@ -44,6 +44,18 @@ declare module 'estree-walker' {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'source-map-js' {
|
||||||
|
export interface SourceMapGenerator {
|
||||||
|
// SourceMapGenerator has this method but the types do not include it
|
||||||
|
toJSON(): RawSourceMap
|
||||||
|
_sources: Set<string>
|
||||||
|
_names: Set<string>
|
||||||
|
_mappings: {
|
||||||
|
add(mapping: MappingItem): void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
declare interface String {
|
declare interface String {
|
||||||
/**
|
/**
|
||||||
* @deprecated Please use String.prototype.slice instead of String.prototype.substring in the repository.
|
* @deprecated Please use String.prototype.slice instead of String.prototype.substring in the repository.
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2018-present, Yuxi (Evan) You
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
# @vue/reactivity-transform
|
|
||||||
|
|
||||||
> ⚠️ This is experimental and the proposal has been dropped.
|
|
||||||
> The feature is now marked as deprecated and will be removed from Vue core
|
|
||||||
> in 3.4.
|
|
||||||
>
|
|
||||||
> See reason for deprecation [here](https://github.com/vuejs/rfcs/discussions/369#discussioncomment-5059028).
|
|
||||||
|
|
||||||
## Basic Rules
|
|
||||||
|
|
||||||
- Ref-creating APIs have `$`-prefixed versions that create reactive variables instead. They also do not need to be explicitly imported. These include:
|
|
||||||
- `ref`
|
|
||||||
- `computed`
|
|
||||||
- `shallowRef`
|
|
||||||
- `customRef`
|
|
||||||
- `toRef`
|
|
||||||
- `$()` can be used to destructure an object into reactive variables, or turn existing refs into reactive variables
|
|
||||||
- `$$()` to "escape" the transform, which allows access to underlying refs
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { watchEffect } from 'vue'
|
|
||||||
|
|
||||||
// bind ref as a variable
|
|
||||||
let count = $ref(0)
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
// no need for .value
|
|
||||||
console.log(count)
|
|
||||||
})
|
|
||||||
|
|
||||||
// assignments are reactive
|
|
||||||
count++
|
|
||||||
|
|
||||||
// get the actual ref
|
|
||||||
console.log($$(count)) // { value: 1 }
|
|
||||||
```
|
|
||||||
|
|
||||||
Macros can be optionally imported to make it more explicit:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// not necessary, but also works
|
|
||||||
import { $, $ref } from 'vue/macros'
|
|
||||||
|
|
||||||
let count = $ref(0)
|
|
||||||
const { x, y } = $(useMouse())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Global Types
|
|
||||||
|
|
||||||
To enable types for the macros globally, include the following in a `.d.ts` file:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
/// <reference types="vue/macros-global" />
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
This package is the lower-level transform that can be used standalone. Higher-level tooling (e.g. `@vitejs/plugin-vue` and `vue-loader`) will provide integration via options.
|
|
||||||
|
|
||||||
### `shouldTransform`
|
|
||||||
|
|
||||||
Can be used to do a cheap check to determine whether full transform should be performed.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { shouldTransform } from '@vue/reactivity-transform'
|
|
||||||
|
|
||||||
shouldTransform(`let a = ref(0)`) // false
|
|
||||||
shouldTransform(`let a = $ref(0)`) // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### `transform`
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { transform } from '@vue/reactivity-transform'
|
|
||||||
|
|
||||||
const src = `let a = $ref(0); a++`
|
|
||||||
const {
|
|
||||||
code, // import { ref as _ref } from 'vue'; let a = (ref(0)); a.value++"
|
|
||||||
map
|
|
||||||
} = transform(src, {
|
|
||||||
filename: 'foo.ts',
|
|
||||||
sourceMap: true,
|
|
||||||
|
|
||||||
// @babel/parser plugins to enable.
|
|
||||||
// 'typescript' and 'jsx' will be auto-inferred from filename if provided,
|
|
||||||
// so in most cases explicit parserPlugins are not necessary
|
|
||||||
parserPlugins: [
|
|
||||||
/* ... */
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options**
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface RefTransformOptions {
|
|
||||||
filename?: string
|
|
||||||
sourceMap?: boolean // default: false
|
|
||||||
parserPlugins?: ParserPlugin[]
|
|
||||||
importHelpersFrom?: string // default: "vue"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `transformAST`
|
|
||||||
|
|
||||||
Transform with an existing Babel AST + MagicString instance. This is used internally by `@vue/compiler-sfc` to avoid double parse/transform cost.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { transformAST } from '@vue/reactivity-transform'
|
|
||||||
import { parse } from '@babel/parser'
|
|
||||||
import MagicString from 'magic-string'
|
|
||||||
|
|
||||||
const src = `let a = $ref(0); a++`
|
|
||||||
const ast = parse(src, { sourceType: 'module' })
|
|
||||||
const s = new MagicString(src)
|
|
||||||
|
|
||||||
const {
|
|
||||||
rootRefs, // ['a']
|
|
||||||
importedHelpers // ['ref']
|
|
||||||
} = transformAST(ast, s)
|
|
||||||
|
|
||||||
console.log(s.toString()) // let a = _ref(0); a.value++
|
|
||||||
```
|
|
||||||
|
|
@ -1,292 +0,0 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`$ unwrapping 1`] = `
|
|
||||||
"
|
|
||||||
import { ref, shallowRef } from 'vue'
|
|
||||||
let foo = (ref())
|
|
||||||
export let a = (ref(1))
|
|
||||||
let b = (shallowRef({
|
|
||||||
count: 0
|
|
||||||
}))
|
|
||||||
let c = () => {}
|
|
||||||
let d
|
|
||||||
label: var e = (ref())
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`$$ 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(1)
|
|
||||||
const b = (a)
|
|
||||||
const c = ({ a })
|
|
||||||
callExternal((a))
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`$$ with some edge cases 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
;( /* 2 */ count /* 2 */ )
|
|
||||||
;( count /* 2 */, /**/ a )
|
|
||||||
;( (count /* 2 */, /**/ a) /**/ )
|
|
||||||
{
|
|
||||||
a:(count,a)
|
|
||||||
}
|
|
||||||
;((count) + 1)
|
|
||||||
;([count])
|
|
||||||
; (count )
|
|
||||||
console.log(((a)))
|
|
||||||
;(a,b)
|
|
||||||
;(((a++,b)))
|
|
||||||
count = ( a++ ,b)
|
|
||||||
count = ()=>(a++,b)
|
|
||||||
let r1 = _ref(a, (a++,b))
|
|
||||||
let r2 = { a:(a++,b),b: (a) }
|
|
||||||
switch((c)){
|
|
||||||
case d:
|
|
||||||
;(a)
|
|
||||||
;((h,f))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
((count++,(count),(count,a)))
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`$computed declaration 1`] = `
|
|
||||||
"import { computed as _computed } from 'vue'
|
|
||||||
|
|
||||||
let a = _computed(() => 1)
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`$ref & $shallowRef declarations 1`] = `
|
|
||||||
"import { ref as _ref, shallowRef as _shallowRef } from 'vue'
|
|
||||||
|
|
||||||
let foo = _ref()
|
|
||||||
export let a = _ref(1)
|
|
||||||
let b = _shallowRef({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
let c = () => {}
|
|
||||||
let d
|
|
||||||
label: var e = _ref()
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`accessing ref binding 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(1)
|
|
||||||
console.log(a.value)
|
|
||||||
function get() {
|
|
||||||
return a.value + 1
|
|
||||||
}
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`array destructure 1`] = `
|
|
||||||
"import { ref as _ref, toRef as _toRef } from 'vue'
|
|
||||||
|
|
||||||
let n = _ref(1), __$temp_1 = (useFoo()),
|
|
||||||
a = _toRef(__$temp_1, 0),
|
|
||||||
b = _toRef(__$temp_1, 1, 1);
|
|
||||||
console.log(n.value, a.value, b.value)
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`handle TS casting syntax 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(1)
|
|
||||||
console.log(a.value!)
|
|
||||||
console.log(a.value! + 1)
|
|
||||||
console.log(a.value as number)
|
|
||||||
console.log((a.value as number) + 1)
|
|
||||||
console.log(<number>a.value)
|
|
||||||
console.log(<number>a.value + 1)
|
|
||||||
console.log(a.value! + (a.value as number))
|
|
||||||
console.log(a.value! + <number>a.value)
|
|
||||||
console.log((a.value as number) + <number>a.value)
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`macro import alias and removal 1`] = `
|
|
||||||
"import { ref as _ref, toRef as _toRef } from 'vue'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let a = _ref(1)
|
|
||||||
const __$temp_1 = (useMouse()),
|
|
||||||
x = _toRef(__$temp_1, 'x'),
|
|
||||||
y = _toRef(__$temp_1, 'y');
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`mixing $ref & $computed declarations 1`] = `
|
|
||||||
"import { ref as _ref, computed as _computed } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(1), b = _computed(() => a.value + 1)
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`multi $ref declarations 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(1), b = _ref(2), c = _ref({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`mutating ref binding 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(1)
|
|
||||||
let b = _ref({ count: 0 })
|
|
||||||
function inc() {
|
|
||||||
a.value++
|
|
||||||
a.value = a.value + 1
|
|
||||||
b.value.count++
|
|
||||||
b.value.count = b.value.count + 1
|
|
||||||
;({ a: a.value } = { a: 2 })
|
|
||||||
;[a.value] = [1]
|
|
||||||
}
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`nested destructure 1`] = `
|
|
||||||
"import { toRef as _toRef } from 'vue'
|
|
||||||
|
|
||||||
let __$temp_1 = (useFoo()),
|
|
||||||
b = _toRef(__$temp_1[0].a, 'b');
|
|
||||||
let __$temp_2 = (useBar()),
|
|
||||||
d = _toRef(__$temp_2.c, 0),
|
|
||||||
e = _toRef(__$temp_2.c, 1);
|
|
||||||
console.log(b.value, d.value, e.value)
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`nested scopes 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(0)
|
|
||||||
let b = _ref(0)
|
|
||||||
let c = 0
|
|
||||||
|
|
||||||
a.value++ // outer a
|
|
||||||
b.value++ // outer b
|
|
||||||
c++ // outer c
|
|
||||||
|
|
||||||
let bar = _ref(0)
|
|
||||||
bar.value++ // outer bar
|
|
||||||
|
|
||||||
function foo({ a }) {
|
|
||||||
a++ // inner a
|
|
||||||
b.value++ // inner b
|
|
||||||
let c = _ref(0)
|
|
||||||
c.value++ // inner c
|
|
||||||
let d = _ref(0)
|
|
||||||
|
|
||||||
function bar(c) {
|
|
||||||
c++ // nested c
|
|
||||||
d.value++ // nested d
|
|
||||||
}
|
|
||||||
bar() // inner bar
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
let a = _ref(0)
|
|
||||||
a.value++ // if block a
|
|
||||||
}
|
|
||||||
|
|
||||||
return ({ a, b, c, d })
|
|
||||||
}
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`object destructure 1`] = `
|
|
||||||
"import { ref as _ref, toRef as _toRef } from 'vue'
|
|
||||||
|
|
||||||
let n = _ref(1), __$temp_1 = (useFoo()),
|
|
||||||
a = _toRef(__$temp_1, 'a'),
|
|
||||||
c = _toRef(__$temp_1, 'b'),
|
|
||||||
d = _toRef(__$temp_1, 'd', 1),
|
|
||||||
f = _toRef(__$temp_1, 'e', 2),
|
|
||||||
h = _toRef(__$temp_1, g);
|
|
||||||
let __$temp_2 = (useSomething(() => 1)),
|
|
||||||
foo = _toRef(__$temp_2, 'foo');;
|
|
||||||
console.log(n.value, a.value, c.value, d.value, f.value, h.value, foo.value)
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`object destructure w/ mid-path default values 1`] = `
|
|
||||||
"import { toRef as _toRef } from 'vue'
|
|
||||||
|
|
||||||
const __$temp_1 = (useFoo()),
|
|
||||||
b = _toRef((__$temp_1.a || { b: 123 }), 'b');
|
|
||||||
console.log(b.value)
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should not overwrite current scope 1`] = `
|
|
||||||
"
|
|
||||||
const fn = () => {
|
|
||||||
const $ = () => 'foo'
|
|
||||||
const $ref = () => 'bar'
|
|
||||||
const $$ = () => 'baz'
|
|
||||||
console.log($())
|
|
||||||
console.log($ref())
|
|
||||||
console.log($$())
|
|
||||||
}
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should not overwrite importing 1`] = `
|
|
||||||
"
|
|
||||||
import { $, $$ } from './foo'
|
|
||||||
$('foo')
|
|
||||||
$$('bar')
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should not rewrite scope variable 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
|
|
||||||
let a = _ref(1)
|
|
||||||
let b = _ref(1)
|
|
||||||
let d = _ref(1)
|
|
||||||
const e = 1
|
|
||||||
function test() {
|
|
||||||
const a = 2
|
|
||||||
console.log(a)
|
|
||||||
console.log(b.value)
|
|
||||||
let c = { c: 3 }
|
|
||||||
console.log(c)
|
|
||||||
console.log(d.value)
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
let err = _ref(null)
|
|
||||||
try {
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should not rewrite type identifiers 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
const props = defineProps<{msg: string; ids?: string[]}>()
|
|
||||||
let ids = _ref([])"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`using ref binding in property shorthand 1`] = `
|
|
||||||
"import { ref as _ref } from 'vue'
|
|
||||||
|
|
||||||
let a = _ref(1)
|
|
||||||
const b = { a: a.value }
|
|
||||||
function test() {
|
|
||||||
const { a } = b
|
|
||||||
}
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
@ -1,564 +0,0 @@
|
||||||
import { parse } from '@babel/parser'
|
|
||||||
import { transform } from '../src'
|
|
||||||
|
|
||||||
function assertCode(code: string) {
|
|
||||||
// parse the generated code to make sure it is valid
|
|
||||||
try {
|
|
||||||
parse(code, {
|
|
||||||
sourceType: 'module',
|
|
||||||
plugins: ['typescript']
|
|
||||||
})
|
|
||||||
} catch (e: any) {
|
|
||||||
console.log(code)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
expect(code).toMatchSnapshot()
|
|
||||||
}
|
|
||||||
|
|
||||||
test('$ unwrapping', () => {
|
|
||||||
const { code, rootRefs } = transform(`
|
|
||||||
import { ref, shallowRef } from 'vue'
|
|
||||||
let foo = $(ref())
|
|
||||||
export let a = $(ref(1))
|
|
||||||
let b = $(shallowRef({
|
|
||||||
count: 0
|
|
||||||
}))
|
|
||||||
let c = () => {}
|
|
||||||
let d
|
|
||||||
label: var e = $(ref())
|
|
||||||
`)
|
|
||||||
expect(code).not.toMatch(`$(ref())`)
|
|
||||||
expect(code).not.toMatch(`$(ref(1))`)
|
|
||||||
expect(code).not.toMatch(`$(shallowRef({`)
|
|
||||||
expect(code).toMatch(`let foo = (ref())`)
|
|
||||||
expect(code).toMatch(`export let a = (ref(1))`)
|
|
||||||
expect(code).toMatch(`
|
|
||||||
let b = (shallowRef({
|
|
||||||
count: 0
|
|
||||||
}))
|
|
||||||
`)
|
|
||||||
// normal declarations left untouched
|
|
||||||
expect(code).toMatch(`let c = () => {}`)
|
|
||||||
expect(code).toMatch(`let d`)
|
|
||||||
expect(code).toMatch(`label: var e = (ref())`)
|
|
||||||
expect(rootRefs).toStrictEqual(['foo', 'a', 'b', 'e'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('$ref & $shallowRef declarations', () => {
|
|
||||||
const { code, rootRefs, importedHelpers } = transform(`
|
|
||||||
let foo = $ref()
|
|
||||||
export let a = $ref(1)
|
|
||||||
let b = $shallowRef({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
let c = () => {}
|
|
||||||
let d
|
|
||||||
label: var e = $ref()
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(
|
|
||||||
`import { ref as _ref, shallowRef as _shallowRef } from 'vue'`
|
|
||||||
)
|
|
||||||
expect(code).not.toMatch(`$ref()`)
|
|
||||||
expect(code).not.toMatch(`$ref(1)`)
|
|
||||||
expect(code).not.toMatch(`$shallowRef({`)
|
|
||||||
expect(code).toMatch(`let foo = _ref()`)
|
|
||||||
expect(code).toMatch(`let a = _ref(1)`)
|
|
||||||
expect(code).toMatch(`
|
|
||||||
let b = _shallowRef({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
`)
|
|
||||||
// normal declarations left untouched
|
|
||||||
expect(code).toMatch(`let c = () => {}`)
|
|
||||||
expect(code).toMatch(`let d`)
|
|
||||||
expect(code).toMatch(`label: var e = _ref()`)
|
|
||||||
expect(rootRefs).toStrictEqual(['foo', 'a', 'b', 'e'])
|
|
||||||
expect(importedHelpers).toStrictEqual(['ref', 'shallowRef'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('multi $ref declarations', () => {
|
|
||||||
const { code, rootRefs, importedHelpers } = transform(`
|
|
||||||
let a = $ref(1), b = $ref(2), c = $ref({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`
|
|
||||||
let a = _ref(1), b = _ref(2), c = _ref({
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
`)
|
|
||||||
expect(rootRefs).toStrictEqual(['a', 'b', 'c'])
|
|
||||||
expect(importedHelpers).toStrictEqual(['ref'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('$computed declaration', () => {
|
|
||||||
const { code, rootRefs, importedHelpers } = transform(`
|
|
||||||
let a = $computed(() => 1)
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`
|
|
||||||
let a = _computed(() => 1)
|
|
||||||
`)
|
|
||||||
expect(rootRefs).toStrictEqual(['a'])
|
|
||||||
expect(importedHelpers).toStrictEqual(['computed'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('mixing $ref & $computed declarations', () => {
|
|
||||||
const { code, rootRefs, importedHelpers } = transform(`
|
|
||||||
let a = $ref(1), b = $computed(() => a + 1)
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`
|
|
||||||
let a = _ref(1), b = _computed(() => a.value + 1)
|
|
||||||
`)
|
|
||||||
expect(rootRefs).toStrictEqual(['a', 'b'])
|
|
||||||
expect(importedHelpers).toStrictEqual(['ref', 'computed'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('accessing ref binding', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
let a = $ref(1)
|
|
||||||
console.log(a)
|
|
||||||
function get() {
|
|
||||||
return a + 1
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`console.log(a.value)`)
|
|
||||||
expect(code).toMatch(`return a.value + 1`)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('cases that should not append .value', () => {
|
|
||||||
test('member expression', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
let a = $ref(1)
|
|
||||||
console.log(b.a)
|
|
||||||
`)
|
|
||||||
expect(code).not.toMatch(`a.value`)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('function argument', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
let a = $ref(1)
|
|
||||||
function get(a) {
|
|
||||||
return a + 1
|
|
||||||
}
|
|
||||||
function get2({ a }) {
|
|
||||||
return a + 1
|
|
||||||
}
|
|
||||||
function get3([a]) {
|
|
||||||
return a + 1
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
expect(code).not.toMatch(`a.value`)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('for in/of loops', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
let a = $ref(1)
|
|
||||||
for (const [a, b] of arr) {
|
|
||||||
console.log(a)
|
|
||||||
}
|
|
||||||
for (let a in arr) {
|
|
||||||
console.log(a)
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
expect(code).not.toMatch(`a.value`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('mutating ref binding', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
let a = $ref(1)
|
|
||||||
let b = $ref({ count: 0 })
|
|
||||||
function inc() {
|
|
||||||
a++
|
|
||||||
a = a + 1
|
|
||||||
b.count++
|
|
||||||
b.count = b.count + 1
|
|
||||||
;({ a } = { a: 2 })
|
|
||||||
;[a] = [1]
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`a.value++`)
|
|
||||||
expect(code).toMatch(`a.value = a.value + 1`)
|
|
||||||
expect(code).toMatch(`b.value.count++`)
|
|
||||||
expect(code).toMatch(`b.value.count = b.value.count + 1`)
|
|
||||||
expect(code).toMatch(`;({ a: a.value } = { a: 2 })`)
|
|
||||||
expect(code).toMatch(`;[a.value] = [1]`)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('using ref binding in property shorthand', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
let a = $ref(1)
|
|
||||||
const b = { a }
|
|
||||||
function test() {
|
|
||||||
const { a } = b
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`const b = { a: a.value }`)
|
|
||||||
// should not convert destructure
|
|
||||||
expect(code).toMatch(`const { a } = b`)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should not rewrite scope variable', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
|
|
||||||
let a = $ref(1)
|
|
||||||
let b = $ref(1)
|
|
||||||
let d = $ref(1)
|
|
||||||
const e = 1
|
|
||||||
function test() {
|
|
||||||
const a = 2
|
|
||||||
console.log(a)
|
|
||||||
console.log(b)
|
|
||||||
let c = { c: 3 }
|
|
||||||
console.log(c)
|
|
||||||
console.log(d)
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
let err = $ref(null)
|
|
||||||
try {
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
expect(code).toMatch('console.log(a)')
|
|
||||||
expect(code).toMatch('console.log(b.value)')
|
|
||||||
expect(code).toMatch('console.log(c)')
|
|
||||||
expect(code).toMatch('console.log(d.value)')
|
|
||||||
expect(code).toMatch('console.log(e)')
|
|
||||||
expect(code).toMatch('console.log(err)')
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('object destructure', () => {
|
|
||||||
const { code, rootRefs } = transform(`
|
|
||||||
let n = $ref(1), { a, b: c, d = 1, e: f = 2, [g]: h } = $(useFoo())
|
|
||||||
let { foo } = $(useSomething(() => 1));
|
|
||||||
console.log(n, a, c, d, f, h, foo)
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`a = _toRef(__$temp_1, 'a')`)
|
|
||||||
expect(code).toMatch(`c = _toRef(__$temp_1, 'b')`)
|
|
||||||
expect(code).toMatch(`d = _toRef(__$temp_1, 'd', 1)`)
|
|
||||||
expect(code).toMatch(`f = _toRef(__$temp_1, 'e', 2)`)
|
|
||||||
expect(code).toMatch(`h = _toRef(__$temp_1, g)`)
|
|
||||||
expect(code).toMatch(`foo = _toRef(__$temp_2, 'foo')`)
|
|
||||||
expect(code).toMatch(
|
|
||||||
`console.log(n.value, a.value, c.value, d.value, f.value, h.value, foo.value)`
|
|
||||||
)
|
|
||||||
expect(rootRefs).toStrictEqual(['n', 'a', 'c', 'd', 'f', 'h', 'foo'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('object destructure w/ mid-path default values', () => {
|
|
||||||
const { code, rootRefs } = transform(`
|
|
||||||
const { a: { b } = { b: 123 }} = $(useFoo())
|
|
||||||
console.log(b)
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`b = _toRef((__$temp_1.a || { b: 123 }), 'b')`)
|
|
||||||
expect(code).toMatch(`console.log(b.value)`)
|
|
||||||
expect(rootRefs).toStrictEqual(['b'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('array destructure', () => {
|
|
||||||
const { code, rootRefs } = transform(`
|
|
||||||
let n = $ref(1), [a, b = 1] = $(useFoo())
|
|
||||||
console.log(n, a, b)
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`a = _toRef(__$temp_1, 0)`)
|
|
||||||
expect(code).toMatch(`b = _toRef(__$temp_1, 1, 1)`)
|
|
||||||
expect(code).toMatch(`console.log(n.value, a.value, b.value)`)
|
|
||||||
expect(rootRefs).toStrictEqual(['n', 'a', 'b'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('nested destructure', () => {
|
|
||||||
const { code, rootRefs } = transform(`
|
|
||||||
let [{ a: { b }}] = $(useFoo())
|
|
||||||
let { c: [d, e] } = $(useBar())
|
|
||||||
console.log(b, d, e)
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`b = _toRef(__$temp_1[0].a, 'b')`)
|
|
||||||
expect(code).toMatch(`d = _toRef(__$temp_2.c, 0)`)
|
|
||||||
expect(code).toMatch(`e = _toRef(__$temp_2.c, 1)`)
|
|
||||||
expect(rootRefs).toStrictEqual(['b', 'd', 'e'])
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('$$', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
let a = $ref(1)
|
|
||||||
const b = $$(a)
|
|
||||||
const c = $$({ a })
|
|
||||||
callExternal($$(a))
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`const b = (a)`)
|
|
||||||
expect(code).toMatch(`const c = ({ a })`)
|
|
||||||
expect(code).toMatch(`callExternal((a))`)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('$$ with some edge cases', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
$$( /* 2 */ count /* 2 */ )
|
|
||||||
$$( count /* 2 */, /**/ a )
|
|
||||||
$$( (count /* 2 */, /**/ a) /**/ )
|
|
||||||
{
|
|
||||||
a:$$(count,a)
|
|
||||||
}
|
|
||||||
$$((count) + 1)
|
|
||||||
$$([count])
|
|
||||||
$$ (count )
|
|
||||||
console.log($$($$(a)))
|
|
||||||
$$(a,b)
|
|
||||||
$$($$((a++,b)))
|
|
||||||
count = $$( a++ ,b)
|
|
||||||
count = ()=>$$(a++,b)
|
|
||||||
let r1 = $ref(a, $$(a++,b))
|
|
||||||
let r2 = { a:$$(a++,b),b:$$ (a) }
|
|
||||||
switch($$(c)){
|
|
||||||
case d:
|
|
||||||
$$(a)
|
|
||||||
$$($$(h,f))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
($$(count++,$$(count),$$(count,a)))
|
|
||||||
`)
|
|
||||||
expect(code).toMatch(`/* 2 */ count /* 2 */`)
|
|
||||||
expect(code).toMatch(`;( count /* 2 */, /**/ a )`)
|
|
||||||
expect(code).toMatch(`;( (count /* 2 */, /**/ a) /**/ )`)
|
|
||||||
expect(code).toMatch(`a:(count,a)`)
|
|
||||||
expect(code).toMatch(`;((count) + 1)`)
|
|
||||||
expect(code).toMatch(`;([count])`)
|
|
||||||
expect(code).toMatch(`;(a,b)`)
|
|
||||||
expect(code).toMatch(`log(((a)))`)
|
|
||||||
expect(code).toMatch(`count = ( a++ ,b)`)
|
|
||||||
expect(code).toMatch(`()=>(a++,b)`)
|
|
||||||
expect(code).toMatch(`_ref(a, (a++,b))`)
|
|
||||||
expect(code).toMatch(`{ a:(a++,b),b: (a) }`)
|
|
||||||
expect(code).toMatch(`switch((c))`)
|
|
||||||
expect(code).toMatch(`;((h,f))`)
|
|
||||||
expect(code).toMatch(`((count++,(count),(count,a)))`)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('nested scopes', () => {
|
|
||||||
const { code, rootRefs } = transform(`
|
|
||||||
let a = $ref(0)
|
|
||||||
let b = $ref(0)
|
|
||||||
let c = 0
|
|
||||||
|
|
||||||
a++ // outer a
|
|
||||||
b++ // outer b
|
|
||||||
c++ // outer c
|
|
||||||
|
|
||||||
let bar = $ref(0)
|
|
||||||
bar++ // outer bar
|
|
||||||
|
|
||||||
function foo({ a }) {
|
|
||||||
a++ // inner a
|
|
||||||
b++ // inner b
|
|
||||||
let c = $ref(0)
|
|
||||||
c++ // inner c
|
|
||||||
let d = $ref(0)
|
|
||||||
|
|
||||||
function bar(c) {
|
|
||||||
c++ // nested c
|
|
||||||
d++ // nested d
|
|
||||||
}
|
|
||||||
bar() // inner bar
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
let a = $ref(0)
|
|
||||||
a++ // if block a
|
|
||||||
}
|
|
||||||
|
|
||||||
return $$({ a, b, c, d })
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
expect(rootRefs).toStrictEqual(['a', 'b', 'bar'])
|
|
||||||
|
|
||||||
expect(code).toMatch('a.value++ // outer a')
|
|
||||||
expect(code).toMatch('b.value++ // outer b')
|
|
||||||
expect(code).toMatch('c++ // outer c')
|
|
||||||
|
|
||||||
expect(code).toMatch('a++ // inner a') // shadowed by function arg
|
|
||||||
expect(code).toMatch('b.value++ // inner b')
|
|
||||||
expect(code).toMatch('c.value++ // inner c') // shadowed by local ref binding
|
|
||||||
|
|
||||||
expect(code).toMatch('c++ // nested c') // shadowed by inline fn arg
|
|
||||||
expect(code).toMatch(`d.value++ // nested d`)
|
|
||||||
|
|
||||||
expect(code).toMatch(`a.value++ // if block a`) // if block
|
|
||||||
|
|
||||||
expect(code).toMatch(`bar.value++ // outer bar`)
|
|
||||||
// inner bar shadowed by function declaration
|
|
||||||
expect(code).toMatch(`bar() // inner bar`)
|
|
||||||
|
|
||||||
expect(code).toMatch(`return ({ a, b, c, d })`)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
//#4062
|
|
||||||
test('should not rewrite type identifiers', () => {
|
|
||||||
const { code } = transform(
|
|
||||||
`const props = defineProps<{msg: string; ids?: string[]}>()
|
|
||||||
let ids = $ref([])`,
|
|
||||||
{
|
|
||||||
parserPlugins: ['typescript']
|
|
||||||
}
|
|
||||||
)
|
|
||||||
expect(code).not.toMatch('.value')
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
// #4254
|
|
||||||
test('handle TS casting syntax', () => {
|
|
||||||
const { code } = transform(
|
|
||||||
`
|
|
||||||
let a = $ref(1)
|
|
||||||
console.log(a!)
|
|
||||||
console.log(a! + 1)
|
|
||||||
console.log(a as number)
|
|
||||||
console.log((a as number) + 1)
|
|
||||||
console.log(<number>a)
|
|
||||||
console.log(<number>a + 1)
|
|
||||||
console.log(a! + (a as number))
|
|
||||||
console.log(a! + <number>a)
|
|
||||||
console.log((a as number) + <number>a)
|
|
||||||
`,
|
|
||||||
{
|
|
||||||
parserPlugins: ['typescript']
|
|
||||||
}
|
|
||||||
)
|
|
||||||
expect(code).toMatch('console.log(a.value!)')
|
|
||||||
expect(code).toMatch('console.log(a.value as number)')
|
|
||||||
expect(code).toMatch('console.log(<number>a.value)')
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('macro import alias and removal', () => {
|
|
||||||
const { code } = transform(
|
|
||||||
`
|
|
||||||
import { $ as fromRefs, $ref } from 'vue/macros'
|
|
||||||
|
|
||||||
let a = $ref(1)
|
|
||||||
const { x, y } = fromRefs(useMouse())
|
|
||||||
`
|
|
||||||
)
|
|
||||||
// should remove imports
|
|
||||||
expect(code).not.toMatch(`from 'vue/macros'`)
|
|
||||||
expect(code).toMatch(`let a = _ref(1)`)
|
|
||||||
expect(code).toMatch(`const __$temp_1 = (useMouse())`)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
// #6838
|
|
||||||
test('should not overwrite importing', () => {
|
|
||||||
const { code } = transform(
|
|
||||||
`
|
|
||||||
import { $, $$ } from './foo'
|
|
||||||
$('foo')
|
|
||||||
$$('bar')
|
|
||||||
`
|
|
||||||
)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
// #6838
|
|
||||||
test('should not overwrite current scope', () => {
|
|
||||||
const { code } = transform(
|
|
||||||
`
|
|
||||||
const fn = () => {
|
|
||||||
const $ = () => 'foo'
|
|
||||||
const $ref = () => 'bar'
|
|
||||||
const $$ = () => 'baz'
|
|
||||||
console.log($())
|
|
||||||
console.log($ref())
|
|
||||||
console.log($$())
|
|
||||||
}
|
|
||||||
`
|
|
||||||
)
|
|
||||||
assertCode(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('errors', () => {
|
|
||||||
test('$ref w/ destructure', () => {
|
|
||||||
expect(() => transform(`let { a } = $ref(1)`)).toThrow(
|
|
||||||
`cannot be used with destructure`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('$computed w/ destructure', () => {
|
|
||||||
expect(() => transform(`let { a } = $computed(() => 1)`)).toThrow(
|
|
||||||
`cannot be used with destructure`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('warn usage in non-init positions', () => {
|
|
||||||
expect(() =>
|
|
||||||
transform(
|
|
||||||
`let bar = $ref(1)
|
|
||||||
bar = $ref(2)`
|
|
||||||
)
|
|
||||||
).toThrow(`$ref can only be used as the initializer`)
|
|
||||||
|
|
||||||
expect(() => transform(`let bar = { foo: $computed(1) }`)).toThrow(
|
|
||||||
`$computed can only be used as the initializer`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('not transform the prototype attributes', () => {
|
|
||||||
const { code } = transform(`
|
|
||||||
const hasOwnProperty = Object.prototype.hasOwnProperty
|
|
||||||
const hasOwn = (val, key) => hasOwnProperty.call(val, key)
|
|
||||||
`)
|
|
||||||
expect(code).not.toMatch('.value')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('rest element in $() destructure', () => {
|
|
||||||
expect(() => transform(`let { a, ...b } = $(foo())`)).toThrow(
|
|
||||||
`does not support rest element`
|
|
||||||
)
|
|
||||||
expect(() => transform(`let [a, ...b] = $(foo())`)).toThrow(
|
|
||||||
`does not support rest element`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('assignment to constant variable', () => {
|
|
||||||
expect(() =>
|
|
||||||
transform(`
|
|
||||||
const foo = $ref(0)
|
|
||||||
foo = 1
|
|
||||||
`)
|
|
||||||
).toThrow(`Assignment to constant variable.`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
transform(`
|
|
||||||
const [a, b] = $([1, 2])
|
|
||||||
a = 1
|
|
||||||
`)
|
|
||||||
).toThrow(`Assignment to constant variable.`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
transform(`
|
|
||||||
const foo = $ref(0)
|
|
||||||
foo++
|
|
||||||
`)
|
|
||||||
).toThrow(`Assignment to constant variable.`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
transform(`
|
|
||||||
const foo = $ref(0)
|
|
||||||
bar = foo
|
|
||||||
`)
|
|
||||||
).not.toThrow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@vue/reactivity-transform",
|
|
||||||
"version": "3.3.9",
|
|
||||||
"description": "@vue/reactivity-transform",
|
|
||||||
"main": "dist/reactivity-transform.cjs.js",
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"buildOptions": {
|
|
||||||
"formats": [
|
|
||||||
"cjs"
|
|
||||||
],
|
|
||||||
"prod": false
|
|
||||||
},
|
|
||||||
"types": "dist/reactivity-transform.d.ts",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/vuejs/core-vapor.git",
|
|
||||||
"directory": "packages/reactivity-transform"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"vue"
|
|
||||||
],
|
|
||||||
"author": "Evan You",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/vuejs/core-vapor/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/vuejs/core-vapor/tree/dev/packages/reactivity-transform#readme",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/parser": "^7.23.3",
|
|
||||||
"@vue/compiler-core": "workspace:*",
|
|
||||||
"@vue/shared": "workspace:*",
|
|
||||||
"estree-walker": "^2.0.2",
|
|
||||||
"magic-string": "^0.30.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/core": "^7.23.3",
|
|
||||||
"@babel/types": "^7.23.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export function plugin() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './reactivityTransform'
|
|
||||||
|
|
@ -1,794 +0,0 @@
|
||||||
import {
|
|
||||||
Node,
|
|
||||||
Identifier,
|
|
||||||
BlockStatement,
|
|
||||||
CallExpression,
|
|
||||||
ObjectPattern,
|
|
||||||
ArrayPattern,
|
|
||||||
Program,
|
|
||||||
VariableDeclarator,
|
|
||||||
Expression,
|
|
||||||
VariableDeclaration,
|
|
||||||
ImportDeclaration,
|
|
||||||
ImportSpecifier,
|
|
||||||
ImportDefaultSpecifier,
|
|
||||||
ImportNamespaceSpecifier
|
|
||||||
} from '@babel/types'
|
|
||||||
import MagicString, { SourceMap } from 'magic-string'
|
|
||||||
import { walk } from 'estree-walker'
|
|
||||||
import {
|
|
||||||
extractIdentifiers,
|
|
||||||
isFunctionType,
|
|
||||||
isInDestructureAssignment,
|
|
||||||
isReferencedIdentifier,
|
|
||||||
isStaticProperty,
|
|
||||||
walkFunctionParams
|
|
||||||
} from '@vue/compiler-core'
|
|
||||||
import { parse, ParserPlugin } from '@babel/parser'
|
|
||||||
import { hasOwn, isArray, isString, genPropsAccessExp } from '@vue/shared'
|
|
||||||
|
|
||||||
const CONVERT_SYMBOL = '$'
|
|
||||||
const ESCAPE_SYMBOL = '$$'
|
|
||||||
const IMPORT_SOURCE = 'vue/macros'
|
|
||||||
const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef']
|
|
||||||
const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated will be removed in 3.4
|
|
||||||
*/
|
|
||||||
export function shouldTransform(src: string): boolean {
|
|
||||||
return transformCheckRE.test(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Binding {
|
|
||||||
isConst?: boolean
|
|
||||||
isProp?: boolean
|
|
||||||
}
|
|
||||||
type Scope = Record<string, Binding | false>
|
|
||||||
|
|
||||||
export interface RefTransformOptions {
|
|
||||||
filename?: string
|
|
||||||
sourceMap?: boolean
|
|
||||||
parserPlugins?: ParserPlugin[]
|
|
||||||
importHelpersFrom?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RefTransformResults {
|
|
||||||
code: string
|
|
||||||
map: SourceMap | null
|
|
||||||
rootRefs: string[]
|
|
||||||
importedHelpers: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImportBinding {
|
|
||||||
local: string
|
|
||||||
imported: string
|
|
||||||
source: string
|
|
||||||
specifier: ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated will be removed in 3.4
|
|
||||||
*/
|
|
||||||
export function transform(
|
|
||||||
src: string,
|
|
||||||
{
|
|
||||||
filename,
|
|
||||||
sourceMap,
|
|
||||||
parserPlugins,
|
|
||||||
importHelpersFrom = 'vue'
|
|
||||||
}: RefTransformOptions = {}
|
|
||||||
): RefTransformResults {
|
|
||||||
const plugins: ParserPlugin[] = parserPlugins || []
|
|
||||||
if (filename) {
|
|
||||||
if (/\.tsx?$/.test(filename)) {
|
|
||||||
plugins.push('typescript')
|
|
||||||
}
|
|
||||||
if (filename.endsWith('x')) {
|
|
||||||
plugins.push('jsx')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ast = parse(src, {
|
|
||||||
sourceType: 'module',
|
|
||||||
plugins
|
|
||||||
})
|
|
||||||
const s = new MagicString(src)
|
|
||||||
const res = transformAST(ast.program, s, 0)
|
|
||||||
|
|
||||||
// inject helper imports
|
|
||||||
if (res.importedHelpers.length) {
|
|
||||||
s.prepend(
|
|
||||||
`import { ${res.importedHelpers
|
|
||||||
.map(h => `${h} as _${h}`)
|
|
||||||
.join(', ')} } from '${importHelpersFrom}'\n`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...res,
|
|
||||||
code: s.toString(),
|
|
||||||
map: sourceMap
|
|
||||||
? s.generateMap({
|
|
||||||
source: filename,
|
|
||||||
hires: true,
|
|
||||||
includeContent: true
|
|
||||||
})
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated will be removed in 3.4
|
|
||||||
*/
|
|
||||||
export function transformAST(
|
|
||||||
ast: Program,
|
|
||||||
s: MagicString,
|
|
||||||
offset = 0,
|
|
||||||
knownRefs?: string[],
|
|
||||||
knownProps?: Record<
|
|
||||||
string, // public prop key
|
|
||||||
{
|
|
||||||
local: string // local identifier, may be different
|
|
||||||
default?: any
|
|
||||||
isConst?: boolean
|
|
||||||
}
|
|
||||||
>
|
|
||||||
): {
|
|
||||||
rootRefs: string[]
|
|
||||||
importedHelpers: string[]
|
|
||||||
} {
|
|
||||||
warnExperimental()
|
|
||||||
|
|
||||||
const userImports: Record<string, ImportBinding> = Object.create(null)
|
|
||||||
for (const node of ast.body) {
|
|
||||||
if (node.type !== 'ImportDeclaration') continue
|
|
||||||
walkImportDeclaration(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// macro import handling
|
|
||||||
let convertSymbol: string | undefined
|
|
||||||
let escapeSymbol: string | undefined
|
|
||||||
for (const { local, imported, source, specifier } of Object.values(
|
|
||||||
userImports
|
|
||||||
)) {
|
|
||||||
if (source === IMPORT_SOURCE) {
|
|
||||||
if (imported === ESCAPE_SYMBOL) {
|
|
||||||
escapeSymbol = local
|
|
||||||
} else if (imported === CONVERT_SYMBOL) {
|
|
||||||
convertSymbol = local
|
|
||||||
} else if (imported !== local) {
|
|
||||||
error(
|
|
||||||
`macro imports for ref-creating methods do not support aliasing.`,
|
|
||||||
specifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// default symbol
|
|
||||||
if (!convertSymbol && !userImports[CONVERT_SYMBOL]) {
|
|
||||||
convertSymbol = CONVERT_SYMBOL
|
|
||||||
}
|
|
||||||
if (!escapeSymbol && !userImports[ESCAPE_SYMBOL]) {
|
|
||||||
escapeSymbol = ESCAPE_SYMBOL
|
|
||||||
}
|
|
||||||
|
|
||||||
const importedHelpers = new Set<string>()
|
|
||||||
const rootScope: Scope = {}
|
|
||||||
const scopeStack: Scope[] = [rootScope]
|
|
||||||
let currentScope: Scope = rootScope
|
|
||||||
let escapeScope: CallExpression | undefined // inside $$()
|
|
||||||
const excludedIds = new WeakSet<Identifier>()
|
|
||||||
const parentStack: Node[] = []
|
|
||||||
const propsLocalToPublicMap: Record<string, string> = Object.create(null)
|
|
||||||
|
|
||||||
if (knownRefs) {
|
|
||||||
for (const key of knownRefs) {
|
|
||||||
rootScope[key] = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (knownProps) {
|
|
||||||
for (const key in knownProps) {
|
|
||||||
const { local, isConst } = knownProps[key]
|
|
||||||
rootScope[local] = {
|
|
||||||
isProp: true,
|
|
||||||
isConst: !!isConst
|
|
||||||
}
|
|
||||||
propsLocalToPublicMap[local] = key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkImportDeclaration(node: ImportDeclaration) {
|
|
||||||
const source = node.source.value
|
|
||||||
if (source === IMPORT_SOURCE) {
|
|
||||||
s.remove(node.start! + offset, node.end! + offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const specifier of node.specifiers) {
|
|
||||||
const local = specifier.local.name
|
|
||||||
const imported =
|
|
||||||
(specifier.type === 'ImportSpecifier' &&
|
|
||||||
specifier.imported.type === 'Identifier' &&
|
|
||||||
specifier.imported.name) ||
|
|
||||||
'default'
|
|
||||||
userImports[local] = {
|
|
||||||
source,
|
|
||||||
local,
|
|
||||||
imported,
|
|
||||||
specifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isRefCreationCall(callee: string): string | false {
|
|
||||||
if (!convertSymbol || currentScope[convertSymbol] !== undefined) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (callee === convertSymbol) {
|
|
||||||
return convertSymbol
|
|
||||||
}
|
|
||||||
if (callee[0] === '$' && shorthands.includes(callee.slice(1))) {
|
|
||||||
return callee
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function error(msg: string, node: Node): never {
|
|
||||||
const e = new Error(msg)
|
|
||||||
;(e as any).node = node
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
function helper(msg: string) {
|
|
||||||
importedHelpers.add(msg)
|
|
||||||
return `_${msg}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerBinding(id: Identifier, binding?: Binding) {
|
|
||||||
excludedIds.add(id)
|
|
||||||
if (currentScope) {
|
|
||||||
currentScope[id.name] = binding ? binding : false
|
|
||||||
} else {
|
|
||||||
error(
|
|
||||||
'registerBinding called without active scope, something is wrong.',
|
|
||||||
id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerRefBinding = (id: Identifier, isConst = false) =>
|
|
||||||
registerBinding(id, { isConst })
|
|
||||||
|
|
||||||
let tempVarCount = 0
|
|
||||||
function genTempVar() {
|
|
||||||
return `__$temp_${++tempVarCount}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function snip(node: Node) {
|
|
||||||
return s.original.slice(node.start! + offset, node.end! + offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkScope(node: Program | BlockStatement, isRoot = false) {
|
|
||||||
for (const stmt of node.body) {
|
|
||||||
if (stmt.type === 'VariableDeclaration') {
|
|
||||||
walkVariableDeclaration(stmt, isRoot)
|
|
||||||
} else if (
|
|
||||||
stmt.type === 'FunctionDeclaration' ||
|
|
||||||
stmt.type === 'ClassDeclaration'
|
|
||||||
) {
|
|
||||||
if (stmt.declare || !stmt.id) continue
|
|
||||||
registerBinding(stmt.id)
|
|
||||||
} else if (
|
|
||||||
(stmt.type === 'ForOfStatement' || stmt.type === 'ForInStatement') &&
|
|
||||||
stmt.left.type === 'VariableDeclaration'
|
|
||||||
) {
|
|
||||||
walkVariableDeclaration(stmt.left)
|
|
||||||
} else if (
|
|
||||||
stmt.type === 'ExportNamedDeclaration' &&
|
|
||||||
stmt.declaration &&
|
|
||||||
stmt.declaration.type === 'VariableDeclaration'
|
|
||||||
) {
|
|
||||||
walkVariableDeclaration(stmt.declaration, isRoot)
|
|
||||||
} else if (
|
|
||||||
stmt.type === 'LabeledStatement' &&
|
|
||||||
stmt.body.type === 'VariableDeclaration'
|
|
||||||
) {
|
|
||||||
walkVariableDeclaration(stmt.body, isRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkVariableDeclaration(stmt: VariableDeclaration, isRoot = false) {
|
|
||||||
if (stmt.declare) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for (const decl of stmt.declarations) {
|
|
||||||
let refCall
|
|
||||||
const isCall =
|
|
||||||
decl.init &&
|
|
||||||
decl.init.type === 'CallExpression' &&
|
|
||||||
decl.init.callee.type === 'Identifier'
|
|
||||||
if (
|
|
||||||
isCall &&
|
|
||||||
(refCall = isRefCreationCall((decl as any).init.callee.name))
|
|
||||||
) {
|
|
||||||
processRefDeclaration(
|
|
||||||
refCall,
|
|
||||||
decl.id,
|
|
||||||
decl.init as CallExpression,
|
|
||||||
stmt.kind === 'const'
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const isProps =
|
|
||||||
isRoot && isCall && (decl as any).init.callee.name === 'defineProps'
|
|
||||||
for (const id of extractIdentifiers(decl.id)) {
|
|
||||||
if (isProps) {
|
|
||||||
// for defineProps destructure, only exclude them since they
|
|
||||||
// are already passed in as knownProps
|
|
||||||
excludedIds.add(id)
|
|
||||||
} else {
|
|
||||||
registerBinding(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processRefDeclaration(
|
|
||||||
method: string,
|
|
||||||
id: VariableDeclarator['id'],
|
|
||||||
call: CallExpression,
|
|
||||||
isConst: boolean
|
|
||||||
) {
|
|
||||||
excludedIds.add(call.callee as Identifier)
|
|
||||||
if (method === convertSymbol) {
|
|
||||||
// $
|
|
||||||
// remove macro
|
|
||||||
s.remove(call.callee.start! + offset, call.callee.end! + offset)
|
|
||||||
if (id.type === 'Identifier') {
|
|
||||||
// single variable
|
|
||||||
registerRefBinding(id, isConst)
|
|
||||||
} else if (id.type === 'ObjectPattern') {
|
|
||||||
processRefObjectPattern(id, call, isConst)
|
|
||||||
} else if (id.type === 'ArrayPattern') {
|
|
||||||
processRefArrayPattern(id, call, isConst)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// shorthands
|
|
||||||
if (id.type === 'Identifier') {
|
|
||||||
registerRefBinding(id, isConst)
|
|
||||||
// replace call
|
|
||||||
s.overwrite(
|
|
||||||
call.start! + offset,
|
|
||||||
call.start! + method.length + offset,
|
|
||||||
helper(method.slice(1))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
error(`${method}() cannot be used with destructure patterns.`, call)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processRefObjectPattern(
|
|
||||||
pattern: ObjectPattern,
|
|
||||||
call: CallExpression,
|
|
||||||
isConst: boolean,
|
|
||||||
tempVar?: string,
|
|
||||||
path: PathSegment[] = []
|
|
||||||
) {
|
|
||||||
if (!tempVar) {
|
|
||||||
tempVar = genTempVar()
|
|
||||||
// const { x } = $(useFoo()) --> const __$temp_1 = useFoo()
|
|
||||||
s.overwrite(pattern.start! + offset, pattern.end! + offset, tempVar)
|
|
||||||
}
|
|
||||||
|
|
||||||
let nameId: Identifier | undefined
|
|
||||||
for (const p of pattern.properties) {
|
|
||||||
let key: Expression | string | undefined
|
|
||||||
let defaultValue: Expression | undefined
|
|
||||||
if (p.type === 'ObjectProperty') {
|
|
||||||
if (p.key.start! === p.value.start!) {
|
|
||||||
// shorthand { foo }
|
|
||||||
nameId = p.key as Identifier
|
|
||||||
if (p.value.type === 'Identifier') {
|
|
||||||
// avoid shorthand value identifier from being processed
|
|
||||||
excludedIds.add(p.value)
|
|
||||||
} else if (
|
|
||||||
p.value.type === 'AssignmentPattern' &&
|
|
||||||
p.value.left.type === 'Identifier'
|
|
||||||
) {
|
|
||||||
// { foo = 1 }
|
|
||||||
excludedIds.add(p.value.left)
|
|
||||||
defaultValue = p.value.right
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
key = p.computed ? (p.key as Expression) : (p.key as Identifier).name
|
|
||||||
if (p.value.type === 'Identifier') {
|
|
||||||
// { foo: bar }
|
|
||||||
nameId = p.value
|
|
||||||
} else if (p.value.type === 'ObjectPattern') {
|
|
||||||
processRefObjectPattern(p.value, call, isConst, tempVar, [
|
|
||||||
...path,
|
|
||||||
key
|
|
||||||
])
|
|
||||||
} else if (p.value.type === 'ArrayPattern') {
|
|
||||||
processRefArrayPattern(p.value, call, isConst, tempVar, [
|
|
||||||
...path,
|
|
||||||
key
|
|
||||||
])
|
|
||||||
} else if (p.value.type === 'AssignmentPattern') {
|
|
||||||
if (p.value.left.type === 'Identifier') {
|
|
||||||
// { foo: bar = 1 }
|
|
||||||
nameId = p.value.left
|
|
||||||
defaultValue = p.value.right
|
|
||||||
} else if (p.value.left.type === 'ObjectPattern') {
|
|
||||||
processRefObjectPattern(p.value.left, call, isConst, tempVar, [
|
|
||||||
...path,
|
|
||||||
[key, p.value.right]
|
|
||||||
])
|
|
||||||
} else if (p.value.left.type === 'ArrayPattern') {
|
|
||||||
processRefArrayPattern(p.value.left, call, isConst, tempVar, [
|
|
||||||
...path,
|
|
||||||
[key, p.value.right]
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
// MemberExpression case is not possible here, ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// rest element { ...foo }
|
|
||||||
error(`reactivity destructure does not support rest elements.`, p)
|
|
||||||
}
|
|
||||||
if (nameId) {
|
|
||||||
registerRefBinding(nameId, isConst)
|
|
||||||
// inject toRef() after original replaced pattern
|
|
||||||
const source = pathToString(tempVar, path)
|
|
||||||
const keyStr = isString(key)
|
|
||||||
? `'${key}'`
|
|
||||||
: key
|
|
||||||
? snip(key)
|
|
||||||
: `'${nameId.name}'`
|
|
||||||
const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``
|
|
||||||
s.appendLeft(
|
|
||||||
call.end! + offset,
|
|
||||||
`,\n ${nameId.name} = ${helper(
|
|
||||||
'toRef'
|
|
||||||
)}(${source}, ${keyStr}${defaultStr})`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nameId) {
|
|
||||||
s.appendLeft(call.end! + offset, ';')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processRefArrayPattern(
|
|
||||||
pattern: ArrayPattern,
|
|
||||||
call: CallExpression,
|
|
||||||
isConst: boolean,
|
|
||||||
tempVar?: string,
|
|
||||||
path: PathSegment[] = []
|
|
||||||
) {
|
|
||||||
if (!tempVar) {
|
|
||||||
// const [x] = $(useFoo()) --> const __$temp_1 = useFoo()
|
|
||||||
tempVar = genTempVar()
|
|
||||||
s.overwrite(pattern.start! + offset, pattern.end! + offset, tempVar)
|
|
||||||
}
|
|
||||||
|
|
||||||
let nameId: Identifier | undefined
|
|
||||||
for (let i = 0; i < pattern.elements.length; i++) {
|
|
||||||
const e = pattern.elements[i]
|
|
||||||
if (!e) continue
|
|
||||||
let defaultValue: Expression | undefined
|
|
||||||
if (e.type === 'Identifier') {
|
|
||||||
// [a] --> [__a]
|
|
||||||
nameId = e
|
|
||||||
} else if (e.type === 'AssignmentPattern') {
|
|
||||||
// [a = 1]
|
|
||||||
nameId = e.left as Identifier
|
|
||||||
defaultValue = e.right
|
|
||||||
} else if (e.type === 'RestElement') {
|
|
||||||
// [...a]
|
|
||||||
error(`reactivity destructure does not support rest elements.`, e)
|
|
||||||
} else if (e.type === 'ObjectPattern') {
|
|
||||||
processRefObjectPattern(e, call, isConst, tempVar, [...path, i])
|
|
||||||
} else if (e.type === 'ArrayPattern') {
|
|
||||||
processRefArrayPattern(e, call, isConst, tempVar, [...path, i])
|
|
||||||
}
|
|
||||||
if (nameId) {
|
|
||||||
registerRefBinding(nameId, isConst)
|
|
||||||
// inject toRef() after original replaced pattern
|
|
||||||
const source = pathToString(tempVar, path)
|
|
||||||
const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``
|
|
||||||
s.appendLeft(
|
|
||||||
call.end! + offset,
|
|
||||||
`,\n ${nameId.name} = ${helper(
|
|
||||||
'toRef'
|
|
||||||
)}(${source}, ${i}${defaultStr})`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nameId) {
|
|
||||||
s.appendLeft(call.end! + offset, ';')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PathSegmentAtom = Expression | string | number
|
|
||||||
|
|
||||||
type PathSegment =
|
|
||||||
| PathSegmentAtom
|
|
||||||
| [PathSegmentAtom, Expression /* default value */]
|
|
||||||
|
|
||||||
function pathToString(source: string, path: PathSegment[]): string {
|
|
||||||
if (path.length) {
|
|
||||||
for (const seg of path) {
|
|
||||||
if (isArray(seg)) {
|
|
||||||
source = `(${source}${segToString(seg[0])} || ${snip(seg[1])})`
|
|
||||||
} else {
|
|
||||||
source += segToString(seg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return source
|
|
||||||
}
|
|
||||||
|
|
||||||
function segToString(seg: PathSegmentAtom): string {
|
|
||||||
if (typeof seg === 'number') {
|
|
||||||
return `[${seg}]`
|
|
||||||
} else if (typeof seg === 'string') {
|
|
||||||
return `.${seg}`
|
|
||||||
} else {
|
|
||||||
return snip(seg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rewriteId(
|
|
||||||
scope: Scope,
|
|
||||||
id: Identifier,
|
|
||||||
parent: Node,
|
|
||||||
parentStack: Node[]
|
|
||||||
): boolean {
|
|
||||||
if (hasOwn(scope, id.name)) {
|
|
||||||
const binding = scope[id.name]
|
|
||||||
|
|
||||||
if (binding) {
|
|
||||||
if (
|
|
||||||
binding.isConst &&
|
|
||||||
((parent.type === 'AssignmentExpression' && id === parent.left) ||
|
|
||||||
parent.type === 'UpdateExpression')
|
|
||||||
) {
|
|
||||||
error(`Assignment to constant variable.`, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { isProp } = binding
|
|
||||||
if (isStaticProperty(parent) && parent.shorthand) {
|
|
||||||
// let binding used in a property shorthand
|
|
||||||
// skip for destructure patterns
|
|
||||||
if (
|
|
||||||
!(parent as any).inPattern ||
|
|
||||||
isInDestructureAssignment(parent, parentStack)
|
|
||||||
) {
|
|
||||||
if (isProp) {
|
|
||||||
if (escapeScope) {
|
|
||||||
// prop binding in $$()
|
|
||||||
// { prop } -> { prop: __props_prop }
|
|
||||||
registerEscapedPropBinding(id)
|
|
||||||
s.appendLeft(
|
|
||||||
id.end! + offset,
|
|
||||||
`: __props_${propsLocalToPublicMap[id.name]}`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// { prop } -> { prop: __props.prop }
|
|
||||||
s.appendLeft(
|
|
||||||
id.end! + offset,
|
|
||||||
`: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// { foo } -> { foo: foo.value }
|
|
||||||
s.appendLeft(id.end! + offset, `: ${id.name}.value`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isProp) {
|
|
||||||
if (escapeScope) {
|
|
||||||
// x --> __props_x
|
|
||||||
registerEscapedPropBinding(id)
|
|
||||||
s.overwrite(
|
|
||||||
id.start! + offset,
|
|
||||||
id.end! + offset,
|
|
||||||
`__props_${propsLocalToPublicMap[id.name]}`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// x --> __props.x
|
|
||||||
s.overwrite(
|
|
||||||
id.start! + offset,
|
|
||||||
id.end! + offset,
|
|
||||||
genPropsAccessExp(propsLocalToPublicMap[id.name])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// x --> x.value
|
|
||||||
s.appendLeft(id.end! + offset, '.value')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const propBindingRefs: Record<string, true> = {}
|
|
||||||
function registerEscapedPropBinding(id: Identifier) {
|
|
||||||
if (!propBindingRefs.hasOwnProperty(id.name)) {
|
|
||||||
propBindingRefs[id.name] = true
|
|
||||||
const publicKey = propsLocalToPublicMap[id.name]
|
|
||||||
s.prependRight(
|
|
||||||
offset,
|
|
||||||
`const __props_${publicKey} = ${helper(
|
|
||||||
`toRef`
|
|
||||||
)}(__props, '${publicKey}');\n`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check root scope first
|
|
||||||
walkScope(ast, true)
|
|
||||||
walk(ast, {
|
|
||||||
enter(node: Node, parent?: Node) {
|
|
||||||
parent && parentStack.push(parent)
|
|
||||||
|
|
||||||
// function scopes
|
|
||||||
if (isFunctionType(node)) {
|
|
||||||
scopeStack.push((currentScope = {}))
|
|
||||||
walkFunctionParams(node, registerBinding)
|
|
||||||
if (node.body.type === 'BlockStatement') {
|
|
||||||
walkScope(node.body)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch param
|
|
||||||
if (node.type === 'CatchClause') {
|
|
||||||
scopeStack.push((currentScope = {}))
|
|
||||||
if (node.param && node.param.type === 'Identifier') {
|
|
||||||
registerBinding(node.param)
|
|
||||||
}
|
|
||||||
walkScope(node.body)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-function block scopes
|
|
||||||
if (node.type === 'BlockStatement' && !isFunctionType(parent!)) {
|
|
||||||
scopeStack.push((currentScope = {}))
|
|
||||||
walkScope(node)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip type nodes
|
|
||||||
if (
|
|
||||||
parent &&
|
|
||||||
parent.type.startsWith('TS') &&
|
|
||||||
parent.type !== 'TSAsExpression' &&
|
|
||||||
parent.type !== 'TSNonNullExpression' &&
|
|
||||||
parent.type !== 'TSTypeAssertion'
|
|
||||||
) {
|
|
||||||
return this.skip()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === 'Identifier') {
|
|
||||||
const binding = rootScope[node.name]
|
|
||||||
if (
|
|
||||||
// if inside $$(), skip unless this is a destructured prop binding
|
|
||||||
!(escapeScope && (!binding || !binding.isProp)) &&
|
|
||||||
isReferencedIdentifier(node, parent!, parentStack) &&
|
|
||||||
!excludedIds.has(node)
|
|
||||||
) {
|
|
||||||
// walk up the scope chain to check if id should be appended .value
|
|
||||||
let i = scopeStack.length
|
|
||||||
while (i--) {
|
|
||||||
if (rewriteId(scopeStack[i], node, parent!, parentStack)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
|
|
||||||
const callee = node.callee.name
|
|
||||||
|
|
||||||
const refCall = isRefCreationCall(callee)
|
|
||||||
if (refCall && (!parent || parent.type !== 'VariableDeclarator')) {
|
|
||||||
return error(
|
|
||||||
`${refCall} can only be used as the initializer of ` +
|
|
||||||
`a variable declaration.`,
|
|
||||||
node
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
escapeSymbol &&
|
|
||||||
currentScope[escapeSymbol] === undefined &&
|
|
||||||
callee === escapeSymbol
|
|
||||||
) {
|
|
||||||
escapeScope = node
|
|
||||||
s.remove(node.callee.start! + offset, node.callee.end! + offset)
|
|
||||||
|
|
||||||
if (parent?.type === 'ExpressionStatement') {
|
|
||||||
// edge case where the call expression is an expression statement
|
|
||||||
// if its own - prepend semicolon to avoid it being parsed as
|
|
||||||
// function invocation of previous line
|
|
||||||
let i =
|
|
||||||
(node.leadingComments
|
|
||||||
? node.leadingComments[0].start
|
|
||||||
: node.start)! + offset
|
|
||||||
while (i--) {
|
|
||||||
const char = s.original.charAt(i)
|
|
||||||
if (char === '\n') {
|
|
||||||
// only insert semi if it's actually the first thing after
|
|
||||||
// newline
|
|
||||||
s.prependRight(node.start! + offset, ';')
|
|
||||||
break
|
|
||||||
} else if (!/\s/.test(char)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leave(node: Node, parent?: Node) {
|
|
||||||
parent && parentStack.pop()
|
|
||||||
if (
|
|
||||||
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
|
||||||
isFunctionType(node)
|
|
||||||
) {
|
|
||||||
scopeStack.pop()
|
|
||||||
currentScope = scopeStack[scopeStack.length - 1] || null
|
|
||||||
}
|
|
||||||
if (node === escapeScope) {
|
|
||||||
escapeScope = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
rootRefs: Object.keys(rootScope).filter(key => {
|
|
||||||
const binding = rootScope[key]
|
|
||||||
return binding && !binding.isProp
|
|
||||||
}),
|
|
||||||
importedHelpers: [...importedHelpers]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasWarned: Record<string, boolean> = {}
|
|
||||||
|
|
||||||
function warnExperimental() {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
warnOnce(
|
|
||||||
`Reactivity Transform was an experimental feature and has now been deprecated. ` +
|
|
||||||
`It will be removed from Vue core in 3.4. If you intend to continue using it, ` +
|
|
||||||
`switch to https://vue-macros.sxzz.moe/features/reactivity-transform.html.\n` +
|
|
||||||
`See reason for deprecation here: https://github.com/vuejs/rfcs/discussions/369#discussioncomment-5059028`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function warnOnce(msg: string) {
|
|
||||||
const isNodeProd =
|
|
||||||
typeof process !== 'undefined' && process.env.NODE_ENV === 'production'
|
|
||||||
if (!isNodeProd && !__TEST__ && !hasWarned[msg]) {
|
|
||||||
hasWarned[msg] = true
|
|
||||||
warn(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function warn(msg: string) {
|
|
||||||
console.warn(
|
|
||||||
`\x1b[1m\x1b[33m[@vue/reactivity-transform]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -184,7 +184,7 @@ describe('reactivity/computed', () => {
|
||||||
// mutate n
|
// mutate n
|
||||||
n.value++
|
n.value++
|
||||||
// on the 2nd run, plusOne.value should have already updated.
|
// on the 2nd run, plusOne.value should have already updated.
|
||||||
expect(plusOneValues).toMatchObject([1, 2, 2])
|
expect(plusOneValues).toMatchObject([1, 2])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should warn if trying to set a readonly computed', () => {
|
it('should warn if trying to set a readonly computed', () => {
|
||||||
|
|
@ -288,4 +288,167 @@ describe('reactivity/computed', () => {
|
||||||
oldValue: 2
|
oldValue: 2
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// https://github.com/vuejs/core/pull/5912#issuecomment-1497596875
|
||||||
|
it('should query deps dirty sequentially', () => {
|
||||||
|
const cSpy = vi.fn()
|
||||||
|
|
||||||
|
const a = ref<null | { v: number }>({
|
||||||
|
v: 1
|
||||||
|
})
|
||||||
|
const b = computed(() => {
|
||||||
|
return a.value
|
||||||
|
})
|
||||||
|
const c = computed(() => {
|
||||||
|
cSpy()
|
||||||
|
return b.value?.v
|
||||||
|
})
|
||||||
|
const d = computed(() => {
|
||||||
|
if (b.value) {
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
d.value
|
||||||
|
a.value!.v = 2
|
||||||
|
a.value = null
|
||||||
|
d.value
|
||||||
|
expect(cSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://github.com/vuejs/core/pull/5912#issuecomment-1738257692
|
||||||
|
it('chained computed dirty reallocation after querying dirty', () => {
|
||||||
|
let _msg: string | undefined
|
||||||
|
|
||||||
|
const items = ref<number[]>()
|
||||||
|
const isLoaded = computed(() => {
|
||||||
|
return !!items.value
|
||||||
|
})
|
||||||
|
const msg = computed(() => {
|
||||||
|
if (isLoaded.value) {
|
||||||
|
return 'The items are loaded'
|
||||||
|
} else {
|
||||||
|
return 'The items are not loaded'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
effect(() => {
|
||||||
|
_msg = msg.value
|
||||||
|
})
|
||||||
|
|
||||||
|
items.value = [1, 2, 3]
|
||||||
|
items.value = [1, 2, 3]
|
||||||
|
items.value = undefined
|
||||||
|
|
||||||
|
expect(_msg).toBe('The items are not loaded')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chained computed dirty reallocation after trigger computed getter', () => {
|
||||||
|
let _msg: string | undefined
|
||||||
|
|
||||||
|
const items = ref<number[]>()
|
||||||
|
const isLoaded = computed(() => {
|
||||||
|
return !!items.value
|
||||||
|
})
|
||||||
|
const msg = computed(() => {
|
||||||
|
if (isLoaded.value) {
|
||||||
|
return 'The items are loaded'
|
||||||
|
} else {
|
||||||
|
return 'The items are not loaded'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
_msg = msg.value
|
||||||
|
items.value = [1, 2, 3]
|
||||||
|
isLoaded.value // <- trigger computed getter
|
||||||
|
_msg = msg.value
|
||||||
|
items.value = undefined
|
||||||
|
_msg = msg.value
|
||||||
|
|
||||||
|
expect(_msg).toBe('The items are not loaded')
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://github.com/vuejs/core/pull/5912#issuecomment-1739159832
|
||||||
|
it('deps order should be consistent with the last time get value', () => {
|
||||||
|
const cSpy = vi.fn()
|
||||||
|
|
||||||
|
const a = ref(0)
|
||||||
|
const b = computed(() => {
|
||||||
|
return a.value % 3 !== 0
|
||||||
|
})
|
||||||
|
const c = computed(() => {
|
||||||
|
cSpy()
|
||||||
|
if (a.value % 3 === 2) {
|
||||||
|
return 'expensive'
|
||||||
|
}
|
||||||
|
return 'cheap'
|
||||||
|
})
|
||||||
|
const d = computed(() => {
|
||||||
|
return a.value % 3 === 2
|
||||||
|
})
|
||||||
|
const e = computed(() => {
|
||||||
|
if (b.value) {
|
||||||
|
if (d.value) {
|
||||||
|
return 'Avoiding expensive calculation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.value
|
||||||
|
})
|
||||||
|
|
||||||
|
e.value
|
||||||
|
a.value++
|
||||||
|
e.value
|
||||||
|
|
||||||
|
expect(e.effect.deps.length).toBe(3)
|
||||||
|
expect(e.effect.deps.indexOf((b as any).dep)).toBe(0)
|
||||||
|
expect(e.effect.deps.indexOf((d as any).dep)).toBe(1)
|
||||||
|
expect(e.effect.deps.indexOf((c as any).dep)).toBe(2)
|
||||||
|
expect(cSpy).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
|
a.value++
|
||||||
|
e.value
|
||||||
|
|
||||||
|
expect(cSpy).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should trigger by the second computed that maybe dirty', () => {
|
||||||
|
const cSpy = vi.fn()
|
||||||
|
|
||||||
|
const src1 = ref(0)
|
||||||
|
const src2 = ref(0)
|
||||||
|
const c1 = computed(() => src1.value)
|
||||||
|
const c2 = computed(() => (src1.value % 2) + src2.value)
|
||||||
|
const c3 = computed(() => {
|
||||||
|
cSpy()
|
||||||
|
c1.value
|
||||||
|
c2.value
|
||||||
|
})
|
||||||
|
|
||||||
|
c3.value
|
||||||
|
src1.value = 2
|
||||||
|
c3.value
|
||||||
|
expect(cSpy).toHaveBeenCalledTimes(2)
|
||||||
|
src2.value = 1
|
||||||
|
c3.value
|
||||||
|
expect(cSpy).toHaveBeenCalledTimes(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should trigger the second effect', () => {
|
||||||
|
const fnSpy = vi.fn()
|
||||||
|
const v = ref(1)
|
||||||
|
const c = computed(() => v.value)
|
||||||
|
|
||||||
|
effect(() => {
|
||||||
|
c.value
|
||||||
|
})
|
||||||
|
effect(() => {
|
||||||
|
c.value
|
||||||
|
fnSpy()
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(fnSpy).toBeCalledTimes(1)
|
||||||
|
v.value = 2
|
||||||
|
expect(fnSpy).toBeCalledTimes(2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,32 @@
|
||||||
import { computed, deferredComputed, effect, ref } from '../src'
|
import { computed, effect, ref } from '../src'
|
||||||
|
|
||||||
describe('deferred computed', () => {
|
describe('deferred computed', () => {
|
||||||
const tick = Promise.resolve()
|
test('should not trigger if value did not change', () => {
|
||||||
|
|
||||||
test('should only trigger once on multiple mutations', async () => {
|
|
||||||
const src = ref(0)
|
const src = ref(0)
|
||||||
const c = deferredComputed(() => src.value)
|
const c = computed(() => src.value % 2)
|
||||||
const spy = vi.fn()
|
const spy = vi.fn()
|
||||||
effect(() => {
|
effect(() => {
|
||||||
spy(c.value)
|
spy(c.value)
|
||||||
})
|
})
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
src.value = 1
|
|
||||||
src.value = 2
|
|
||||||
src.value = 3
|
|
||||||
// not called yet
|
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
|
||||||
await tick
|
|
||||||
// should only trigger once
|
|
||||||
expect(spy).toHaveBeenCalledTimes(2)
|
|
||||||
expect(spy).toHaveBeenCalledWith(c.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should not trigger if value did not change', async () => {
|
|
||||||
const src = ref(0)
|
|
||||||
const c = deferredComputed(() => src.value % 2)
|
|
||||||
const spy = vi.fn()
|
|
||||||
effect(() => {
|
|
||||||
spy(c.value)
|
|
||||||
})
|
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
|
||||||
src.value = 1
|
|
||||||
src.value = 2
|
src.value = 2
|
||||||
|
|
||||||
await tick
|
|
||||||
// should not trigger
|
// should not trigger
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
src.value = 3
|
src.value = 3
|
||||||
src.value = 4
|
|
||||||
src.value = 5
|
src.value = 5
|
||||||
await tick
|
|
||||||
// should trigger because latest value changes
|
// should trigger because latest value changes
|
||||||
expect(spy).toHaveBeenCalledTimes(2)
|
expect(spy).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('chained computed trigger', async () => {
|
test('chained computed trigger', () => {
|
||||||
const effectSpy = vi.fn()
|
const effectSpy = vi.fn()
|
||||||
const c1Spy = vi.fn()
|
const c1Spy = vi.fn()
|
||||||
const c2Spy = vi.fn()
|
const c2Spy = vi.fn()
|
||||||
|
|
||||||
const src = ref(0)
|
const src = ref(0)
|
||||||
const c1 = deferredComputed(() => {
|
const c1 = computed(() => {
|
||||||
c1Spy()
|
c1Spy()
|
||||||
return src.value % 2
|
return src.value % 2
|
||||||
})
|
})
|
||||||
|
|
@ -69,19 +44,18 @@ describe('deferred computed', () => {
|
||||||
expect(effectSpy).toHaveBeenCalledTimes(1)
|
expect(effectSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
src.value = 1
|
src.value = 1
|
||||||
await tick
|
|
||||||
expect(c1Spy).toHaveBeenCalledTimes(2)
|
expect(c1Spy).toHaveBeenCalledTimes(2)
|
||||||
expect(c2Spy).toHaveBeenCalledTimes(2)
|
expect(c2Spy).toHaveBeenCalledTimes(2)
|
||||||
expect(effectSpy).toHaveBeenCalledTimes(2)
|
expect(effectSpy).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('chained computed avoid re-compute', async () => {
|
test('chained computed avoid re-compute', () => {
|
||||||
const effectSpy = vi.fn()
|
const effectSpy = vi.fn()
|
||||||
const c1Spy = vi.fn()
|
const c1Spy = vi.fn()
|
||||||
const c2Spy = vi.fn()
|
const c2Spy = vi.fn()
|
||||||
|
|
||||||
const src = ref(0)
|
const src = ref(0)
|
||||||
const c1 = deferredComputed(() => {
|
const c1 = computed(() => {
|
||||||
c1Spy()
|
c1Spy()
|
||||||
return src.value % 2
|
return src.value % 2
|
||||||
})
|
})
|
||||||
|
|
@ -98,26 +72,24 @@ describe('deferred computed', () => {
|
||||||
src.value = 2
|
src.value = 2
|
||||||
src.value = 4
|
src.value = 4
|
||||||
src.value = 6
|
src.value = 6
|
||||||
await tick
|
expect(c1Spy).toHaveBeenCalledTimes(4)
|
||||||
// c1 should re-compute once.
|
|
||||||
expect(c1Spy).toHaveBeenCalledTimes(2)
|
|
||||||
// c2 should not have to re-compute because c1 did not change.
|
// c2 should not have to re-compute because c1 did not change.
|
||||||
expect(c2Spy).toHaveBeenCalledTimes(1)
|
expect(c2Spy).toHaveBeenCalledTimes(1)
|
||||||
// effect should not trigger because c2 did not change.
|
// effect should not trigger because c2 did not change.
|
||||||
expect(effectSpy).toHaveBeenCalledTimes(1)
|
expect(effectSpy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('chained computed value invalidation', async () => {
|
test('chained computed value invalidation', () => {
|
||||||
const effectSpy = vi.fn()
|
const effectSpy = vi.fn()
|
||||||
const c1Spy = vi.fn()
|
const c1Spy = vi.fn()
|
||||||
const c2Spy = vi.fn()
|
const c2Spy = vi.fn()
|
||||||
|
|
||||||
const src = ref(0)
|
const src = ref(0)
|
||||||
const c1 = deferredComputed(() => {
|
const c1 = computed(() => {
|
||||||
c1Spy()
|
c1Spy()
|
||||||
return src.value % 2
|
return src.value % 2
|
||||||
})
|
})
|
||||||
const c2 = deferredComputed(() => {
|
const c2 = computed(() => {
|
||||||
c2Spy()
|
c2Spy()
|
||||||
return c1.value + 1
|
return c1.value + 1
|
||||||
})
|
})
|
||||||
|
|
@ -139,17 +111,17 @@ describe('deferred computed', () => {
|
||||||
expect(c2Spy).toHaveBeenCalledTimes(2)
|
expect(c2Spy).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('sync access of invalidated chained computed should not prevent final effect from running', async () => {
|
test('sync access of invalidated chained computed should not prevent final effect from running', () => {
|
||||||
const effectSpy = vi.fn()
|
const effectSpy = vi.fn()
|
||||||
const c1Spy = vi.fn()
|
const c1Spy = vi.fn()
|
||||||
const c2Spy = vi.fn()
|
const c2Spy = vi.fn()
|
||||||
|
|
||||||
const src = ref(0)
|
const src = ref(0)
|
||||||
const c1 = deferredComputed(() => {
|
const c1 = computed(() => {
|
||||||
c1Spy()
|
c1Spy()
|
||||||
return src.value % 2
|
return src.value % 2
|
||||||
})
|
})
|
||||||
const c2 = deferredComputed(() => {
|
const c2 = computed(() => {
|
||||||
c2Spy()
|
c2Spy()
|
||||||
return c1.value + 1
|
return c1.value + 1
|
||||||
})
|
})
|
||||||
|
|
@ -162,14 +134,13 @@ describe('deferred computed', () => {
|
||||||
src.value = 1
|
src.value = 1
|
||||||
// sync access c2
|
// sync access c2
|
||||||
c2.value
|
c2.value
|
||||||
await tick
|
|
||||||
expect(effectSpy).toHaveBeenCalledTimes(2)
|
expect(effectSpy).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should not compute if deactivated before scheduler is called', async () => {
|
test('should not compute if deactivated before scheduler is called', () => {
|
||||||
const c1Spy = vi.fn()
|
const c1Spy = vi.fn()
|
||||||
const src = ref(0)
|
const src = ref(0)
|
||||||
const c1 = deferredComputed(() => {
|
const c1 = computed(() => {
|
||||||
c1Spy()
|
c1Spy()
|
||||||
return src.value % 2
|
return src.value % 2
|
||||||
})
|
})
|
||||||
|
|
@ -179,7 +150,6 @@ describe('deferred computed', () => {
|
||||||
c1.effect.stop()
|
c1.effect.stop()
|
||||||
// trigger
|
// trigger
|
||||||
src.value++
|
src.value++
|
||||||
await tick
|
|
||||||
expect(c1Spy).toHaveBeenCalledTimes(1)
|
expect(c1Spy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
ref,
|
|
||||||
reactive,
|
reactive,
|
||||||
effect,
|
effect,
|
||||||
stop,
|
stop,
|
||||||
|
|
@ -12,7 +11,8 @@ import {
|
||||||
readonly,
|
readonly,
|
||||||
ReactiveEffectRunner
|
ReactiveEffectRunner
|
||||||
} from '../src/index'
|
} from '../src/index'
|
||||||
import { ITERATE_KEY } from '../src/effect'
|
import { pauseScheduling, resetScheduling } from '../src/effect'
|
||||||
|
import { ITERATE_KEY, getDepFromReactive } from '../src/reactiveEffect'
|
||||||
|
|
||||||
describe('reactivity/effect', () => {
|
describe('reactivity/effect', () => {
|
||||||
it('should run the passed function once (wrapped by a effect)', () => {
|
it('should run the passed function once (wrapped by a effect)', () => {
|
||||||
|
|
@ -574,8 +574,8 @@ describe('reactivity/effect', () => {
|
||||||
expect(output.fx2).toBe(1 + 3 + 3)
|
expect(output.fx2).toBe(1 + 3 + 3)
|
||||||
expect(fx1Spy).toHaveBeenCalledTimes(1)
|
expect(fx1Spy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
// Invoked twice due to change of fx1.
|
// Invoked due to change of fx1.
|
||||||
expect(fx2Spy).toHaveBeenCalledTimes(2)
|
expect(fx2Spy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
fx1Spy.mockClear()
|
fx1Spy.mockClear()
|
||||||
fx2Spy.mockClear()
|
fx2Spy.mockClear()
|
||||||
|
|
@ -821,26 +821,6 @@ describe('reactivity/effect', () => {
|
||||||
expect(dummy).toBe(3)
|
expect(dummy).toBe(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
// #5707
|
|
||||||
// when an effect completes its run, it should clear the tracking bits of
|
|
||||||
// its tracked deps. However, if the effect stops itself, the deps list is
|
|
||||||
// emptied so their bits are never cleared.
|
|
||||||
it('edge case: self-stopping effect tracking ref', () => {
|
|
||||||
const c = ref(true)
|
|
||||||
const runner = effect(() => {
|
|
||||||
// reference ref
|
|
||||||
if (!c.value) {
|
|
||||||
// stop itself while running
|
|
||||||
stop(runner)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// trigger run
|
|
||||||
c.value = !c.value
|
|
||||||
// should clear bits
|
|
||||||
expect((c as any).dep.w).toBe(0)
|
|
||||||
expect((c as any).dep.n).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('events: onStop', () => {
|
it('events: onStop', () => {
|
||||||
const onStop = vi.fn()
|
const onStop = vi.fn()
|
||||||
const runner = effect(() => {}, {
|
const runner = effect(() => {}, {
|
||||||
|
|
@ -1015,4 +995,83 @@ describe('reactivity/effect', () => {
|
||||||
expect(has).toBe(false)
|
expect(has).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should be triggered once with pauseScheduling', () => {
|
||||||
|
const counter = reactive({ num: 0 })
|
||||||
|
|
||||||
|
const counterSpy = vi.fn(() => counter.num)
|
||||||
|
effect(counterSpy)
|
||||||
|
|
||||||
|
counterSpy.mockClear()
|
||||||
|
|
||||||
|
pauseScheduling()
|
||||||
|
counter.num++
|
||||||
|
counter.num++
|
||||||
|
resetScheduling()
|
||||||
|
expect(counterSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('empty dep cleanup', () => {
|
||||||
|
it('should remove the dep when the effect is stopped', () => {
|
||||||
|
const obj = reactive({ prop: 1 })
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined()
|
||||||
|
const runner = effect(() => obj.prop)
|
||||||
|
const dep = getDepFromReactive(toRaw(obj), 'prop')
|
||||||
|
expect(dep).toHaveLength(1)
|
||||||
|
obj.prop = 2
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep)
|
||||||
|
expect(dep).toHaveLength(1)
|
||||||
|
stop(runner)
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined()
|
||||||
|
obj.prop = 3
|
||||||
|
runner()
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should only remove the dep when the last effect is stopped', () => {
|
||||||
|
const obj = reactive({ prop: 1 })
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined()
|
||||||
|
const runner1 = effect(() => obj.prop)
|
||||||
|
const dep = getDepFromReactive(toRaw(obj), 'prop')
|
||||||
|
expect(dep).toHaveLength(1)
|
||||||
|
const runner2 = effect(() => obj.prop)
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep)
|
||||||
|
expect(dep).toHaveLength(2)
|
||||||
|
obj.prop = 2
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep)
|
||||||
|
expect(dep).toHaveLength(2)
|
||||||
|
stop(runner1)
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep)
|
||||||
|
expect(dep).toHaveLength(1)
|
||||||
|
obj.prop = 3
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep)
|
||||||
|
expect(dep).toHaveLength(1)
|
||||||
|
stop(runner2)
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined()
|
||||||
|
obj.prop = 4
|
||||||
|
runner1()
|
||||||
|
runner2()
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove the dep when it is no longer used by the effect', () => {
|
||||||
|
const obj = reactive<{ a: number; b: number; c: 'a' | 'b' }>({
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
c: 'a'
|
||||||
|
})
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined()
|
||||||
|
effect(() => obj[obj.c])
|
||||||
|
const depC = getDepFromReactive(toRaw(obj), 'c')
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'a')).toHaveLength(1)
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'b')).toBeUndefined()
|
||||||
|
expect(depC).toHaveLength(1)
|
||||||
|
obj.c = 'b'
|
||||||
|
obj.a = 4
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'a')).toBeUndefined()
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'b')).toHaveLength(1)
|
||||||
|
expect(getDepFromReactive(toRaw(obj), 'c')).toBe(depC)
|
||||||
|
expect(depC).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import {
|
||||||
|
ComputedRef,
|
||||||
|
computed,
|
||||||
|
effect,
|
||||||
|
reactive,
|
||||||
|
shallowRef as ref,
|
||||||
|
toRaw
|
||||||
|
} from '../src/index'
|
||||||
|
import { getDepFromReactive } from '../src/reactiveEffect'
|
||||||
|
|
||||||
|
describe.skipIf(!global.gc)('reactivity/gc', () => {
|
||||||
|
const gc = () => {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
global.gc!()
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// #9233
|
||||||
|
it('should release computed cache', async () => {
|
||||||
|
const src = ref<{} | undefined>({})
|
||||||
|
const srcRef = new WeakRef(src.value!)
|
||||||
|
|
||||||
|
let c: ComputedRef | undefined = computed(() => src.value)
|
||||||
|
|
||||||
|
c.value // cache src value
|
||||||
|
src.value = undefined // release value
|
||||||
|
c = undefined // release computed
|
||||||
|
|
||||||
|
await gc()
|
||||||
|
expect(srcRef.deref()).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should release reactive property dep', async () => {
|
||||||
|
const src = reactive({ foo: 1 })
|
||||||
|
|
||||||
|
let c: ComputedRef | undefined = computed(() => src.foo)
|
||||||
|
|
||||||
|
c.value
|
||||||
|
expect(getDepFromReactive(toRaw(src), 'foo')).not.toBeUndefined()
|
||||||
|
|
||||||
|
c = undefined
|
||||||
|
await gc()
|
||||||
|
await gc()
|
||||||
|
expect(getDepFromReactive(toRaw(src), 'foo')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not release effect for ref', async () => {
|
||||||
|
const spy = vi.fn()
|
||||||
|
const src = ref(0)
|
||||||
|
|
||||||
|
effect(() => {
|
||||||
|
spy()
|
||||||
|
src.value
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
await gc()
|
||||||
|
src.value++
|
||||||
|
expect(spy).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not release effect for reactive', async () => {
|
||||||
|
const spy = vi.fn()
|
||||||
|
const src = reactive({ foo: 1 })
|
||||||
|
|
||||||
|
effect(() => {
|
||||||
|
spy()
|
||||||
|
src.foo
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
await gc()
|
||||||
|
src.foo++
|
||||||
|
expect(spy).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -99,6 +99,39 @@ describe('reactivity/reactive/Array', () => {
|
||||||
expect(fn).toHaveBeenCalledTimes(1)
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('shift on Array should trigger dependency once', () => {
|
||||||
|
const arr = reactive([1, 2, 3])
|
||||||
|
const fn = vi.fn()
|
||||||
|
effect(() => {
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
arr[i]
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
})
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
arr.shift()
|
||||||
|
expect(fn).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
//#6018
|
||||||
|
test('edge case: avoid trigger effect in deleteProperty when array length-decrease mutation methods called', () => {
|
||||||
|
const arr = ref([1])
|
||||||
|
const fn1 = vi.fn()
|
||||||
|
const fn2 = vi.fn()
|
||||||
|
effect(() => {
|
||||||
|
fn1()
|
||||||
|
if (arr.value.length > 0) {
|
||||||
|
arr.value.slice()
|
||||||
|
fn2()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(fn1).toHaveBeenCalledTimes(1)
|
||||||
|
expect(fn2).toHaveBeenCalledTimes(1)
|
||||||
|
arr.value.splice(0)
|
||||||
|
expect(fn1).toHaveBeenCalledTimes(2)
|
||||||
|
expect(fn2).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('add existing index on Array should not trigger length dependency', () => {
|
test('add existing index on Array should not trigger length dependency', () => {
|
||||||
const array = new Array(3)
|
const array = new Array(3)
|
||||||
const observed = reactive(array)
|
const observed = reactive(array)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/reactivity",
|
"name": "@vue/reactivity",
|
||||||
"version": "3.3.9",
|
"version": "3.4.0-alpha.3",
|
||||||
"description": "@vue/reactivity",
|
"description": "@vue/reactivity",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/reactivity.esm-bundler.js",
|
"module": "dist/reactivity.esm-bundler.js",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import {
|
||||||
reactive,
|
reactive,
|
||||||
readonly,
|
readonly,
|
||||||
toRaw,
|
toRaw,
|
||||||
ReactiveFlags,
|
|
||||||
Target,
|
Target,
|
||||||
readonlyMap,
|
readonlyMap,
|
||||||
reactiveMap,
|
reactiveMap,
|
||||||
|
|
@ -11,14 +10,14 @@ import {
|
||||||
isReadonly,
|
isReadonly,
|
||||||
isShallow
|
isShallow
|
||||||
} from './reactive'
|
} from './reactive'
|
||||||
import { TrackOpTypes, TriggerOpTypes } from './operations'
|
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||||
import {
|
import {
|
||||||
track,
|
|
||||||
trigger,
|
|
||||||
ITERATE_KEY,
|
|
||||||
pauseTracking,
|
pauseTracking,
|
||||||
resetTracking
|
resetTracking,
|
||||||
|
pauseScheduling,
|
||||||
|
resetScheduling
|
||||||
} from './effect'
|
} from './effect'
|
||||||
|
import { track, trigger, ITERATE_KEY } from './reactiveEffect'
|
||||||
import {
|
import {
|
||||||
isObject,
|
isObject,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
|
|
@ -71,7 +70,9 @@ function createArrayInstrumentations() {
|
||||||
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
|
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
|
||||||
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
|
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
|
||||||
pauseTracking()
|
pauseTracking()
|
||||||
|
pauseScheduling()
|
||||||
const res = (toRaw(this) as any)[key].apply(this, args)
|
const res = (toRaw(this) as any)[key].apply(this, args)
|
||||||
|
resetScheduling()
|
||||||
resetTracking()
|
resetTracking()
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import { toRaw, ReactiveFlags, toReactive, toReadonly } from './reactive'
|
import { toRaw, toReactive, toReadonly } from './reactive'
|
||||||
import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect'
|
import {
|
||||||
import { TrackOpTypes, TriggerOpTypes } from './operations'
|
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, hasOwn, hasChanged, toRawType, isMap } from '@vue/shared'
|
||||||
|
|
||||||
export type CollectionTypes = IterableCollections | WeakCollections
|
export type CollectionTypes = IterableCollections | WeakCollections
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { DebuggerOptions, ReactiveEffect } from './effect'
|
import { DebuggerOptions, ReactiveEffect } from './effect'
|
||||||
import { Ref, trackRefValue, triggerRefValue } from './ref'
|
import { Ref, trackRefValue, triggerRefValue } from './ref'
|
||||||
import { isFunction, NOOP } from '@vue/shared'
|
import { hasChanged, isFunction, NOOP } from '@vue/shared'
|
||||||
import { ReactiveFlags, toRaw } from './reactive'
|
import { toRaw } from './reactive'
|
||||||
import { Dep } from './dep'
|
import { Dep } from './dep'
|
||||||
|
import { DirtyLevels, ReactiveFlags } from './constants'
|
||||||
|
|
||||||
declare const ComputedRefSymbol: unique symbol
|
declare const ComputedRefSymbol: unique symbol
|
||||||
|
|
||||||
|
|
@ -15,8 +16,8 @@ export interface WritableComputedRef<T> extends Ref<T> {
|
||||||
readonly effect: ReactiveEffect<T>
|
readonly effect: ReactiveEffect<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComputedGetter<T> = (...args: any[]) => T
|
export type ComputedGetter<T> = (oldValue?: T) => T
|
||||||
export type ComputedSetter<T> = (v: T) => void
|
export type ComputedSetter<T> = (newValue: T) => void
|
||||||
|
|
||||||
export interface WritableComputedOptions<T> {
|
export interface WritableComputedOptions<T> {
|
||||||
get: ComputedGetter<T>
|
get: ComputedGetter<T>
|
||||||
|
|
@ -32,7 +33,6 @@ export class ComputedRefImpl<T> {
|
||||||
public readonly __v_isRef = true
|
public readonly __v_isRef = true
|
||||||
public readonly [ReactiveFlags.IS_READONLY]: boolean = false
|
public readonly [ReactiveFlags.IS_READONLY]: boolean = false
|
||||||
|
|
||||||
public _dirty = true
|
|
||||||
public _cacheable: boolean
|
public _cacheable: boolean
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
@ -41,12 +41,10 @@ export class ComputedRefImpl<T> {
|
||||||
isReadonly: boolean,
|
isReadonly: boolean,
|
||||||
isSSR: boolean
|
isSSR: boolean
|
||||||
) {
|
) {
|
||||||
this.effect = new ReactiveEffect(getter, () => {
|
this.effect = new ReactiveEffect(
|
||||||
if (!this._dirty) {
|
() => getter(this._value),
|
||||||
this._dirty = true
|
() => triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty)
|
||||||
triggerRefValue(this)
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
this.effect.computed = this
|
this.effect.computed = this
|
||||||
this.effect.active = this._cacheable = !isSSR
|
this.effect.active = this._cacheable = !isSSR
|
||||||
this[ReactiveFlags.IS_READONLY] = isReadonly
|
this[ReactiveFlags.IS_READONLY] = isReadonly
|
||||||
|
|
@ -56,9 +54,10 @@ export class ComputedRefImpl<T> {
|
||||||
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
|
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
|
||||||
const self = toRaw(this)
|
const self = toRaw(this)
|
||||||
trackRefValue(self)
|
trackRefValue(self)
|
||||||
if (self._dirty || !self._cacheable) {
|
if (!self._cacheable || self.effect.dirty) {
|
||||||
self._dirty = false
|
if (hasChanged(self._value, (self._value = self.effect.run()!))) {
|
||||||
self._value = self.effect.run()!
|
triggerRefValue(self, DirtyLevels.ComputedValueDirty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return self._value
|
return self._value
|
||||||
}
|
}
|
||||||
|
|
@ -66,6 +65,16 @@ export class ComputedRefImpl<T> {
|
||||||
set value(newValue: T) {
|
set value(newValue: T) {
|
||||||
this._setter(newValue)
|
this._setter(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x
|
||||||
|
get _dirty() {
|
||||||
|
return this.effect.dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
set _dirty(v) {
|
||||||
|
this.effect.dirty = v
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// using literal strings instead of numbers so that it's easier to inspect
|
||||||
|
// debugger events
|
||||||
|
|
||||||
|
export enum TrackOpTypes {
|
||||||
|
GET = 'get',
|
||||||
|
HAS = 'has',
|
||||||
|
ITERATE = 'iterate'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TriggerOpTypes {
|
||||||
|
SET = 'set',
|
||||||
|
ADD = 'add',
|
||||||
|
DELETE = 'delete',
|
||||||
|
CLEAR = 'clear'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ReactiveFlags {
|
||||||
|
SKIP = '__v_skip',
|
||||||
|
IS_REACTIVE = '__v_isReactive',
|
||||||
|
IS_READONLY = '__v_isReadonly',
|
||||||
|
IS_SHALLOW = '__v_isShallow',
|
||||||
|
RAW = '__v_raw'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DirtyLevels {
|
||||||
|
NotDirty = 0,
|
||||||
|
ComputedValueMaybeDirty = 1,
|
||||||
|
ComputedValueDirty = 2,
|
||||||
|
Dirty = 3
|
||||||
|
}
|
||||||
|
|
@ -1,88 +1,6 @@
|
||||||
import { Dep } from './dep'
|
import { computed } from './computed'
|
||||||
import { ReactiveEffect } from './effect'
|
|
||||||
import { ComputedGetter, ComputedRef } from './computed'
|
|
||||||
import { ReactiveFlags, toRaw } from './reactive'
|
|
||||||
import { trackRefValue, triggerRefValue } from './ref'
|
|
||||||
|
|
||||||
const tick = /*#__PURE__*/ Promise.resolve()
|
/**
|
||||||
const queue: any[] = []
|
* @deprecated use `computed` instead. See #5912
|
||||||
let queued = false
|
*/
|
||||||
|
export const deferredComputed = computed
|
||||||
const scheduler = (fn: any) => {
|
|
||||||
queue.push(fn)
|
|
||||||
if (!queued) {
|
|
||||||
queued = true
|
|
||||||
tick.then(flush)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const flush = () => {
|
|
||||||
for (let i = 0; i < queue.length; i++) {
|
|
||||||
queue[i]()
|
|
||||||
}
|
|
||||||
queue.length = 0
|
|
||||||
queued = false
|
|
||||||
}
|
|
||||||
|
|
||||||
class DeferredComputedRefImpl<T> {
|
|
||||||
public dep?: Dep = undefined
|
|
||||||
|
|
||||||
private _value!: T
|
|
||||||
private _dirty = true
|
|
||||||
public readonly effect: ReactiveEffect<T>
|
|
||||||
|
|
||||||
public readonly __v_isRef = true
|
|
||||||
public readonly [ReactiveFlags.IS_READONLY] = true
|
|
||||||
|
|
||||||
constructor(getter: ComputedGetter<T>) {
|
|
||||||
let compareTarget: any
|
|
||||||
let hasCompareTarget = false
|
|
||||||
let scheduled = false
|
|
||||||
this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => {
|
|
||||||
if (this.dep) {
|
|
||||||
if (computedTrigger) {
|
|
||||||
compareTarget = this._value
|
|
||||||
hasCompareTarget = true
|
|
||||||
} else if (!scheduled) {
|
|
||||||
const valueToCompare = hasCompareTarget ? compareTarget : this._value
|
|
||||||
scheduled = true
|
|
||||||
hasCompareTarget = false
|
|
||||||
scheduler(() => {
|
|
||||||
if (this.effect.active && this._get() !== valueToCompare) {
|
|
||||||
triggerRefValue(this)
|
|
||||||
}
|
|
||||||
scheduled = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// chained upstream computeds are notified synchronously to ensure
|
|
||||||
// value invalidation in case of sync access; normal effects are
|
|
||||||
// deferred to be triggered in scheduler.
|
|
||||||
for (const e of this.dep) {
|
|
||||||
if (e.computed instanceof DeferredComputedRefImpl) {
|
|
||||||
e.scheduler!(true /* computedTrigger */)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._dirty = true
|
|
||||||
})
|
|
||||||
this.effect.computed = this as any
|
|
||||||
}
|
|
||||||
|
|
||||||
private _get() {
|
|
||||||
if (this._dirty) {
|
|
||||||
this._dirty = false
|
|
||||||
return (this._value = this.effect.run()!)
|
|
||||||
}
|
|
||||||
return this._value
|
|
||||||
}
|
|
||||||
|
|
||||||
get value() {
|
|
||||||
trackRefValue(this)
|
|
||||||
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
|
|
||||||
return toRaw(this)._get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deferredComputed<T>(getter: () => T): ComputedRef<T> {
|
|
||||||
return new DeferredComputedRefImpl(getter) as any
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,17 @@
|
||||||
import { ReactiveEffect, trackOpBit } from './effect'
|
import type { ReactiveEffect } from './effect'
|
||||||
|
import type { ComputedRefImpl } from './computed'
|
||||||
|
|
||||||
export type Dep = Set<ReactiveEffect> & TrackedMarkers
|
export type Dep = Map<ReactiveEffect, number> & {
|
||||||
|
cleanup: () => void
|
||||||
/**
|
computed?: ComputedRefImpl<any>
|
||||||
* wasTracked and newTracked maintain the status for several levels of effect
|
|
||||||
* tracking recursion. One bit per level is used to define whether the dependency
|
|
||||||
* was/is tracked.
|
|
||||||
*/
|
|
||||||
type TrackedMarkers = {
|
|
||||||
/**
|
|
||||||
* wasTracked
|
|
||||||
*/
|
|
||||||
w: number
|
|
||||||
/**
|
|
||||||
* newTracked
|
|
||||||
*/
|
|
||||||
n: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createDep = (effects?: ReactiveEffect[]): Dep => {
|
export const createDep = (
|
||||||
const dep = new Set<ReactiveEffect>(effects) as Dep
|
cleanup: () => void,
|
||||||
dep.w = 0
|
computed?: ComputedRefImpl<any>
|
||||||
dep.n = 0
|
): Dep => {
|
||||||
|
const dep = new Map() as Dep
|
||||||
|
dep.cleanup = cleanup
|
||||||
|
dep.computed = computed
|
||||||
return dep
|
return dep
|
||||||
}
|
}
|
||||||
|
|
||||||
export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0
|
|
||||||
|
|
||||||
export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0
|
|
||||||
|
|
||||||
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
|
|
||||||
if (deps.length) {
|
|
||||||
for (let i = 0; i < deps.length; i++) {
|
|
||||||
deps[i].w |= trackOpBit // set was tracked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
|
|
||||||
const { deps } = effect
|
|
||||||
if (deps.length) {
|
|
||||||
let ptr = 0
|
|
||||||
for (let i = 0; i < deps.length; i++) {
|
|
||||||
const dep = deps[i]
|
|
||||||
if (wasTracked(dep) && !newTracked(dep)) {
|
|
||||||
dep.delete(effect)
|
|
||||||
} else {
|
|
||||||
deps[ptr++] = dep
|
|
||||||
}
|
|
||||||
// clear bits
|
|
||||||
dep.w &= ~trackOpBit
|
|
||||||
dep.n &= ~trackOpBit
|
|
||||||
}
|
|
||||||
deps.length = ptr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,8 @@
|
||||||
import { TrackOpTypes, TriggerOpTypes } from './operations'
|
import { NOOP, extend } from '@vue/shared'
|
||||||
import { extend, isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
|
import type { ComputedRefImpl } from './computed'
|
||||||
|
import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||||
|
import type { Dep } from './dep'
|
||||||
import { EffectScope, recordEffectScope } from './effectScope'
|
import { EffectScope, recordEffectScope } from './effectScope'
|
||||||
import {
|
|
||||||
createDep,
|
|
||||||
Dep,
|
|
||||||
finalizeDepMarkers,
|
|
||||||
initDepMarkers,
|
|
||||||
newTracked,
|
|
||||||
wasTracked
|
|
||||||
} from './dep'
|
|
||||||
import { ComputedRefImpl } from './computed'
|
|
||||||
|
|
||||||
// The main WeakMap that stores {target -> key -> dep} connections.
|
|
||||||
// Conceptually, it's easier to think of a dependency as a Dep class
|
|
||||||
// which maintains a Set of subscribers, but we simply store them as
|
|
||||||
// raw Sets to reduce memory overhead.
|
|
||||||
type KeyToDepMap = Map<any, Dep>
|
|
||||||
const targetMap = new WeakMap<object, KeyToDepMap>()
|
|
||||||
|
|
||||||
// The number of effects currently being tracked recursively.
|
|
||||||
let effectTrackDepth = 0
|
|
||||||
|
|
||||||
export let trackOpBit = 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The bitwise track markers support at most 30 levels of recursion.
|
|
||||||
* This value is chosen to enable modern JS engines to use a SMI on all platforms.
|
|
||||||
* When recursion depth is greater, fall back to using a full cleanup.
|
|
||||||
*/
|
|
||||||
const maxMarkerBits = 30
|
|
||||||
|
|
||||||
export type EffectScheduler = (...args: any[]) => any
|
export type EffectScheduler = (...args: any[]) => any
|
||||||
|
|
||||||
|
|
@ -47,13 +21,9 @@ export type DebuggerEventExtraInfo = {
|
||||||
|
|
||||||
export let activeEffect: ReactiveEffect | undefined
|
export let activeEffect: ReactiveEffect | undefined
|
||||||
|
|
||||||
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
|
|
||||||
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')
|
|
||||||
|
|
||||||
export class ReactiveEffect<T = any> {
|
export class ReactiveEffect<T = any> {
|
||||||
active = true
|
active = true
|
||||||
deps: Dep[] = []
|
deps: Dep[] = []
|
||||||
parent: ReactiveEffect | undefined = undefined
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be attached after creation
|
* Can be attached after creation
|
||||||
|
|
@ -64,10 +34,6 @@ export class ReactiveEffect<T = any> {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
allowRecurse?: boolean
|
allowRecurse?: boolean
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
private deferStop?: boolean
|
|
||||||
|
|
||||||
onStop?: () => void
|
onStop?: () => void
|
||||||
// dev only
|
// dev only
|
||||||
|
|
@ -75,77 +41,115 @@ export class ReactiveEffect<T = any> {
|
||||||
// dev only
|
// dev only
|
||||||
onTrigger?: (event: DebuggerEvent) => void
|
onTrigger?: (event: DebuggerEvent) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_dirtyLevel = DirtyLevels.Dirty
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_trackId = 0
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_runnings = 0
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_queryings = 0
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_depsLength = 0
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public fn: () => T,
|
public fn: () => T,
|
||||||
public scheduler: EffectScheduler | null = null,
|
public trigger: () => void,
|
||||||
|
public scheduler?: EffectScheduler,
|
||||||
scope?: EffectScope
|
scope?: EffectScope
|
||||||
) {
|
) {
|
||||||
recordEffectScope(this, scope)
|
recordEffectScope(this, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get dirty() {
|
||||||
|
if (this._dirtyLevel === DirtyLevels.ComputedValueMaybeDirty) {
|
||||||
|
this._dirtyLevel = DirtyLevels.NotDirty
|
||||||
|
this._queryings++
|
||||||
|
pauseTracking()
|
||||||
|
for (const dep of this.deps) {
|
||||||
|
if (dep.computed) {
|
||||||
|
triggerComputed(dep.computed)
|
||||||
|
if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetTracking()
|
||||||
|
this._queryings--
|
||||||
|
}
|
||||||
|
return this._dirtyLevel >= DirtyLevels.ComputedValueDirty
|
||||||
|
}
|
||||||
|
|
||||||
|
public set dirty(v) {
|
||||||
|
this._dirtyLevel = v ? DirtyLevels.Dirty : DirtyLevels.NotDirty
|
||||||
|
}
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
|
this._dirtyLevel = DirtyLevels.NotDirty
|
||||||
if (!this.active) {
|
if (!this.active) {
|
||||||
return this.fn()
|
return this.fn()
|
||||||
}
|
}
|
||||||
let parent: ReactiveEffect | undefined = activeEffect
|
|
||||||
let lastShouldTrack = shouldTrack
|
let lastShouldTrack = shouldTrack
|
||||||
while (parent) {
|
let lastEffect = activeEffect
|
||||||
if (parent === this) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parent = parent.parent
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
this.parent = activeEffect
|
|
||||||
activeEffect = this
|
|
||||||
shouldTrack = true
|
shouldTrack = true
|
||||||
|
activeEffect = this
|
||||||
trackOpBit = 1 << ++effectTrackDepth
|
this._runnings++
|
||||||
|
preCleanupEffect(this)
|
||||||
if (effectTrackDepth <= maxMarkerBits) {
|
|
||||||
initDepMarkers(this)
|
|
||||||
} else {
|
|
||||||
cleanupEffect(this)
|
|
||||||
}
|
|
||||||
return this.fn()
|
return this.fn()
|
||||||
} finally {
|
} finally {
|
||||||
if (effectTrackDepth <= maxMarkerBits) {
|
postCleanupEffect(this)
|
||||||
finalizeDepMarkers(this)
|
this._runnings--
|
||||||
}
|
activeEffect = lastEffect
|
||||||
|
|
||||||
trackOpBit = 1 << --effectTrackDepth
|
|
||||||
|
|
||||||
activeEffect = this.parent
|
|
||||||
shouldTrack = lastShouldTrack
|
shouldTrack = lastShouldTrack
|
||||||
this.parent = undefined
|
|
||||||
|
|
||||||
if (this.deferStop) {
|
|
||||||
this.stop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
// stopped while running itself - defer the cleanup
|
if (this.active) {
|
||||||
if (activeEffect === this) {
|
preCleanupEffect(this)
|
||||||
this.deferStop = true
|
postCleanupEffect(this)
|
||||||
} else if (this.active) {
|
this.onStop?.()
|
||||||
cleanupEffect(this)
|
|
||||||
if (this.onStop) {
|
|
||||||
this.onStop()
|
|
||||||
}
|
|
||||||
this.active = false
|
this.active = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupEffect(effect: ReactiveEffect) {
|
function triggerComputed(computed: ComputedRefImpl<any>) {
|
||||||
const { deps } = effect
|
return computed.value
|
||||||
if (deps.length) {
|
}
|
||||||
for (let i = 0; i < deps.length; i++) {
|
|
||||||
deps[i].delete(effect)
|
function preCleanupEffect(effect: ReactiveEffect) {
|
||||||
|
effect._trackId++
|
||||||
|
effect._depsLength = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function postCleanupEffect(effect: ReactiveEffect) {
|
||||||
|
if (effect.deps && effect.deps.length > effect._depsLength) {
|
||||||
|
for (let i = effect._depsLength; i < effect.deps.length; i++) {
|
||||||
|
cleanupDepEffect(effect.deps[i], effect)
|
||||||
|
}
|
||||||
|
effect.deps.length = effect._depsLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupDepEffect(dep: Dep, effect: ReactiveEffect) {
|
||||||
|
const trackId = dep.get(effect)
|
||||||
|
if (trackId !== undefined && effect._trackId !== trackId) {
|
||||||
|
dep.delete(effect)
|
||||||
|
if (dep.size === 0) {
|
||||||
|
dep.cleanup()
|
||||||
}
|
}
|
||||||
deps.length = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +189,11 @@ export function effect<T = any>(
|
||||||
fn = (fn as ReactiveEffectRunner).effect.fn
|
fn = (fn as ReactiveEffectRunner).effect.fn
|
||||||
}
|
}
|
||||||
|
|
||||||
const _effect = new ReactiveEffect(fn)
|
const _effect = new ReactiveEffect(fn, NOOP, () => {
|
||||||
|
if (_effect.dirty) {
|
||||||
|
_effect.run()
|
||||||
|
}
|
||||||
|
})
|
||||||
if (options) {
|
if (options) {
|
||||||
extend(_effect, options)
|
extend(_effect, options)
|
||||||
if (options.scope) recordEffectScope(_effect, options.scope)
|
if (options.scope) recordEffectScope(_effect, options.scope)
|
||||||
|
|
@ -208,6 +216,8 @@ export function stop(runner: ReactiveEffectRunner) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export let shouldTrack = true
|
export let shouldTrack = true
|
||||||
|
export let pauseScheduleStack = 0
|
||||||
|
|
||||||
const trackStack: boolean[] = []
|
const trackStack: boolean[] = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -234,196 +244,70 @@ export function resetTracking() {
|
||||||
shouldTrack = last === undefined ? true : last
|
shouldTrack = last === undefined ? true : last
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function pauseScheduling() {
|
||||||
* Tracks access to a reactive property.
|
pauseScheduleStack++
|
||||||
*
|
}
|
||||||
* This will check which effect is running at the moment and record it as dep
|
|
||||||
* which records all effects that depend on the reactive property.
|
|
||||||
*
|
|
||||||
* @param target - Object holding the reactive property.
|
|
||||||
* @param type - Defines the type of access to the reactive property.
|
|
||||||
* @param key - Identifier of the reactive property to track.
|
|
||||||
*/
|
|
||||||
export function track(target: object, type: TrackOpTypes, key: unknown) {
|
|
||||||
if (shouldTrack && activeEffect) {
|
|
||||||
let depsMap = targetMap.get(target)
|
|
||||||
if (!depsMap) {
|
|
||||||
targetMap.set(target, (depsMap = new Map()))
|
|
||||||
}
|
|
||||||
let dep = depsMap.get(key)
|
|
||||||
if (!dep) {
|
|
||||||
depsMap.set(key, (dep = createDep()))
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventInfo = __DEV__
|
export function resetScheduling() {
|
||||||
? { effect: activeEffect, target, type, key }
|
pauseScheduleStack--
|
||||||
: undefined
|
while (!pauseScheduleStack && queueEffectSchedulers.length) {
|
||||||
|
queueEffectSchedulers.shift()!()
|
||||||
trackEffects(dep, eventInfo)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function trackEffects(
|
export function trackEffect(
|
||||||
|
effect: ReactiveEffect,
|
||||||
dep: Dep,
|
dep: Dep,
|
||||||
debuggerEventExtraInfo?: DebuggerEventExtraInfo
|
debuggerEventExtraInfo?: DebuggerEventExtraInfo
|
||||||
) {
|
) {
|
||||||
let shouldTrack = false
|
if (dep.get(effect) !== effect._trackId) {
|
||||||
if (effectTrackDepth <= maxMarkerBits) {
|
dep.set(effect, effect._trackId)
|
||||||
if (!newTracked(dep)) {
|
const oldDep = effect.deps[effect._depsLength]
|
||||||
dep.n |= trackOpBit // set newly tracked
|
if (oldDep !== dep) {
|
||||||
shouldTrack = !wasTracked(dep)
|
if (oldDep) {
|
||||||
}
|
cleanupDepEffect(oldDep, effect)
|
||||||
} else {
|
|
||||||
// Full cleanup mode.
|
|
||||||
shouldTrack = !dep.has(activeEffect!)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldTrack) {
|
|
||||||
dep.add(activeEffect!)
|
|
||||||
activeEffect!.deps.push(dep)
|
|
||||||
if (__DEV__ && activeEffect!.onTrack) {
|
|
||||||
activeEffect!.onTrack(
|
|
||||||
extend(
|
|
||||||
{
|
|
||||||
effect: activeEffect!
|
|
||||||
},
|
|
||||||
debuggerEventExtraInfo!
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds all deps associated with the target (or a specific property) and
|
|
||||||
* triggers the effects stored within.
|
|
||||||
*
|
|
||||||
* @param target - The reactive object.
|
|
||||||
* @param type - Defines the type of the operation that needs to trigger effects.
|
|
||||||
* @param key - Can be used to target a specific reactive property in the target object.
|
|
||||||
*/
|
|
||||||
export function trigger(
|
|
||||||
target: object,
|
|
||||||
type: TriggerOpTypes,
|
|
||||||
key?: unknown,
|
|
||||||
newValue?: unknown,
|
|
||||||
oldValue?: unknown,
|
|
||||||
oldTarget?: Map<unknown, unknown> | Set<unknown>
|
|
||||||
) {
|
|
||||||
const depsMap = targetMap.get(target)
|
|
||||||
if (!depsMap) {
|
|
||||||
// never been tracked
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let deps: (Dep | undefined)[] = []
|
|
||||||
if (type === TriggerOpTypes.CLEAR) {
|
|
||||||
// collection being cleared
|
|
||||||
// trigger all effects for target
|
|
||||||
deps = [...depsMap.values()]
|
|
||||||
} else if (key === 'length' && isArray(target)) {
|
|
||||||
const newLength = Number(newValue)
|
|
||||||
depsMap.forEach((dep, key) => {
|
|
||||||
if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
|
|
||||||
deps.push(dep)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// schedule runs for SET | ADD | DELETE
|
|
||||||
if (key !== void 0) {
|
|
||||||
deps.push(depsMap.get(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
// also run for iteration key on ADD | DELETE | Map.SET
|
|
||||||
switch (type) {
|
|
||||||
case TriggerOpTypes.ADD:
|
|
||||||
if (!isArray(target)) {
|
|
||||||
deps.push(depsMap.get(ITERATE_KEY))
|
|
||||||
if (isMap(target)) {
|
|
||||||
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
|
|
||||||
}
|
|
||||||
} else if (isIntegerKey(key)) {
|
|
||||||
// new index added to array -> length changes
|
|
||||||
deps.push(depsMap.get('length'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case TriggerOpTypes.DELETE:
|
|
||||||
if (!isArray(target)) {
|
|
||||||
deps.push(depsMap.get(ITERATE_KEY))
|
|
||||||
if (isMap(target)) {
|
|
||||||
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case TriggerOpTypes.SET:
|
|
||||||
if (isMap(target)) {
|
|
||||||
deps.push(depsMap.get(ITERATE_KEY))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventInfo = __DEV__
|
|
||||||
? { target, type, key, newValue, oldValue, oldTarget }
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
if (deps.length === 1) {
|
|
||||||
if (deps[0]) {
|
|
||||||
if (__DEV__) {
|
|
||||||
triggerEffects(deps[0], eventInfo)
|
|
||||||
} else {
|
|
||||||
triggerEffects(deps[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const effects: ReactiveEffect[] = []
|
|
||||||
for (const dep of deps) {
|
|
||||||
if (dep) {
|
|
||||||
effects.push(...dep)
|
|
||||||
}
|
}
|
||||||
|
effect.deps[effect._depsLength++] = dep
|
||||||
|
} else {
|
||||||
|
effect._depsLength++
|
||||||
}
|
}
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
triggerEffects(createDep(effects), eventInfo)
|
effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!))
|
||||||
} else {
|
|
||||||
triggerEffects(createDep(effects))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const queueEffectSchedulers: (() => void)[] = []
|
||||||
|
|
||||||
export function triggerEffects(
|
export function triggerEffects(
|
||||||
dep: Dep | ReactiveEffect[],
|
dep: Dep,
|
||||||
|
dirtyLevel: DirtyLevels,
|
||||||
debuggerEventExtraInfo?: DebuggerEventExtraInfo
|
debuggerEventExtraInfo?: DebuggerEventExtraInfo
|
||||||
) {
|
) {
|
||||||
// spread into array for stabilization
|
pauseScheduling()
|
||||||
const effects = isArray(dep) ? dep : [...dep]
|
for (const effect of dep.keys()) {
|
||||||
for (const effect of effects) {
|
if (!effect.allowRecurse && effect._runnings) {
|
||||||
if (effect.computed) {
|
continue
|
||||||
triggerEffect(effect, debuggerEventExtraInfo)
|
}
|
||||||
}
|
if (
|
||||||
}
|
effect._dirtyLevel < dirtyLevel &&
|
||||||
for (const effect of effects) {
|
(!effect._runnings || dirtyLevel !== DirtyLevels.ComputedValueDirty)
|
||||||
if (!effect.computed) {
|
) {
|
||||||
triggerEffect(effect, debuggerEventExtraInfo)
|
const lastDirtyLevel = effect._dirtyLevel
|
||||||
|
effect._dirtyLevel = dirtyLevel
|
||||||
|
if (
|
||||||
|
lastDirtyLevel === DirtyLevels.NotDirty &&
|
||||||
|
(!effect._queryings || dirtyLevel !== DirtyLevels.ComputedValueDirty)
|
||||||
|
) {
|
||||||
|
if (__DEV__) {
|
||||||
|
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
|
||||||
|
}
|
||||||
|
effect.trigger()
|
||||||
|
if (effect.scheduler) {
|
||||||
|
queueEffectSchedulers.push(effect.scheduler)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
resetScheduling()
|
||||||
|
|
||||||
function triggerEffect(
|
|
||||||
effect: ReactiveEffect,
|
|
||||||
debuggerEventExtraInfo?: DebuggerEventExtraInfo
|
|
||||||
) {
|
|
||||||
if (effect !== activeEffect || effect.allowRecurse) {
|
|
||||||
if (__DEV__ && effect.onTrigger) {
|
|
||||||
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
|
|
||||||
}
|
|
||||||
if (effect.scheduler) {
|
|
||||||
effect.scheduler()
|
|
||||||
} else {
|
|
||||||
effect.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDepFromReactive(object: any, key: string | number | symbol) {
|
|
||||||
return targetMap.get(object)?.get(key)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue