Merge tag 'v3.5.0-beta.1'

This commit is contained in:
三咲智子 Kevin Deng 2024-08-09 00:55:35 +08:00
commit 4468a2bea8
No known key found for this signature in database
236 changed files with 7166 additions and 4514 deletions

View File

@ -44,7 +44,7 @@ This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
### Full Message Format ### Full Message Format
A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
``` ```
<type>(<scope>): <subject> <type>(<scope>): <subject>
@ -74,9 +74,9 @@ The scope could be anything specifying the place of the commit change. For examp
The subject contains a succinct description of the change: The subject contains a succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes" - use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize the first letter - don't capitalize the first letter
* no dot (.) at the end - no dot (.) at the end
### Body ### Body

View File

@ -35,7 +35,6 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
Another aspect of it is that large scale stylistic changes result in massive diffs that touch multiple files, adding noise to the git history and makes tracing behavior changes across commits more cumbersome. Another aspect of it is that large scale stylistic changes result in massive diffs that touch multiple files, adding noise to the git history and makes tracing behavior changes across commits more cumbersome.
### Pull Request Checklist ### Pull Request Checklist
- Vue core has two primary work branches: `main` and `minor`. - Vue core has two primary work branches: `main` and `minor`.

View File

@ -80,6 +80,7 @@ Depending on the type of the PR, different considerations need to be taken into
- Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`. - Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`.
- Performance - Performance
- Be careful about code changes in "hot paths", in particular the Virtual DOM renderer (`runtime-core/src/renderer.ts`) and component instantiation code. - Be careful about code changes in "hot paths", in particular the Virtual DOM renderer (`runtime-core/src/renderer.ts`) and component instantiation code.
- Potential Breakage - Potential Breakage

View File

@ -7,108 +7,7 @@ on:
branches: branches:
- main - main
permissions:
contents: read # to fetch code (actions/checkout)
jobs: jobs:
unit-test: test:
runs-on: ubuntu-latest if: ${{ ! startsWith(github.event.head_commit.message, 'release:') && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository uses: ./.github/workflows/test.yml
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4.0.0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm install
- name: Run unit tests
run: pnpm run test-unit
lint-and-test-dts:
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4.0.0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm install
- name: Run eslint
run: pnpm run lint
- name: Run prettier
run: pnpm run format-check
- name: Run type declaration tests
run: pnpm run test-dts
release:
runs-on: ubuntu-latest
needs: [unit-test, lint-and-test-dts]
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- run: pnpm install
- name: Build
run: pnpm build --withTypes
- name: Publish
run: pnpm dlx pkg-pr-new@0.0 publish './packages/*' --template './playground' --pnpm
# benchmarks:
# runs-on: ubuntu-latest
# if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
# env:
# PUPPETEER_SKIP_DOWNLOAD: 'true'
# steps:
# - uses: actions/checkout@v4
# - name: Install pnpm
# uses: pnpm/action-setup@v3.0.0
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version-file: '.node-version'
# cache: 'pnpm'
# - run: pnpm install
# - name: Run benchmarks
# uses: CodSpeedHQ/action@v2
# with:
# run: pnpm vitest bench --run
# token: ${{ secrets.CODSPEED_TOKEN }}

55
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Release
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
test:
uses: ./.github/workflows/test.yml
release:
# prevents this action from running on forks
if: github.repository == 'vuejs/core'
needs: [test]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
# Use Release environment for deployment protection
environment: Release
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- name: Install deps
run: pnpm install
- name: Build and publish
id: publish
run: |
pnpm release --publishOnly
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub release
id: release_tag
uses: yyx990803/release-tag@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
body: |
For stable releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
For pre-releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/minor/CHANGELOG.md) of the `minor` branch.

View File

@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- minor
pull_request: pull_request:
branches: branches:
- main - main
@ -53,12 +54,16 @@ jobs:
name: size-data name: size-data
path: temp/size path: temp/size
- name: Save PR number - name: Save PR number & base branch
if: ${{github.event_name == 'pull_request'}} if: ${{github.event_name == 'pull_request'}}
run: echo ${{ github.event.number }} > ./pr.txt run: |
echo ${{ github.event.number }} > ./number.txt
echo ${{ github.base_ref }} > ./base.txt
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{github.event_name == 'pull_request'}} if: ${{github.event_name == 'pull_request'}}
with: with:
name: pr-number name: pr-info
path: pr.txt path: |
number.txt
base.txt

View File

@ -35,18 +35,24 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install
- name: Download PR number - name: Download PR info
uses: dawidd6/action-download-artifact@v6 uses: dawidd6/action-download-artifact@v6
with: with:
name: pr-number name: pr-info
run_id: ${{ github.event.workflow_run.id }} run_id: ${{ github.event.workflow_run.id }}
path: /tmp/pr-number path: /tmp/pr-info
- name: Read PR Number - name: Read PR Number
id: pr-number id: pr-number
uses: juliangruber/read-file-action@v1 uses: juliangruber/read-file-action@v1
with: with:
path: /tmp/pr-number/pr.txt path: /tmp/pr-info/number.txt
- name: Read PR base branch
id: pr-base
uses: juliangruber/read-file-action@v1
with:
path: /tmp/pr-info/base.txt
- name: Download Size Data - name: Download Size Data
uses: dawidd6/action-download-artifact@v6 uses: dawidd6/action-download-artifact@v6
@ -58,7 +64,7 @@ jobs:
- name: Download Previous Size Data - name: Download Previous Size Data
uses: dawidd6/action-download-artifact@v6 uses: dawidd6/action-download-artifact@v6
with: with:
branch: main branch: ${{ steps.pr-base.outputs.content }}
workflow: size-data.yml workflow: size-data.yml
event: push event: push
name: size-data name: size-data

82
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,82 @@
name: 'test'
on: workflow_call
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
unit-test:
runs-on: ubuntu-latest
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4.0.0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm install
- name: Run unit tests
run: pnpm run test-unit
lint-and-test-dts:
runs-on: ubuntu-latest
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4.0.0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm install
- name: Run eslint
run: pnpm run lint
- name: Run prettier
run: pnpm run format-check
- name: Run type declaration tests
run: pnpm run test-dts
release:
runs-on: ubuntu-latest
needs: [unit-test, lint-and-test-dts]
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4.0.0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- run: pnpm install
- name: Build
run: pnpm build --withTypes
- name: Publish
run: pnpm dlx pkg-pr-new@0.0 publish './packages/*' --template './playground' --pnpm

View File

@ -1,4 +1,3 @@
dist dist
*.md
*.html
pnpm-lock.yaml pnpm-lock.yaml
CHANGELOG*.md

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,6 @@ Please note that we do not consider XSS via template expressions a valid attack
We would like to thank the following security researchers for responsibly disclosing security issues to us. We would like to thank the following security researchers for responsibly disclosing security issues to us.
- Jeet Pal - [@jeetpal2007](https://github.com/jeetpal2007) | [Email](jeetpal2007@gmail.com) | [LinkedIn](https://in.linkedin.com/in/jeet-pal-22601a290 ) - Jeet Pal - [@jeetpal2007](https://github.com/jeetpal2007) | [Email](jeetpal2007@gmail.com) | [LinkedIn](https://in.linkedin.com/in/jeet-pal-22601a290)
- Mix - [@mnixry](https://github.com/mnixry) - Mix - [@mnixry](https://github.com/mnixry)
- Aviv Keller - [@RedYetiDev](https://github.com/redyetidev) | [LinkedIn](https://www.linkedin.com/in/redyetidev) <redyetidev@gmail.com> - Aviv Keller - [@RedYetiDev](https://github.com/redyetidev) | [LinkedIn](https://www.linkedin.com/in/redyetidev) <redyetidev@gmail.com>

1008
changelogs/CHANGELOG-3.4.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "3.5.0-alpha.5", "version": "3.5.0-beta.1",
"packageManager": "pnpm@9.6.0", "packageManager": "pnpm@9.6.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
@ -66,10 +66,11 @@
"@rollup/plugin-json": "^6.1.0", "@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "5.0.4", "@rollup/plugin-replace": "5.0.4",
"@swc/core": "^1.7.3", "@swc/core": "^1.7.6",
"@types/hash-sum": "^1.0.2", "@types/hash-sum": "^1.0.2",
"@types/node": "^20.14.13", "@types/node": "^20.14.14",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"@types/serve-handler": "^6.1.4",
"@vitest/coverage-istanbul": "^1.6.0", "@vitest/coverage-istanbul": "^1.6.0",
"@vitest/ui": "^1.6.0", "@vitest/ui": "^1.6.0",
"@vue/consolidate": "1.0.0", "@vue/consolidate": "1.0.0",
@ -82,30 +83,31 @@
"eslint-plugin-vitest": "^0.5.4", "eslint-plugin-vitest": "^0.5.4",
"estree-walker": "catalog:", "estree-walker": "catalog:",
"jsdom": "^24.1.1", "jsdom": "^24.1.1",
"lint-staged": "^15.2.7", "lint-staged": "^15.2.8",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"magic-string": "^0.30.10", "magic-string": "^0.30.11",
"markdown-table": "^3.0.3", "markdown-table": "^3.0.3",
"marked": "^12.0.2", "marked": "^13.0.3",
"npm-run-all2": "^6.2.2", "npm-run-all2": "^6.2.2",
"picocolors": "^1.0.1", "picocolors": "^1.0.1",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"pug": "^3.0.3", "pug": "^3.0.3",
"puppeteer": "~22.14.0", "puppeteer": "~22.15.0",
"rimraf": "^5.0.9", "rimraf": "^6.0.1",
"rollup": "^4.19.1", "rollup": "^4.20.0",
"rollup-plugin-dts": "^6.1.1", "rollup-plugin-dts": "^6.1.1",
"rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-polyfill-node": "^0.13.0",
"semver": "^7.6.3", "semver": "^7.6.3",
"serve": "^14.2.3", "serve": "^14.2.3",
"serve-handler": "^6.1.5",
"simple-git-hooks": "^2.11.1", "simple-git-hooks": "^2.11.1",
"todomvc-app-css": "^2.4.3", "todomvc-app-css": "^2.4.3",
"tslib": "^2.6.3", "tslib": "^2.6.3",
"tsx": "^4.16.2", "tsx": "^4.16.5",
"typescript": "~5.4.5", "typescript": "~5.5.4",
"typescript-eslint": "^7.17.0", "typescript-eslint": "^8.0.0",
"vite": "catalog:", "vite": "catalog:",
"vitest": "^1.6.0" "vitest": "^1.6.0"
}, },

View File

@ -14,44 +14,92 @@ return function render(_ctx, _cache, $props, $setup, $data, $options) {
}" }"
`; `;
exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for loop 1`] = ` exports[`compiler: expression transform > should allow leak of var declarations in for loop 1`] = `
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue "const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) { return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", { return (_openBlock(), _createElementBlock("div", {
onClick: () => { onClick: () => {
for (let i = 0; i < _ctx.list.length; i++) { for (var i = 0; i < _ctx.list.length; i++) {
_ctx.log(i) _ctx.log(i)
}
} }
_ctx.error(i)
}
}, null, 8 /* PROPS */, ["onClick"])) }, null, 8 /* PROPS */, ["onClick"]))
}" }"
`; `;
exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...in 1`] = ` exports[`compiler: expression transform > should not prefix catch block param 1`] = `
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue "const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) { return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", { return (_openBlock(), _createElementBlock("div", {
onClick: () => { onClick: () => {
for (const x in _ctx.list) { try {} catch (err) { console.error(err) }
_ctx.log(x) console.log(_ctx.err)
} }
}
}, null, 8 /* PROPS */, ["onClick"])) }, null, 8 /* PROPS */, ["onClick"]))
}" }"
`; `;
exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...of 1`] = ` exports[`compiler: expression transform > should not prefix destructured catch block param 1`] = `
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue "const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) { return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", { return (_openBlock(), _createElementBlock("div", {
onClick: () => { onClick: () => {
for (const x of _ctx.list) { try {
_ctx.log(x) throw new Error('sup?')
} } catch ({ message: { length } }) {
console.error(length)
} }
console.log(_ctx.length)
}
}, null, 8 /* PROPS */, ["onClick"]))
}"
`;
exports[`compiler: expression transform > should not prefix temp variable of for loop 1`] = `
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", {
onClick: () => {
for (let i = 0; i < _ctx.list.length; i++) {
_ctx.log(i)
}
_ctx.error(_ctx.i)
}
}, null, 8 /* PROPS */, ["onClick"]))
}"
`;
exports[`compiler: expression transform > should not prefix temp variable of for...in 1`] = `
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", {
onClick: () => {
for (const x in _ctx.list) {
_ctx.log(x)
}
_ctx.error(_ctx.x)
}
}, null, 8 /* PROPS */, ["onClick"]))
}"
`;
exports[`compiler: expression transform > should not prefix temp variable of for...of 1`] = `
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", {
onClick: () => {
for (const x of _ctx.list) {
_ctx.log(x)
}
_ctx.error(_ctx.x)
}
}, null, 8 /* PROPS */, ["onClick"])) }, null, 8 /* PROPS */, ["onClick"]))
}" }"
`; `;

View File

@ -27,6 +27,10 @@ function parseWithExpressionTransform(
return ast.children[0] return ast.children[0]
} }
function compile(template: string) {
return baseCompile(template, { prefixIdentifiers: true })
}
describe('compiler: expression transform', () => { describe('compiler: expression transform', () => {
test('interpolation (root)', () => { test('interpolation (root)', () => {
const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode
@ -291,6 +295,7 @@ describe('compiler: expression transform', () => {
], ],
}) })
}) })
test('should not prefix an object property key', () => { test('should not prefix an object property key', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { foo() { baz() }, value: bar } }}`, `{{ { foo() { baz() }, value: bar } }}`,
@ -457,6 +462,90 @@ describe('compiler: expression transform', () => {
}) })
}) })
test('should not prefix temp variable of for...in', () => {
const { code } = compile(
`<div @click="() => {
for (const x in list) {
log(x)
}
error(x)
}"/>`,
)
expect(code).not.toMatch(`log(_ctx.x)`)
expect(code).toMatch(`error(_ctx.x)`)
expect(code).toMatchSnapshot()
})
test('should not prefix temp variable of for...of', () => {
const { code } = compile(
`<div @click="() => {
for (const x of list) {
log(x)
}
error(x)
}"/>`,
)
expect(code).not.toMatch(`log(_ctx.x)`)
expect(code).toMatch(`error(_ctx.x)`)
expect(code).toMatchSnapshot()
})
test('should not prefix temp variable of for loop', () => {
const { code } = compile(
`<div @click="() => {
for (let i = 0; i < list.length; i++) {
log(i)
}
error(i)
}"/>`,
)
expect(code).not.toMatch(`log(_ctx.i)`)
expect(code).toMatch(`error(_ctx.i)`)
expect(code).toMatchSnapshot()
})
test('should allow leak of var declarations in for loop', () => {
const { code } = compile(
`<div @click="() => {
for (var i = 0; i < list.length; i++) {
log(i)
}
error(i)
}"/>`,
)
expect(code).not.toMatch(`log(_ctx.i)`)
expect(code).not.toMatch(`error(_ctx.i)`)
expect(code).toMatchSnapshot()
})
test('should not prefix catch block param', () => {
const { code } = compile(
`<div @click="() => {
try {} catch (err) { console.error(err) }
console.log(err)
}"/>`,
)
expect(code).not.toMatch(`console.error(_ctx.err)`)
expect(code).toMatch(`console.log(_ctx.err)`)
expect(code).toMatchSnapshot()
})
test('should not prefix destructured catch block param', () => {
const { code } = compile(
`<div @click="() => {
try {
throw new Error('sup?')
} catch ({ message: { length } }) {
console.error(length)
}
console.log(length)
}"/>`,
)
expect(code).not.toMatch(`console.error(_ctx.length)`)
expect(code).toMatch(`console.log(_ctx.length)`)
expect(code).toMatchSnapshot()
})
describe('ES Proposals support', () => { describe('ES Proposals support', () => {
test('bigInt', () => { test('bigInt', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
@ -555,42 +644,6 @@ describe('compiler: expression transform', () => {
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
test('should not prefix temp variable of for...in', () => {
const { code } = compileWithBindingMetadata(
`<div @click="() => {
for (const x in list) {
log(x)
}
}"/>`,
)
expect(code).not.toMatch(`_ctx.x`)
expect(code).toMatchSnapshot()
})
test('should not prefix temp variable of for...of', () => {
const { code } = compileWithBindingMetadata(
`<div @click="() => {
for (const x of list) {
log(x)
}
}"/>`,
)
expect(code).not.toMatch(`_ctx.x`)
expect(code).toMatchSnapshot()
})
test('should not prefix temp variable of for loop', () => {
const { code } = compileWithBindingMetadata(
`<div @click="() => {
for (let i = 0; i < list.length; i++) {
log(i)
}
}"/>`,
)
expect(code).not.toMatch(`_ctx.i`)
expect(code).toMatchSnapshot()
})
test('inline mode', () => { test('inline mode', () => {
const { code } = compileWithBindingMetadata( const { code } = compileWithBindingMetadata(
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`, `<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`,

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-core", "name": "@vue/compiler-core",
"version": "3.5.0-alpha.5", "version": "3.5.0-beta.1",
"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",
@ -48,7 +48,7 @@
"dependencies": { "dependencies": {
"@babel/parser": "catalog:", "@babel/parser": "catalog:",
"@vue/shared": "workspace:*", "@vue/shared": "workspace:*",
"entities": "^4.5.0", "entities": "^5.0.0",
"estree-walker": "catalog:", "estree-walker": "catalog:",
"source-map-js": "catalog:" "source-map-js": "catalog:"
}, },

View File

@ -625,7 +625,7 @@ export function createVNodeCall(
isBlock: VNodeCall['isBlock'] = false, isBlock: VNodeCall['isBlock'] = false,
disableTracking: VNodeCall['disableTracking'] = false, disableTracking: VNodeCall['disableTracking'] = false,
isComponent: VNodeCall['isComponent'] = false, isComponent: VNodeCall['isComponent'] = false,
loc = locStub, loc: SourceLocation = locStub,
): VNodeCall { ): VNodeCall {
if (context) { if (context) {
if (isBlock) { if (isBlock) {
@ -858,18 +858,24 @@ export function createReturnStatement(
} }
} }
export function getVNodeHelper(ssr: boolean, isComponent: boolean) { export function getVNodeHelper(
ssr: boolean,
isComponent: boolean,
): typeof CREATE_VNODE | typeof CREATE_ELEMENT_VNODE {
return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE
} }
export function getVNodeBlockHelper(ssr: boolean, isComponent: boolean) { export function getVNodeBlockHelper(
ssr: boolean,
isComponent: boolean,
): typeof CREATE_BLOCK | typeof CREATE_ELEMENT_BLOCK {
return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK
} }
export function convertToBlock( export function convertToBlock(
node: VNodeCall, node: VNodeCall,
{ helper, removeHelper, inSSR }: TransformContext, { helper, removeHelper, inSSR }: TransformContext,
) { ): void {
if (!node.isBlock) { if (!node.isBlock) {
node.isBlock = true node.isBlock = true
removeHelper(getVNodeHelper(inSSR, node.isComponent)) removeHelper(getVNodeHelper(inSSR, node.isComponent))

View File

@ -2,6 +2,9 @@
// do not import runtime methods // do not import runtime methods
import type { import type {
BlockStatement, BlockStatement,
ForInStatement,
ForOfStatement,
ForStatement,
Function, Function,
Identifier, Identifier,
Node, Node,
@ -25,7 +28,7 @@ export function walkIdentifiers(
includeAll = false, includeAll = false,
parentStack: Node[] = [], parentStack: Node[] = [],
knownIds: Record<string, number> = Object.create(null), knownIds: Record<string, number> = Object.create(null),
) { ): void {
if (__BROWSER__) { if (__BROWSER__) {
return return
} }
@ -77,6 +80,14 @@ export function walkIdentifiers(
markScopeIdentifier(node, id, knownIds), markScopeIdentifier(node, id, knownIds),
) )
} }
} else if (node.type === 'CatchClause' && node.param) {
for (const id of extractIdentifiers(node.param)) {
markScopeIdentifier(node, id, knownIds)
}
} else if (isForStatement(node)) {
walkForStatement(node, false, id =>
markScopeIdentifier(node, id, knownIds),
)
} }
}, },
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) { leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
@ -97,7 +108,7 @@ export function isReferencedIdentifier(
id: Identifier, id: Identifier,
parent: Node | null, parent: Node | null,
parentStack: Node[], parentStack: Node[],
) { ): boolean {
if (__BROWSER__) { if (__BROWSER__) {
return false return false
} }
@ -166,7 +177,7 @@ export function isInNewExpression(parentStack: Node[]): boolean {
export function walkFunctionParams( export function walkFunctionParams(
node: Function, node: Function,
onIdent: (id: Identifier) => void, onIdent: (id: Identifier) => void,
) { ): void {
for (const p of node.params) { for (const p of node.params) {
for (const id of extractIdentifiers(p)) { for (const id of extractIdentifiers(p)) {
onIdent(id) onIdent(id)
@ -177,7 +188,7 @@ export function walkFunctionParams(
export function walkBlockDeclarations( export function walkBlockDeclarations(
block: BlockStatement | Program, block: BlockStatement | Program,
onIdent: (node: Identifier) => void, onIdent: (node: Identifier) => void,
) { ): void {
for (const stmt of block.body) { for (const stmt of block.body) {
if (stmt.type === 'VariableDeclaration') { if (stmt.type === 'VariableDeclaration') {
if (stmt.declare) continue if (stmt.declare) continue
@ -192,18 +203,36 @@ export function walkBlockDeclarations(
) { ) {
if (stmt.declare || !stmt.id) continue if (stmt.declare || !stmt.id) continue
onIdent(stmt.id) onIdent(stmt.id)
} else if ( } else if (isForStatement(stmt)) {
stmt.type === 'ForOfStatement' || walkForStatement(stmt, true, onIdent)
stmt.type === 'ForInStatement' || }
stmt.type === 'ForStatement' }
) { }
const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left
if (variable && variable.type === 'VariableDeclaration') { function isForStatement(
for (const decl of variable.declarations) { stmt: Node,
for (const id of extractIdentifiers(decl.id)) { ): stmt is ForStatement | ForOfStatement | ForInStatement {
onIdent(id) return (
} stmt.type === 'ForOfStatement' ||
} stmt.type === 'ForInStatement' ||
stmt.type === 'ForStatement'
)
}
function walkForStatement(
stmt: ForStatement | ForOfStatement | ForInStatement,
isVar: boolean,
onIdent: (id: Identifier) => void,
) {
const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left
if (
variable &&
variable.type === 'VariableDeclaration' &&
(variable.kind === 'var' ? isVar : !isVar)
) {
for (const decl of variable.declarations) {
for (const id of extractIdentifiers(decl.id)) {
onIdent(id)
} }
} }
} }
@ -284,7 +313,7 @@ export const isStaticProperty = (node?: Node): node is ObjectProperty =>
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') && (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
!node.computed !node.computed
export const isStaticPropertyKey = (node: Node, parent: Node) => export const isStaticPropertyKey = (node: Node, parent: Node): boolean =>
isStaticProperty(parent) && parent.key === node isStaticProperty(parent) && parent.key === node
/** /**
@ -466,7 +495,7 @@ function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
return true return true
} }
export const TS_NODE_TYPES = [ export const TS_NODE_TYPES: string[] = [
'TSAsExpression', // foo as number 'TSAsExpression', // foo as number
'TSTypeAssertion', // (<number>foo) 'TSTypeAssertion', // (<number>foo)
'TSNonNullExpression', // foo! 'TSNonNullExpression', // foo!

View File

@ -106,7 +106,7 @@ function getCompatValue(
export function isCompatEnabled( export function isCompatEnabled(
key: CompilerDeprecationTypes, key: CompilerDeprecationTypes,
context: MergedParserOptions | TransformContext, context: MergedParserOptions | TransformContext,
) { ): boolean {
const mode = getCompatValue('MODE', context) const mode = getCompatValue('MODE', context)
const value = getCompatValue(key, context) const value = getCompatValue(key, context)
// in v3 mode, only enable if explicitly set to true // in v3 mode, only enable if explicitly set to true
@ -132,7 +132,7 @@ export function warnDeprecation(
context: MergedParserOptions | TransformContext, context: MergedParserOptions | TransformContext,
loc: SourceLocation | null, loc: SourceLocation | null,
...args: any[] ...args: any[]
) { ): void {
const val = getCompatValue(key, context) const val = getCompatValue(key, context)
if (val === 'suppress-warning') { if (val === 'suppress-warning') {
return return

View File

@ -9,11 +9,11 @@ export interface CoreCompilerError extends CompilerError {
code: ErrorCodes code: ErrorCodes
} }
export function defaultOnError(error: CompilerError) { export function defaultOnError(error: CompilerError): never {
throw error throw error
} }
export function defaultOnWarn(msg: CompilerError) { export function defaultOnWarn(msg: CompilerError): void {
__DEV__ && console.warn(`[Vue warn] ${msg.message}`) __DEV__ && console.warn(`[Vue warn] ${msg.message}`)
} }

View File

@ -44,7 +44,7 @@ import {
isSimpleIdentifier, isSimpleIdentifier,
isStaticArgOf, isStaticArgOf,
} from './utils' } from './utils'
import { decodeHTML } from 'entities/lib/decode.js' import { decodeHTML } from 'entities/dist/decode.js'
import { import {
type ParserOptions as BabelOptions, type ParserOptions as BabelOptions,
parse, parse,

View File

@ -1,51 +1,85 @@
export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``) export const FRAGMENT: unique symbol = Symbol(__DEV__ ? `Fragment` : ``)
export const TELEPORT = Symbol(__DEV__ ? `Teleport` : ``) export const TELEPORT: unique symbol = Symbol(__DEV__ ? `Teleport` : ``)
export const SUSPENSE = Symbol(__DEV__ ? `Suspense` : ``) export const SUSPENSE: unique symbol = Symbol(__DEV__ ? `Suspense` : ``)
export const KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``) export const KEEP_ALIVE: unique symbol = Symbol(__DEV__ ? `KeepAlive` : ``)
export const BASE_TRANSITION = Symbol(__DEV__ ? `BaseTransition` : ``) export const BASE_TRANSITION: unique symbol = Symbol(
export const OPEN_BLOCK = Symbol(__DEV__ ? `openBlock` : ``) __DEV__ ? `BaseTransition` : ``,
export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``) )
export const CREATE_ELEMENT_BLOCK = Symbol(__DEV__ ? `createElementBlock` : ``) export const OPEN_BLOCK: unique symbol = Symbol(__DEV__ ? `openBlock` : ``)
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``) export const CREATE_BLOCK: unique symbol = Symbol(__DEV__ ? `createBlock` : ``)
export const CREATE_ELEMENT_VNODE = Symbol(__DEV__ ? `createElementVNode` : ``) export const CREATE_ELEMENT_BLOCK: unique symbol = Symbol(
export const CREATE_COMMENT = Symbol(__DEV__ ? `createCommentVNode` : ``) __DEV__ ? `createElementBlock` : ``,
export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``) )
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``) export const CREATE_VNODE: unique symbol = Symbol(__DEV__ ? `createVNode` : ``)
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``) export const CREATE_ELEMENT_VNODE: unique symbol = Symbol(
export const RESOLVE_DYNAMIC_COMPONENT = Symbol( __DEV__ ? `createElementVNode` : ``,
)
export const CREATE_COMMENT: unique symbol = Symbol(
__DEV__ ? `createCommentVNode` : ``,
)
export const CREATE_TEXT: unique symbol = Symbol(
__DEV__ ? `createTextVNode` : ``,
)
export const CREATE_STATIC: unique symbol = Symbol(
__DEV__ ? `createStaticVNode` : ``,
)
export const RESOLVE_COMPONENT: unique symbol = Symbol(
__DEV__ ? `resolveComponent` : ``,
)
export const RESOLVE_DYNAMIC_COMPONENT: unique symbol = Symbol(
__DEV__ ? `resolveDynamicComponent` : ``, __DEV__ ? `resolveDynamicComponent` : ``,
) )
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``) export const RESOLVE_DIRECTIVE: unique symbol = Symbol(
export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``) __DEV__ ? `resolveDirective` : ``,
export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``) )
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``) export const RESOLVE_FILTER: unique symbol = Symbol(
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``) __DEV__ ? `resolveFilter` : ``,
export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``) )
export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``) export const WITH_DIRECTIVES: unique symbol = Symbol(
export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``) __DEV__ ? `withDirectives` : ``,
export const NORMALIZE_CLASS = Symbol(__DEV__ ? `normalizeClass` : ``) )
export const NORMALIZE_STYLE = Symbol(__DEV__ ? `normalizeStyle` : ``) export const RENDER_LIST: unique symbol = Symbol(__DEV__ ? `renderList` : ``)
export const NORMALIZE_PROPS = Symbol(__DEV__ ? `normalizeProps` : ``) export const RENDER_SLOT: unique symbol = Symbol(__DEV__ ? `renderSlot` : ``)
export const GUARD_REACTIVE_PROPS = Symbol(__DEV__ ? `guardReactiveProps` : ``) export const CREATE_SLOTS: unique symbol = Symbol(__DEV__ ? `createSlots` : ``)
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``) export const TO_DISPLAY_STRING: unique symbol = Symbol(
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``) __DEV__ ? `toDisplayString` : ``,
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``) )
export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``) export const MERGE_PROPS: unique symbol = Symbol(__DEV__ ? `mergeProps` : ``)
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``) export const NORMALIZE_CLASS: unique symbol = Symbol(
__DEV__ ? `normalizeClass` : ``,
)
export const NORMALIZE_STYLE: unique symbol = Symbol(
__DEV__ ? `normalizeStyle` : ``,
)
export const NORMALIZE_PROPS: unique symbol = Symbol(
__DEV__ ? `normalizeProps` : ``,
)
export const GUARD_REACTIVE_PROPS: unique symbol = Symbol(
__DEV__ ? `guardReactiveProps` : ``,
)
export const TO_HANDLERS: unique symbol = Symbol(__DEV__ ? `toHandlers` : ``)
export const CAMELIZE: unique symbol = Symbol(__DEV__ ? `camelize` : ``)
export const CAPITALIZE: unique symbol = Symbol(__DEV__ ? `capitalize` : ``)
export const TO_HANDLER_KEY: unique symbol = Symbol(
__DEV__ ? `toHandlerKey` : ``,
)
export const SET_BLOCK_TRACKING: unique symbol = Symbol(
__DEV__ ? `setBlockTracking` : ``,
)
/** /**
* @deprecated no longer needed in 3.5+ because we no longer hoist element nodes * @deprecated no longer needed in 3.5+ because we no longer hoist element nodes
* but kept for backwards compat * but kept for backwards compat
*/ */
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``) export const PUSH_SCOPE_ID: unique symbol = Symbol(__DEV__ ? `pushScopeId` : ``)
/** /**
* @deprecated kept for backwards compat * @deprecated kept for backwards compat
*/ */
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``) export const POP_SCOPE_ID: unique symbol = Symbol(__DEV__ ? `popScopeId` : ``)
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``) export const WITH_CTX: unique symbol = Symbol(__DEV__ ? `withCtx` : ``)
export const UNREF = Symbol(__DEV__ ? `unref` : ``) export const UNREF: unique symbol = Symbol(__DEV__ ? `unref` : ``)
export const IS_REF = Symbol(__DEV__ ? `isRef` : ``) export const IS_REF: unique symbol = Symbol(__DEV__ ? `isRef` : ``)
export const WITH_MEMO = Symbol(__DEV__ ? `withMemo` : ``) export const WITH_MEMO: unique symbol = Symbol(__DEV__ ? `withMemo` : ``)
export const IS_MEMO_SAME = Symbol(__DEV__ ? `isMemoSame` : ``) export const IS_MEMO_SAME: unique symbol = Symbol(__DEV__ ? `isMemoSame` : ``)
// Name mapping for runtime helpers that need to be imported from 'vue' in // Name mapping for runtime helpers that need to be imported from 'vue' in
// generated code. Make sure these are correctly exported in the runtime! // generated code. Make sure these are correctly exported in the runtime!
@ -91,7 +125,7 @@ export const helperNameMap: Record<symbol, string> = {
[IS_MEMO_SAME]: `isMemoSame`, [IS_MEMO_SAME]: `isMemoSame`,
} }
export function registerRuntimeHelpers(helpers: Record<symbol, string>) { export function registerRuntimeHelpers(helpers: Record<symbol, string>): void {
Object.getOwnPropertySymbols(helpers).forEach(s => { Object.getOwnPropertySymbols(helpers).forEach(s => {
helperNameMap[s] = helpers[s] helperNameMap[s] = helpers[s]
}) })

View File

@ -36,7 +36,7 @@ import {
EntityDecoder, EntityDecoder,
fromCodePoint, fromCodePoint,
htmlDecodeTree, htmlDecodeTree,
} from 'entities/lib/decode.js' } from 'entities/dist/decode.js'
export enum ParseMode { export enum ParseMode {
BASE, BASE,
@ -213,7 +213,15 @@ export interface Callbacks {
* We don't have `Script`, `Style`, or `Title` here. Instead, we re-use the *End * We don't have `Script`, `Style`, or `Title` here. Instead, we re-use the *End
* sequences with an increased offset. * sequences with an increased offset.
*/ */
export const Sequences = { export const Sequences: {
Cdata: Uint8Array
CdataEnd: Uint8Array
CommentEnd: Uint8Array
ScriptEnd: Uint8Array
StyleEnd: Uint8Array
TitleEnd: Uint8Array
TextareaEnd: Uint8Array
} = {
Cdata: new Uint8Array([0x43, 0x44, 0x41, 0x54, 0x41, 0x5b]), // CDATA[ Cdata: new Uint8Array([0x43, 0x44, 0x41, 0x54, 0x41, 0x5b]), // CDATA[
CdataEnd: new Uint8Array([0x5d, 0x5d, 0x3e]), // ]]> CdataEnd: new Uint8Array([0x5d, 0x5d, 0x3e]), // ]]>
CommentEnd: new Uint8Array([0x2d, 0x2d, 0x3e]), // `-->` CommentEnd: new Uint8Array([0x2d, 0x2d, 0x3e]), // `-->`
@ -227,7 +235,7 @@ export const Sequences = {
export default class Tokenizer { export default class Tokenizer {
/** The current state the tokenizer is in. */ /** The current state the tokenizer is in. */
public state = State.Text public state: State = State.Text
/** The read buffer. */ /** The read buffer. */
private buffer = '' private buffer = ''
/** The beginning of the section that is currently being read. */ /** The beginning of the section that is currently being read. */
@ -249,8 +257,8 @@ export default class Tokenizer {
private readonly entityDecoder?: EntityDecoder private readonly entityDecoder?: EntityDecoder
public mode = ParseMode.BASE public mode: ParseMode = ParseMode.BASE
public get inSFCRoot() { public get inSFCRoot(): boolean {
return this.mode === ParseMode.SFC && this.stack.length === 0 return this.mode === ParseMode.SFC && this.stack.length === 0
} }
@ -526,7 +534,7 @@ export default class Tokenizer {
this.state = State.SpecialStartSequence this.state = State.SpecialStartSequence
} }
public enterRCDATA(sequence: Uint8Array, offset: number) { public enterRCDATA(sequence: Uint8Array, offset: number): void {
this.inRCDATA = true this.inRCDATA = true
this.currentSequence = sequence this.currentSequence = sequence
this.sequenceIndex = offset this.sequenceIndex = offset
@ -917,7 +925,7 @@ export default class Tokenizer {
* *
* States that are more likely to be hit are higher up, as a performance improvement. * States that are more likely to be hit are higher up, as a performance improvement.
*/ */
public parse(input: string) { public parse(input: string): void {
this.buffer = input this.buffer = input
while (this.index < this.buffer.length) { while (this.index < this.buffer.length) {
const c = this.buffer.charCodeAt(this.index) const c = this.buffer.charCodeAt(this.index)

View File

@ -326,7 +326,7 @@ export function createTransformContext(
return context return context
} }
export function transform(root: RootNode, options: TransformOptions) { export function transform(root: RootNode, options: TransformOptions): void {
const context = createTransformContext(root, options) const context = createTransformContext(root, options)
traverseNode(root, context) traverseNode(root, context)
if (options.hoistStatic) { if (options.hoistStatic) {
@ -403,7 +403,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
export function traverseChildren( export function traverseChildren(
parent: ParentNode, parent: ParentNode,
context: TransformContext, context: TransformContext,
) { ): void {
let i = 0 let i = 0
const nodeRemoved = () => { const nodeRemoved = () => {
i-- i--
@ -422,7 +422,7 @@ export function traverseChildren(
export function traverseNode( export function traverseNode(
node: RootNode | TemplateChildNode, node: RootNode | TemplateChildNode,
context: TransformContext, context: TransformContext,
) { ): void {
context.currentNode = node context.currentNode = node
// apply transform plugins // apply transform plugins
const { nodeTransforms } = context const { nodeTransforms } = context

View File

@ -31,7 +31,7 @@ import {
OPEN_BLOCK, OPEN_BLOCK,
} from '../runtimeHelpers' } from '../runtimeHelpers'
export function cacheStatic(root: RootNode, context: TransformContext) { export function cacheStatic(root: RootNode, context: TransformContext): void {
walk( walk(
root, root,
undefined, undefined,

View File

@ -228,7 +228,7 @@ export function resolveComponentType(
node: ComponentNode, node: ComponentNode,
context: TransformContext, context: TransformContext,
ssr = false, ssr = false,
) { ): string | symbol | CallExpression {
let { tag } = node let { tag } = node
// 1. dynamic component // 1. dynamic component
@ -250,7 +250,7 @@ export function resolveComponentType(
exp = isProp.exp exp = isProp.exp
if (!exp) { if (!exp) {
// #10469 handle :is shorthand // #10469 handle :is shorthand
exp = createSimpleExpression(`is`, false, isProp.loc) exp = createSimpleExpression(`is`, false, isProp.arg!.loc)
if (!__BROWSER__) { if (!__BROWSER__) {
exp = isProp.exp = processExpression(exp, context) exp = isProp.exp = processExpression(exp, context)
} }
@ -374,7 +374,7 @@ export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
export function buildProps( export function buildProps(
node: ElementNode, node: ElementNode,
context: TransformContext, context: TransformContext,
props: ElementNode['props'] = node.props, props: ElementNode['props'] | undefined = node.props,
isComponent: boolean, isComponent: boolean,
isDynamicComponent: boolean, isDynamicComponent: boolean,
ssr = false, ssr = false,

View File

@ -99,7 +99,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
export const transformBindShorthand = ( export const transformBindShorthand = (
dir: DirectiveNode, dir: DirectiveNode,
context: TransformContext, context: TransformContext,
) => { ): void => {
const arg = dir.arg! const arg = dir.arg!
const propName = camelize((arg as SimpleExpressionNode).content) const propName = camelize((arg as SimpleExpressionNode).content)

View File

@ -1,4 +1,5 @@
import { import {
type NodeTransform,
type TransformContext, type TransformContext,
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
} from '../transform' } from '../transform'
@ -49,7 +50,7 @@ import { validateBrowserExpression } from '../validateExpression'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { transformBindShorthand } from './vBind' import { transformBindShorthand } from './vBind'
export const transformFor = createStructuralDirectiveTransform( export const transformFor: NodeTransform = createStructuralDirectiveTransform(
'for', 'for',
(node, dir, context) => { (node, dir, context) => {
const { helper, removeHelper } = context const { helper, removeHelper } = context
@ -299,7 +300,7 @@ export function processFor(
const onExit = processCodegen && processCodegen(forNode) const onExit = processCodegen && processCodegen(forNode)
return () => { return (): void => {
scopes.vFor-- scopes.vFor--
if (!__BROWSER__ && context.prefixIdentifiers) { if (!__BROWSER__ && context.prefixIdentifiers) {
value && removeIdentifiers(value) value && removeIdentifiers(value)
@ -313,7 +314,7 @@ export function processFor(
export function finalizeForParseResult( export function finalizeForParseResult(
result: ForParseResult, result: ForParseResult,
context: TransformContext, context: TransformContext,
) { ): void {
if (result.finalized) return if (result.finalized) return
if (!__BROWSER__ && context.prefixIdentifiers) { if (!__BROWSER__ && context.prefixIdentifiers) {

View File

@ -1,4 +1,5 @@
import { import {
type NodeTransform,
type TransformContext, type TransformContext,
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
traverseNode, traverseNode,
@ -33,7 +34,7 @@ import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils' import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
import { PatchFlagNames, PatchFlags } from '@vue/shared' import { PatchFlagNames, PatchFlags } from '@vue/shared'
export const transformIf = createStructuralDirectiveTransform( export const transformIf: NodeTransform = createStructuralDirectiveTransform(
/^(if|else|else-if)$/, /^(if|else|else-if)$/,
(node, dir, context) => { (node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => { return processIf(node, dir, context, (ifNode, branch, isRoot) => {
@ -83,7 +84,7 @@ export function processIf(
branch: IfBranchNode, branch: IfBranchNode,
isRoot: boolean, isRoot: boolean,
) => (() => void) | undefined, ) => (() => void) | undefined,
) { ): (() => void) | undefined {
if ( if (
dir.name !== 'else' && dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim()) (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())

View File

@ -153,18 +153,15 @@ export const isMemberExpressionBrowser = (path: string): boolean => {
return !currentOpenBracketCount && !currentOpenParensCount return !currentOpenBracketCount && !currentOpenParensCount
} }
export const isMemberExpressionNode = __BROWSER__ export const isMemberExpressionNode: (
? (NOOP as any as ( path: string,
path: string, context: Pick<TransformContext, 'expressionPlugins'>,
options: Pick<TransformContext, 'expressionPlugins'>, ) => boolean = __BROWSER__
) => boolean) ? (NOOP as any)
: ( : (path, context): boolean => {
path: string,
options: Pick<TransformContext, 'expressionPlugins'>,
): boolean => {
try { try {
let ret: Expression = parseExpression(path, { let ret: Expression = parseExpression(path, {
plugins: options.expressionPlugins, plugins: context.expressionPlugins,
}) })
ret = unwrapTSNode(ret) as Expression ret = unwrapTSNode(ret) as Expression
return ( return (
@ -177,9 +174,10 @@ export const isMemberExpressionNode = __BROWSER__
} }
} }
export const isMemberExpression = __BROWSER__ export const isMemberExpression: (
? isMemberExpressionBrowser path: string,
: isMemberExpressionNode context: TransformContext,
) => boolean = __BROWSER__ ? isMemberExpressionBrowser : isMemberExpressionNode
export function advancePositionWithClone( export function advancePositionWithClone(
pos: Position, pos: Position,
@ -223,7 +221,7 @@ export function advancePositionWithMutation(
return pos return pos
} }
export function assert(condition: boolean, msg?: string) { export function assert(condition: boolean, msg?: string): void {
/* istanbul ignore if */ /* istanbul ignore if */
if (!condition) { if (!condition) {
throw new Error(msg || `unexpected compiler condition`) throw new Error(msg || `unexpected compiler condition`)
@ -338,7 +336,7 @@ export function injectProp(
node: VNodeCall | RenderSlotCall, node: VNodeCall | RenderSlotCall,
prop: Property, prop: Property,
context: TransformContext, context: TransformContext,
) { ): void {
let propsWithInjection: ObjectExpression | CallExpression | undefined let propsWithInjection: ObjectExpression | CallExpression | undefined
/** /**
* 1. mergeProps(...) * 1. mergeProps(...)
@ -505,7 +503,9 @@ export function hasScopeRef(
} }
} }
export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) { export function getMemoedVNodeCall(
node: BlockCodegenNode | MemoExpression,
): VNodeCall | RenderSlotCall {
if (node.type === NodeTypes.JS_CALL_EXPRESSION && node.callee === WITH_MEMO) { if (node.type === NodeTypes.JS_CALL_EXPRESSION && node.callee === WITH_MEMO) {
return node.arguments[1].returns as VNodeCall return node.arguments[1].returns as VNodeCall
} else { } else {
@ -513,4 +513,4 @@ export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) {
} }
} }
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/ export const forAliasRE: RegExp = /([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/

View File

@ -30,7 +30,7 @@ export function validateBrowserExpression(
context: TransformContext, context: TransformContext,
asParams = false, asParams = false,
asRawStatements = false, asRawStatements = false,
) { ): void {
const exp = node.content const exp = node.content
// empty expressions are validated per-directive since some directives // empty expressions are validated per-directive since some directives

View File

@ -1 +1 @@
# @vue/compiler-dom # @vue/compiler-dom

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-dom", "name": "@vue/compiler-dom",
"version": "3.5.0-alpha.5", "version": "3.5.0-beta.1",
"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",

View File

@ -1,18 +1,30 @@
import { registerRuntimeHelpers } from '@vue/compiler-core' import { registerRuntimeHelpers } from '@vue/compiler-core'
export const V_MODEL_RADIO = Symbol(__DEV__ ? `vModelRadio` : ``) export const V_MODEL_RADIO: unique symbol = Symbol(__DEV__ ? `vModelRadio` : ``)
export const V_MODEL_CHECKBOX = Symbol(__DEV__ ? `vModelCheckbox` : ``) export const V_MODEL_CHECKBOX: unique symbol = Symbol(
export const V_MODEL_TEXT = Symbol(__DEV__ ? `vModelText` : ``) __DEV__ ? `vModelCheckbox` : ``,
export const V_MODEL_SELECT = Symbol(__DEV__ ? `vModelSelect` : ``) )
export const V_MODEL_DYNAMIC = Symbol(__DEV__ ? `vModelDynamic` : ``) export const V_MODEL_TEXT: unique symbol = Symbol(__DEV__ ? `vModelText` : ``)
export const V_MODEL_SELECT: unique symbol = Symbol(
__DEV__ ? `vModelSelect` : ``,
)
export const V_MODEL_DYNAMIC: unique symbol = Symbol(
__DEV__ ? `vModelDynamic` : ``,
)
export const V_ON_WITH_MODIFIERS = Symbol(__DEV__ ? `vOnModifiersGuard` : ``) export const V_ON_WITH_MODIFIERS: unique symbol = Symbol(
export const V_ON_WITH_KEYS = Symbol(__DEV__ ? `vOnKeysGuard` : ``) __DEV__ ? `vOnModifiersGuard` : ``,
)
export const V_ON_WITH_KEYS: unique symbol = Symbol(
__DEV__ ? `vOnKeysGuard` : ``,
)
export const V_SHOW = Symbol(__DEV__ ? `vShow` : ``) export const V_SHOW: unique symbol = Symbol(__DEV__ ? `vShow` : ``)
export const TRANSITION = Symbol(__DEV__ ? `Transition` : ``) export const TRANSITION: unique symbol = Symbol(__DEV__ ? `Transition` : ``)
export const TRANSITION_GROUP = Symbol(__DEV__ ? `TransitionGroup` : ``) export const TRANSITION_GROUP: unique symbol = Symbol(
__DEV__ ? `TransitionGroup` : ``,
)
registerRuntimeHelpers({ registerRuntimeHelpers({
[V_MODEL_RADIO]: `vModelRadio`, [V_MODEL_RADIO]: `vModelRadio`,

View File

@ -425,5 +425,15 @@ h1 { color: red }
`At least one <template> or <script> is required in a single file component`, `At least one <template> or <script> is required in a single file component`,
) )
}) })
test('should throw error if template functional is given', () => {
assertWarning(
parse(`<template functional></template>`).errors,
`<template functional> is no longer supported in Vue 3, since ` +
`functional components no longer have significant performance ` +
`difference from stateful ones. Just use a normal <template> ` +
`instead.`,
)
})
}) })
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-sfc", "name": "@vue/compiler-sfc",
"version": "3.5.0-alpha.5", "version": "3.5.0-beta.1",
"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",
@ -59,7 +59,7 @@
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"lru-cache": "10.1.0", "lru-cache": "10.1.0",
"merge-source-map": "^1.1.0", "merge-source-map": "^1.1.0",
"minimatch": "^9.0.5", "minimatch": "~9.0.5",
"postcss-modules": "^6.0.0", "postcss-modules": "^6.0.0",
"postcss-selector-parser": "^6.1.1", "postcss-selector-parser": "^6.1.1",
"pug": "^3.0.3", "pug": "^3.0.3",

View File

@ -1,4 +1,4 @@
export const version = __VERSION__ export const version: string = __VERSION__
// API // API
export { parse } from './parse' export { parse } from './parse'
@ -18,7 +18,7 @@ import {
errorMessages as coreErrorMessages, errorMessages as coreErrorMessages,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
export const errorMessages = { export const errorMessages: Record<number, string> = {
...coreErrorMessages, ...coreErrorMessages,
...DOMErrorMessages, ...DOMErrorMessages,
} }

View File

@ -17,6 +17,7 @@ import { parseCssVars } from './style/cssVars'
import { createCache } from './cache' import { createCache } from './cache'
import type { ImportBinding } from './compileScript' import type { ImportBinding } from './compileScript'
import { isImportUsed } from './script/importUsageCheck' import { isImportUsed } from './script/importUsageCheck'
import type { LRUCache } from 'lru-cache'
export const DEFAULT_FILENAME = 'anonymous.vue' export const DEFAULT_FILENAME = 'anonymous.vue'
@ -105,7 +106,9 @@ export interface SFCParseResult {
errors: (CompilerError | SyntaxError)[] errors: (CompilerError | SyntaxError)[]
} }
export const parseCache = createCache<SFCParseResult>() export const parseCache:
| Map<string, SFCParseResult>
| LRUCache<string, SFCParseResult> = createCache<SFCParseResult>()
function genCacheKey(source: string, options: SFCParseOptions): string { function genCacheKey(source: string, options: SFCParseOptions): string {
return ( return (

View File

@ -17,11 +17,12 @@ export class ScriptCompileContext {
scriptAst: Program | null scriptAst: Program | null
scriptSetupAst: Program | null scriptSetupAst: Program | null
source = this.descriptor.source source: string = this.descriptor.source
filename = this.descriptor.filename filename: string = this.descriptor.filename
s = new MagicString(this.source) s: MagicString = new MagicString(this.source)
startOffset = this.descriptor.scriptSetup?.loc.start.offset startOffset: number | undefined =
endOffset = this.descriptor.scriptSetup?.loc.end.offset this.descriptor.scriptSetup?.loc.start.offset
endOffset: number | undefined = this.descriptor.scriptSetup?.loc.end.offset
// import / type analysis // import / type analysis
scope?: TypeScope scope?: TypeScope
@ -163,7 +164,7 @@ export function resolveParserPlugins(
lang: string, lang: string,
userPlugins?: ParserPlugin[], userPlugins?: ParserPlugin[],
dts = false, dts = false,
) { ): ParserPlugin[] {
const plugins: ParserPlugin[] = [] const plugins: ParserPlugin[] = []
if ( if (
!userPlugins || !userPlugins ||

View File

@ -48,7 +48,7 @@ export function processDefineProps(
ctx: ScriptCompileContext, ctx: ScriptCompileContext,
node: Node, node: Node,
declId?: LVal, declId?: LVal,
) { ): boolean {
if (!isCallOf(node, DEFINE_PROPS)) { if (!isCallOf(node, DEFINE_PROPS)) {
return processWithDefaults(ctx, node, declId) return processWithDefaults(ctx, node, declId)
} }

View File

@ -26,7 +26,7 @@ import { DEFINE_PROPS } from './defineProps'
export function processPropsDestructure( export function processPropsDestructure(
ctx: ScriptCompileContext, ctx: ScriptCompileContext,
declId: ObjectPattern, declId: ObjectPattern,
) { ): void {
if (ctx.options.propsDestructure === 'error') { if (ctx.options.propsDestructure === 'error') {
ctx.error(`Props destructure is explicitly prohibited via config.`, declId) ctx.error(`Props destructure is explicitly prohibited via config.`, declId)
} else if (ctx.options.propsDestructure === false) { } else if (ctx.options.propsDestructure === false) {
@ -97,7 +97,7 @@ type Scope = Record<string, boolean>
export function transformDestructuredProps( export function transformDestructuredProps(
ctx: ScriptCompileContext, ctx: ScriptCompileContext,
vueImportAliases: Record<string, string>, vueImportAliases: Record<string, string>,
) { ): void {
if (ctx.options.propsDestructure === false) { if (ctx.options.propsDestructure === false) {
return return
} }

View File

@ -3,13 +3,14 @@ import type { ScriptCompileContext } from './context'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import { rewriteDefaultAST } from '../rewriteDefault' import { rewriteDefaultAST } from '../rewriteDefault'
import { genNormalScriptCssVarsCode } from '../style/cssVars' import { genNormalScriptCssVarsCode } from '../style/cssVars'
import type { SFCScriptBlock } from '../parse'
export const normalScriptDefaultVar = `__default__` export const normalScriptDefaultVar = `__default__`
export function processNormalScript( export function processNormalScript(
ctx: ScriptCompileContext, ctx: ScriptCompileContext,
scopeId: string, scopeId: string,
) { ): SFCScriptBlock {
const script = ctx.descriptor.script! const script = ctx.descriptor.script!
if (script.lang && !ctx.isJS && !ctx.isTS) { if (script.lang && !ctx.isJS && !ctx.isTS) {
// do not process non js/ts script blocks // do not process non js/ts script blocks

View File

@ -823,7 +823,7 @@ let loadTS: (() => typeof TS) | undefined
/** /**
* @private * @private
*/ */
export function registerTS(_loadTS: () => typeof TS) { export function registerTS(_loadTS: () => typeof TS): void {
loadTS = () => { loadTS = () => {
try { try {
return _loadTS() return _loadTS()
@ -1107,7 +1107,7 @@ const fileToScopeCache = createCache<TypeScope>()
/** /**
* @private * @private
*/ */
export function invalidateTypeCache(filename: string) { export function invalidateTypeCache(filename: string): void {
filename = normalizePath(filename) filename = normalizePath(filename)
fileToScopeCache.delete(filename) fileToScopeCache.delete(filename)
tsConfigCache.delete(filename) tsConfigCache.delete(filename)
@ -1439,7 +1439,7 @@ function attachNamespace(
} }
} }
export function recordImports(body: Statement[]) { export function recordImports(body: Statement[]): Record<string, Import> {
const imports: TypeScope['imports'] = Object.create(null) const imports: TypeScope['imports'] = Object.create(null)
for (const s of body) { for (const s of body) {
recordImport(s, imports) recordImport(s, imports)
@ -1462,7 +1462,7 @@ function recordImport(node: Node, imports: TypeScope['imports']) {
export function inferRuntimeType( export function inferRuntimeType(
ctx: TypeResolveContext, ctx: TypeResolveContext,
node: Node & MaybeWithScope, node: Node & MaybeWithScope,
scope = node._ownerScope || ctxToScope(ctx), scope: TypeScope = node._ownerScope || ctxToScope(ctx),
isKeyOf = false, isKeyOf = false,
): string[] { ): string[] {
try { try {

View File

@ -39,7 +39,7 @@ export function processAwait(
node: AwaitExpression, node: AwaitExpression,
needSemi: boolean, needSemi: boolean,
isStatement: boolean, isStatement: boolean,
) { ): void {
const argumentStart = const argumentStart =
node.argument.extra && node.argument.extra.parenthesized node.argument.extra && node.argument.extra.parenthesized
? (node.argument.extra.parenStart as number) ? (node.argument.extra.parenStart as number)

View File

@ -12,7 +12,10 @@ import path from 'path'
export const UNKNOWN_TYPE = 'Unknown' export const UNKNOWN_TYPE = 'Unknown'
export function resolveObjectKey(node: Node, computed: boolean) { export function resolveObjectKey(
node: Node,
computed: boolean,
): string | undefined {
switch (node.type) { switch (node.type) {
case 'StringLiteral': case 'StringLiteral':
case 'NumericLiteral': case 'NumericLiteral':
@ -23,11 +26,13 @@ export function resolveObjectKey(node: Node, computed: boolean) {
return undefined return undefined
} }
export function concatStrings(strs: Array<string | null | undefined | false>) { export function concatStrings(
strs: Array<string | null | undefined | false>,
): string {
return strs.filter((s): s is string => !!s).join(', ') return strs.filter((s): s is string => !!s).join(', ')
} }
export function isLiteralNode(node: Node) { export function isLiteralNode(node: Node): boolean {
return node.type.endsWith('Literal') return node.type.endsWith('Literal')
} }
@ -46,7 +51,7 @@ export function isCallOf(
) )
} }
export function toRuntimeTypeString(types: string[]) { export function toRuntimeTypeString(types: string[]): string {
return types.length > 1 ? `[${types.join(', ')}]` : types[0] return types.length > 1 ? `[${types.join(', ')}]` : types[0]
} }
@ -55,7 +60,7 @@ export function getImportedName(
| ImportSpecifier | ImportSpecifier
| ImportDefaultSpecifier | ImportDefaultSpecifier
| ImportNamespaceSpecifier, | ImportNamespaceSpecifier,
) { ): string {
if (specifier.type === 'ImportSpecifier') if (specifier.type === 'ImportSpecifier')
return specifier.imported.type === 'Identifier' return specifier.imported.type === 'Identifier'
? specifier.imported.name ? specifier.imported.name
@ -89,7 +94,9 @@ function toFileNameLowerCase(x: string) {
* but TS does not expose it directly. This implementation is repllicated from * but TS does not expose it directly. This implementation is repllicated from
* the TS source code. * the TS source code.
*/ */
export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean) { export function createGetCanonicalFileName(
useCaseSensitiveFileNames: boolean,
): (str: string) => string {
return useCaseSensitiveFileNames ? identity : toFileNameLowerCase return useCaseSensitiveFileNames ? identity : toFileNameLowerCase
} }
@ -97,25 +104,31 @@ export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean) {
// posix behavior. // posix behavior.
const normalize = (path.posix || path).normalize const normalize = (path.posix || path).normalize
const windowsSlashRE = /\\/g const windowsSlashRE = /\\/g
export function normalizePath(p: string) { export function normalizePath(p: string): string {
return normalize(p.replace(windowsSlashRE, '/')) return normalize(p.replace(windowsSlashRE, '/'))
} }
export const joinPaths = (path.posix || path).join export const joinPaths: (...paths: string[]) => string = (path.posix || path)
.join
/** /**
* key may contain symbols * key may contain symbols
* e.g. onUpdate:modelValue -> "onUpdate:modelValue" * e.g. onUpdate:modelValue -> "onUpdate:modelValue"
*/ */
export const propNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~\-]/ export const propNameEscapeSymbolsRE: RegExp =
/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~\-]/
export function getEscapedPropName(key: string) { export function getEscapedPropName(key: string): string {
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
} }
export const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g export const cssVarNameEscapeSymbolsRE: RegExp =
/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
export function getEscapedCssVarName(key: string, doubleEscape: boolean) { export function getEscapedCssVarName(
key: string,
doubleEscape: boolean,
): string {
return key.replace(cssVarNameEscapeSymbolsRE, s => return key.replace(cssVarNameEscapeSymbolsRE, s =>
doubleEscape ? `\\\\${s}` : `\\${s}`, doubleEscape ? `\\\\${s}` : `\\${s}`,
) )

View File

@ -1,6 +1,6 @@
const hasWarned: Record<string, boolean> = {} const hasWarned: Record<string, boolean> = {}
export function warnOnce(msg: string) { export function warnOnce(msg: string): void {
const isNodeProd = const isNodeProd =
typeof process !== 'undefined' && process.env.NODE_ENV === 'production' typeof process !== 'undefined' && process.env.NODE_ENV === 'production'
if (!isNodeProd && !__TEST__ && !hasWarned[msg]) { if (!isNodeProd && !__TEST__ && !hasWarned[msg]) {
@ -9,7 +9,7 @@ export function warnOnce(msg: string) {
} }
} }
export function warn(msg: string) { export function warn(msg: string): void {
console.warn( console.warn(
`\x1b[1m\x1b[33m[@vue/compiler-sfc]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`, `\x1b[1m\x1b[33m[@vue/compiler-sfc]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`,
) )

View File

@ -1 +1 @@
# @vue/compiler-ssr # @vue/compiler-ssr

View File

@ -288,12 +288,27 @@ describe('ssr: element', () => {
}></div>\`" }></div>\`"
`) `)
}) })
})
test('custom dir', () => { describe('custom directives', () => {
// #8112 should respect textContent / innerHTML from directive getSSRProps
// if the element has no children
test('custom dir without children', () => {
expect(getCompiledString(`<div v-xxx:x.y="z" />`)).toMatchInlineSnapshot(` expect(getCompiledString(`<div v-xxx:x.y="z" />`)).toMatchInlineSnapshot(`
"\`<div\${
_ssrRenderAttrs(_temp0 = _ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.z, "x", { y: true }))
}>\${
("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
}</div>\`"
`)
})
test('custom dir with children', () => {
expect(getCompiledString(`<div v-xxx:x.y="z">hello</div>`))
.toMatchInlineSnapshot(`
"\`<div\${ "\`<div\${
_ssrRenderAttrs(_ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.z, "x", { y: true })) _ssrRenderAttrs(_ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.z, "x", { y: true }))
}></div>\`" }>hello</div>\`"
`) `)
}) })
@ -301,30 +316,36 @@ describe('ssr: element', () => {
expect(getCompiledString(`<div class="foo" v-xxx />`)) expect(getCompiledString(`<div class="foo" v-xxx />`))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"\`<div\${ "\`<div\${
_ssrRenderAttrs(_mergeProps({ class: "foo" }, _ssrGetDirectiveProps(_ctx, _directive_xxx))) _ssrRenderAttrs(_temp0 = _mergeProps({ class: "foo" }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
}></div>\`" }>\${
("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
}</div>\`"
`) `)
}) })
test('custom dir with v-bind', () => { test('custom dir with v-bind', () => {
expect(getCompiledString(`<div :title="foo" :class="bar" v-xxx />`)) expect(getCompiledString(`<div :title="foo" :class="bar" v-xxx />`))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"\`<div\${ "\`<div\${
_ssrRenderAttrs(_mergeProps({ _ssrRenderAttrs(_temp0 = _mergeProps({
title: _ctx.foo, title: _ctx.foo,
class: _ctx.bar class: _ctx.bar
}, _ssrGetDirectiveProps(_ctx, _directive_xxx))) }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
}></div>\`" }>\${
`) ("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
}</div>\`"
`)
}) })
test('custom dir with object v-bind', () => { test('custom dir with object v-bind', () => {
expect(getCompiledString(`<div v-bind="x" v-xxx />`)) expect(getCompiledString(`<div v-bind="x" v-xxx />`))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"\`<div\${ "\`<div\${
_ssrRenderAttrs(_mergeProps(_ctx.x, _ssrGetDirectiveProps(_ctx, _directive_xxx))) _ssrRenderAttrs(_temp0 = _mergeProps(_ctx.x, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
}></div>\`" }>\${
`) ("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
}</div>\`"
`)
}) })
test('custom dir with object v-bind + normal bindings', () => { test('custom dir with object v-bind + normal bindings', () => {
@ -332,11 +353,13 @@ describe('ssr: element', () => {
getCompiledString(`<div v-bind="x" class="foo" v-xxx title="bar" />`), getCompiledString(`<div v-bind="x" class="foo" v-xxx title="bar" />`),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"\`<div\${ "\`<div\${
_ssrRenderAttrs(_mergeProps(_ctx.x, { _ssrRenderAttrs(_temp0 = _mergeProps(_ctx.x, {
class: "foo", class: "foo",
title: "bar" title: "bar"
}, _ssrGetDirectiveProps(_ctx, _directive_xxx))) }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
}></div>\`" }>\${
("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
}</div>\`"
`) `)
}) })
}) })

View File

@ -38,6 +38,28 @@ describe('transition-group', () => {
`) `)
}) })
// #11514
test('with static tag + comment', () => {
expect(
compile(
`<transition-group tag="ul"><div v-for="i in list"/><div v-if="false"></div></transition-group>`,
).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<ul\${_ssrRenderAttrs(_attrs)}>\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
if (false) {
_push(\`<div></div>\`)
}
_push(\`</ul>\`)
}"
`)
})
test('with dynamic tag', () => { test('with dynamic tag', () => {
expect( expect(
compile( compile(

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-ssr", "name": "@vue/compiler-ssr",
"version": "3.5.0-alpha.5", "version": "3.5.0-beta.1",
"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",

View File

@ -1,26 +1,34 @@
import { registerRuntimeHelpers } from '@vue/compiler-dom' import { registerRuntimeHelpers } from '@vue/compiler-dom'
export const SSR_INTERPOLATE = Symbol(`ssrInterpolate`) export const SSR_INTERPOLATE: unique symbol = Symbol(`ssrInterpolate`)
export const SSR_RENDER_VNODE = Symbol(`ssrRenderVNode`) export const SSR_RENDER_VNODE: unique symbol = Symbol(`ssrRenderVNode`)
export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`) export const SSR_RENDER_COMPONENT: unique symbol = Symbol(`ssrRenderComponent`)
export const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`) export const SSR_RENDER_SLOT: unique symbol = Symbol(`ssrRenderSlot`)
export const SSR_RENDER_SLOT_INNER = Symbol(`ssrRenderSlotInner`) export const SSR_RENDER_SLOT_INNER: unique symbol = Symbol(`ssrRenderSlotInner`)
export const SSR_RENDER_CLASS = Symbol(`ssrRenderClass`) export const SSR_RENDER_CLASS: unique symbol = Symbol(`ssrRenderClass`)
export const SSR_RENDER_STYLE = Symbol(`ssrRenderStyle`) export const SSR_RENDER_STYLE: unique symbol = Symbol(`ssrRenderStyle`)
export const SSR_RENDER_ATTRS = Symbol(`ssrRenderAttrs`) export const SSR_RENDER_ATTRS: unique symbol = Symbol(`ssrRenderAttrs`)
export const SSR_RENDER_ATTR = Symbol(`ssrRenderAttr`) export const SSR_RENDER_ATTR: unique symbol = Symbol(`ssrRenderAttr`)
export const SSR_RENDER_DYNAMIC_ATTR = Symbol(`ssrRenderDynamicAttr`) export const SSR_RENDER_DYNAMIC_ATTR: unique symbol =
export const SSR_RENDER_LIST = Symbol(`ssrRenderList`) Symbol(`ssrRenderDynamicAttr`)
export const SSR_INCLUDE_BOOLEAN_ATTR = Symbol(`ssrIncludeBooleanAttr`) export const SSR_RENDER_LIST: unique symbol = Symbol(`ssrRenderList`)
export const SSR_LOOSE_EQUAL = Symbol(`ssrLooseEqual`) export const SSR_INCLUDE_BOOLEAN_ATTR: unique symbol = Symbol(
export const SSR_LOOSE_CONTAIN = Symbol(`ssrLooseContain`) `ssrIncludeBooleanAttr`,
export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`) )
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`) export const SSR_LOOSE_EQUAL: unique symbol = Symbol(`ssrLooseEqual`)
export const SSR_RENDER_TELEPORT = Symbol(`ssrRenderTeleport`) export const SSR_LOOSE_CONTAIN: unique symbol = Symbol(`ssrLooseContain`)
export const SSR_RENDER_SUSPENSE = Symbol(`ssrRenderSuspense`) export const SSR_RENDER_DYNAMIC_MODEL: unique symbol = Symbol(
export const SSR_GET_DIRECTIVE_PROPS = Symbol(`ssrGetDirectiveProps`) `ssrRenderDynamicModel`,
)
export const SSR_GET_DYNAMIC_MODEL_PROPS: unique symbol = Symbol(
`ssrGetDynamicModelProps`,
)
export const SSR_RENDER_TELEPORT: unique symbol = Symbol(`ssrRenderTeleport`)
export const SSR_RENDER_SUSPENSE: unique symbol = Symbol(`ssrRenderSuspense`)
export const SSR_GET_DIRECTIVE_PROPS: unique symbol =
Symbol(`ssrGetDirectiveProps`)
export const ssrHelpers = { export const ssrHelpers: Record<symbol, string> = {
[SSR_INTERPOLATE]: `ssrInterpolate`, [SSR_INTERPOLATE]: `ssrInterpolate`,
[SSR_RENDER_VNODE]: `ssrRenderVNode`, [SSR_RENDER_VNODE]: `ssrRenderVNode`,
[SSR_RENDER_COMPONENT]: `ssrRenderComponent`, [SSR_RENDER_COMPONENT]: `ssrRenderComponent`,

View File

@ -1,9 +1,11 @@
import { import {
type BlockStatement, type BlockStatement,
type CallExpression, type CallExpression,
type CompilerError,
type CompilerOptions, type CompilerOptions,
ElementTypes, ElementTypes,
type IfStatement, type IfStatement,
type JSChildNode,
NodeTypes, NodeTypes,
type RootNode, type RootNode,
type TemplateChildNode, type TemplateChildNode,
@ -33,7 +35,10 @@ import { SSRErrorCodes, createSSRCompilerError } from './errors'
// transform pass to convert the template AST into a fresh JS AST before // transform pass to convert the template AST into a fresh JS AST before
// passing it to codegen. // passing it to codegen.
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) { export function ssrCodegenTransform(
ast: RootNode,
options: CompilerOptions,
): void {
const context = createSSRTransformContext(ast, options) const context = createSSRTransformContext(ast, options)
// inject SFC <style> CSS variables // inject SFC <style> CSS variables
@ -70,14 +75,24 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
ast.helpers = new Set(Array.from(ast.helpers).filter(h => !(h in ssrHelpers))) ast.helpers = new Set(Array.from(ast.helpers).filter(h => !(h in ssrHelpers)))
} }
export type SSRTransformContext = ReturnType<typeof createSSRTransformContext> export interface SSRTransformContext {
root: RootNode
options: CompilerOptions
body: (JSChildNode | IfStatement)[]
helpers: Set<symbol>
withSlotScopeId: boolean
onError: (error: CompilerError) => void
helper<T extends symbol>(name: T): T
pushStringPart(part: TemplateLiteral['elements'][0]): void
pushStatement(statement: IfStatement | CallExpression): void
}
function createSSRTransformContext( function createSSRTransformContext(
root: RootNode, root: RootNode,
options: CompilerOptions, options: CompilerOptions,
helpers: Set<symbol> = new Set(), helpers: Set<symbol> = new Set(),
withSlotScopeId = false, withSlotScopeId = false,
) { ): SSRTransformContext {
const body: BlockStatement['body'] = [] const body: BlockStatement['body'] = []
let currentString: TemplateLiteral | null = null let currentString: TemplateLiteral | null = null
@ -96,7 +111,7 @@ function createSSRTransformContext(
helpers.add(name) helpers.add(name)
return name return name
}, },
pushStringPart(part: TemplateLiteral['elements'][0]) { pushStringPart(part) {
if (!currentString) { if (!currentString) {
const currentCall = createCallExpression(`_push`) const currentCall = createCallExpression(`_push`)
body.push(currentCall) body.push(currentCall)
@ -111,7 +126,7 @@ function createSSRTransformContext(
bufferedElements.push(part) bufferedElements.push(part)
} }
}, },
pushStatement(statement: IfStatement | CallExpression) { pushStatement(statement) {
// close current string // close current string
currentString = null currentString = null
body.push(statement) body.push(statement)
@ -142,7 +157,7 @@ export function processChildren(
asFragment = false, asFragment = false,
disableNestedFragments = false, disableNestedFragments = false,
disableCommentAsIfAlternate = false, disableCommentAsIfAlternate = false,
) { ): void {
if (asFragment) { if (asFragment) {
context.pushStringPart(`<!--[-->`) context.pushStringPart(`<!--[-->`)
} }
@ -231,7 +246,7 @@ export function processChildrenAsStatement(
parent: Container, parent: Container,
parentContext: SSRTransformContext, parentContext: SSRTransformContext,
asFragment = false, asFragment = false,
withSlotScopeId = parentContext.withSlotScopeId, withSlotScopeId: boolean = parentContext.withSlotScopeId,
): BlockStatement { ): BlockStatement {
const childContext = createChildContext(parentContext, withSlotScopeId) const childContext = createChildContext(parentContext, withSlotScopeId)
processChildren(parent, childContext, asFragment) processChildren(parent, childContext, asFragment)

View File

@ -205,7 +205,7 @@ export function ssrProcessComponent(
node: ComponentNode, node: ComponentNode,
context: SSRTransformContext, context: SSRTransformContext,
parent: { children: TemplateChildNode[] }, parent: { children: TemplateChildNode[] },
) { ): void {
const component = componentTypeMap.get(node)! const component = componentTypeMap.get(node)!
if (!node.ssrCodegenNode) { if (!node.ssrCodegenNode) {
// this is a built-in component that fell-through. // this is a built-in component that fell-through.
@ -268,7 +268,10 @@ export function ssrProcessComponent(
} }
} }
export const rawOptionsMap = new WeakMap<RootNode, CompilerOptions>() export const rawOptionsMap: WeakMap<RootNode, CompilerOptions> = new WeakMap<
RootNode,
CompilerOptions
>()
const [baseNodeTransforms, baseDirectiveTransforms] = const [baseNodeTransforms, baseDirectiveTransforms] =
getBaseTransformPreset(true) getBaseTransformPreset(true)

View File

@ -163,6 +163,25 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
]), ]),
] ]
} }
} else if (directives.length && !node.children.length) {
const tempId = `_temp${context.temps++}`
propsExp.arguments = [
createAssignmentExpression(
createSimpleExpression(tempId, false),
mergedProps,
),
]
rawChildrenMap.set(
node,
createConditionalExpression(
createSimpleExpression(`"textContent" in ${tempId}`, false),
createCallExpression(context.helper(SSR_INTERPOLATE), [
createSimpleExpression(`${tempId}.textContent`, false),
]),
createSimpleExpression(`${tempId}.innerHTML ?? ''`, false),
false,
),
)
} }
if (needTagForRuntime) { if (needTagForRuntime) {
@ -417,7 +436,7 @@ function findVModel(node: PlainElementNode): DirectiveNode | undefined {
export function ssrProcessElement( export function ssrProcessElement(
node: PlainElementNode, node: PlainElementNode,
context: SSRTransformContext, context: SSRTransformContext,
) { ): void {
const isVoidTag = context.options.isVoidTag || NO const isVoidTag = context.options.isVoidTag || NO
const elementsToAdd = node.ssrCodegenNode!.elements const elementsToAdd = node.ssrCodegenNode!.elements
for (let j = 0; j < elementsToAdd.length; j++) { for (let j = 0; j < elementsToAdd.length; j++) {

View File

@ -73,7 +73,7 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
export function ssrProcessSlotOutlet( export function ssrProcessSlotOutlet(
node: SlotOutletNode, node: SlotOutletNode,
context: SSRTransformContext, context: SSRTransformContext,
) { ): void {
const renderCall = node.ssrCodegenNode! const renderCall = node.ssrCodegenNode!
// has fallback content // has fallback content

View File

@ -29,7 +29,7 @@ export function ssrTransformSuspense(
node: ComponentNode, node: ComponentNode,
context: TransformContext, context: TransformContext,
) { ) {
return () => { return (): void => {
if (node.children.length) { if (node.children.length) {
const wipEntry: WIPEntry = { const wipEntry: WIPEntry = {
slotsExp: null!, // to be immediately set slotsExp: null!, // to be immediately set
@ -62,7 +62,7 @@ export function ssrTransformSuspense(
export function ssrProcessSuspense( export function ssrProcessSuspense(
node: ComponentNode, node: ComponentNode,
context: SSRTransformContext, context: SSRTransformContext,
) { ): void {
// complete wip slots with ssr code // complete wip slots with ssr code
const wipEntry = wipMap.get(node) const wipEntry = wipMap.get(node)
if (!wipEntry) { if (!wipEntry) {

View File

@ -18,7 +18,7 @@ import { SSR_RENDER_TELEPORT } from '../runtimeHelpers'
export function ssrProcessTeleport( export function ssrProcessTeleport(
node: ComponentNode, node: ComponentNode,
context: SSRTransformContext, context: SSRTransformContext,
) { ): void {
const targetProp = findProp(node, 'to') const targetProp = findProp(node, 'to')
if (!targetProp) { if (!targetProp) {
context.onError( context.onError(

View File

@ -15,7 +15,7 @@ export function ssrTransformTransition(
node: ComponentNode, node: ComponentNode,
context: TransformContext, context: TransformContext,
) { ) {
return () => { return (): void => {
const appear = findProp(node, 'appear', false, true) const appear = findProp(node, 'appear', false, true)
wipMap.set(node, !!appear) wipMap.set(node, !!appear)
} }
@ -24,7 +24,7 @@ export function ssrTransformTransition(
export function ssrProcessTransition( export function ssrProcessTransition(
node: ComponentNode, node: ComponentNode,
context: SSRTransformContext, context: SSRTransformContext,
) { ): void {
// #5351: filter out comment children inside transition // #5351: filter out comment children inside transition
node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT) node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)

View File

@ -29,7 +29,7 @@ export function ssrTransformTransitionGroup(
node: ComponentNode, node: ComponentNode,
context: TransformContext, context: TransformContext,
) { ) {
return () => { return (): void => {
const tag = findProp(node, 'tag') const tag = findProp(node, 'tag')
if (tag) { if (tag) {
const otherProps = node.props.filter(p => p !== tag) const otherProps = node.props.filter(p => p !== tag)
@ -60,7 +60,7 @@ export function ssrTransformTransitionGroup(
export function ssrProcessTransitionGroup( export function ssrProcessTransitionGroup(
node: ComponentNode, node: ComponentNode,
context: SSRTransformContext, context: SSRTransformContext,
) { ): void {
const entry = wipMap.get(node) const entry = wipMap.get(node)
if (entry) { if (entry) {
const { tag, propsExp, scopeId } = entry const { tag, propsExp, scopeId } = entry
@ -108,7 +108,7 @@ export function ssrProcessTransitionGroup(
context.pushStringPart(` ${scopeId}`) context.pushStringPart(` ${scopeId}`)
} }
context.pushStringPart(`>`) context.pushStringPart(`>`)
processChildren(node, context, false, true) processChildren(node, context, false, true, true)
context.pushStringPart(`</${tag.value!.content}>`) context.pushStringPart(`</${tag.value!.content}>`)
} }
} else { } else {

View File

@ -1,5 +1,6 @@
import { import {
type ForNode, type ForNode,
type NodeTransform,
NodeTypes, NodeTypes,
createCallExpression, createCallExpression,
createForLoopParams, createForLoopParams,
@ -14,10 +15,8 @@ import {
import { SSR_RENDER_LIST } from '../runtimeHelpers' import { SSR_RENDER_LIST } from '../runtimeHelpers'
// Plugin for the first transform pass, which simply constructs the AST node // Plugin for the first transform pass, which simply constructs the AST node
export const ssrTransformFor = createStructuralDirectiveTransform( export const ssrTransformFor: NodeTransform =
'for', createStructuralDirectiveTransform('for', processFor)
processFor,
)
// This is called during the 2nd transform pass to construct the SSR-specific // This is called during the 2nd transform pass to construct the SSR-specific
// codegen nodes. // codegen nodes.
@ -25,7 +24,7 @@ export function ssrProcessFor(
node: ForNode, node: ForNode,
context: SSRTransformContext, context: SSRTransformContext,
disableNestedFragments = false, disableNestedFragments = false,
) { ): void {
const needFragmentWrapper = const needFragmentWrapper =
!disableNestedFragments && !disableNestedFragments &&
(node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT) (node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT)

View File

@ -2,6 +2,7 @@ import {
type BlockStatement, type BlockStatement,
type IfBranchNode, type IfBranchNode,
type IfNode, type IfNode,
type NodeTransform,
NodeTypes, NodeTypes,
createBlockStatement, createBlockStatement,
createCallExpression, createCallExpression,
@ -15,7 +16,7 @@ import {
} from '../ssrCodegenTransform' } from '../ssrCodegenTransform'
// Plugin for the first transform pass, which simply constructs the AST node // Plugin for the first transform pass, which simply constructs the AST node
export const ssrTransformIf = createStructuralDirectiveTransform( export const ssrTransformIf: NodeTransform = createStructuralDirectiveTransform(
/^(if|else|else-if)$/, /^(if|else|else-if)$/,
processIf, processIf,
) )
@ -27,7 +28,7 @@ export function ssrProcessIf(
context: SSRTransformContext, context: SSRTransformContext,
disableNestedFragments = false, disableNestedFragments = false,
disableCommentAsIfAlternate = false, disableCommentAsIfAlternate = false,
) { ): void {
const [rootBranch] = node.branches const [rootBranch] = node.branches
const ifStatement = createIfStatement( const ifStatement = createIfStatement(
rootBranch.condition!, rootBranch.condition!,

View File

@ -1593,6 +1593,7 @@ describe('expose typing', () => {
import type { import type {
AllowedComponentProps, AllowedComponentProps,
ComponentCustomProps, ComponentCustomProps,
ComponentInstance,
ComponentOptionsMixin, ComponentOptionsMixin,
DefineComponent, DefineComponent,
Directive, Directive,
@ -1756,6 +1757,24 @@ describe('__typeEmits backdoor, call signature syntax', () => {
c.$emit('update', 123) c.$emit('update', 123)
}) })
describe('__typeRefs backdoor, object syntax', () => {
type Refs = {
foo: number
}
const Parent = defineComponent({
__typeRefs: {} as { child: ComponentInstance<typeof Child> },
})
const Child = defineComponent({
__typeRefs: {} as Refs,
})
const c = new Parent()
const refs = c.$refs
expectType<ComponentInstance<typeof Child>>(refs.child)
expectType<number>(refs.child.$refs.foo)
})
defineComponent({ defineComponent({
props: { props: {
foo: [String, null], foo: [String, null],

View File

@ -68,7 +68,7 @@ describe('inject', () => {
}) })
describe('defineCustomElement using defineComponent return type', () => { describe('defineCustomElement using defineComponent return type', () => {
test('with emits', () => { test('with object emits', () => {
const Comp1Vue = defineComponent({ const Comp1Vue = defineComponent({
props: { props: {
a: String, a: String,
@ -80,6 +80,23 @@ describe('defineCustomElement using defineComponent return type', () => {
const Comp = defineCustomElement(Comp1Vue) const Comp = defineCustomElement(Comp1Vue)
expectType<VueElementConstructor>(Comp) expectType<VueElementConstructor>(Comp)
expectType<string | undefined>(new Comp().a) const instance = new Comp()
expectType<string | undefined>(instance.a)
instance.a = ''
})
test('with array emits', () => {
const Comp1Vue = defineComponent({
props: {
a: Number,
},
emits: ['click'],
})
const Comp = defineCustomElement(Comp1Vue)
expectType<VueElementConstructor>(Comp)
const instance = new Comp()
expectType<number | undefined>(instance.a)
instance.a = 42
}) })
}) })

View File

@ -2,6 +2,7 @@ import {
type InjectionKey, type InjectionKey,
type Ref, type Ref,
createApp, createApp,
defineComponent,
inject, inject,
provide, provide,
ref, ref,
@ -52,3 +53,9 @@ provide<Cube>(123, { size: 'foo' })
const app = createApp({}) const app = createApp({})
// @ts-expect-error // @ts-expect-error
app.provide(injectionKeyRef, ref({})) app.provide(injectionKeyRef, ref({}))
defineComponent({
provide: {
[injectionKeyRef]: { size: 'foo' },
},
})

View File

@ -5,6 +5,7 @@ import {
type Ref, type Ref,
type ShallowRef, type ShallowRef,
type ToRefs, type ToRefs,
type WritableComputedRef,
computed, computed,
isRef, isRef,
proxyRefs, proxyRefs,
@ -181,6 +182,31 @@ describe('allow getter and setter types to be unrelated', <T>() => {
const d = {} as T const d = {} as T
const e = ref(d) const e = ref(d)
e.value = d e.value = d
const f = ref(ref(0))
expectType<number>(f.value)
// @ts-expect-error
f.value = ref(1)
})
// computed
describe('allow computed getter and setter types to be unrelated', () => {
const obj = ref({
name: 'foo',
})
const c = computed({
get() {
return JSON.stringify(obj.value)
},
set(val: typeof obj.value) {
obj.value = val
},
})
c.value = { name: 'bar' } // object
expectType<string>(c.value)
}) })
// shallowRef // shallowRef
@ -465,8 +491,21 @@ describe('toRef <-> toValue', () => {
}) })
// unref // unref
declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string> // #8747
expectType<string>(unref(text)) declare const unref1: number | Ref<number> | ComputedRef<number>
expectType<number>(unref(unref1))
// #11356
declare const unref2:
| MaybeRef<string>
| ShallowRef<string>
| ComputedRef<string>
| WritableComputedRef<string>
expectType<string>(unref(unref2))
// toValue
expectType<number>(toValue(unref1))
expectType<string>(toValue(unref2))
// useTemplateRef // useTemplateRef
const tRef = useTemplateRef('foo') const tRef = useTemplateRef('foo')

View File

@ -43,7 +43,8 @@ describe('defineProps w/ generics', () => {
test() test()
}) })
describe('defineProps w/ type declaration + withDefaults', () => { describe('defineProps w/ type declaration + withDefaults', <T extends
string>() => {
const res = withDefaults( const res = withDefaults(
defineProps<{ defineProps<{
number?: number number?: number
@ -56,6 +57,7 @@ describe('defineProps w/ type declaration + withDefaults', () => {
z?: string z?: string
bool?: boolean bool?: boolean
boolAndUndefined: boolean | undefined boolAndUndefined: boolean | undefined
foo?: T
}>(), }>(),
{ {
number: 123, number: 123,
@ -65,6 +67,7 @@ describe('defineProps w/ type declaration + withDefaults', () => {
genStr: () => '', genStr: () => '',
y: undefined, y: undefined,
z: 'string', z: 'string',
foo: '' as any,
}, },
) )
@ -81,6 +84,7 @@ describe('defineProps w/ type declaration + withDefaults', () => {
expectType<string | undefined>(res.x) expectType<string | undefined>(res.x)
expectType<string | undefined>(res.y) expectType<string | undefined>(res.y)
expectType<string>(res.z) expectType<string>(res.z)
expectType<T>(res.foo)
expectType<boolean>(res.bool) expectType<boolean>(res.bool)
expectType<boolean>(res.boolAndUndefined) expectType<boolean>(res.boolAndUndefined)
@ -138,6 +142,31 @@ describe('defineProps w/ object union + withDefaults', () => {
>(props) >(props)
}) })
describe('defineProps w/ generic discriminate union + withDefaults', () => {
interface B {
b?: string
}
interface S<T> extends B {
mode: 'single'
v: T
}
interface M<T> extends B {
mode: 'multiple'
v: T[]
}
type Props = S<string> | M<string>
const props = withDefaults(defineProps<Props>(), {
b: 'b',
})
if (props.mode === 'single') {
expectType<string>(props.v)
}
if (props.mode === 'multiple') {
expectType<string[]>(props.v)
}
})
describe('defineProps w/ generic type declaration + withDefaults', <T extends describe('defineProps w/ generic type declaration + withDefaults', <T extends
number, TA extends { number, TA extends {
a: string a: string

View File

@ -1282,4 +1282,48 @@ describe('reactivity/effect', () => {
).not.toHaveBeenWarned() ).not.toHaveBeenWarned()
}) })
}) })
test('should pause/resume effect', () => {
const obj = reactive({ foo: 1 })
const fnSpy = vi.fn(() => obj.foo)
const runner = effect(fnSpy)
expect(fnSpy).toHaveBeenCalledTimes(1)
expect(obj.foo).toBe(1)
runner.effect.pause()
obj.foo++
expect(fnSpy).toHaveBeenCalledTimes(1)
expect(obj.foo).toBe(2)
runner.effect.resume()
expect(fnSpy).toHaveBeenCalledTimes(2)
expect(obj.foo).toBe(2)
obj.foo++
expect(fnSpy).toHaveBeenCalledTimes(3)
expect(obj.foo).toBe(3)
})
test('should be executed once immediately when resume is called', () => {
const obj = reactive({ foo: 1 })
const fnSpy = vi.fn(() => obj.foo)
const runner = effect(fnSpy)
expect(fnSpy).toHaveBeenCalledTimes(1)
expect(obj.foo).toBe(1)
runner.effect.pause()
obj.foo++
expect(fnSpy).toHaveBeenCalledTimes(1)
expect(obj.foo).toBe(2)
obj.foo++
expect(fnSpy).toHaveBeenCalledTimes(1)
expect(obj.foo).toBe(3)
runner.effect.resume()
expect(fnSpy).toHaveBeenCalledTimes(2)
expect(obj.foo).toBe(3)
})
}) })

View File

@ -295,4 +295,31 @@ describe('reactivity/effect/scope', () => {
expect(getCurrentScope()).toBe(parentScope) expect(getCurrentScope()).toBe(parentScope)
}) })
}) })
it('should pause/resume EffectScope', async () => {
const counter = reactive({ num: 0 })
const fnSpy = vi.fn(() => counter.num)
const scope = new EffectScope()
scope.run(() => {
effect(fnSpy)
})
expect(fnSpy).toHaveBeenCalledTimes(1)
counter.num++
await nextTick()
expect(fnSpy).toHaveBeenCalledTimes(2)
scope.pause()
counter.num++
await nextTick()
expect(fnSpy).toHaveBeenCalledTimes(2)
counter.num++
await nextTick()
expect(fnSpy).toHaveBeenCalledTimes(2)
scope.resume()
expect(fnSpy).toHaveBeenCalledTimes(3)
})
}) })

View File

@ -478,4 +478,38 @@ describe('reactivity/ref', () => {
expect(toValue(c)).toBe(3) expect(toValue(c)).toBe(3)
expect(toValue(d)).toBe(4) expect(toValue(d)).toBe(4)
}) })
test('ref w/ customRef w/ getterRef w/ objectRef should store value cache', () => {
const refValue = ref(1)
// @ts-expect-error private field
expect(refValue._value).toBe(1)
let customRefValueCache = 0
const customRefValue = customRef((track, trigger) => {
return {
get() {
track()
return customRefValueCache
},
set(value: number) {
customRefValueCache = value
trigger()
},
}
})
customRefValue.value
// @ts-expect-error internal field
expect(customRefValue._value).toBe(0)
const getterRefValue = toRef(() => 1)
getterRefValue.value
// @ts-expect-error internal field
expect(getterRefValue._value).toBe(1)
const objectRefValue = toRef({ value: 1 }, 'value')
objectRefValue.value
// @ts-expect-error internal field
expect(objectRefValue._value).toBe(1)
})
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/reactivity", "name": "@vue/reactivity",
"version": "3.5.0-alpha.5", "version": "3.5.0-beta.1",
"description": "@vue/reactivity", "description": "@vue/reactivity",
"main": "index.js", "main": "index.js",
"module": "dist/reactivity.esm-bundler.js", "module": "dist/reactivity.esm-bundler.js",

View File

@ -52,7 +52,7 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
protected readonly _isShallow = false, protected readonly _isShallow = false,
) {} ) {}
get(target: Target, key: string | symbol, receiver: object) { get(target: Target, key: string | symbol, receiver: object): any {
const isReadonly = this._isReadonly, const isReadonly = this._isReadonly,
isShallow = this._isShallow isShallow = this._isShallow
if (key === ReactiveFlags.IS_REACTIVE) { if (key === ReactiveFlags.IS_REACTIVE) {
@ -73,7 +73,7 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
: reactiveMap : reactiveMap
).get(target) || ).get(target) ||
// receiver is not the reactive proxy, but has the same prototype // receiver is not the reactive proxy, but has the same prototype
// this means the reciever is a user proxy of the reactive proxy // this means the receiver is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver) Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) { ) {
return target return target
@ -240,12 +240,11 @@ export const mutableHandlers: ProxyHandler<object> =
export const readonlyHandlers: ProxyHandler<object> = export const readonlyHandlers: ProxyHandler<object> =
/*#__PURE__*/ new ReadonlyReactiveHandler() /*#__PURE__*/ new ReadonlyReactiveHandler()
export const shallowReactiveHandlers = /*#__PURE__*/ new MutableReactiveHandler( export const shallowReactiveHandlers: MutableReactiveHandler =
true, /*#__PURE__*/ new MutableReactiveHandler(true)
)
// Props handlers are special in the sense that it should not unwrap top-level // Props handlers are special in the sense that it should not unwrap top-level
// refs (in order to allow refs to be explicitly passed down), but should // refs (in order to allow refs to be explicitly passed down), but should
// retain the reactivity of the normal readonly object. // retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers = export const shallowReadonlyHandlers: ReadonlyReactiveHandler =
/*#__PURE__*/ new ReadonlyReactiveHandler(true) /*#__PURE__*/ new ReadonlyReactiveHandler(true)

View File

@ -85,7 +85,7 @@ let activeWatcher: ReactiveEffect | undefined = undefined
/** /**
* Returns the current active effect if there is one. * Returns the current active effect if there is one.
*/ */
export function getCurrentWatcher() { export function getCurrentWatcher(): ReactiveEffect<any> | undefined {
return activeWatcher return activeWatcher
} }
@ -96,7 +96,10 @@ export function getCurrentWatcher() {
* *
* @param cleanupFn - The callback function to attach to the effect's cleanup. * @param cleanupFn - The callback function to attach to the effect's cleanup.
*/ */
export function onWatcherCleanup(cleanupFn: () => void, failSilently = false) { export function onWatcherCleanup(
cleanupFn: () => void,
failSilently = false,
): void {
if (activeWatcher) { if (activeWatcher) {
const cleanups = const cleanups =
cleanupMap.get(activeWatcher) || cleanupMap.get(activeWatcher) ||
@ -133,11 +136,15 @@ export function baseWatch(
) )
} }
const reactiveGetter = (source: object) => const reactiveGetter = (source: object) => {
deep === true // traverse will happen in wrapped getter below
? source // traverse will happen in wrapped getter below if (deep) return source
: // for deep: false, only traverse root-level properties // for `deep: false | 0` or shallow reactive, only traverse root-level properties
traverse(source, deep === false ? 1 : undefined) if (isShallow(source) || deep === false || deep === 0)
return traverse(source, 1)
// for `deep: undefined` on a reactive object, deeply traverse all properties
return traverse(source)
}
let effect: ReactiveEffect let effect: ReactiveEffect
let getter: () => any let getter: () => any
@ -207,7 +214,8 @@ export function baseWatch(
if (cb && deep) { if (cb && deep) {
const baseGetter = getter const baseGetter = getter
getter = () => traverse(baseGetter()) const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
} }
if (once) { if (once) {
@ -322,9 +330,9 @@ export function baseWatch(
export function traverse( export function traverse(
value: unknown, value: unknown,
depth = Infinity, depth: number = Infinity,
seen?: Set<unknown>, seen?: Set<unknown>,
) { ): unknown {
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) { if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
return value return value
} }

View File

@ -20,7 +20,7 @@ export interface ComputedRef<T = any> extends WritableComputedRef<T> {
[ComputedRefSymbol]: true [ComputedRefSymbol]: true
} }
export interface WritableComputedRef<T> extends Ref<T> { export interface WritableComputedRef<T, S = T> extends Ref<T, S> {
/** /**
* @deprecated computed no longer uses effect * @deprecated computed no longer uses effect
*/ */
@ -30,9 +30,9 @@ export interface WritableComputedRef<T> extends Ref<T> {
export type ComputedGetter<T> = (oldValue?: T) => T export type ComputedGetter<T> = (oldValue?: T) => T
export type ComputedSetter<T> = (newValue: T) => void export type ComputedSetter<T> = (newValue: T) => void
export interface WritableComputedOptions<T> { export interface WritableComputedOptions<T, S = T> {
get: ComputedGetter<T> get: ComputedGetter<T>
set: ComputedSetter<T> set: ComputedSetter<S>
} }
/** /**
@ -47,15 +47,17 @@ export class ComputedRefImpl<T = any> implements Subscriber {
/** /**
* @internal * @internal
*/ */
readonly dep = new Dep(this) readonly dep: Dep = new Dep(this)
/** /**
* @internal * @internal
*/ */
readonly [ReactiveFlags.IS_REF] = true readonly __v_isRef = true
// TODO isolatedDeclarations ReactiveFlags.IS_REF
/** /**
* @internal * @internal
*/ */
readonly [ReactiveFlags.IS_READONLY]: boolean readonly __v_isReadonly: boolean
// TODO isolatedDeclarations ReactiveFlags.IS_READONLY
// A computed is also a subscriber that tracks other deps // A computed is also a subscriber that tracks other deps
/** /**
* @internal * @internal
@ -68,17 +70,17 @@ export class ComputedRefImpl<T = any> implements Subscriber {
/** /**
* @internal * @internal
*/ */
flags = EffectFlags.DIRTY flags: EffectFlags = EffectFlags.DIRTY
/** /**
* @internal * @internal
*/ */
globalVersion = globalVersion - 1 globalVersion: number = globalVersion - 1
/** /**
* @internal * @internal
*/ */
isSSR: boolean isSSR: boolean
// for backwards compat // for backwards compat
effect = this effect: this = this
// dev only // dev only
onTrack?: (event: DebuggerEvent) => void onTrack?: (event: DebuggerEvent) => void
@ -103,7 +105,7 @@ export class ComputedRefImpl<T = any> implements Subscriber {
/** /**
* @internal * @internal
*/ */
notify() { notify(): void {
// avoid infinite self recursion // avoid infinite self recursion
if (activeSub !== this) { if (activeSub !== this) {
this.flags |= EffectFlags.DIRTY this.flags |= EffectFlags.DIRTY
@ -113,7 +115,7 @@ export class ComputedRefImpl<T = any> implements Subscriber {
} }
} }
get value() { get value(): T {
const link = __DEV__ const link = __DEV__
? this.dep.track({ ? this.dep.track({
target: this, target: this,
@ -175,10 +177,10 @@ export function computed<T>(
getter: ComputedGetter<T>, getter: ComputedGetter<T>,
debugOptions?: DebuggerOptions, debugOptions?: DebuggerOptions,
): ComputedRef<T> ): ComputedRef<T>
export function computed<T>( export function computed<T, S = T>(
options: WritableComputedOptions<T>, options: WritableComputedOptions<T, S>,
debugOptions?: DebuggerOptions, debugOptions?: DebuggerOptions,
): WritableComputedRef<T> ): WritableComputedRef<T, S>
export function computed<T>( export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>, getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions, debugOptions?: DebuggerOptions,

View File

@ -39,7 +39,7 @@ export class Dep {
*/ */
subsHead?: Link subsHead?: Link
constructor(public computed?: ComputedRefImpl) { constructor(public computed?: ComputedRefImpl | undefined) {
if (__DEV__) { if (__DEV__) {
this.subsHead = undefined this.subsHead = undefined
} }
@ -115,13 +115,13 @@ export class Dep {
return link return link
} }
trigger(debugInfo?: DebuggerEventExtraInfo) { trigger(debugInfo?: DebuggerEventExtraInfo): void {
this.version++ this.version++
globalVersion++ globalVersion++
this.notify(debugInfo) this.notify(debugInfo)
} }
notify(debugInfo?: DebuggerEventExtraInfo) { notify(debugInfo?: DebuggerEventExtraInfo): void {
startBatch() startBatch()
try { try {
if (__DEV__) { if (__DEV__) {
@ -185,9 +185,15 @@ function addSub(link: Link) {
type KeyToDepMap = Map<any, Dep> type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<object, KeyToDepMap>() const targetMap = new WeakMap<object, KeyToDepMap>()
export const ITERATE_KEY = Symbol(__DEV__ ? 'Object iterate' : '') export const ITERATE_KEY: unique symbol = Symbol(
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map keys iterate' : '') __DEV__ ? 'Object iterate' : '',
export const ARRAY_ITERATE_KEY = Symbol(__DEV__ ? 'Array iterate' : '') )
export const MAP_KEY_ITERATE_KEY: unique symbol = Symbol(
__DEV__ ? 'Map keys iterate' : '',
)
export const ARRAY_ITERATE_KEY: unique symbol = Symbol(
__DEV__ ? 'Array iterate' : '',
)
/** /**
* Tracks access to a reactive property. * Tracks access to a reactive property.
@ -199,7 +205,7 @@ export const ARRAY_ITERATE_KEY = Symbol(__DEV__ ? 'Array iterate' : '')
* @param type - Defines the type of access to the reactive property. * @param type - Defines the type of access to the reactive property.
* @param key - Identifier of the reactive property to track. * @param key - Identifier of the reactive property to track.
*/ */
export function track(target: object, type: TrackOpTypes, key: unknown) { export function track(target: object, type: TrackOpTypes, key: unknown): void {
if (shouldTrack && activeSub) { if (shouldTrack && activeSub) {
let depsMap = targetMap.get(target) let depsMap = targetMap.get(target)
if (!depsMap) { if (!depsMap) {
@ -236,7 +242,7 @@ export function trigger(
newValue?: unknown, newValue?: unknown,
oldValue?: unknown, oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>, oldTarget?: Map<unknown, unknown> | Set<unknown>,
) { ): void {
const depsMap = targetMap.get(target) const depsMap = targetMap.get(target)
if (!depsMap) { if (!depsMap) {
// never been tracked // never been tracked
@ -328,7 +334,10 @@ export function trigger(
/** /**
* Test only * Test only
*/ */
export function getDepFromReactive(object: any, key: string | number | symbol) { export function getDepFromReactive(
object: any,
key: string | number | symbol,
): Dep | undefined {
// eslint-disable-next-line // eslint-disable-next-line
return targetMap.get(object)?.get(key) return targetMap.get(object)?.get(key)
} }

View File

@ -46,6 +46,7 @@ export enum EffectFlags {
DIRTY = 1 << 4, DIRTY = 1 << 4,
ALLOW_RECURSE = 1 << 5, ALLOW_RECURSE = 1 << 5,
NO_BATCH = 1 << 6, NO_BATCH = 1 << 6,
PAUSED = 1 << 7,
} }
/** /**
@ -107,6 +108,8 @@ export interface Link {
prevActiveLink?: Link prevActiveLink?: Link
} }
const pausedQueueEffects = new WeakSet<ReactiveEffect>()
export class ReactiveEffect<T = any> export class ReactiveEffect<T = any>
implements Subscriber, ReactiveEffectOptions implements Subscriber, ReactiveEffectOptions
{ {
@ -142,10 +145,24 @@ export class ReactiveEffect<T = any>
} }
} }
pause(): void {
this.flags |= EffectFlags.PAUSED
}
resume(): void {
if (this.flags & EffectFlags.PAUSED) {
this.flags &= ~EffectFlags.PAUSED
if (pausedQueueEffects.has(this)) {
pausedQueueEffects.delete(this)
this.trigger()
}
}
}
/** /**
* @internal * @internal
*/ */
notify() { notify(): void {
if ( if (
this.flags & EffectFlags.RUNNING && this.flags & EffectFlags.RUNNING &&
!(this.flags & EffectFlags.ALLOW_RECURSE) !(this.flags & EffectFlags.ALLOW_RECURSE)
@ -162,7 +179,7 @@ export class ReactiveEffect<T = any>
} }
} }
run() { run(): T {
// TODO cleanupEffect // TODO cleanupEffect
if (!(this.flags & EffectFlags.ACTIVE)) { if (!(this.flags & EffectFlags.ACTIVE)) {
@ -194,7 +211,7 @@ export class ReactiveEffect<T = any>
} }
} }
stop() { stop(): void {
if (this.flags & EffectFlags.ACTIVE) { if (this.flags & EffectFlags.ACTIVE) {
for (let link = this.deps; link; link = link.nextDep) { for (let link = this.deps; link; link = link.nextDep) {
removeSub(link) removeSub(link)
@ -206,8 +223,10 @@ export class ReactiveEffect<T = any>
} }
} }
trigger() { trigger(): void {
if (this.scheduler) { if (this.flags & EffectFlags.PAUSED) {
pausedQueueEffects.add(this)
} else if (this.scheduler) {
this.scheduler() this.scheduler()
} else { } else {
this.runIfDirty() this.runIfDirty()
@ -217,13 +236,13 @@ export class ReactiveEffect<T = any>
/** /**
* @internal * @internal
*/ */
runIfDirty() { runIfDirty(): void {
if (isDirty(this)) { if (isDirty(this)) {
this.run() this.run()
} }
} }
get dirty() { get dirty(): boolean {
return isDirty(this) return isDirty(this)
} }
} }
@ -234,7 +253,7 @@ let batchedEffect: ReactiveEffect | undefined
/** /**
* @internal * @internal
*/ */
export function startBatch() { export function startBatch(): void {
batchDepth++ batchDepth++
} }
@ -242,7 +261,7 @@ export function startBatch() {
* Run batched effects when all batches have ended * Run batched effects when all batches have ended
* @internal * @internal
*/ */
export function endBatch() { export function endBatch(): void {
if (batchDepth > 1) { if (batchDepth > 1) {
batchDepth-- batchDepth--
return return
@ -331,7 +350,7 @@ function isDirty(sub: Subscriber): boolean {
* Returning false indicates the refresh failed * Returning false indicates the refresh failed
* @internal * @internal
*/ */
export function refreshComputed(computed: ComputedRefImpl) { export function refreshComputed(computed: ComputedRefImpl): false | undefined {
if (computed.flags & EffectFlags.RUNNING) { if (computed.flags & EffectFlags.RUNNING) {
return false return false
} }
@ -455,7 +474,7 @@ export function effect<T = any>(
* *
* @param runner - Association with the effect to stop tracking. * @param runner - Association with the effect to stop tracking.
*/ */
export function stop(runner: ReactiveEffectRunner) { export function stop(runner: ReactiveEffectRunner): void {
runner.effect.stop() runner.effect.stop()
} }
@ -468,7 +487,7 @@ const trackStack: boolean[] = []
/** /**
* Temporarily pauses tracking. * Temporarily pauses tracking.
*/ */
export function pauseTracking() { export function pauseTracking(): void {
trackStack.push(shouldTrack) trackStack.push(shouldTrack)
shouldTrack = false shouldTrack = false
} }
@ -476,7 +495,7 @@ export function pauseTracking() {
/** /**
* Re-enables effect tracking (if it was paused). * Re-enables effect tracking (if it was paused).
*/ */
export function enableTracking() { export function enableTracking(): void {
trackStack.push(shouldTrack) trackStack.push(shouldTrack)
shouldTrack = true shouldTrack = true
} }
@ -484,7 +503,7 @@ export function enableTracking() {
/** /**
* Resets the previous global effect tracking state. * Resets the previous global effect tracking state.
*/ */
export function resetTracking() { export function resetTracking(): void {
const last = trackStack.pop() const last = trackStack.pop()
shouldTrack = last === undefined ? true : last shouldTrack = last === undefined ? true : last
} }
@ -501,7 +520,7 @@ export function resetTracking() {
* @param failSilently - if `true`, will not throw warning when called without * @param failSilently - if `true`, will not throw warning when called without
* an active effect. * an active effect.
*/ */
export function onEffectCleanup(fn: () => void, failSilently = false) { export function onEffectCleanup(fn: () => void, failSilently = false): void {
if (activeSub instanceof ReactiveEffect) { if (activeSub instanceof ReactiveEffect) {
activeSub.cleanup = fn activeSub.cleanup = fn
} else if (__DEV__ && !failSilently) { } else if (__DEV__ && !failSilently) {

View File

@ -17,6 +17,8 @@ export class EffectScope {
*/ */
cleanups: (() => void)[] = [] cleanups: (() => void)[] = []
private _isPaused = false
/** /**
* only assigned by undetached scope * only assigned by undetached scope
* @internal * @internal
@ -44,10 +46,43 @@ export class EffectScope {
} }
} }
get active() { get active(): boolean {
return this._active return this._active
} }
pause(): void {
if (this._active) {
this._isPaused = true
if (this.scopes) {
for (let i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].pause()
}
}
for (let i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].pause()
}
}
}
/**
* Resumes the effect scope, including all child scopes and effects.
*/
resume(): void {
if (this._active) {
if (this._isPaused) {
this._isPaused = false
if (this.scopes) {
for (let i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].resume()
}
}
for (let i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].resume()
}
}
}
}
run<T>(fn: () => T): T | undefined { run<T>(fn: () => T): T | undefined {
if (this._active) { if (this._active) {
const currentEffectScope = activeEffectScope const currentEffectScope = activeEffectScope
@ -67,7 +102,7 @@ export class EffectScope {
* This should only be called on non-detached scopes * This should only be called on non-detached scopes
* @internal * @internal
*/ */
on() { on(): void {
this.prevScope = activeEffectScope this.prevScope = activeEffectScope
activeEffectScope = this activeEffectScope = this
} }
@ -76,11 +111,11 @@ export class EffectScope {
* This should only be called on non-detached scopes * This should only be called on non-detached scopes
* @internal * @internal
*/ */
off() { off(): void {
activeEffectScope = this.prevScope activeEffectScope = this.prevScope
} }
stop(fromParent?: boolean) { stop(fromParent?: boolean): void {
if (this._active) { if (this._active) {
let i, l let i, l
for (i = 0, l = this.effects.length; i < l; i++) { for (i = 0, l = this.effects.length; i < l; i++) {
@ -118,7 +153,7 @@ export class EffectScope {
* @param detached - Can be used to create a "detached" effect scope. * @param detached - Can be used to create a "detached" effect scope.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#effectscope} * @see {@link https://vuejs.org/api/reactivity-advanced.html#effectscope}
*/ */
export function effectScope(detached?: boolean) { export function effectScope(detached?: boolean): EffectScope {
return new EffectScope(detached) return new EffectScope(detached)
} }
@ -127,7 +162,7 @@ export function effectScope(detached?: boolean) {
* *
* @see {@link https://vuejs.org/api/reactivity-advanced.html#getcurrentscope} * @see {@link https://vuejs.org/api/reactivity-advanced.html#getcurrentscope}
*/ */
export function getCurrentScope() { export function getCurrentScope(): EffectScope | undefined {
return activeEffectScope return activeEffectScope
} }
@ -138,7 +173,7 @@ export function getCurrentScope() {
* @param fn - The callback function to attach to the scope's cleanup. * @param fn - The callback function to attach to the scope's cleanup.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#onscopedispose} * @see {@link https://vuejs.org/api/reactivity-advanced.html#onscopedispose}
*/ */
export function onScopeDispose(fn: () => void, failSilently = false) { export function onScopeDispose(fn: () => void, failSilently = false): void {
if (activeEffectScope) { if (activeEffectScope) {
activeEffectScope.cleanups.push(fn) activeEffectScope.cleanups.push(fn)
} else if (__DEV__ && !failSilently) { } else if (__DEV__ && !failSilently) {

View File

@ -23,10 +23,16 @@ export interface Target {
[ReactiveFlags.RAW]?: any [ReactiveFlags.RAW]?: any
} }
export const reactiveMap = new WeakMap<Target, any>() export const reactiveMap: WeakMap<Target, any> = new WeakMap<Target, any>()
export const shallowReactiveMap = new WeakMap<Target, any>() export const shallowReactiveMap: WeakMap<Target, any> = new WeakMap<
export const readonlyMap = new WeakMap<Target, any>() Target,
export const shallowReadonlyMap = new WeakMap<Target, any>() any
>()
export const readonlyMap: WeakMap<Target, any> = new WeakMap<Target, any>()
export const shallowReadonlyMap: WeakMap<Target, any> = new WeakMap<
Target,
any
>()
enum TargetType { enum TargetType {
INVALID = 0, INVALID = 0,
@ -60,8 +66,8 @@ export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
declare const ReactiveMarkerSymbol: unique symbol declare const ReactiveMarkerSymbol: unique symbol
export declare class ReactiveMarker { export interface ReactiveMarker {
private [ReactiveMarkerSymbol]?: void [ReactiveMarkerSymbol]?: void
} }
export type Reactive<T> = UnwrapNestedRefs<T> & export type Reactive<T> = UnwrapNestedRefs<T> &

View File

@ -16,7 +16,7 @@ import {
toRaw, toRaw,
toReactive, toReactive,
} from './reactive' } from './reactive'
import type { ComputedRef } from './computed' import type { ComputedRef, WritableComputedRef } from './computed'
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
import { warn } from './warning' import { warn } from './warning'
@ -52,7 +52,9 @@ export function isRef(r: any): r is Ref {
* @param value - The object to wrap in the ref. * @param value - The object to wrap in the ref.
* @see {@link https://vuejs.org/api/reactivity-core.html#ref} * @see {@link https://vuejs.org/api/reactivity-core.html#ref}
*/ */
export function ref<T>(value: T): Ref<UnwrapRef<T>, UnwrapRef<T> | T> export function ref<T>(
value: T,
): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T>
export function ref<T = any>(): Ref<T | undefined> export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) { export function ref(value?: unknown) {
return createRef(value, false) return createRef(value, false)
@ -179,7 +181,7 @@ class RefImpl<T = any> {
* @param ref - The ref whose tied effects shall be executed. * @param ref - The ref whose tied effects shall be executed.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref} * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref}
*/ */
export function triggerRef(ref: Ref) { export function triggerRef(ref: Ref): void {
if (__DEV__) { if (__DEV__) {
;(ref as unknown as RefImpl).dep.trigger({ ;(ref as unknown as RefImpl).dep.trigger({
target: ref, target: ref,
@ -192,8 +194,13 @@ export function triggerRef(ref: Ref) {
} }
} }
export type MaybeRef<T = any> = T | Ref<T> export type MaybeRef<T = any> =
export type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T) | T
| Ref<T>
| ShallowRef<T>
| WritableComputedRef<T>
export type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T)
/** /**
* Returns the inner value if the argument is a ref, otherwise return the * Returns the inner value if the argument is a ref, otherwise return the
@ -211,7 +218,7 @@ export type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T)
* @param ref - Ref or plain value to be converted into the plain value. * @param ref - Ref or plain value to be converted into the plain value.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#unref} * @see {@link https://vuejs.org/api/reactivity-utilities.html#unref}
*/ */
export function unref<T>(ref: MaybeRef<T> | ComputedRef<T> | ShallowRef<T>): T { export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T {
return isRef(ref) ? ref.value : ref return isRef(ref) ? ref.value : ref
} }
@ -231,9 +238,7 @@ export function unref<T>(ref: MaybeRef<T> | ComputedRef<T> | ShallowRef<T>): T {
* @param source - A getter, an existing ref, or a non-function value. * @param source - A getter, an existing ref, or a non-function value.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue} * @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue}
*/ */
export function toValue<T>( export function toValue<T>(source: MaybeRefOrGetter<T>): T {
source: MaybeRefOrGetter<T> | ComputedRef<T> | ShallowRef<T>,
): T {
return isFunction(source) ? source() : unref(source) return isFunction(source) ? source() : unref(source)
} }
@ -282,6 +287,8 @@ class CustomRefImpl<T> {
public readonly [ReactiveFlags.IS_REF] = true public readonly [ReactiveFlags.IS_REF] = true
public _value: T = undefined!
constructor(factory: CustomRefFactory<T>) { constructor(factory: CustomRefFactory<T>) {
const dep = (this.dep = new Dep()) const dep = (this.dep = new Dep())
const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep)) const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep))
@ -290,7 +297,7 @@ class CustomRefImpl<T> {
} }
get value() { get value() {
return this._get() return (this._value = this._get())
} }
set value(newVal) { set value(newVal) {
@ -334,6 +341,7 @@ export function toRefs<T extends object>(object: T): ToRefs<T> {
class ObjectRefImpl<T extends object, K extends keyof T> { class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly [ReactiveFlags.IS_REF] = true public readonly [ReactiveFlags.IS_REF] = true
public _value: T[K] = undefined!
constructor( constructor(
private readonly _object: T, private readonly _object: T,
@ -343,7 +351,7 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
get value() { get value() {
const val = this._object[this._key] const val = this._object[this._key]
return val === undefined ? this._defaultValue! : val return (this._value = val === undefined ? this._defaultValue! : val)
} }
set value(newVal) { set value(newVal) {
@ -358,9 +366,11 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
class GetterRefImpl<T> { class GetterRefImpl<T> {
public readonly [ReactiveFlags.IS_REF] = true public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_READONLY] = true public readonly [ReactiveFlags.IS_READONLY] = true
public _value: T = undefined!
constructor(private readonly _getter: () => T) {} constructor(private readonly _getter: () => T) {}
get value() { get value() {
return this._getter() return (this._value = this._getter())
} }
} }

View File

@ -1,3 +1,3 @@
export function warn(msg: string, ...args: any[]) { export function warn(msg: string, ...args: any[]): void {
console.warn(`[Vue warn] ${msg}`, ...args) console.warn(`[Vue warn] ${msg}`, ...args)
} }

View File

@ -13,7 +13,7 @@ const { render, createApp } = createRenderer({
patchProp, patchProp,
insert, insert,
remove, remove,
createElement createElement,
// ... // ...
}) })

View File

@ -116,12 +116,25 @@ describe('api: createApp', () => {
const app = createApp({ const app = createApp({
setup() { setup() {
provide('foo', 'should not be seen') provide('foo', 'should not be seen')
// nested createApp
const childApp = createApp({
setup() {
provide('foo', 'foo from child')
},
})
childApp.provide('foo', 2)
expect(childApp.runWithContext(() => inject('foo'))).toBe(2)
return () => h('div') return () => h('div')
}, },
}) })
app.provide('foo', 1) app.provide('foo', 1)
expect(app.runWithContext(() => inject('foo'))).toBe(1) expect(app.runWithContext(() => inject('foo'))).toBe(1)
const root = nodeOps.createElement('div')
app.mount(root)
expect( expect(
app.runWithContext(() => { app.runWithContext(() => {

View File

@ -1562,6 +1562,186 @@ describe('api: watch', () => {
expect(spy2).toHaveBeenCalledTimes(1) expect(spy2).toHaveBeenCalledTimes(1)
}) })
it('watching reactive depth', async () => {
const state = reactive({
a: {
b: {
c: {
d: {
e: 1,
},
},
},
},
})
const cb = vi.fn()
watch(state, cb, { deep: 2 })
state.a.b = { c: { d: { e: 2 } } }
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
state.a.b.c = { d: { e: 3 } }
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
state.a.b = { c: { d: { e: 4 } } }
await nextTick()
expect(cb).toHaveBeenCalledTimes(2)
})
it('watching ref depth', async () => {
const state = ref({
a: {
b: 2,
},
})
const cb = vi.fn()
watch(state, cb, { deep: 1 })
state.value.a.b = 3
await nextTick()
expect(cb).toHaveBeenCalledTimes(0)
state.value.a = { b: 3 }
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
})
it('watching array depth', async () => {
const arr = ref([
{
a: {
b: 2,
},
},
{
a: {
b: 3,
},
},
])
const cb = vi.fn()
watch(arr, cb, { deep: 2 })
arr.value[0].a.b = 3
await nextTick()
expect(cb).toHaveBeenCalledTimes(0)
arr.value[0].a = { b: 3 }
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
arr.value[1].a = { b: 4 }
await nextTick()
expect(cb).toHaveBeenCalledTimes(2)
arr.value.push({ a: { b: 5 } })
await nextTick()
expect(cb).toHaveBeenCalledTimes(3)
arr.value.pop()
await nextTick()
expect(cb).toHaveBeenCalledTimes(4)
})
test('pause / resume', async () => {
const count = ref(0)
const cb = vi.fn()
const { pause, resume } = watch(count, cb)
count.value++
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
expect(cb).toHaveBeenLastCalledWith(1, 0, expect.any(Function))
pause()
count.value++
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
expect(cb).toHaveBeenLastCalledWith(1, 0, expect.any(Function))
resume()
count.value++
await nextTick()
expect(cb).toHaveBeenCalledTimes(2)
expect(cb).toHaveBeenLastCalledWith(3, 1, expect.any(Function))
count.value++
await nextTick()
expect(cb).toHaveBeenCalledTimes(3)
expect(cb).toHaveBeenLastCalledWith(4, 3, expect.any(Function))
pause()
count.value++
await nextTick()
expect(cb).toHaveBeenCalledTimes(3)
expect(cb).toHaveBeenLastCalledWith(4, 3, expect.any(Function))
resume()
await nextTick()
expect(cb).toHaveBeenCalledTimes(4)
expect(cb).toHaveBeenLastCalledWith(5, 4, expect.any(Function))
})
it('shallowReactive', async () => {
const state = shallowReactive({
msg: ref('hello'),
foo: {
a: ref(1),
b: 2,
},
bar: 'bar',
})
const spy = vi.fn()
watch(state, spy)
state.msg.value = 'hi'
await nextTick()
expect(spy).not.toHaveBeenCalled()
state.bar = 'bar2'
await nextTick()
expect(spy).toHaveBeenCalledTimes(1)
state.foo.a.value++
state.foo.b++
await nextTick()
expect(spy).toHaveBeenCalledTimes(1)
state.bar = 'bar3'
await nextTick()
expect(spy).toHaveBeenCalledTimes(2)
})
it('watching reactive with deep: false', async () => {
const state = reactive({
foo: {
a: 2,
},
bar: 'bar',
})
const spy = vi.fn()
watch(state, spy, { deep: false })
state.foo.a++
await nextTick()
expect(spy).toHaveBeenCalledTimes(0)
state.bar = 'bar2'
await nextTick()
expect(spy).toHaveBeenCalledTimes(1)
})
test("effect should be removed from scope's effects after it is stopped", () => { test("effect should be removed from scope's effects after it is stopped", () => {
const num = ref(0) const num = ref(0)
let unwatch: () => void let unwatch: () => void

View File

@ -155,12 +155,12 @@ describe('component: emit', () => {
render() {}, render() {},
created() { created() {
// @ts-expect-error // @ts-expect-error
this.$emit('bar') this.$emit('bar-baz')
}, },
}) })
render(h(Foo), nodeOps.createElement('div')) render(h(Foo), nodeOps.createElement('div'))
expect( expect(
`Component emitted event "bar" but it is neither declared`, `Component emitted event "bar-baz" but it is neither declared in the emits option nor as an "onBarBaz" prop`,
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
@ -172,12 +172,12 @@ describe('component: emit', () => {
render() {}, render() {},
created() { created() {
// @ts-expect-error // @ts-expect-error
this.$emit('bar') this.$emit('bar-baz')
}, },
}) })
render(h(Foo), nodeOps.createElement('div')) render(h(Foo), nodeOps.createElement('div'))
expect( expect(
`Component emitted event "bar" but it is neither declared`, `Component emitted event "bar-baz" but it is neither declared in the emits option nor as an "onBarBaz" prop`,
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
@ -197,6 +197,22 @@ describe('component: emit', () => {
).not.toHaveBeenWarned() ).not.toHaveBeenWarned()
}) })
test('should not warn if has equivalent onXXX prop with kebab-cased event', () => {
const Foo = defineComponent({
props: ['onFooBar'],
emits: [],
render() {},
created() {
// @ts-expect-error
this.$emit('foo-bar')
},
})
render(h(Foo), nodeOps.createElement('div'))
expect(
`Component emitted event "foo-bar" but it is neither declared`,
).not.toHaveBeenWarned()
})
test('validator warning', () => { test('validator warning', () => {
const Foo = defineComponent({ const Foo = defineComponent({
emits: { emits: {

View File

@ -213,7 +213,7 @@ describe('component: slots', () => {
expect(instance.slots.default()).toMatchObject([normalizeVNode('footer')]) expect(instance.slots.default()).toMatchObject([normalizeVNode('footer')])
}) })
test('should respect $stable flag', async () => { test('should respect $stable flag with a value of true', async () => {
const flag1 = ref(1) const flag1 = ref(1)
const flag2 = ref(2) const flag2 = ref(2)
const spy = vi.fn() const spy = vi.fn()
@ -255,6 +255,48 @@ describe('component: slots', () => {
expect(spy).toHaveBeenCalledTimes(2) expect(spy).toHaveBeenCalledTimes(2)
}) })
test('should respect $stable flag with a value of false', async () => {
const flag1 = ref(1)
const flag2 = ref(2)
const spy = vi.fn()
const Child = () => {
spy()
return 'child'
}
const App = {
setup() {
return () => [
flag1.value,
h(
Child,
{ n: flag2.value },
{
foo: () => 'foo',
$stable: false,
},
),
]
},
}
render(h(App), nodeOps.createElement('div'))
expect(spy).toHaveBeenCalledTimes(1)
// parent re-render, props didn't change, slots are not stable
// -> child should update
flag1.value++
await nextTick()
expect(spy).toHaveBeenCalledTimes(2)
// parent re-render, props changed
// -> child should update
flag2.value++
await nextTick()
expect(spy).toHaveBeenCalledTimes(3)
})
test('should not warn when mounting another app in setup', () => { test('should not warn when mounting another app in setup', () => {
const Comp = { const Comp = {
setup(_: any, { slots }: any) { setup(_: any, { slots }: any) {

View File

@ -8,6 +8,7 @@ import {
KeepAlive, KeepAlive,
Suspense, Suspense,
type SuspenseProps, type SuspenseProps,
createCommentVNode,
h, h,
nextTick, nextTick,
nodeOps, nodeOps,
@ -2035,7 +2036,7 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`<div>sync</div>`) expect(serializeInner(root)).toBe(`<div>sync</div>`)
}) })
// #10899 // #10899 / #11427
test('KeepAlive + Suspense switch before branch resolves', async () => { test('KeepAlive + Suspense switch before branch resolves', async () => {
const Async1 = defineAsyncComponent({ const Async1 = defineAsyncComponent({
render() { render() {
@ -2052,14 +2053,20 @@ describe('Suspense', () => {
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
const App = { const App = {
render() { render() {
return h(KeepAlive, null, { return h(
default: () => { KeepAlive,
return h(Suspense, null, { {
default: h(components[viewRef.value]), max: 1,
fallback: h('div', 'loading'),
})
}, },
}) {
default: () => {
return h(Suspense, null, {
default: h(components[viewRef.value]),
fallback: h('div', 'loading'),
})
},
},
)
}, },
} }
render(h(App), root) render(h(App), root)
@ -2085,6 +2092,35 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`<div>async2</div>`) expect(serializeInner(root)).toBe(`<div>async2</div>`)
}) })
test('KeepAlive + Suspense + comment slot', async () => {
const toggle = ref(false)
const Async = defineAsyncComponent({
render() {
return h('div', 'async1')
},
})
const App = {
render() {
return h(KeepAlive, null, {
default: () => {
return h(Suspense, null, {
default: toggle.value ? h(Async) : createCommentVNode('v-if'),
})
},
})
},
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<!--v-if-->`)
toggle.value = true
await nextTick()
await Promise.all(deps)
expect(serializeInner(root)).toBe(`<div>async1</div>`)
})
// #6416 follow up / #10017 // #6416 follow up / #10017
test('Suspense patched during HOC async component re-mount', async () => { test('Suspense patched during HOC async component re-mount', async () => {
const key = ref('k') const key = ref('k')

View File

@ -657,4 +657,96 @@ describe('useModel', () => {
expect(setValue).toBeCalledTimes(2) expect(setValue).toBeCalledTimes(2)
expect(msg.value).toBe(defaultVal) expect(msg.value).toBe(defaultVal)
}) })
// #11526
test('custom getter', () => {
let changeChildMsg!: (val: boolean) => void
const getter = (value: boolean) => !value
const Comp = defineComponent({
props: ['msg'],
emits: ['update:msg'],
setup(props) {
const childMsg = useModel(props, 'msg', {
get: getter,
set: value => !value,
})
changeChildMsg = (val: boolean) => (childMsg.value = val)
return () => {
return childMsg.value
}
},
})
const defaultVal = false
const msg = ref(defaultVal)
const Parent = defineComponent({
setup() {
return () =>
h(Comp, {
msg: msg.value,
'onUpdate:msg': val => {
msg.value = val
},
})
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
changeChildMsg(!getter(msg.value))
expect(msg.value).toBe(true)
changeChildMsg(!getter(msg.value))
expect(msg.value).toBe(false)
})
// #11541
test('custom setter', () => {
let changeChildMsg!: (val: boolean) => void
const Comp = defineComponent({
props: ['msg'],
emits: ['update:msg'],
setup(props) {
const childMsg = useModel(props, 'msg', {
set: value => {
if (value === msg.value) {
return null
} else {
return value
}
},
})
changeChildMsg = (val: boolean) => (childMsg.value = val)
return () => {
return childMsg.value
}
},
})
const defaultVal = false
const msg = ref(defaultVal)
const Parent = defineComponent({
setup() {
return () =>
h(Comp, {
msg: msg.value,
'onUpdate:msg': val => {
msg.value = val
},
})
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
changeChildMsg(true)
expect(msg.value).toBe(true)
changeChildMsg(true)
expect(msg.value).toBe(null)
})
}) })

View File

@ -68,4 +68,18 @@ describe('useTemplateRef', () => {
expect(t2!.value).toBe(root.children[0]) expect(t2!.value).toBe(root.children[0])
expect(t1!.value).toBe(null) expect(t1!.value).toBe(null)
}) })
test('should warn on duplicate useTemplateRef', () => {
const root = nodeOps.createElement('div')
render(
h(() => {
useTemplateRef('foo')
useTemplateRef('foo')
return ''
}),
root,
)
expect(`useTemplateRef('foo') already exists.`).toHaveBeenWarned()
})
}) })

View File

@ -1388,6 +1388,26 @@ describe('SSR hydration', () => {
expect((container.firstChild!.firstChild as any)._value).toBe(true) expect((container.firstChild!.firstChild as any)._value).toBe(true)
}) })
// #7203
test('force hydrate custom element with dynamic props', () => {
class MyElement extends HTMLElement {
foo = ''
constructor() {
super()
}
}
customElements.define('my-element-7203', MyElement)
const msg = ref('bar')
const container = document.createElement('div')
container.innerHTML = '<my-element-7203></my-element-7203>'
const app = createSSRApp({
render: () => h('my-element-7203', { foo: msg.value }),
})
app.mount(container)
expect((container.firstChild as any).foo).toBe(msg.value)
})
// #5728 // #5728
test('empty text node in slot', () => { test('empty text node in slot', () => {
const Comp = { const Comp = {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/runtime-core", "name": "@vue/runtime-core",
"version": "3.5.0-alpha.5", "version": "3.5.0-beta.1",
"description": "@vue/runtime-core", "description": "@vue/runtime-core",
"main": "index.js", "main": "index.js",
"module": "dist/runtime-core.esm-bundler.js", "module": "dist/runtime-core.esm-bundler.js",

View File

@ -50,8 +50,18 @@ export interface App<HostElement = any> {
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
mount( mount(
rootContainer: HostElement | string, rootContainer: HostElement | string,
/**
* @internal
*/
isHydrate?: boolean, isHydrate?: boolean,
/**
* @internal
*/
namespace?: boolean | ElementNamespace, namespace?: boolean | ElementNamespace,
/**
* @internal
*/
vnode?: VNode,
): ComponentPublicInstance ): ComponentPublicInstance
unmount(): void unmount(): void
onUnmount(cb: () => void): void onUnmount(cb: () => void): void
@ -76,6 +86,11 @@ export interface App<HostElement = any> {
_context: AppContext _context: AppContext
_instance: ComponentInternalInstance | null _instance: ComponentInternalInstance | null
/**
* @internal custom element vnode
*/
_ceVNode?: VNode
/** /**
* v2 compat only * v2 compat only
*/ */
@ -337,7 +352,7 @@ export function createAppAPI<HostElement>(
` you need to unmount the previous app by calling \`app.unmount()\` first.`, ` you need to unmount the previous app by calling \`app.unmount()\` first.`,
) )
} }
const vnode = createVNode(rootComponent, rootProps) const vnode = app._ceVNode || createVNode(rootComponent, rootProps)
// store app context on the root VNode. // store app context on the root VNode.
// this will be set on the root instance on initial mount. // this will be set on the root instance on initial mount.
vnode.appContext = context vnode.appContext = context

View File

@ -67,6 +67,7 @@ export type DefineComponent<
Exposed extends string = string, Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions, Provide extends ComponentProvideOptions = ComponentProvideOptions,
MakeDefaultsOptional extends boolean = true, MakeDefaultsOptional extends boolean = true,
TypeRefs extends Record<string, unknown> = {},
> = ComponentPublicInstanceConstructor< > = ComponentPublicInstanceConstructor<
CreateComponentPublicInstanceWithMixins< CreateComponentPublicInstanceWithMixins<
Props, Props,
@ -84,7 +85,8 @@ export type DefineComponent<
S, S,
LC & GlobalComponents, LC & GlobalComponents,
Directives & GlobalDirectives, Directives & GlobalDirectives,
Exposed Exposed,
TypeRefs
> >
> & > &
ComponentOptionsBase< ComponentOptionsBase<
@ -209,6 +211,7 @@ export function defineComponent<
: { [key in RuntimePropsKeys]?: any } : { [key in RuntimePropsKeys]?: any }
: TypeProps, : TypeProps,
ResolvedProps = Readonly<InferredProps & EmitsToProps<ResolvedEmits>>, ResolvedProps = Readonly<InferredProps & EmitsToProps<ResolvedEmits>>,
TypeRefs extends Record<string, unknown> = {},
>( >(
options: { options: {
props?: (RuntimePropsOptions & ThisType<void>) | RuntimePropsKeys[] props?: (RuntimePropsOptions & ThisType<void>) | RuntimePropsKeys[]
@ -220,6 +223,10 @@ export function defineComponent<
* @private for language-tools use only * @private for language-tools use only
*/ */
__typeEmits?: TypeEmits __typeEmits?: TypeEmits
/**
* @private for language-tools use only
*/
__typeRefs?: TypeRefs
} & ComponentOptionsBase< } & ComponentOptionsBase<
ResolvedProps, ResolvedProps,
SetupBindings, SetupBindings,
@ -279,7 +286,8 @@ export function defineComponent<
Provide, Provide,
// MakeDefaultsOptional - if TypeProps is provided, set to false to use // MakeDefaultsOptional - if TypeProps is provided, set to false to use
// user props types verbatim // user props types verbatim
unknown extends TypeProps ? true : false unknown extends TypeProps ? true : false,
TypeRefs
> >
// implementation, close to no-op // implementation, close to no-op

View File

@ -4,12 +4,14 @@ import { currentRenderingInstance } from './componentRenderContext'
import { currentApp } from './apiCreateApp' import { currentApp } from './apiCreateApp'
import { warn } from './warning' import { warn } from './warning'
export interface InjectionKey<T> extends Symbol {} interface InjectionConstraint<T> {}
export type InjectionKey<T> = symbol & InjectionConstraint<T>
export function provide<T, K = InjectionKey<T> | string | number>( export function provide<T, K = InjectionKey<T> | string | number>(
key: K, key: K,
value: K extends InjectionKey<infer V> ? V : T, value: K extends InjectionKey<infer V> ? V : T,
) { ): void {
if (!currentInstance) { if (!currentInstance) {
if (__DEV__) { if (__DEV__) {
warn(`provide() can only be used inside setup().`) warn(`provide() can only be used inside setup().`)
@ -56,11 +58,14 @@ export function inject(
// #2400 // #2400
// to support `app.use` plugins, // to support `app.use` plugins,
// fallback to appContext's `provides` if the instance is at root // fallback to appContext's `provides` if the instance is at root
const provides = instance // #11488, in a nested createApp, prioritize using the provides from currentApp
? instance.parent == null const provides = currentApp
? instance.vnode.appContext && instance.vnode.appContext.provides ? currentApp._context.provides
: instance.parent.provides : instance
: currentApp!._context.provides ? instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides
: undefined
if (provides && (key as string | symbol) in provides) { if (provides && (key as string | symbol) in provides) {
// TS doesn't allow symbol as index type // TS doesn't allow symbol as index type

View File

@ -63,9 +63,12 @@ export function injectHook(
} }
} }
export const createHook = const createHook =
<T extends Function = () => any>(lifecycle: LifecycleHooks) => <T extends Function = () => any>(lifecycle: LifecycleHooks) =>
(hook: T, target: ComponentInternalInstance | null = currentInstance) => { (
hook: T,
target: ComponentInternalInstance | null = currentInstance,
): void => {
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch) // post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
if ( if (
!isInSSRComponentSetup || !isInSSRComponentSetup ||
@ -74,22 +77,30 @@ export const createHook =
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target) injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
} }
} }
type CreateHook<T = any> = (
hook: T,
target?: ComponentInternalInstance | null,
) => void
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT) export const onBeforeMount: CreateHook = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED) export const onMounted: CreateHook = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE) export const onBeforeUpdate: CreateHook = createHook(
export const onUpdated = createHook(LifecycleHooks.UPDATED) LifecycleHooks.BEFORE_UPDATE,
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT) )
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED) export const onUpdated: CreateHook = createHook(LifecycleHooks.UPDATED)
export const onServerPrefetch = createHook(LifecycleHooks.SERVER_PREFETCH) export const onBeforeUnmount: CreateHook = createHook(
LifecycleHooks.BEFORE_UNMOUNT,
)
export const onUnmounted: CreateHook = createHook(LifecycleHooks.UNMOUNTED)
export const onServerPrefetch: CreateHook = createHook(
LifecycleHooks.SERVER_PREFETCH,
)
export type DebuggerHook = (e: DebuggerEvent) => void export type DebuggerHook = (e: DebuggerEvent) => void
export const onRenderTriggered = createHook<DebuggerHook>( export const onRenderTriggered: CreateHook<DebuggerHook> =
LifecycleHooks.RENDER_TRIGGERED, createHook<DebuggerHook>(LifecycleHooks.RENDER_TRIGGERED)
) export const onRenderTracked: CreateHook<DebuggerHook> =
export const onRenderTracked = createHook<DebuggerHook>( createHook<DebuggerHook>(LifecycleHooks.RENDER_TRACKED)
LifecycleHooks.RENDER_TRACKED,
)
export type ErrorCapturedHook<TError = unknown> = ( export type ErrorCapturedHook<TError = unknown> = (
err: TError, err: TError,
@ -100,6 +111,6 @@ export type ErrorCapturedHook<TError = unknown> = (
export function onErrorCaptured<TError = Error>( export function onErrorCaptured<TError = Error>(
hook: ErrorCapturedHook<TError>, hook: ErrorCapturedHook<TError>,
target: ComponentInternalInstance | null = currentInstance, target: ComponentInternalInstance | null = currentInstance,
) { ): void {
injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target) injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target)
} }

View File

@ -1,4 +1,5 @@
import { import {
type IfAny,
type LooseRequired, type LooseRequired,
type Prettify, type Prettify,
type UnionToIntersection, type UnionToIntersection,
@ -176,7 +177,7 @@ type ShortEmits<T extends Record<string, any>> = UnionToIntersection<
*/ */
export function defineExpose< export function defineExpose<
Exposed extends Record<string, any> = Record<string, any>, Exposed extends Record<string, any> = Record<string, any>,
>(exposed?: Exposed) { >(exposed?: Exposed): void {
if (__DEV__) { if (__DEV__) {
warnRuntimeUsage(`defineExpose`) warnRuntimeUsage(`defineExpose`)
} }
@ -329,7 +330,7 @@ type PropsWithDefaults<
> = Readonly<MappedOmit<T, keyof Defaults>> & { > = Readonly<MappedOmit<T, keyof Defaults>> & {
readonly [K in keyof Defaults]-?: K extends keyof T readonly [K in keyof Defaults]-?: K extends keyof T
? Defaults[K] extends undefined ? Defaults[K] extends undefined
? T[K] ? IfAny<Defaults[K], NotUndefined<T[K]>, T[K]>
: NotUndefined<T[K]> : NotUndefined<T[K]>
: never : never
} & { } & {
@ -395,7 +396,7 @@ function getContext(): SetupContext {
*/ */
export function normalizePropsOrEmits( export function normalizePropsOrEmits(
props: ComponentPropsOptions | EmitsOptions, props: ComponentPropsOptions | EmitsOptions,
) { ): ComponentObjectPropsOptions | ObjectEmitsOptions {
return isArray(props) return isArray(props)
? props.reduce( ? props.reduce(
(normalized, p) => ((normalized[p] = null), normalized), (normalized, p) => ((normalized[p] = null), normalized),
@ -443,7 +444,7 @@ export function mergeDefaults(
export function mergeModels( export function mergeModels(
a: ComponentPropsOptions | EmitsOptions, a: ComponentPropsOptions | EmitsOptions,
b: ComponentPropsOptions | EmitsOptions, b: ComponentPropsOptions | EmitsOptions,
) { ): ComponentPropsOptions | EmitsOptions {
if (!a || !b) return a || b if (!a || !b) return a || b
if (isArray(a) && isArray(b)) return a.concat(b) if (isArray(a) && isArray(b)) return a.concat(b)
return extend({}, normalizePropsOrEmits(a), normalizePropsOrEmits(b)) return extend({}, normalizePropsOrEmits(a), normalizePropsOrEmits(b))
@ -488,7 +489,7 @@ export function createPropsRestProxy(
* ``` * ```
* @internal * @internal
*/ */
export function withAsyncContext(getAwaitable: () => any) { export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
const ctx = getCurrentInstance()! const ctx = getCurrentInstance()!
if (__DEV__ && !ctx) { if (__DEV__ && !ctx) {
warn( warn(

View File

@ -61,24 +61,30 @@ export interface WatchOptionsBase extends DebuggerOptions {
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase { export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate immediate?: Immediate
deep?: boolean deep?: boolean | number
once?: boolean once?: boolean
} }
export type WatchStopHandle = () => void export type WatchStopHandle = () => void
export interface WatchHandle extends WatchStopHandle {
pause: () => void
resume: () => void
stop: () => void
}
// Simple effect. // Simple effect.
export function watchEffect( export function watchEffect(
effect: WatchEffect, effect: WatchEffect,
options?: WatchOptionsBase, options?: WatchOptionsBase,
): WatchStopHandle { ): WatchHandle {
return doWatch(effect, null, options) return doWatch(effect, null, options)
} }
export function watchPostEffect( export function watchPostEffect(
effect: WatchEffect, effect: WatchEffect,
options?: DebuggerOptions, options?: DebuggerOptions,
) { ): WatchStopHandle {
return doWatch( return doWatch(
effect, effect,
null, null,
@ -89,7 +95,7 @@ export function watchPostEffect(
export function watchSyncEffect( export function watchSyncEffect(
effect: WatchEffect, effect: WatchEffect,
options?: DebuggerOptions, options?: DebuggerOptions,
) { ): WatchStopHandle {
return doWatch( return doWatch(
effect, effect,
null, null,
@ -97,14 +103,14 @@ export function watchSyncEffect(
) )
} }
type MultiWatchSources = (WatchSource<unknown> | object)[] export type MultiWatchSources = (WatchSource<unknown> | object)[]
// overload: single source + cb // overload: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>( export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>, source: WatchSource<T>,
cb: WatchCallback<T, MaybeUndefined<T, Immediate>>, cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
options?: WatchOptions<Immediate>, options?: WatchOptions<Immediate>,
): WatchStopHandle ): WatchHandle
// overload: reactive array or tuple of multiple sources + cb // overload: reactive array or tuple of multiple sources + cb
export function watch< export function watch<
@ -116,7 +122,7 @@ export function watch<
? WatchCallback<T, MaybeUndefined<T, Immediate>> ? WatchCallback<T, MaybeUndefined<T, Immediate>>
: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>, : WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>, options?: WatchOptions<Immediate>,
): WatchStopHandle ): WatchHandle
// overload: array of multiple sources + cb // overload: array of multiple sources + cb
export function watch< export function watch<
@ -126,7 +132,7 @@ export function watch<
sources: [...T], sources: [...T],
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>, cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>, options?: WatchOptions<Immediate>,
): WatchStopHandle ): WatchHandle
// overload: watching reactive object w/ cb // overload: watching reactive object w/ cb
export function watch< export function watch<
@ -136,14 +142,14 @@ export function watch<
source: T, source: T,
cb: WatchCallback<T, MaybeUndefined<T, Immediate>>, cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
options?: WatchOptions<Immediate>, options?: WatchOptions<Immediate>,
): WatchStopHandle ): WatchHandle
// implementation // implementation
export function watch<T = any, Immediate extends Readonly<boolean> = false>( export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>, source: T | WatchSource<T>,
cb: any, cb: any,
options?: WatchOptions<Immediate>, options?: WatchOptions<Immediate>,
): WatchStopHandle { ): WatchHandle {
if (__DEV__ && !isFunction(cb)) { if (__DEV__ && !isFunction(cb)) {
warn( warn(
`\`watch(fn, options?)\` signature has been moved to a separate API. ` + `\`watch(fn, options?)\` signature has been moved to a separate API. ` +
@ -169,17 +175,9 @@ function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object, source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null, cb: WatchCallback | null,
options: WatchOptions = EMPTY_OBJ, options: WatchOptions = EMPTY_OBJ,
): WatchStopHandle { ): WatchHandle {
const { immediate, deep, flush, once } = options const { immediate, deep, flush, once } = options
// TODO remove in 3.5
if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
warn(
`watch() "deep" option with number value will be used as watch depth in future versions. ` +
`Please use a boolean instead to avoid potential breakage.`,
)
}
if (__DEV__ && !cb) { if (__DEV__ && !cb) {
if (immediate !== undefined) { if (immediate !== undefined) {
warn( warn(
@ -214,8 +212,11 @@ function doWatch(
// immediately watch or watchEffect // immediately watch or watchEffect
extendOptions.once = true extendOptions.once = true
} else { } else {
// watch(source, cb) const watchHandle: WatchHandle = () => {}
return NOOP watchHandle.stop = NOOP
watchHandle.resume = NOOP
watchHandle.pause = NOOP
return watchHandle
} }
} }
@ -226,15 +227,19 @@ function doWatch(
const effect = baseWatch(source, cb, extend({}, options, extendOptions)) const effect = baseWatch(source, cb, extend({}, options, extendOptions))
const scope = getCurrentScope() const scope = getCurrentScope()
const unwatch = () => { const watchHandle: WatchHandle = () => {
effect!.stop() effect.stop()
if (scope) { if (scope) {
remove(scope.effects, effect) remove(scope.effects, effect)
} }
} }
if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) watchHandle.pause = effect.pause.bind(effect)
return unwatch watchHandle.resume = effect.resume.bind(effect)
watchHandle.stop = watchHandle
if (__SSR__ && ssrCleanup) ssrCleanup.push(watchHandle)
return watchHandle
} }
// this.$watch // this.$watch
@ -243,7 +248,7 @@ export function instanceWatch(
source: string | Function, source: string | Function,
value: WatchCallback | ObjectWatchOptionItem, value: WatchCallback | ObjectWatchOptionItem,
options?: WatchOptions, options?: WatchOptions,
): WatchStopHandle { ): WatchHandle {
const publicThis = this.proxy as any const publicThis = this.proxy as any
const getter = isString(source) const getter = isString(source)
? source.includes('.') ? source.includes('.')
@ -265,7 +270,7 @@ export function instanceWatch(
export function createPathGetter(ctx: any, path: string) { export function createPathGetter(ctx: any, path: string) {
const segments = path.split('.') const segments = path.split('.')
return () => { return (): any => {
let cur = ctx let cur = ctx
for (let i = 0; i < segments.length && cur; i++) { for (let i = 0; i < segments.length && cur; i++) {
cur = cur[segments[i]] cur = cur[segments[i]]

View File

@ -431,7 +431,7 @@ const warnCount: Record<string, number> = Object.create(null)
// test only // test only
let warningEnabled = true let warningEnabled = true
export function toggleDeprecationWarning(flag: boolean) { export function toggleDeprecationWarning(flag: boolean): void {
warningEnabled = flag warningEnabled = flag
} }
@ -439,7 +439,7 @@ export function warnDeprecation(
key: DeprecationTypes, key: DeprecationTypes,
instance: ComponentInternalInstance | null, instance: ComponentInternalInstance | null,
...args: any[] ...args: any[]
) { ): void {
if (!__DEV__) { if (!__DEV__) {
return return
} }
@ -502,7 +502,7 @@ export const globalCompatConfig: CompatConfig = {
MODE: 2, MODE: 2,
} }
export function configureCompat(config: CompatConfig) { export function configureCompat(config: CompatConfig): void {
if (__DEV__) { if (__DEV__) {
validateCompatConfig(config) validateCompatConfig(config)
} }
@ -516,7 +516,7 @@ const warnedInvalidKeys: Record<string, boolean> = {}
export function validateCompatConfig( export function validateCompatConfig(
config: CompatConfig, config: CompatConfig,
instance?: ComponentInternalInstance, instance?: ComponentInternalInstance,
) { ): void {
if (seenConfigObjects.has(config)) { if (seenConfigObjects.has(config)) {
return return
} }
@ -554,7 +554,7 @@ export function validateCompatConfig(
export function getCompatConfigForKey( export function getCompatConfigForKey(
key: DeprecationTypes | 'MODE', key: DeprecationTypes | 'MODE',
instance: ComponentInternalInstance | null, instance: ComponentInternalInstance | null,
) { ): CompatConfig[DeprecationTypes | 'MODE'] {
const instanceConfig = const instanceConfig =
instance && (instance.type as ComponentOptions).compatConfig instance && (instance.type as ComponentOptions).compatConfig
if (instanceConfig && key in instanceConfig) { if (instanceConfig && key in instanceConfig) {
@ -594,7 +594,7 @@ export function assertCompatEnabled(
key: DeprecationTypes, key: DeprecationTypes,
instance: ComponentInternalInstance | null, instance: ComponentInternalInstance | null,
...args: any[] ...args: any[]
) { ): void {
if (!isCompatEnabled(key, instance)) { if (!isCompatEnabled(key, instance)) {
throw new Error(`${key} compat has been disabled.`) throw new Error(`${key} compat has been disabled.`)
} else if (__DEV__) { } else if (__DEV__) {
@ -610,7 +610,7 @@ export function softAssertCompatEnabled(
key: DeprecationTypes, key: DeprecationTypes,
instance: ComponentInternalInstance | null, instance: ComponentInternalInstance | null,
...args: any[] ...args: any[]
) { ): boolean {
if (__DEV__) { if (__DEV__) {
warnDeprecation(key, instance, ...args) warnDeprecation(key, instance, ...args)
} }
@ -626,7 +626,7 @@ export function checkCompatEnabled(
key: DeprecationTypes, key: DeprecationTypes,
instance: ComponentInternalInstance | null, instance: ComponentInternalInstance | null,
...args: any[] ...args: any[]
) { ): boolean {
const enabled = isCompatEnabled(key, instance) const enabled = isCompatEnabled(key, instance)
if (__DEV__ && enabled) { if (__DEV__ && enabled) {
warnDeprecation(key, instance, ...args) warnDeprecation(key, instance, ...args)

View File

@ -23,7 +23,9 @@ const normalizedAsyncComponentMap = new WeakMap<
Component Component
>() >()
export function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) { export function convertLegacyAsyncComponent(
comp: LegacyAsyncComponent,
): Component {
if (normalizedAsyncComponentMap.has(comp)) { if (normalizedAsyncComponentMap.has(comp)) {
return normalizedAsyncComponentMap.get(comp)! return normalizedAsyncComponentMap.get(comp)!
} }

View File

@ -19,7 +19,9 @@ export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
}, },
} }
export function convertLegacyFunctionalComponent(comp: ComponentOptions) { export function convertLegacyFunctionalComponent(
comp: ComponentOptions,
): FunctionalComponent {
if (normalizedFunctionalComponentMap.has(comp)) { if (normalizedFunctionalComponentMap.has(comp)) {
return normalizedFunctionalComponentMap.get(comp)! return normalizedFunctionalComponentMap.get(comp)!
} }

View File

@ -13,7 +13,7 @@ export const compatModelEventPrefix = `onModelCompat:`
const warnedTypes = new WeakSet() const warnedTypes = new WeakSet()
export function convertLegacyVModelProps(vnode: VNode) { export function convertLegacyVModelProps(vnode: VNode): void {
const { type, shapeFlag, props, dynamicProps } = vnode const { type, shapeFlag, props, dynamicProps } = vnode
const comp = type as ComponentOptions const comp = type as ComponentOptions
if (shapeFlag & ShapeFlags.COMPONENT && props && 'modelValue' in props) { if (shapeFlag & ShapeFlags.COMPONENT && props && 'modelValue' in props) {
@ -68,7 +68,7 @@ export function compatModelEmit(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
event: string, event: string,
args: any[], args: any[],
) { ): void {
if (!isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)) { if (!isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)) {
return return
} }

View File

@ -1,7 +1,7 @@
import { isPlainObject } from '@vue/shared' import { isPlainObject } from '@vue/shared'
import { DeprecationTypes, warnDeprecation } from './compatConfig' import { DeprecationTypes, warnDeprecation } from './compatConfig'
export function deepMergeData(to: any, from: any) { export function deepMergeData(to: any, from: any): any {
for (const key in from) { for (const key in from) {
const toVal = to[key] const toVal = to[key]
const fromVal = from[key] const fromVal = from[key]

Some files were not shown because too many files have changed in this diff Show More