mirror of https://github.com/vuejs/core.git
Merge tag 'v3.5.0-beta.1'
This commit is contained in:
commit
4468a2bea8
|
@ -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:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize the first letter
|
||||
* no dot (.) at the end
|
||||
- use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
- don't capitalize the first letter
|
||||
- no dot (.) at the end
|
||||
|
||||
### Body
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
### Pull Request Checklist
|
||||
|
||||
- Vue core has two primary work branches: `main` and `minor`.
|
||||
|
|
|
@ -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`.
|
||||
|
||||
- Performance
|
||||
|
||||
- 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
|
||||
|
|
|
@ -7,108 +7,7 @@ on:
|
|||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
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 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 }}
|
||||
test:
|
||||
if: ${{ ! startsWith(github.event.head_commit.message, 'release:') && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) }}
|
||||
uses: ./.github/workflows/test.yml
|
||||
|
|
|
@ -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.
|
|
@ -4,6 +4,7 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- main
|
||||
- minor
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
@ -53,12 +54,16 @@ jobs:
|
|||
name: size-data
|
||||
path: temp/size
|
||||
|
||||
- name: Save PR number
|
||||
- name: Save PR number & base branch
|
||||
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
|
||||
if: ${{github.event_name == 'pull_request'}}
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr.txt
|
||||
name: pr-info
|
||||
path: |
|
||||
number.txt
|
||||
base.txt
|
||||
|
|
|
@ -35,18 +35,24 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Download PR number
|
||||
- name: Download PR info
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
name: pr-number
|
||||
name: pr-info
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: /tmp/pr-number
|
||||
path: /tmp/pr-info
|
||||
|
||||
- name: Read PR Number
|
||||
id: pr-number
|
||||
uses: juliangruber/read-file-action@v1
|
||||
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
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
|
@ -58,7 +64,7 @@ jobs:
|
|||
- name: Download Previous Size Data
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
branch: main
|
||||
branch: ${{ steps.pr-base.outputs.content }}
|
||||
workflow: size-data.yml
|
||||
event: push
|
||||
name: size-data
|
||||
|
|
|
@ -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
|
|
@ -1,4 +1,3 @@
|
|||
dist
|
||||
*.md
|
||||
*.html
|
||||
pnpm-lock.yaml
|
||||
CHANGELOG*.md
|
||||
|
|
1019
CHANGELOG.md
1019
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
||||
|
||||
- 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)
|
||||
- Aviv Keller - [@RedYetiDev](https://github.com/redyetidev) | [LinkedIn](https://www.linkedin.com/in/redyetidev) <redyetidev@gmail.com>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.0-alpha.5",
|
||||
"version": "3.5.0-beta.1",
|
||||
"packageManager": "pnpm@9.6.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
@ -66,10 +66,11 @@
|
|||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.7.3",
|
||||
"@swc/core": "^1.7.6",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^20.14.13",
|
||||
"@types/node": "^20.14.14",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitest/coverage-istanbul": "^1.6.0",
|
||||
"@vitest/ui": "^1.6.0",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
|
@ -82,30 +83,31 @@
|
|||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^24.1.1",
|
||||
"lint-staged": "^15.2.7",
|
||||
"lint-staged": "^15.2.8",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.10",
|
||||
"magic-string": "^0.30.11",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^12.0.2",
|
||||
"marked": "^13.0.3",
|
||||
"npm-run-all2": "^6.2.2",
|
||||
"picocolors": "^1.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~22.14.0",
|
||||
"rimraf": "^5.0.9",
|
||||
"rollup": "^4.19.1",
|
||||
"puppeteer": "~22.15.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.20.0",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.3",
|
||||
"serve": "^14.2.3",
|
||||
"serve-handler": "^6.1.5",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "~5.4.5",
|
||||
"typescript-eslint": "^7.17.0",
|
||||
"tsx": "^4.16.5",
|
||||
"typescript": "~5.5.4",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
|
|
|
@ -14,43 +14,91 @@ 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
|
||||
|
||||
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", {
|
||||
onClick: () => {
|
||||
for (var i = 0; i < _ctx.list.length; i++) {
|
||||
_ctx.log(i)
|
||||
}
|
||||
_ctx.error(i)
|
||||
}
|
||||
}, null, 8 /* PROPS */, ["onClick"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: expression transform > should not prefix catch block param 1`] = `
|
||||
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", {
|
||||
onClick: () => {
|
||||
try {} catch (err) { console.error(err) }
|
||||
console.log(_ctx.err)
|
||||
}
|
||||
}, null, 8 /* PROPS */, ["onClick"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: expression transform > should not prefix destructured catch block param 1`] = `
|
||||
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", {
|
||||
onClick: () => {
|
||||
try {
|
||||
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 > bindingMetadata > should not prefix temp variable of for...in 1`] = `
|
||||
exports[`compiler: expression transform > should not prefix temp variable of for...in 1`] = `
|
||||
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
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 > bindingMetadata > should not prefix temp variable of for...of 1`] = `
|
||||
exports[`compiler: expression transform > should not prefix temp variable of for...of 1`] = `
|
||||
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
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"]))
|
||||
}"
|
||||
|
|
|
@ -27,6 +27,10 @@ function parseWithExpressionTransform(
|
|||
return ast.children[0]
|
||||
}
|
||||
|
||||
function compile(template: string) {
|
||||
return baseCompile(template, { prefixIdentifiers: true })
|
||||
}
|
||||
|
||||
describe('compiler: expression transform', () => {
|
||||
test('interpolation (root)', () => {
|
||||
const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode
|
||||
|
@ -291,6 +295,7 @@ describe('compiler: expression transform', () => {
|
|||
],
|
||||
})
|
||||
})
|
||||
|
||||
test('should not prefix an object property key', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
`{{ { 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', () => {
|
||||
test('bigInt', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
|
@ -555,42 +644,6 @@ describe('compiler: expression transform', () => {
|
|||
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', () => {
|
||||
const { code } = compileWithBindingMetadata(
|
||||
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.0-alpha.5",
|
||||
"version": "3.5.0-beta.1",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
@ -48,7 +48,7 @@
|
|||
"dependencies": {
|
||||
"@babel/parser": "catalog:",
|
||||
"@vue/shared": "workspace:*",
|
||||
"entities": "^4.5.0",
|
||||
"entities": "^5.0.0",
|
||||
"estree-walker": "catalog:",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
|
|
|
@ -625,7 +625,7 @@ export function createVNodeCall(
|
|||
isBlock: VNodeCall['isBlock'] = false,
|
||||
disableTracking: VNodeCall['disableTracking'] = false,
|
||||
isComponent: VNodeCall['isComponent'] = false,
|
||||
loc = locStub,
|
||||
loc: SourceLocation = locStub,
|
||||
): VNodeCall {
|
||||
if (context) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export function convertToBlock(
|
||||
node: VNodeCall,
|
||||
{ helper, removeHelper, inSSR }: TransformContext,
|
||||
) {
|
||||
): void {
|
||||
if (!node.isBlock) {
|
||||
node.isBlock = true
|
||||
removeHelper(getVNodeHelper(inSSR, node.isComponent))
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
// do not import runtime methods
|
||||
import type {
|
||||
BlockStatement,
|
||||
ForInStatement,
|
||||
ForOfStatement,
|
||||
ForStatement,
|
||||
Function,
|
||||
Identifier,
|
||||
Node,
|
||||
|
@ -25,7 +28,7 @@ export function walkIdentifiers(
|
|||
includeAll = false,
|
||||
parentStack: Node[] = [],
|
||||
knownIds: Record<string, number> = Object.create(null),
|
||||
) {
|
||||
): void {
|
||||
if (__BROWSER__) {
|
||||
return
|
||||
}
|
||||
|
@ -77,6 +80,14 @@ export function walkIdentifiers(
|
|||
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) {
|
||||
|
@ -97,7 +108,7 @@ export function isReferencedIdentifier(
|
|||
id: Identifier,
|
||||
parent: Node | null,
|
||||
parentStack: Node[],
|
||||
) {
|
||||
): boolean {
|
||||
if (__BROWSER__) {
|
||||
return false
|
||||
}
|
||||
|
@ -166,7 +177,7 @@ export function isInNewExpression(parentStack: Node[]): boolean {
|
|||
export function walkFunctionParams(
|
||||
node: Function,
|
||||
onIdent: (id: Identifier) => void,
|
||||
) {
|
||||
): void {
|
||||
for (const p of node.params) {
|
||||
for (const id of extractIdentifiers(p)) {
|
||||
onIdent(id)
|
||||
|
@ -177,7 +188,7 @@ export function walkFunctionParams(
|
|||
export function walkBlockDeclarations(
|
||||
block: BlockStatement | Program,
|
||||
onIdent: (node: Identifier) => void,
|
||||
) {
|
||||
): void {
|
||||
for (const stmt of block.body) {
|
||||
if (stmt.type === 'VariableDeclaration') {
|
||||
if (stmt.declare) continue
|
||||
|
@ -192,21 +203,39 @@ export function walkBlockDeclarations(
|
|||
) {
|
||||
if (stmt.declare || !stmt.id) continue
|
||||
onIdent(stmt.id)
|
||||
} else if (
|
||||
} else if (isForStatement(stmt)) {
|
||||
walkForStatement(stmt, true, onIdent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isForStatement(
|
||||
stmt: Node,
|
||||
): stmt is ForStatement | ForOfStatement | ForInStatement {
|
||||
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') {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function extractIdentifiers(
|
||||
|
@ -284,7 +313,7 @@ export const isStaticProperty = (node?: Node): node is ObjectProperty =>
|
|||
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
|
||||
!node.computed
|
||||
|
||||
export const isStaticPropertyKey = (node: Node, parent: Node) =>
|
||||
export const isStaticPropertyKey = (node: Node, parent: Node): boolean =>
|
||||
isStaticProperty(parent) && parent.key === node
|
||||
|
||||
/**
|
||||
|
@ -466,7 +495,7 @@ function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
|
|||
return true
|
||||
}
|
||||
|
||||
export const TS_NODE_TYPES = [
|
||||
export const TS_NODE_TYPES: string[] = [
|
||||
'TSAsExpression', // foo as number
|
||||
'TSTypeAssertion', // (<number>foo)
|
||||
'TSNonNullExpression', // foo!
|
||||
|
|
|
@ -106,7 +106,7 @@ function getCompatValue(
|
|||
export function isCompatEnabled(
|
||||
key: CompilerDeprecationTypes,
|
||||
context: MergedParserOptions | TransformContext,
|
||||
) {
|
||||
): boolean {
|
||||
const mode = getCompatValue('MODE', context)
|
||||
const value = getCompatValue(key, context)
|
||||
// in v3 mode, only enable if explicitly set to true
|
||||
|
@ -132,7 +132,7 @@ export function warnDeprecation(
|
|||
context: MergedParserOptions | TransformContext,
|
||||
loc: SourceLocation | null,
|
||||
...args: any[]
|
||||
) {
|
||||
): void {
|
||||
const val = getCompatValue(key, context)
|
||||
if (val === 'suppress-warning') {
|
||||
return
|
||||
|
|
|
@ -9,11 +9,11 @@ export interface CoreCompilerError extends CompilerError {
|
|||
code: ErrorCodes
|
||||
}
|
||||
|
||||
export function defaultOnError(error: CompilerError) {
|
||||
export function defaultOnError(error: CompilerError): never {
|
||||
throw error
|
||||
}
|
||||
|
||||
export function defaultOnWarn(msg: CompilerError) {
|
||||
export function defaultOnWarn(msg: CompilerError): void {
|
||||
__DEV__ && console.warn(`[Vue warn] ${msg.message}`)
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ import {
|
|||
isSimpleIdentifier,
|
||||
isStaticArgOf,
|
||||
} from './utils'
|
||||
import { decodeHTML } from 'entities/lib/decode.js'
|
||||
import { decodeHTML } from 'entities/dist/decode.js'
|
||||
import {
|
||||
type ParserOptions as BabelOptions,
|
||||
parse,
|
||||
|
|
|
@ -1,51 +1,85 @@
|
|||
export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``)
|
||||
export const TELEPORT = Symbol(__DEV__ ? `Teleport` : ``)
|
||||
export const SUSPENSE = Symbol(__DEV__ ? `Suspense` : ``)
|
||||
export const KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``)
|
||||
export const BASE_TRANSITION = Symbol(__DEV__ ? `BaseTransition` : ``)
|
||||
export const OPEN_BLOCK = Symbol(__DEV__ ? `openBlock` : ``)
|
||||
export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``)
|
||||
export const CREATE_ELEMENT_BLOCK = Symbol(__DEV__ ? `createElementBlock` : ``)
|
||||
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
|
||||
export const CREATE_ELEMENT_VNODE = Symbol(__DEV__ ? `createElementVNode` : ``)
|
||||
export const CREATE_COMMENT = Symbol(__DEV__ ? `createCommentVNode` : ``)
|
||||
export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
|
||||
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``)
|
||||
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
|
||||
export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
|
||||
export const FRAGMENT: unique symbol = Symbol(__DEV__ ? `Fragment` : ``)
|
||||
export const TELEPORT: unique symbol = Symbol(__DEV__ ? `Teleport` : ``)
|
||||
export const SUSPENSE: unique symbol = Symbol(__DEV__ ? `Suspense` : ``)
|
||||
export const KEEP_ALIVE: unique symbol = Symbol(__DEV__ ? `KeepAlive` : ``)
|
||||
export const BASE_TRANSITION: unique symbol = Symbol(
|
||||
__DEV__ ? `BaseTransition` : ``,
|
||||
)
|
||||
export const OPEN_BLOCK: unique symbol = Symbol(__DEV__ ? `openBlock` : ``)
|
||||
export const CREATE_BLOCK: unique symbol = Symbol(__DEV__ ? `createBlock` : ``)
|
||||
export const CREATE_ELEMENT_BLOCK: unique symbol = Symbol(
|
||||
__DEV__ ? `createElementBlock` : ``,
|
||||
)
|
||||
export const CREATE_VNODE: unique symbol = Symbol(__DEV__ ? `createVNode` : ``)
|
||||
export const CREATE_ELEMENT_VNODE: unique symbol = 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` : ``,
|
||||
)
|
||||
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
|
||||
export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``)
|
||||
export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
|
||||
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
|
||||
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
|
||||
export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``)
|
||||
export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``)
|
||||
export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
|
||||
export const NORMALIZE_CLASS = Symbol(__DEV__ ? `normalizeClass` : ``)
|
||||
export const NORMALIZE_STYLE = Symbol(__DEV__ ? `normalizeStyle` : ``)
|
||||
export const NORMALIZE_PROPS = Symbol(__DEV__ ? `normalizeProps` : ``)
|
||||
export const GUARD_REACTIVE_PROPS = Symbol(__DEV__ ? `guardReactiveProps` : ``)
|
||||
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
|
||||
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
|
||||
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
|
||||
export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``)
|
||||
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
|
||||
export const RESOLVE_DIRECTIVE: unique symbol = Symbol(
|
||||
__DEV__ ? `resolveDirective` : ``,
|
||||
)
|
||||
export const RESOLVE_FILTER: unique symbol = Symbol(
|
||||
__DEV__ ? `resolveFilter` : ``,
|
||||
)
|
||||
export const WITH_DIRECTIVES: unique symbol = Symbol(
|
||||
__DEV__ ? `withDirectives` : ``,
|
||||
)
|
||||
export const RENDER_LIST: unique symbol = Symbol(__DEV__ ? `renderList` : ``)
|
||||
export const RENDER_SLOT: unique symbol = Symbol(__DEV__ ? `renderSlot` : ``)
|
||||
export const CREATE_SLOTS: unique symbol = Symbol(__DEV__ ? `createSlots` : ``)
|
||||
export const TO_DISPLAY_STRING: unique symbol = Symbol(
|
||||
__DEV__ ? `toDisplayString` : ``,
|
||||
)
|
||||
export const MERGE_PROPS: unique symbol = Symbol(__DEV__ ? `mergeProps` : ``)
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
|
||||
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
|
||||
export const UNREF = Symbol(__DEV__ ? `unref` : ``)
|
||||
export const IS_REF = Symbol(__DEV__ ? `isRef` : ``)
|
||||
export const WITH_MEMO = Symbol(__DEV__ ? `withMemo` : ``)
|
||||
export const IS_MEMO_SAME = Symbol(__DEV__ ? `isMemoSame` : ``)
|
||||
export const POP_SCOPE_ID: unique symbol = Symbol(__DEV__ ? `popScopeId` : ``)
|
||||
export const WITH_CTX: unique symbol = Symbol(__DEV__ ? `withCtx` : ``)
|
||||
export const UNREF: unique symbol = Symbol(__DEV__ ? `unref` : ``)
|
||||
export const IS_REF: unique symbol = Symbol(__DEV__ ? `isRef` : ``)
|
||||
export const WITH_MEMO: unique symbol = Symbol(__DEV__ ? `withMemo` : ``)
|
||||
export const IS_MEMO_SAME: unique symbol = Symbol(__DEV__ ? `isMemoSame` : ``)
|
||||
|
||||
// Name mapping for runtime helpers that need to be imported from 'vue' in
|
||||
// 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`,
|
||||
}
|
||||
|
||||
export function registerRuntimeHelpers(helpers: Record<symbol, string>) {
|
||||
export function registerRuntimeHelpers(helpers: Record<symbol, string>): void {
|
||||
Object.getOwnPropertySymbols(helpers).forEach(s => {
|
||||
helperNameMap[s] = helpers[s]
|
||||
})
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
EntityDecoder,
|
||||
fromCodePoint,
|
||||
htmlDecodeTree,
|
||||
} from 'entities/lib/decode.js'
|
||||
} from 'entities/dist/decode.js'
|
||||
|
||||
export enum ParseMode {
|
||||
BASE,
|
||||
|
@ -213,7 +213,15 @@ export interface Callbacks {
|
|||
* We don't have `Script`, `Style`, or `Title` here. Instead, we re-use the *End
|
||||
* 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[
|
||||
CdataEnd: new Uint8Array([0x5d, 0x5d, 0x3e]), // ]]>
|
||||
CommentEnd: new Uint8Array([0x2d, 0x2d, 0x3e]), // `-->`
|
||||
|
@ -227,7 +235,7 @@ export const Sequences = {
|
|||
|
||||
export default class Tokenizer {
|
||||
/** The current state the tokenizer is in. */
|
||||
public state = State.Text
|
||||
public state: State = State.Text
|
||||
/** The read buffer. */
|
||||
private buffer = ''
|
||||
/** The beginning of the section that is currently being read. */
|
||||
|
@ -249,8 +257,8 @@ export default class Tokenizer {
|
|||
|
||||
private readonly entityDecoder?: EntityDecoder
|
||||
|
||||
public mode = ParseMode.BASE
|
||||
public get inSFCRoot() {
|
||||
public mode: ParseMode = ParseMode.BASE
|
||||
public get inSFCRoot(): boolean {
|
||||
return this.mode === ParseMode.SFC && this.stack.length === 0
|
||||
}
|
||||
|
||||
|
@ -526,7 +534,7 @@ export default class Tokenizer {
|
|||
this.state = State.SpecialStartSequence
|
||||
}
|
||||
|
||||
public enterRCDATA(sequence: Uint8Array, offset: number) {
|
||||
public enterRCDATA(sequence: Uint8Array, offset: number): void {
|
||||
this.inRCDATA = true
|
||||
this.currentSequence = sequence
|
||||
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.
|
||||
*/
|
||||
public parse(input: string) {
|
||||
public parse(input: string): void {
|
||||
this.buffer = input
|
||||
while (this.index < this.buffer.length) {
|
||||
const c = this.buffer.charCodeAt(this.index)
|
||||
|
|
|
@ -326,7 +326,7 @@ export function createTransformContext(
|
|||
return context
|
||||
}
|
||||
|
||||
export function transform(root: RootNode, options: TransformOptions) {
|
||||
export function transform(root: RootNode, options: TransformOptions): void {
|
||||
const context = createTransformContext(root, options)
|
||||
traverseNode(root, context)
|
||||
if (options.hoistStatic) {
|
||||
|
@ -403,7 +403,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||
export function traverseChildren(
|
||||
parent: ParentNode,
|
||||
context: TransformContext,
|
||||
) {
|
||||
): void {
|
||||
let i = 0
|
||||
const nodeRemoved = () => {
|
||||
i--
|
||||
|
@ -422,7 +422,7 @@ export function traverseChildren(
|
|||
export function traverseNode(
|
||||
node: RootNode | TemplateChildNode,
|
||||
context: TransformContext,
|
||||
) {
|
||||
): void {
|
||||
context.currentNode = node
|
||||
// apply transform plugins
|
||||
const { nodeTransforms } = context
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
OPEN_BLOCK,
|
||||
} from '../runtimeHelpers'
|
||||
|
||||
export function cacheStatic(root: RootNode, context: TransformContext) {
|
||||
export function cacheStatic(root: RootNode, context: TransformContext): void {
|
||||
walk(
|
||||
root,
|
||||
undefined,
|
||||
|
|
|
@ -228,7 +228,7 @@ export function resolveComponentType(
|
|||
node: ComponentNode,
|
||||
context: TransformContext,
|
||||
ssr = false,
|
||||
) {
|
||||
): string | symbol | CallExpression {
|
||||
let { tag } = node
|
||||
|
||||
// 1. dynamic component
|
||||
|
@ -250,7 +250,7 @@ export function resolveComponentType(
|
|||
exp = isProp.exp
|
||||
if (!exp) {
|
||||
// #10469 handle :is shorthand
|
||||
exp = createSimpleExpression(`is`, false, isProp.loc)
|
||||
exp = createSimpleExpression(`is`, false, isProp.arg!.loc)
|
||||
if (!__BROWSER__) {
|
||||
exp = isProp.exp = processExpression(exp, context)
|
||||
}
|
||||
|
@ -374,7 +374,7 @@ export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
|||
export function buildProps(
|
||||
node: ElementNode,
|
||||
context: TransformContext,
|
||||
props: ElementNode['props'] = node.props,
|
||||
props: ElementNode['props'] | undefined = node.props,
|
||||
isComponent: boolean,
|
||||
isDynamicComponent: boolean,
|
||||
ssr = false,
|
||||
|
|
|
@ -99,7 +99,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
export const transformBindShorthand = (
|
||||
dir: DirectiveNode,
|
||||
context: TransformContext,
|
||||
) => {
|
||||
): void => {
|
||||
const arg = dir.arg!
|
||||
|
||||
const propName = camelize((arg as SimpleExpressionNode).content)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
type NodeTransform,
|
||||
type TransformContext,
|
||||
createStructuralDirectiveTransform,
|
||||
} from '../transform'
|
||||
|
@ -49,7 +50,7 @@ import { validateBrowserExpression } from '../validateExpression'
|
|||
import { PatchFlags } from '@vue/shared'
|
||||
import { transformBindShorthand } from './vBind'
|
||||
|
||||
export const transformFor = createStructuralDirectiveTransform(
|
||||
export const transformFor: NodeTransform = createStructuralDirectiveTransform(
|
||||
'for',
|
||||
(node, dir, context) => {
|
||||
const { helper, removeHelper } = context
|
||||
|
@ -299,7 +300,7 @@ export function processFor(
|
|||
|
||||
const onExit = processCodegen && processCodegen(forNode)
|
||||
|
||||
return () => {
|
||||
return (): void => {
|
||||
scopes.vFor--
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
value && removeIdentifiers(value)
|
||||
|
@ -313,7 +314,7 @@ export function processFor(
|
|||
export function finalizeForParseResult(
|
||||
result: ForParseResult,
|
||||
context: TransformContext,
|
||||
) {
|
||||
): void {
|
||||
if (result.finalized) return
|
||||
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
type NodeTransform,
|
||||
type TransformContext,
|
||||
createStructuralDirectiveTransform,
|
||||
traverseNode,
|
||||
|
@ -33,7 +34,7 @@ import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
|
|||
import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
|
||||
export const transformIf = createStructuralDirectiveTransform(
|
||||
export const transformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||
/^(if|else|else-if)$/,
|
||||
(node, dir, context) => {
|
||||
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
|
||||
|
@ -83,7 +84,7 @@ export function processIf(
|
|||
branch: IfBranchNode,
|
||||
isRoot: boolean,
|
||||
) => (() => void) | undefined,
|
||||
) {
|
||||
): (() => void) | undefined {
|
||||
if (
|
||||
dir.name !== 'else' &&
|
||||
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
|
||||
|
|
|
@ -153,18 +153,15 @@ export const isMemberExpressionBrowser = (path: string): boolean => {
|
|||
return !currentOpenBracketCount && !currentOpenParensCount
|
||||
}
|
||||
|
||||
export const isMemberExpressionNode = __BROWSER__
|
||||
? (NOOP as any as (
|
||||
export const isMemberExpressionNode: (
|
||||
path: string,
|
||||
options: Pick<TransformContext, 'expressionPlugins'>,
|
||||
) => boolean)
|
||||
: (
|
||||
path: string,
|
||||
options: Pick<TransformContext, 'expressionPlugins'>,
|
||||
): boolean => {
|
||||
context: Pick<TransformContext, 'expressionPlugins'>,
|
||||
) => boolean = __BROWSER__
|
||||
? (NOOP as any)
|
||||
: (path, context): boolean => {
|
||||
try {
|
||||
let ret: Expression = parseExpression(path, {
|
||||
plugins: options.expressionPlugins,
|
||||
plugins: context.expressionPlugins,
|
||||
})
|
||||
ret = unwrapTSNode(ret) as Expression
|
||||
return (
|
||||
|
@ -177,9 +174,10 @@ export const isMemberExpressionNode = __BROWSER__
|
|||
}
|
||||
}
|
||||
|
||||
export const isMemberExpression = __BROWSER__
|
||||
? isMemberExpressionBrowser
|
||||
: isMemberExpressionNode
|
||||
export const isMemberExpression: (
|
||||
path: string,
|
||||
context: TransformContext,
|
||||
) => boolean = __BROWSER__ ? isMemberExpressionBrowser : isMemberExpressionNode
|
||||
|
||||
export function advancePositionWithClone(
|
||||
pos: Position,
|
||||
|
@ -223,7 +221,7 @@ export function advancePositionWithMutation(
|
|||
return pos
|
||||
}
|
||||
|
||||
export function assert(condition: boolean, msg?: string) {
|
||||
export function assert(condition: boolean, msg?: string): void {
|
||||
/* istanbul ignore if */
|
||||
if (!condition) {
|
||||
throw new Error(msg || `unexpected compiler condition`)
|
||||
|
@ -338,7 +336,7 @@ export function injectProp(
|
|||
node: VNodeCall | RenderSlotCall,
|
||||
prop: Property,
|
||||
context: TransformContext,
|
||||
) {
|
||||
): void {
|
||||
let propsWithInjection: ObjectExpression | CallExpression | undefined
|
||||
/**
|
||||
* 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) {
|
||||
return node.arguments[1].returns as VNodeCall
|
||||
} 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]*)/
|
||||
|
|
|
@ -30,7 +30,7 @@ export function validateBrowserExpression(
|
|||
context: TransformContext,
|
||||
asParams = false,
|
||||
asRawStatements = false,
|
||||
) {
|
||||
): void {
|
||||
const exp = node.content
|
||||
|
||||
// empty expressions are validated per-directive since some directives
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.0-alpha.5",
|
||||
"version": "3.5.0-beta.1",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
import { registerRuntimeHelpers } from '@vue/compiler-core'
|
||||
|
||||
export const V_MODEL_RADIO = Symbol(__DEV__ ? `vModelRadio` : ``)
|
||||
export const V_MODEL_CHECKBOX = Symbol(__DEV__ ? `vModelCheckbox` : ``)
|
||||
export const V_MODEL_TEXT = Symbol(__DEV__ ? `vModelText` : ``)
|
||||
export const V_MODEL_SELECT = Symbol(__DEV__ ? `vModelSelect` : ``)
|
||||
export const V_MODEL_DYNAMIC = Symbol(__DEV__ ? `vModelDynamic` : ``)
|
||||
export const V_MODEL_RADIO: unique symbol = Symbol(__DEV__ ? `vModelRadio` : ``)
|
||||
export const V_MODEL_CHECKBOX: unique symbol = Symbol(
|
||||
__DEV__ ? `vModelCheckbox` : ``,
|
||||
)
|
||||
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_KEYS = Symbol(__DEV__ ? `vOnKeysGuard` : ``)
|
||||
export const V_ON_WITH_MODIFIERS: unique symbol = Symbol(
|
||||
__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_GROUP = Symbol(__DEV__ ? `TransitionGroup` : ``)
|
||||
export const TRANSITION: unique symbol = Symbol(__DEV__ ? `Transition` : ``)
|
||||
export const TRANSITION_GROUP: unique symbol = Symbol(
|
||||
__DEV__ ? `TransitionGroup` : ``,
|
||||
)
|
||||
|
||||
registerRuntimeHelpers({
|
||||
[V_MODEL_RADIO]: `vModelRadio`,
|
||||
|
|
|
@ -425,5 +425,15 @@ h1 { color: red }
|
|||
`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.`,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.5.0-alpha.5",
|
||||
"version": "3.5.0-beta.1",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -59,7 +59,7 @@
|
|||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "^9.0.5",
|
||||
"minimatch": "~9.0.5",
|
||||
"postcss-modules": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const version = __VERSION__
|
||||
export const version: string = __VERSION__
|
||||
|
||||
// API
|
||||
export { parse } from './parse'
|
||||
|
@ -18,7 +18,7 @@ import {
|
|||
errorMessages as coreErrorMessages,
|
||||
} from '@vue/compiler-dom'
|
||||
|
||||
export const errorMessages = {
|
||||
export const errorMessages: Record<number, string> = {
|
||||
...coreErrorMessages,
|
||||
...DOMErrorMessages,
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { parseCssVars } from './style/cssVars'
|
|||
import { createCache } from './cache'
|
||||
import type { ImportBinding } from './compileScript'
|
||||
import { isImportUsed } from './script/importUsageCheck'
|
||||
import type { LRUCache } from 'lru-cache'
|
||||
|
||||
export const DEFAULT_FILENAME = 'anonymous.vue'
|
||||
|
||||
|
@ -105,7 +106,9 @@ export interface SFCParseResult {
|
|||
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 {
|
||||
return (
|
||||
|
|
|
@ -17,11 +17,12 @@ export class ScriptCompileContext {
|
|||
scriptAst: Program | null
|
||||
scriptSetupAst: Program | null
|
||||
|
||||
source = this.descriptor.source
|
||||
filename = this.descriptor.filename
|
||||
s = new MagicString(this.source)
|
||||
startOffset = this.descriptor.scriptSetup?.loc.start.offset
|
||||
endOffset = this.descriptor.scriptSetup?.loc.end.offset
|
||||
source: string = this.descriptor.source
|
||||
filename: string = this.descriptor.filename
|
||||
s: MagicString = new MagicString(this.source)
|
||||
startOffset: number | undefined =
|
||||
this.descriptor.scriptSetup?.loc.start.offset
|
||||
endOffset: number | undefined = this.descriptor.scriptSetup?.loc.end.offset
|
||||
|
||||
// import / type analysis
|
||||
scope?: TypeScope
|
||||
|
@ -163,7 +164,7 @@ export function resolveParserPlugins(
|
|||
lang: string,
|
||||
userPlugins?: ParserPlugin[],
|
||||
dts = false,
|
||||
) {
|
||||
): ParserPlugin[] {
|
||||
const plugins: ParserPlugin[] = []
|
||||
if (
|
||||
!userPlugins ||
|
||||
|
|
|
@ -48,7 +48,7 @@ export function processDefineProps(
|
|||
ctx: ScriptCompileContext,
|
||||
node: Node,
|
||||
declId?: LVal,
|
||||
) {
|
||||
): boolean {
|
||||
if (!isCallOf(node, DEFINE_PROPS)) {
|
||||
return processWithDefaults(ctx, node, declId)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import { DEFINE_PROPS } from './defineProps'
|
|||
export function processPropsDestructure(
|
||||
ctx: ScriptCompileContext,
|
||||
declId: ObjectPattern,
|
||||
) {
|
||||
): void {
|
||||
if (ctx.options.propsDestructure === 'error') {
|
||||
ctx.error(`Props destructure is explicitly prohibited via config.`, declId)
|
||||
} else if (ctx.options.propsDestructure === false) {
|
||||
|
@ -97,7 +97,7 @@ type Scope = Record<string, boolean>
|
|||
export function transformDestructuredProps(
|
||||
ctx: ScriptCompileContext,
|
||||
vueImportAliases: Record<string, string>,
|
||||
) {
|
||||
): void {
|
||||
if (ctx.options.propsDestructure === false) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -3,13 +3,14 @@ import type { ScriptCompileContext } from './context'
|
|||
import MagicString from 'magic-string'
|
||||
import { rewriteDefaultAST } from '../rewriteDefault'
|
||||
import { genNormalScriptCssVarsCode } from '../style/cssVars'
|
||||
import type { SFCScriptBlock } from '../parse'
|
||||
|
||||
export const normalScriptDefaultVar = `__default__`
|
||||
|
||||
export function processNormalScript(
|
||||
ctx: ScriptCompileContext,
|
||||
scopeId: string,
|
||||
) {
|
||||
): SFCScriptBlock {
|
||||
const script = ctx.descriptor.script!
|
||||
if (script.lang && !ctx.isJS && !ctx.isTS) {
|
||||
// do not process non js/ts script blocks
|
||||
|
|
|
@ -823,7 +823,7 @@ let loadTS: (() => typeof TS) | undefined
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
export function registerTS(_loadTS: () => typeof TS) {
|
||||
export function registerTS(_loadTS: () => typeof TS): void {
|
||||
loadTS = () => {
|
||||
try {
|
||||
return _loadTS()
|
||||
|
@ -1107,7 +1107,7 @@ const fileToScopeCache = createCache<TypeScope>()
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
export function invalidateTypeCache(filename: string) {
|
||||
export function invalidateTypeCache(filename: string): void {
|
||||
filename = normalizePath(filename)
|
||||
fileToScopeCache.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)
|
||||
for (const s of body) {
|
||||
recordImport(s, imports)
|
||||
|
@ -1462,7 +1462,7 @@ function recordImport(node: Node, imports: TypeScope['imports']) {
|
|||
export function inferRuntimeType(
|
||||
ctx: TypeResolveContext,
|
||||
node: Node & MaybeWithScope,
|
||||
scope = node._ownerScope || ctxToScope(ctx),
|
||||
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
||||
isKeyOf = false,
|
||||
): string[] {
|
||||
try {
|
||||
|
|
|
@ -39,7 +39,7 @@ export function processAwait(
|
|||
node: AwaitExpression,
|
||||
needSemi: boolean,
|
||||
isStatement: boolean,
|
||||
) {
|
||||
): void {
|
||||
const argumentStart =
|
||||
node.argument.extra && node.argument.extra.parenthesized
|
||||
? (node.argument.extra.parenStart as number)
|
||||
|
|
|
@ -12,7 +12,10 @@ import path from 'path'
|
|||
|
||||
export const UNKNOWN_TYPE = 'Unknown'
|
||||
|
||||
export function resolveObjectKey(node: Node, computed: boolean) {
|
||||
export function resolveObjectKey(
|
||||
node: Node,
|
||||
computed: boolean,
|
||||
): string | undefined {
|
||||
switch (node.type) {
|
||||
case 'StringLiteral':
|
||||
case 'NumericLiteral':
|
||||
|
@ -23,11 +26,13 @@ export function resolveObjectKey(node: Node, computed: boolean) {
|
|||
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(', ')
|
||||
}
|
||||
|
||||
export function isLiteralNode(node: Node) {
|
||||
export function isLiteralNode(node: Node): boolean {
|
||||
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]
|
||||
}
|
||||
|
||||
|
@ -55,7 +60,7 @@ export function getImportedName(
|
|||
| ImportSpecifier
|
||||
| ImportDefaultSpecifier
|
||||
| ImportNamespaceSpecifier,
|
||||
) {
|
||||
): string {
|
||||
if (specifier.type === 'ImportSpecifier')
|
||||
return specifier.imported.type === 'Identifier'
|
||||
? specifier.imported.name
|
||||
|
@ -89,7 +94,9 @@ function toFileNameLowerCase(x: string) {
|
|||
* but TS does not expose it directly. This implementation is repllicated from
|
||||
* the TS source code.
|
||||
*/
|
||||
export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean) {
|
||||
export function createGetCanonicalFileName(
|
||||
useCaseSensitiveFileNames: boolean,
|
||||
): (str: string) => string {
|
||||
return useCaseSensitiveFileNames ? identity : toFileNameLowerCase
|
||||
}
|
||||
|
||||
|
@ -97,25 +104,31 @@ export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean) {
|
|||
// posix behavior.
|
||||
const normalize = (path.posix || path).normalize
|
||||
const windowsSlashRE = /\\/g
|
||||
export function normalizePath(p: string) {
|
||||
export function normalizePath(p: string): string {
|
||||
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
|
||||
* 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
|
||||
}
|
||||
|
||||
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 =>
|
||||
doubleEscape ? `\\\\${s}` : `\\${s}`,
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const hasWarned: Record<string, boolean> = {}
|
||||
|
||||
export function warnOnce(msg: string) {
|
||||
export function warnOnce(msg: string): void {
|
||||
const isNodeProd =
|
||||
typeof process !== 'undefined' && process.env.NODE_ENV === 'production'
|
||||
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(
|
||||
`\x1b[1m\x1b[33m[@vue/compiler-sfc]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`,
|
||||
)
|
||||
|
|
|
@ -288,12 +288,27 @@ describe('ssr: element', () => {
|
|||
}></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(`
|
||||
"\`<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\${
|
||||
_ssrRenderAttrs(_ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.z, "x", { y: true }))
|
||||
}></div>\`"
|
||||
}>hello</div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
|
@ -301,8 +316,10 @@ describe('ssr: element', () => {
|
|||
expect(getCompiledString(`<div class="foo" v-xxx />`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_mergeProps({ class: "foo" }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}></div>\`"
|
||||
_ssrRenderAttrs(_temp0 = _mergeProps({ class: "foo" }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}>\${
|
||||
("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
|
||||
}</div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
|
@ -310,11 +327,13 @@ describe('ssr: element', () => {
|
|||
expect(getCompiledString(`<div :title="foo" :class="bar" v-xxx />`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_mergeProps({
|
||||
_ssrRenderAttrs(_temp0 = _mergeProps({
|
||||
title: _ctx.foo,
|
||||
class: _ctx.bar
|
||||
}, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}></div>\`"
|
||||
}>\${
|
||||
("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
|
||||
}</div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
|
@ -322,8 +341,10 @@ describe('ssr: element', () => {
|
|||
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_mergeProps(_ctx.x, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}></div>\`"
|
||||
_ssrRenderAttrs(_temp0 = _mergeProps(_ctx.x, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}>\${
|
||||
("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
|
||||
}</div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
|
@ -332,11 +353,13 @@ describe('ssr: element', () => {
|
|||
getCompiledString(`<div v-bind="x" class="foo" v-xxx title="bar" />`),
|
||||
).toMatchInlineSnapshot(`
|
||||
"\`<div\${
|
||||
_ssrRenderAttrs(_mergeProps(_ctx.x, {
|
||||
_ssrRenderAttrs(_temp0 = _mergeProps(_ctx.x, {
|
||||
class: "foo",
|
||||
title: "bar"
|
||||
}, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||
}></div>\`"
|
||||
}>\${
|
||||
("textContent" in _temp0) ? _ssrInterpolate(_temp0.textContent) : _temp0.innerHTML ?? ''
|
||||
}</div>\`"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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', () => {
|
||||
expect(
|
||||
compile(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.0-alpha.5",
|
||||
"version": "3.5.0-beta.1",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -1,26 +1,34 @@
|
|||
import { registerRuntimeHelpers } from '@vue/compiler-dom'
|
||||
|
||||
export const SSR_INTERPOLATE = Symbol(`ssrInterpolate`)
|
||||
export const SSR_RENDER_VNODE = Symbol(`ssrRenderVNode`)
|
||||
export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`)
|
||||
export const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`)
|
||||
export const SSR_RENDER_SLOT_INNER = Symbol(`ssrRenderSlotInner`)
|
||||
export const SSR_RENDER_CLASS = Symbol(`ssrRenderClass`)
|
||||
export const SSR_RENDER_STYLE = Symbol(`ssrRenderStyle`)
|
||||
export const SSR_RENDER_ATTRS = Symbol(`ssrRenderAttrs`)
|
||||
export const SSR_RENDER_ATTR = Symbol(`ssrRenderAttr`)
|
||||
export const SSR_RENDER_DYNAMIC_ATTR = Symbol(`ssrRenderDynamicAttr`)
|
||||
export const SSR_RENDER_LIST = Symbol(`ssrRenderList`)
|
||||
export const SSR_INCLUDE_BOOLEAN_ATTR = Symbol(`ssrIncludeBooleanAttr`)
|
||||
export const SSR_LOOSE_EQUAL = Symbol(`ssrLooseEqual`)
|
||||
export const SSR_LOOSE_CONTAIN = Symbol(`ssrLooseContain`)
|
||||
export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
|
||||
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
|
||||
export const SSR_RENDER_TELEPORT = Symbol(`ssrRenderTeleport`)
|
||||
export const SSR_RENDER_SUSPENSE = Symbol(`ssrRenderSuspense`)
|
||||
export const SSR_GET_DIRECTIVE_PROPS = Symbol(`ssrGetDirectiveProps`)
|
||||
export const SSR_INTERPOLATE: unique symbol = Symbol(`ssrInterpolate`)
|
||||
export const SSR_RENDER_VNODE: unique symbol = Symbol(`ssrRenderVNode`)
|
||||
export const SSR_RENDER_COMPONENT: unique symbol = Symbol(`ssrRenderComponent`)
|
||||
export const SSR_RENDER_SLOT: unique symbol = Symbol(`ssrRenderSlot`)
|
||||
export const SSR_RENDER_SLOT_INNER: unique symbol = Symbol(`ssrRenderSlotInner`)
|
||||
export const SSR_RENDER_CLASS: unique symbol = Symbol(`ssrRenderClass`)
|
||||
export const SSR_RENDER_STYLE: unique symbol = Symbol(`ssrRenderStyle`)
|
||||
export const SSR_RENDER_ATTRS: unique symbol = Symbol(`ssrRenderAttrs`)
|
||||
export const SSR_RENDER_ATTR: unique symbol = Symbol(`ssrRenderAttr`)
|
||||
export const SSR_RENDER_DYNAMIC_ATTR: unique symbol =
|
||||
Symbol(`ssrRenderDynamicAttr`)
|
||||
export const SSR_RENDER_LIST: unique symbol = Symbol(`ssrRenderList`)
|
||||
export const SSR_INCLUDE_BOOLEAN_ATTR: unique symbol = Symbol(
|
||||
`ssrIncludeBooleanAttr`,
|
||||
)
|
||||
export const SSR_LOOSE_EQUAL: unique symbol = Symbol(`ssrLooseEqual`)
|
||||
export const SSR_LOOSE_CONTAIN: unique symbol = Symbol(`ssrLooseContain`)
|
||||
export const SSR_RENDER_DYNAMIC_MODEL: unique symbol = Symbol(
|
||||
`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_RENDER_VNODE]: `ssrRenderVNode`,
|
||||
[SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {
|
||||
type BlockStatement,
|
||||
type CallExpression,
|
||||
type CompilerError,
|
||||
type CompilerOptions,
|
||||
ElementTypes,
|
||||
type IfStatement,
|
||||
type JSChildNode,
|
||||
NodeTypes,
|
||||
type RootNode,
|
||||
type TemplateChildNode,
|
||||
|
@ -33,7 +35,10 @@ import { SSRErrorCodes, createSSRCompilerError } from './errors'
|
|||
// transform pass to convert the template AST into a fresh JS AST before
|
||||
// passing it to codegen.
|
||||
|
||||
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
||||
export function ssrCodegenTransform(
|
||||
ast: RootNode,
|
||||
options: CompilerOptions,
|
||||
): void {
|
||||
const context = createSSRTransformContext(ast, options)
|
||||
|
||||
// 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)))
|
||||
}
|
||||
|
||||
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(
|
||||
root: RootNode,
|
||||
options: CompilerOptions,
|
||||
helpers: Set<symbol> = new Set(),
|
||||
withSlotScopeId = false,
|
||||
) {
|
||||
): SSRTransformContext {
|
||||
const body: BlockStatement['body'] = []
|
||||
let currentString: TemplateLiteral | null = null
|
||||
|
||||
|
@ -96,7 +111,7 @@ function createSSRTransformContext(
|
|||
helpers.add(name)
|
||||
return name
|
||||
},
|
||||
pushStringPart(part: TemplateLiteral['elements'][0]) {
|
||||
pushStringPart(part) {
|
||||
if (!currentString) {
|
||||
const currentCall = createCallExpression(`_push`)
|
||||
body.push(currentCall)
|
||||
|
@ -111,7 +126,7 @@ function createSSRTransformContext(
|
|||
bufferedElements.push(part)
|
||||
}
|
||||
},
|
||||
pushStatement(statement: IfStatement | CallExpression) {
|
||||
pushStatement(statement) {
|
||||
// close current string
|
||||
currentString = null
|
||||
body.push(statement)
|
||||
|
@ -142,7 +157,7 @@ export function processChildren(
|
|||
asFragment = false,
|
||||
disableNestedFragments = false,
|
||||
disableCommentAsIfAlternate = false,
|
||||
) {
|
||||
): void {
|
||||
if (asFragment) {
|
||||
context.pushStringPart(`<!--[-->`)
|
||||
}
|
||||
|
@ -231,7 +246,7 @@ export function processChildrenAsStatement(
|
|||
parent: Container,
|
||||
parentContext: SSRTransformContext,
|
||||
asFragment = false,
|
||||
withSlotScopeId = parentContext.withSlotScopeId,
|
||||
withSlotScopeId: boolean = parentContext.withSlotScopeId,
|
||||
): BlockStatement {
|
||||
const childContext = createChildContext(parentContext, withSlotScopeId)
|
||||
processChildren(parent, childContext, asFragment)
|
||||
|
|
|
@ -205,7 +205,7 @@ export function ssrProcessComponent(
|
|||
node: ComponentNode,
|
||||
context: SSRTransformContext,
|
||||
parent: { children: TemplateChildNode[] },
|
||||
) {
|
||||
): void {
|
||||
const component = componentTypeMap.get(node)!
|
||||
if (!node.ssrCodegenNode) {
|
||||
// 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] =
|
||||
getBaseTransformPreset(true)
|
||||
|
|
|
@ -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) {
|
||||
|
@ -417,7 +436,7 @@ function findVModel(node: PlainElementNode): DirectiveNode | undefined {
|
|||
export function ssrProcessElement(
|
||||
node: PlainElementNode,
|
||||
context: SSRTransformContext,
|
||||
) {
|
||||
): void {
|
||||
const isVoidTag = context.options.isVoidTag || NO
|
||||
const elementsToAdd = node.ssrCodegenNode!.elements
|
||||
for (let j = 0; j < elementsToAdd.length; j++) {
|
||||
|
|
|
@ -73,7 +73,7 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
|
|||
export function ssrProcessSlotOutlet(
|
||||
node: SlotOutletNode,
|
||||
context: SSRTransformContext,
|
||||
) {
|
||||
): void {
|
||||
const renderCall = node.ssrCodegenNode!
|
||||
|
||||
// has fallback content
|
||||
|
|
|
@ -29,7 +29,7 @@ export function ssrTransformSuspense(
|
|||
node: ComponentNode,
|
||||
context: TransformContext,
|
||||
) {
|
||||
return () => {
|
||||
return (): void => {
|
||||
if (node.children.length) {
|
||||
const wipEntry: WIPEntry = {
|
||||
slotsExp: null!, // to be immediately set
|
||||
|
@ -62,7 +62,7 @@ export function ssrTransformSuspense(
|
|||
export function ssrProcessSuspense(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext,
|
||||
) {
|
||||
): void {
|
||||
// complete wip slots with ssr code
|
||||
const wipEntry = wipMap.get(node)
|
||||
if (!wipEntry) {
|
||||
|
|
|
@ -18,7 +18,7 @@ import { SSR_RENDER_TELEPORT } from '../runtimeHelpers'
|
|||
export function ssrProcessTeleport(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext,
|
||||
) {
|
||||
): void {
|
||||
const targetProp = findProp(node, 'to')
|
||||
if (!targetProp) {
|
||||
context.onError(
|
||||
|
|
|
@ -15,7 +15,7 @@ export function ssrTransformTransition(
|
|||
node: ComponentNode,
|
||||
context: TransformContext,
|
||||
) {
|
||||
return () => {
|
||||
return (): void => {
|
||||
const appear = findProp(node, 'appear', false, true)
|
||||
wipMap.set(node, !!appear)
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export function ssrTransformTransition(
|
|||
export function ssrProcessTransition(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext,
|
||||
) {
|
||||
): void {
|
||||
// #5351: filter out comment children inside transition
|
||||
node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ export function ssrTransformTransitionGroup(
|
|||
node: ComponentNode,
|
||||
context: TransformContext,
|
||||
) {
|
||||
return () => {
|
||||
return (): void => {
|
||||
const tag = findProp(node, 'tag')
|
||||
if (tag) {
|
||||
const otherProps = node.props.filter(p => p !== tag)
|
||||
|
@ -60,7 +60,7 @@ export function ssrTransformTransitionGroup(
|
|||
export function ssrProcessTransitionGroup(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext,
|
||||
) {
|
||||
): void {
|
||||
const entry = wipMap.get(node)
|
||||
if (entry) {
|
||||
const { tag, propsExp, scopeId } = entry
|
||||
|
@ -108,7 +108,7 @@ export function ssrProcessTransitionGroup(
|
|||
context.pushStringPart(` ${scopeId}`)
|
||||
}
|
||||
context.pushStringPart(`>`)
|
||||
processChildren(node, context, false, true)
|
||||
processChildren(node, context, false, true, true)
|
||||
context.pushStringPart(`</${tag.value!.content}>`)
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
type ForNode,
|
||||
type NodeTransform,
|
||||
NodeTypes,
|
||||
createCallExpression,
|
||||
createForLoopParams,
|
||||
|
@ -14,10 +15,8 @@ import {
|
|||
import { SSR_RENDER_LIST } from '../runtimeHelpers'
|
||||
|
||||
// Plugin for the first transform pass, which simply constructs the AST node
|
||||
export const ssrTransformFor = createStructuralDirectiveTransform(
|
||||
'for',
|
||||
processFor,
|
||||
)
|
||||
export const ssrTransformFor: NodeTransform =
|
||||
createStructuralDirectiveTransform('for', processFor)
|
||||
|
||||
// This is called during the 2nd transform pass to construct the SSR-specific
|
||||
// codegen nodes.
|
||||
|
@ -25,7 +24,7 @@ export function ssrProcessFor(
|
|||
node: ForNode,
|
||||
context: SSRTransformContext,
|
||||
disableNestedFragments = false,
|
||||
) {
|
||||
): void {
|
||||
const needFragmentWrapper =
|
||||
!disableNestedFragments &&
|
||||
(node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT)
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
type BlockStatement,
|
||||
type IfBranchNode,
|
||||
type IfNode,
|
||||
type NodeTransform,
|
||||
NodeTypes,
|
||||
createBlockStatement,
|
||||
createCallExpression,
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
} from '../ssrCodegenTransform'
|
||||
|
||||
// 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)$/,
|
||||
processIf,
|
||||
)
|
||||
|
@ -27,7 +28,7 @@ export function ssrProcessIf(
|
|||
context: SSRTransformContext,
|
||||
disableNestedFragments = false,
|
||||
disableCommentAsIfAlternate = false,
|
||||
) {
|
||||
): void {
|
||||
const [rootBranch] = node.branches
|
||||
const ifStatement = createIfStatement(
|
||||
rootBranch.condition!,
|
||||
|
|
|
@ -1593,6 +1593,7 @@ describe('expose typing', () => {
|
|||
import type {
|
||||
AllowedComponentProps,
|
||||
ComponentCustomProps,
|
||||
ComponentInstance,
|
||||
ComponentOptionsMixin,
|
||||
DefineComponent,
|
||||
Directive,
|
||||
|
@ -1756,6 +1757,24 @@ describe('__typeEmits backdoor, call signature syntax', () => {
|
|||
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({
|
||||
props: {
|
||||
foo: [String, null],
|
||||
|
|
|
@ -68,7 +68,7 @@ describe('inject', () => {
|
|||
})
|
||||
|
||||
describe('defineCustomElement using defineComponent return type', () => {
|
||||
test('with emits', () => {
|
||||
test('with object emits', () => {
|
||||
const Comp1Vue = defineComponent({
|
||||
props: {
|
||||
a: String,
|
||||
|
@ -80,6 +80,23 @@ describe('defineCustomElement using defineComponent return type', () => {
|
|||
const Comp = defineCustomElement(Comp1Vue)
|
||||
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
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
type InjectionKey,
|
||||
type Ref,
|
||||
createApp,
|
||||
defineComponent,
|
||||
inject,
|
||||
provide,
|
||||
ref,
|
||||
|
@ -52,3 +53,9 @@ provide<Cube>(123, { size: 'foo' })
|
|||
const app = createApp({})
|
||||
// @ts-expect-error
|
||||
app.provide(injectionKeyRef, ref({}))
|
||||
|
||||
defineComponent({
|
||||
provide: {
|
||||
[injectionKeyRef]: { size: 'foo' },
|
||||
},
|
||||
})
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
type Ref,
|
||||
type ShallowRef,
|
||||
type ToRefs,
|
||||
type WritableComputedRef,
|
||||
computed,
|
||||
isRef,
|
||||
proxyRefs,
|
||||
|
@ -181,6 +182,31 @@ describe('allow getter and setter types to be unrelated', <T>() => {
|
|||
const d = {} as T
|
||||
const e = ref(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
|
||||
|
@ -465,8 +491,21 @@ describe('toRef <-> toValue', () => {
|
|||
})
|
||||
|
||||
// unref
|
||||
declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string>
|
||||
expectType<string>(unref(text))
|
||||
// #8747
|
||||
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
|
||||
const tRef = useTemplateRef('foo')
|
||||
|
|
|
@ -43,7 +43,8 @@ describe('defineProps w/ generics', () => {
|
|||
test()
|
||||
})
|
||||
|
||||
describe('defineProps w/ type declaration + withDefaults', () => {
|
||||
describe('defineProps w/ type declaration + withDefaults', <T extends
|
||||
string>() => {
|
||||
const res = withDefaults(
|
||||
defineProps<{
|
||||
number?: number
|
||||
|
@ -56,6 +57,7 @@ describe('defineProps w/ type declaration + withDefaults', () => {
|
|||
z?: string
|
||||
bool?: boolean
|
||||
boolAndUndefined: boolean | undefined
|
||||
foo?: T
|
||||
}>(),
|
||||
{
|
||||
number: 123,
|
||||
|
@ -65,6 +67,7 @@ describe('defineProps w/ type declaration + withDefaults', () => {
|
|||
genStr: () => '',
|
||||
y: undefined,
|
||||
z: 'string',
|
||||
foo: '' as any,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -81,6 +84,7 @@ describe('defineProps w/ type declaration + withDefaults', () => {
|
|||
expectType<string | undefined>(res.x)
|
||||
expectType<string | undefined>(res.y)
|
||||
expectType<string>(res.z)
|
||||
expectType<T>(res.foo)
|
||||
|
||||
expectType<boolean>(res.bool)
|
||||
expectType<boolean>(res.boolAndUndefined)
|
||||
|
@ -138,6 +142,31 @@ describe('defineProps w/ object union + withDefaults', () => {
|
|||
>(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
|
||||
number, TA extends {
|
||||
a: string
|
||||
|
|
|
@ -1282,4 +1282,48 @@ describe('reactivity/effect', () => {
|
|||
).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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -295,4 +295,31 @@ describe('reactivity/effect/scope', () => {
|
|||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -478,4 +478,38 @@ describe('reactivity/ref', () => {
|
|||
expect(toValue(c)).toBe(3)
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.5.0-alpha.5",
|
||||
"version": "3.5.0-beta.1",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -52,7 +52,7 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
|||
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,
|
||||
isShallow = this._isShallow
|
||||
if (key === ReactiveFlags.IS_REACTIVE) {
|
||||
|
@ -73,7 +73,7 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
|||
: reactiveMap
|
||||
).get(target) ||
|
||||
// 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)
|
||||
) {
|
||||
return target
|
||||
|
@ -240,12 +240,11 @@ export const mutableHandlers: ProxyHandler<object> =
|
|||
export const readonlyHandlers: ProxyHandler<object> =
|
||||
/*#__PURE__*/ new ReadonlyReactiveHandler()
|
||||
|
||||
export const shallowReactiveHandlers = /*#__PURE__*/ new MutableReactiveHandler(
|
||||
true,
|
||||
)
|
||||
export const shallowReactiveHandlers: MutableReactiveHandler =
|
||||
/*#__PURE__*/ new MutableReactiveHandler(true)
|
||||
|
||||
// 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
|
||||
// retain the reactivity of the normal readonly object.
|
||||
export const shallowReadonlyHandlers =
|
||||
export const shallowReadonlyHandlers: ReadonlyReactiveHandler =
|
||||
/*#__PURE__*/ new ReadonlyReactiveHandler(true)
|
||||
|
|
|
@ -85,7 +85,7 @@ let activeWatcher: ReactiveEffect | undefined = undefined
|
|||
/**
|
||||
* Returns the current active effect if there is one.
|
||||
*/
|
||||
export function getCurrentWatcher() {
|
||||
export function getCurrentWatcher(): ReactiveEffect<any> | undefined {
|
||||
return activeWatcher
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,10 @@ export function getCurrentWatcher() {
|
|||
*
|
||||
* @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) {
|
||||
const cleanups =
|
||||
cleanupMap.get(activeWatcher) ||
|
||||
|
@ -133,11 +136,15 @@ export function baseWatch(
|
|||
)
|
||||
}
|
||||
|
||||
const reactiveGetter = (source: object) =>
|
||||
deep === true
|
||||
? source // traverse will happen in wrapped getter below
|
||||
: // for deep: false, only traverse root-level properties
|
||||
traverse(source, deep === false ? 1 : undefined)
|
||||
const reactiveGetter = (source: object) => {
|
||||
// traverse will happen in wrapped getter below
|
||||
if (deep) return source
|
||||
// for `deep: false | 0` or shallow reactive, only traverse root-level properties
|
||||
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 getter: () => any
|
||||
|
@ -207,7 +214,8 @@ export function baseWatch(
|
|||
|
||||
if (cb && deep) {
|
||||
const baseGetter = getter
|
||||
getter = () => traverse(baseGetter())
|
||||
const depth = deep === true ? Infinity : deep
|
||||
getter = () => traverse(baseGetter(), depth)
|
||||
}
|
||||
|
||||
if (once) {
|
||||
|
@ -322,9 +330,9 @@ export function baseWatch(
|
|||
|
||||
export function traverse(
|
||||
value: unknown,
|
||||
depth = Infinity,
|
||||
depth: number = Infinity,
|
||||
seen?: Set<unknown>,
|
||||
) {
|
||||
): unknown {
|
||||
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
||||
return value
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ export interface ComputedRef<T = any> extends WritableComputedRef<T> {
|
|||
[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
|
||||
*/
|
||||
|
@ -30,9 +30,9 @@ export interface WritableComputedRef<T> extends Ref<T> {
|
|||
export type ComputedGetter<T> = (oldValue?: T) => T
|
||||
export type ComputedSetter<T> = (newValue: T) => void
|
||||
|
||||
export interface WritableComputedOptions<T> {
|
||||
export interface WritableComputedOptions<T, S = T> {
|
||||
get: ComputedGetter<T>
|
||||
set: ComputedSetter<T>
|
||||
set: ComputedSetter<S>
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,15 +47,17 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly dep = new Dep(this)
|
||||
readonly dep: Dep = new Dep(this)
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly [ReactiveFlags.IS_REF] = true
|
||||
readonly __v_isRef = true
|
||||
// TODO isolatedDeclarations ReactiveFlags.IS_REF
|
||||
/**
|
||||
* @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
|
||||
/**
|
||||
* @internal
|
||||
|
@ -68,17 +70,17 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
flags = EffectFlags.DIRTY
|
||||
flags: EffectFlags = EffectFlags.DIRTY
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
globalVersion = globalVersion - 1
|
||||
globalVersion: number = globalVersion - 1
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
isSSR: boolean
|
||||
// for backwards compat
|
||||
effect = this
|
||||
effect: this = this
|
||||
|
||||
// dev only
|
||||
onTrack?: (event: DebuggerEvent) => void
|
||||
|
@ -103,7 +105,7 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
notify() {
|
||||
notify(): void {
|
||||
// avoid infinite self recursion
|
||||
if (activeSub !== this) {
|
||||
this.flags |= EffectFlags.DIRTY
|
||||
|
@ -113,7 +115,7 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
|||
}
|
||||
}
|
||||
|
||||
get value() {
|
||||
get value(): T {
|
||||
const link = __DEV__
|
||||
? this.dep.track({
|
||||
target: this,
|
||||
|
@ -175,10 +177,10 @@ export function computed<T>(
|
|||
getter: ComputedGetter<T>,
|
||||
debugOptions?: DebuggerOptions,
|
||||
): ComputedRef<T>
|
||||
export function computed<T>(
|
||||
options: WritableComputedOptions<T>,
|
||||
export function computed<T, S = T>(
|
||||
options: WritableComputedOptions<T, S>,
|
||||
debugOptions?: DebuggerOptions,
|
||||
): WritableComputedRef<T>
|
||||
): WritableComputedRef<T, S>
|
||||
export function computed<T>(
|
||||
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
|
||||
debugOptions?: DebuggerOptions,
|
||||
|
|
|
@ -39,7 +39,7 @@ export class Dep {
|
|||
*/
|
||||
subsHead?: Link
|
||||
|
||||
constructor(public computed?: ComputedRefImpl) {
|
||||
constructor(public computed?: ComputedRefImpl | undefined) {
|
||||
if (__DEV__) {
|
||||
this.subsHead = undefined
|
||||
}
|
||||
|
@ -115,13 +115,13 @@ export class Dep {
|
|||
return link
|
||||
}
|
||||
|
||||
trigger(debugInfo?: DebuggerEventExtraInfo) {
|
||||
trigger(debugInfo?: DebuggerEventExtraInfo): void {
|
||||
this.version++
|
||||
globalVersion++
|
||||
this.notify(debugInfo)
|
||||
}
|
||||
|
||||
notify(debugInfo?: DebuggerEventExtraInfo) {
|
||||
notify(debugInfo?: DebuggerEventExtraInfo): void {
|
||||
startBatch()
|
||||
try {
|
||||
if (__DEV__) {
|
||||
|
@ -185,9 +185,15 @@ function addSub(link: Link) {
|
|||
type KeyToDepMap = Map<any, Dep>
|
||||
const targetMap = new WeakMap<object, KeyToDepMap>()
|
||||
|
||||
export const ITERATE_KEY = Symbol(__DEV__ ? 'Object iterate' : '')
|
||||
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map keys iterate' : '')
|
||||
export const ARRAY_ITERATE_KEY = Symbol(__DEV__ ? 'Array iterate' : '')
|
||||
export const ITERATE_KEY: unique symbol = Symbol(
|
||||
__DEV__ ? 'Object 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.
|
||||
|
@ -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 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) {
|
||||
let depsMap = targetMap.get(target)
|
||||
if (!depsMap) {
|
||||
|
@ -236,7 +242,7 @@ export function trigger(
|
|||
newValue?: unknown,
|
||||
oldValue?: unknown,
|
||||
oldTarget?: Map<unknown, unknown> | Set<unknown>,
|
||||
) {
|
||||
): void {
|
||||
const depsMap = targetMap.get(target)
|
||||
if (!depsMap) {
|
||||
// never been tracked
|
||||
|
@ -328,7 +334,10 @@ export function trigger(
|
|||
/**
|
||||
* 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
|
||||
return targetMap.get(object)?.get(key)
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ export enum EffectFlags {
|
|||
DIRTY = 1 << 4,
|
||||
ALLOW_RECURSE = 1 << 5,
|
||||
NO_BATCH = 1 << 6,
|
||||
PAUSED = 1 << 7,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,6 +108,8 @@ export interface Link {
|
|||
prevActiveLink?: Link
|
||||
}
|
||||
|
||||
const pausedQueueEffects = new WeakSet<ReactiveEffect>()
|
||||
|
||||
export class ReactiveEffect<T = any>
|
||||
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
|
||||
*/
|
||||
notify() {
|
||||
notify(): void {
|
||||
if (
|
||||
this.flags & EffectFlags.RUNNING &&
|
||||
!(this.flags & EffectFlags.ALLOW_RECURSE)
|
||||
|
@ -162,7 +179,7 @@ export class ReactiveEffect<T = any>
|
|||
}
|
||||
}
|
||||
|
||||
run() {
|
||||
run(): T {
|
||||
// TODO cleanupEffect
|
||||
|
||||
if (!(this.flags & EffectFlags.ACTIVE)) {
|
||||
|
@ -194,7 +211,7 @@ export class ReactiveEffect<T = any>
|
|||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
stop(): void {
|
||||
if (this.flags & EffectFlags.ACTIVE) {
|
||||
for (let link = this.deps; link; link = link.nextDep) {
|
||||
removeSub(link)
|
||||
|
@ -206,8 +223,10 @@ export class ReactiveEffect<T = any>
|
|||
}
|
||||
}
|
||||
|
||||
trigger() {
|
||||
if (this.scheduler) {
|
||||
trigger(): void {
|
||||
if (this.flags & EffectFlags.PAUSED) {
|
||||
pausedQueueEffects.add(this)
|
||||
} else if (this.scheduler) {
|
||||
this.scheduler()
|
||||
} else {
|
||||
this.runIfDirty()
|
||||
|
@ -217,13 +236,13 @@ export class ReactiveEffect<T = any>
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
runIfDirty() {
|
||||
runIfDirty(): void {
|
||||
if (isDirty(this)) {
|
||||
this.run()
|
||||
}
|
||||
}
|
||||
|
||||
get dirty() {
|
||||
get dirty(): boolean {
|
||||
return isDirty(this)
|
||||
}
|
||||
}
|
||||
|
@ -234,7 +253,7 @@ let batchedEffect: ReactiveEffect | undefined
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function startBatch() {
|
||||
export function startBatch(): void {
|
||||
batchDepth++
|
||||
}
|
||||
|
||||
|
@ -242,7 +261,7 @@ export function startBatch() {
|
|||
* Run batched effects when all batches have ended
|
||||
* @internal
|
||||
*/
|
||||
export function endBatch() {
|
||||
export function endBatch(): void {
|
||||
if (batchDepth > 1) {
|
||||
batchDepth--
|
||||
return
|
||||
|
@ -331,7 +350,7 @@ function isDirty(sub: Subscriber): boolean {
|
|||
* Returning false indicates the refresh failed
|
||||
* @internal
|
||||
*/
|
||||
export function refreshComputed(computed: ComputedRefImpl) {
|
||||
export function refreshComputed(computed: ComputedRefImpl): false | undefined {
|
||||
if (computed.flags & EffectFlags.RUNNING) {
|
||||
return false
|
||||
}
|
||||
|
@ -455,7 +474,7 @@ export function effect<T = any>(
|
|||
*
|
||||
* @param runner - Association with the effect to stop tracking.
|
||||
*/
|
||||
export function stop(runner: ReactiveEffectRunner) {
|
||||
export function stop(runner: ReactiveEffectRunner): void {
|
||||
runner.effect.stop()
|
||||
}
|
||||
|
||||
|
@ -468,7 +487,7 @@ const trackStack: boolean[] = []
|
|||
/**
|
||||
* Temporarily pauses tracking.
|
||||
*/
|
||||
export function pauseTracking() {
|
||||
export function pauseTracking(): void {
|
||||
trackStack.push(shouldTrack)
|
||||
shouldTrack = false
|
||||
}
|
||||
|
@ -476,7 +495,7 @@ export function pauseTracking() {
|
|||
/**
|
||||
* Re-enables effect tracking (if it was paused).
|
||||
*/
|
||||
export function enableTracking() {
|
||||
export function enableTracking(): void {
|
||||
trackStack.push(shouldTrack)
|
||||
shouldTrack = true
|
||||
}
|
||||
|
@ -484,7 +503,7 @@ export function enableTracking() {
|
|||
/**
|
||||
* Resets the previous global effect tracking state.
|
||||
*/
|
||||
export function resetTracking() {
|
||||
export function resetTracking(): void {
|
||||
const last = trackStack.pop()
|
||||
shouldTrack = last === undefined ? true : last
|
||||
}
|
||||
|
@ -501,7 +520,7 @@ export function resetTracking() {
|
|||
* @param failSilently - if `true`, will not throw warning when called without
|
||||
* an active effect.
|
||||
*/
|
||||
export function onEffectCleanup(fn: () => void, failSilently = false) {
|
||||
export function onEffectCleanup(fn: () => void, failSilently = false): void {
|
||||
if (activeSub instanceof ReactiveEffect) {
|
||||
activeSub.cleanup = fn
|
||||
} else if (__DEV__ && !failSilently) {
|
||||
|
|
|
@ -17,6 +17,8 @@ export class EffectScope {
|
|||
*/
|
||||
cleanups: (() => void)[] = []
|
||||
|
||||
private _isPaused = false
|
||||
|
||||
/**
|
||||
* only assigned by undetached scope
|
||||
* @internal
|
||||
|
@ -44,10 +46,43 @@ export class EffectScope {
|
|||
}
|
||||
}
|
||||
|
||||
get active() {
|
||||
get active(): boolean {
|
||||
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 {
|
||||
if (this._active) {
|
||||
const currentEffectScope = activeEffectScope
|
||||
|
@ -67,7 +102,7 @@ export class EffectScope {
|
|||
* This should only be called on non-detached scopes
|
||||
* @internal
|
||||
*/
|
||||
on() {
|
||||
on(): void {
|
||||
this.prevScope = activeEffectScope
|
||||
activeEffectScope = this
|
||||
}
|
||||
|
@ -76,11 +111,11 @@ export class EffectScope {
|
|||
* This should only be called on non-detached scopes
|
||||
* @internal
|
||||
*/
|
||||
off() {
|
||||
off(): void {
|
||||
activeEffectScope = this.prevScope
|
||||
}
|
||||
|
||||
stop(fromParent?: boolean) {
|
||||
stop(fromParent?: boolean): void {
|
||||
if (this._active) {
|
||||
let i, l
|
||||
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.
|
||||
* @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)
|
||||
}
|
||||
|
||||
|
@ -127,7 +162,7 @@ export function effectScope(detached?: boolean) {
|
|||
*
|
||||
* @see {@link https://vuejs.org/api/reactivity-advanced.html#getcurrentscope}
|
||||
*/
|
||||
export function getCurrentScope() {
|
||||
export function getCurrentScope(): EffectScope | undefined {
|
||||
return activeEffectScope
|
||||
}
|
||||
|
||||
|
@ -138,7 +173,7 @@ export function getCurrentScope() {
|
|||
* @param fn - The callback function to attach to the scope's cleanup.
|
||||
* @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) {
|
||||
activeEffectScope.cleanups.push(fn)
|
||||
} else if (__DEV__ && !failSilently) {
|
||||
|
|
|
@ -23,10 +23,16 @@ export interface Target {
|
|||
[ReactiveFlags.RAW]?: any
|
||||
}
|
||||
|
||||
export const reactiveMap = new WeakMap<Target, any>()
|
||||
export const shallowReactiveMap = new WeakMap<Target, any>()
|
||||
export const readonlyMap = new WeakMap<Target, any>()
|
||||
export const shallowReadonlyMap = new WeakMap<Target, any>()
|
||||
export const reactiveMap: WeakMap<Target, any> = new WeakMap<Target, any>()
|
||||
export const shallowReactiveMap: WeakMap<Target, any> = new WeakMap<
|
||||
Target,
|
||||
any
|
||||
>()
|
||||
export const readonlyMap: WeakMap<Target, any> = new WeakMap<Target, any>()
|
||||
export const shallowReadonlyMap: WeakMap<Target, any> = new WeakMap<
|
||||
Target,
|
||||
any
|
||||
>()
|
||||
|
||||
enum TargetType {
|
||||
INVALID = 0,
|
||||
|
@ -60,8 +66,8 @@ export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
|
|||
|
||||
declare const ReactiveMarkerSymbol: unique symbol
|
||||
|
||||
export declare class ReactiveMarker {
|
||||
private [ReactiveMarkerSymbol]?: void
|
||||
export interface ReactiveMarker {
|
||||
[ReactiveMarkerSymbol]?: void
|
||||
}
|
||||
|
||||
export type Reactive<T> = UnwrapNestedRefs<T> &
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
toRaw,
|
||||
toReactive,
|
||||
} from './reactive'
|
||||
import type { ComputedRef } from './computed'
|
||||
import type { ComputedRef, WritableComputedRef } from './computed'
|
||||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
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.
|
||||
* @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(value?: unknown) {
|
||||
return createRef(value, false)
|
||||
|
@ -179,7 +181,7 @@ class RefImpl<T = any> {
|
|||
* @param ref - The ref whose tied effects shall be executed.
|
||||
* @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref}
|
||||
*/
|
||||
export function triggerRef(ref: Ref) {
|
||||
export function triggerRef(ref: Ref): void {
|
||||
if (__DEV__) {
|
||||
;(ref as unknown as RefImpl).dep.trigger({
|
||||
target: ref,
|
||||
|
@ -192,8 +194,13 @@ export function triggerRef(ref: Ref) {
|
|||
}
|
||||
}
|
||||
|
||||
export type MaybeRef<T = any> = T | Ref<T>
|
||||
export type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T)
|
||||
export type MaybeRef<T = any> =
|
||||
| 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
|
||||
|
@ -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.
|
||||
* @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
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
* @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue}
|
||||
*/
|
||||
export function toValue<T>(
|
||||
source: MaybeRefOrGetter<T> | ComputedRef<T> | ShallowRef<T>,
|
||||
): T {
|
||||
export function toValue<T>(source: MaybeRefOrGetter<T>): T {
|
||||
return isFunction(source) ? source() : unref(source)
|
||||
}
|
||||
|
||||
|
@ -282,6 +287,8 @@ class CustomRefImpl<T> {
|
|||
|
||||
public readonly [ReactiveFlags.IS_REF] = true
|
||||
|
||||
public _value: T = undefined!
|
||||
|
||||
constructor(factory: CustomRefFactory<T>) {
|
||||
const dep = (this.dep = new Dep())
|
||||
const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep))
|
||||
|
@ -290,7 +297,7 @@ class CustomRefImpl<T> {
|
|||
}
|
||||
|
||||
get value() {
|
||||
return this._get()
|
||||
return (this._value = this._get())
|
||||
}
|
||||
|
||||
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> {
|
||||
public readonly [ReactiveFlags.IS_REF] = true
|
||||
public _value: T[K] = undefined!
|
||||
|
||||
constructor(
|
||||
private readonly _object: T,
|
||||
|
@ -343,7 +351,7 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
|
|||
|
||||
get value() {
|
||||
const val = this._object[this._key]
|
||||
return val === undefined ? this._defaultValue! : val
|
||||
return (this._value = val === undefined ? this._defaultValue! : val)
|
||||
}
|
||||
|
||||
set value(newVal) {
|
||||
|
@ -358,9 +366,11 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
|
|||
class GetterRefImpl<T> {
|
||||
public readonly [ReactiveFlags.IS_REF] = true
|
||||
public readonly [ReactiveFlags.IS_READONLY] = true
|
||||
public _value: T = undefined!
|
||||
|
||||
constructor(private readonly _getter: () => T) {}
|
||||
get value() {
|
||||
return this._getter()
|
||||
return (this._value = this._getter())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ const { render, createApp } = createRenderer({
|
|||
patchProp,
|
||||
insert,
|
||||
remove,
|
||||
createElement
|
||||
createElement,
|
||||
// ...
|
||||
})
|
||||
|
||||
|
|
|
@ -116,12 +116,25 @@ describe('api: createApp', () => {
|
|||
const app = createApp({
|
||||
setup() {
|
||||
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')
|
||||
},
|
||||
})
|
||||
app.provide('foo', 1)
|
||||
|
||||
expect(app.runWithContext(() => inject('foo'))).toBe(1)
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
|
||||
expect(
|
||||
app.runWithContext(() => {
|
||||
|
|
|
@ -1562,6 +1562,186 @@ describe('api: watch', () => {
|
|||
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", () => {
|
||||
const num = ref(0)
|
||||
let unwatch: () => void
|
||||
|
|
|
@ -155,12 +155,12 @@ describe('component: emit', () => {
|
|||
render() {},
|
||||
created() {
|
||||
// @ts-expect-error
|
||||
this.$emit('bar')
|
||||
this.$emit('bar-baz')
|
||||
},
|
||||
})
|
||||
render(h(Foo), nodeOps.createElement('div'))
|
||||
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()
|
||||
})
|
||||
|
||||
|
@ -172,12 +172,12 @@ describe('component: emit', () => {
|
|||
render() {},
|
||||
created() {
|
||||
// @ts-expect-error
|
||||
this.$emit('bar')
|
||||
this.$emit('bar-baz')
|
||||
},
|
||||
})
|
||||
render(h(Foo), nodeOps.createElement('div'))
|
||||
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()
|
||||
})
|
||||
|
||||
|
@ -197,6 +197,22 @@ describe('component: emit', () => {
|
|||
).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', () => {
|
||||
const Foo = defineComponent({
|
||||
emits: {
|
||||
|
|
|
@ -213,7 +213,7 @@ describe('component: slots', () => {
|
|||
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 flag2 = ref(2)
|
||||
const spy = vi.fn()
|
||||
|
@ -255,6 +255,48 @@ describe('component: slots', () => {
|
|||
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', () => {
|
||||
const Comp = {
|
||||
setup(_: any, { slots }: any) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
KeepAlive,
|
||||
Suspense,
|
||||
type SuspenseProps,
|
||||
createCommentVNode,
|
||||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
|
@ -2035,7 +2036,7 @@ describe('Suspense', () => {
|
|||
expect(serializeInner(root)).toBe(`<div>sync</div>`)
|
||||
})
|
||||
|
||||
// #10899
|
||||
// #10899 / #11427
|
||||
test('KeepAlive + Suspense switch before branch resolves', async () => {
|
||||
const Async1 = defineAsyncComponent({
|
||||
render() {
|
||||
|
@ -2052,14 +2053,20 @@ describe('Suspense', () => {
|
|||
const root = nodeOps.createElement('div')
|
||||
const App = {
|
||||
render() {
|
||||
return h(KeepAlive, null, {
|
||||
return h(
|
||||
KeepAlive,
|
||||
{
|
||||
max: 1,
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return h(Suspense, null, {
|
||||
default: h(components[viewRef.value]),
|
||||
fallback: h('div', 'loading'),
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
render(h(App), root)
|
||||
|
@ -2085,6 +2092,35 @@ describe('Suspense', () => {
|
|||
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
|
||||
test('Suspense patched during HOC async component re-mount', async () => {
|
||||
const key = ref('k')
|
||||
|
|
|
@ -657,4 +657,96 @@ describe('useModel', () => {
|
|||
expect(setValue).toBeCalledTimes(2)
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -68,4 +68,18 @@ describe('useTemplateRef', () => {
|
|||
expect(t2!.value).toBe(root.children[0])
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1388,6 +1388,26 @@ describe('SSR hydration', () => {
|
|||
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
|
||||
test('empty text node in slot', () => {
|
||||
const Comp = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.0-alpha.5",
|
||||
"version": "3.5.0-beta.1",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -50,8 +50,18 @@ export interface App<HostElement = any> {
|
|||
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
|
||||
mount(
|
||||
rootContainer: HostElement | string,
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
isHydrate?: boolean,
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
namespace?: boolean | ElementNamespace,
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
vnode?: VNode,
|
||||
): ComponentPublicInstance
|
||||
unmount(): void
|
||||
onUnmount(cb: () => void): void
|
||||
|
@ -76,6 +86,11 @@ export interface App<HostElement = any> {
|
|||
_context: AppContext
|
||||
_instance: ComponentInternalInstance | null
|
||||
|
||||
/**
|
||||
* @internal custom element vnode
|
||||
*/
|
||||
_ceVNode?: VNode
|
||||
|
||||
/**
|
||||
* v2 compat only
|
||||
*/
|
||||
|
@ -337,7 +352,7 @@ export function createAppAPI<HostElement>(
|
|||
` 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.
|
||||
// this will be set on the root instance on initial mount.
|
||||
vnode.appContext = context
|
||||
|
|
|
@ -67,6 +67,7 @@ export type DefineComponent<
|
|||
Exposed extends string = string,
|
||||
Provide extends ComponentProvideOptions = ComponentProvideOptions,
|
||||
MakeDefaultsOptional extends boolean = true,
|
||||
TypeRefs extends Record<string, unknown> = {},
|
||||
> = ComponentPublicInstanceConstructor<
|
||||
CreateComponentPublicInstanceWithMixins<
|
||||
Props,
|
||||
|
@ -84,7 +85,8 @@ export type DefineComponent<
|
|||
S,
|
||||
LC & GlobalComponents,
|
||||
Directives & GlobalDirectives,
|
||||
Exposed
|
||||
Exposed,
|
||||
TypeRefs
|
||||
>
|
||||
> &
|
||||
ComponentOptionsBase<
|
||||
|
@ -209,6 +211,7 @@ export function defineComponent<
|
|||
: { [key in RuntimePropsKeys]?: any }
|
||||
: TypeProps,
|
||||
ResolvedProps = Readonly<InferredProps & EmitsToProps<ResolvedEmits>>,
|
||||
TypeRefs extends Record<string, unknown> = {},
|
||||
>(
|
||||
options: {
|
||||
props?: (RuntimePropsOptions & ThisType<void>) | RuntimePropsKeys[]
|
||||
|
@ -220,6 +223,10 @@ export function defineComponent<
|
|||
* @private for language-tools use only
|
||||
*/
|
||||
__typeEmits?: TypeEmits
|
||||
/**
|
||||
* @private for language-tools use only
|
||||
*/
|
||||
__typeRefs?: TypeRefs
|
||||
} & ComponentOptionsBase<
|
||||
ResolvedProps,
|
||||
SetupBindings,
|
||||
|
@ -279,7 +286,8 @@ export function defineComponent<
|
|||
Provide,
|
||||
// MakeDefaultsOptional - if TypeProps is provided, set to false to use
|
||||
// user props types verbatim
|
||||
unknown extends TypeProps ? true : false
|
||||
unknown extends TypeProps ? true : false,
|
||||
TypeRefs
|
||||
>
|
||||
|
||||
// implementation, close to no-op
|
||||
|
|
|
@ -4,12 +4,14 @@ import { currentRenderingInstance } from './componentRenderContext'
|
|||
import { currentApp } from './apiCreateApp'
|
||||
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>(
|
||||
key: K,
|
||||
value: K extends InjectionKey<infer V> ? V : T,
|
||||
) {
|
||||
): void {
|
||||
if (!currentInstance) {
|
||||
if (__DEV__) {
|
||||
warn(`provide() can only be used inside setup().`)
|
||||
|
@ -56,11 +58,14 @@ export function inject(
|
|||
// #2400
|
||||
// to support `app.use` plugins,
|
||||
// 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
|
||||
const provides = currentApp
|
||||
? currentApp._context.provides
|
||||
: instance
|
||||
? instance.parent == null
|
||||
? instance.vnode.appContext && instance.vnode.appContext.provides
|
||||
: instance.parent.provides
|
||||
: currentApp!._context.provides
|
||||
: undefined
|
||||
|
||||
if (provides && (key as string | symbol) in provides) {
|
||||
// TS doesn't allow symbol as index type
|
||||
|
|
|
@ -63,9 +63,12 @@ export function injectHook(
|
|||
}
|
||||
}
|
||||
|
||||
export const createHook =
|
||||
const createHook =
|
||||
<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)
|
||||
if (
|
||||
!isInSSRComponentSetup ||
|
||||
|
@ -74,22 +77,30 @@ export const createHook =
|
|||
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 onMounted = createHook(LifecycleHooks.MOUNTED)
|
||||
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
|
||||
export const onUpdated = createHook(LifecycleHooks.UPDATED)
|
||||
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
|
||||
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)
|
||||
export const onServerPrefetch = createHook(LifecycleHooks.SERVER_PREFETCH)
|
||||
export const onBeforeMount: CreateHook = createHook(LifecycleHooks.BEFORE_MOUNT)
|
||||
export const onMounted: CreateHook = createHook(LifecycleHooks.MOUNTED)
|
||||
export const onBeforeUpdate: CreateHook = createHook(
|
||||
LifecycleHooks.BEFORE_UPDATE,
|
||||
)
|
||||
export const onUpdated: CreateHook = createHook(LifecycleHooks.UPDATED)
|
||||
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 const onRenderTriggered = createHook<DebuggerHook>(
|
||||
LifecycleHooks.RENDER_TRIGGERED,
|
||||
)
|
||||
export const onRenderTracked = createHook<DebuggerHook>(
|
||||
LifecycleHooks.RENDER_TRACKED,
|
||||
)
|
||||
export const onRenderTriggered: CreateHook<DebuggerHook> =
|
||||
createHook<DebuggerHook>(LifecycleHooks.RENDER_TRIGGERED)
|
||||
export const onRenderTracked: CreateHook<DebuggerHook> =
|
||||
createHook<DebuggerHook>(LifecycleHooks.RENDER_TRACKED)
|
||||
|
||||
export type ErrorCapturedHook<TError = unknown> = (
|
||||
err: TError,
|
||||
|
@ -100,6 +111,6 @@ export type ErrorCapturedHook<TError = unknown> = (
|
|||
export function onErrorCaptured<TError = Error>(
|
||||
hook: ErrorCapturedHook<TError>,
|
||||
target: ComponentInternalInstance | null = currentInstance,
|
||||
) {
|
||||
): void {
|
||||
injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
type IfAny,
|
||||
type LooseRequired,
|
||||
type Prettify,
|
||||
type UnionToIntersection,
|
||||
|
@ -176,7 +177,7 @@ type ShortEmits<T extends Record<string, any>> = UnionToIntersection<
|
|||
*/
|
||||
export function defineExpose<
|
||||
Exposed extends Record<string, any> = Record<string, any>,
|
||||
>(exposed?: Exposed) {
|
||||
>(exposed?: Exposed): void {
|
||||
if (__DEV__) {
|
||||
warnRuntimeUsage(`defineExpose`)
|
||||
}
|
||||
|
@ -329,7 +330,7 @@ type PropsWithDefaults<
|
|||
> = Readonly<MappedOmit<T, keyof Defaults>> & {
|
||||
readonly [K in keyof Defaults]-?: K extends keyof T
|
||||
? Defaults[K] extends undefined
|
||||
? T[K]
|
||||
? IfAny<Defaults[K], NotUndefined<T[K]>, T[K]>
|
||||
: NotUndefined<T[K]>
|
||||
: never
|
||||
} & {
|
||||
|
@ -395,7 +396,7 @@ function getContext(): SetupContext {
|
|||
*/
|
||||
export function normalizePropsOrEmits(
|
||||
props: ComponentPropsOptions | EmitsOptions,
|
||||
) {
|
||||
): ComponentObjectPropsOptions | ObjectEmitsOptions {
|
||||
return isArray(props)
|
||||
? props.reduce(
|
||||
(normalized, p) => ((normalized[p] = null), normalized),
|
||||
|
@ -443,7 +444,7 @@ export function mergeDefaults(
|
|||
export function mergeModels(
|
||||
a: ComponentPropsOptions | EmitsOptions,
|
||||
b: ComponentPropsOptions | EmitsOptions,
|
||||
) {
|
||||
): ComponentPropsOptions | EmitsOptions {
|
||||
if (!a || !b) return a || b
|
||||
if (isArray(a) && isArray(b)) return a.concat(b)
|
||||
return extend({}, normalizePropsOrEmits(a), normalizePropsOrEmits(b))
|
||||
|
@ -488,7 +489,7 @@ export function createPropsRestProxy(
|
|||
* ```
|
||||
* @internal
|
||||
*/
|
||||
export function withAsyncContext(getAwaitable: () => any) {
|
||||
export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
|
||||
const ctx = getCurrentInstance()!
|
||||
if (__DEV__ && !ctx) {
|
||||
warn(
|
||||
|
|
|
@ -61,24 +61,30 @@ export interface WatchOptionsBase extends DebuggerOptions {
|
|||
|
||||
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
|
||||
immediate?: Immediate
|
||||
deep?: boolean
|
||||
deep?: boolean | number
|
||||
once?: boolean
|
||||
}
|
||||
|
||||
export type WatchStopHandle = () => void
|
||||
|
||||
export interface WatchHandle extends WatchStopHandle {
|
||||
pause: () => void
|
||||
resume: () => void
|
||||
stop: () => void
|
||||
}
|
||||
|
||||
// Simple effect.
|
||||
export function watchEffect(
|
||||
effect: WatchEffect,
|
||||
options?: WatchOptionsBase,
|
||||
): WatchStopHandle {
|
||||
): WatchHandle {
|
||||
return doWatch(effect, null, options)
|
||||
}
|
||||
|
||||
export function watchPostEffect(
|
||||
effect: WatchEffect,
|
||||
options?: DebuggerOptions,
|
||||
) {
|
||||
): WatchStopHandle {
|
||||
return doWatch(
|
||||
effect,
|
||||
null,
|
||||
|
@ -89,7 +95,7 @@ export function watchPostEffect(
|
|||
export function watchSyncEffect(
|
||||
effect: WatchEffect,
|
||||
options?: DebuggerOptions,
|
||||
) {
|
||||
): WatchStopHandle {
|
||||
return doWatch(
|
||||
effect,
|
||||
null,
|
||||
|
@ -97,14 +103,14 @@ export function watchSyncEffect(
|
|||
)
|
||||
}
|
||||
|
||||
type MultiWatchSources = (WatchSource<unknown> | object)[]
|
||||
export type MultiWatchSources = (WatchSource<unknown> | object)[]
|
||||
|
||||
// overload: single source + cb
|
||||
export function watch<T, Immediate extends Readonly<boolean> = false>(
|
||||
source: WatchSource<T>,
|
||||
cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
): WatchHandle
|
||||
|
||||
// overload: reactive array or tuple of multiple sources + cb
|
||||
export function watch<
|
||||
|
@ -116,7 +122,7 @@ export function watch<
|
|||
? WatchCallback<T, MaybeUndefined<T, Immediate>>
|
||||
: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
): WatchHandle
|
||||
|
||||
// overload: array of multiple sources + cb
|
||||
export function watch<
|
||||
|
@ -126,7 +132,7 @@ export function watch<
|
|||
sources: [...T],
|
||||
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
): WatchHandle
|
||||
|
||||
// overload: watching reactive object w/ cb
|
||||
export function watch<
|
||||
|
@ -136,14 +142,14 @@ export function watch<
|
|||
source: T,
|
||||
cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
): WatchHandle
|
||||
|
||||
// implementation
|
||||
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
|
||||
source: T | WatchSource<T>,
|
||||
cb: any,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle {
|
||||
): WatchHandle {
|
||||
if (__DEV__ && !isFunction(cb)) {
|
||||
warn(
|
||||
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
|
||||
|
@ -169,17 +175,9 @@ function doWatch(
|
|||
source: WatchSource | WatchSource[] | WatchEffect | object,
|
||||
cb: WatchCallback | null,
|
||||
options: WatchOptions = EMPTY_OBJ,
|
||||
): WatchStopHandle {
|
||||
): WatchHandle {
|
||||
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 (immediate !== undefined) {
|
||||
warn(
|
||||
|
@ -214,8 +212,11 @@ function doWatch(
|
|||
// immediately watch or watchEffect
|
||||
extendOptions.once = true
|
||||
} else {
|
||||
// watch(source, cb)
|
||||
return NOOP
|
||||
const watchHandle: WatchHandle = () => {}
|
||||
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 scope = getCurrentScope()
|
||||
const unwatch = () => {
|
||||
effect!.stop()
|
||||
const watchHandle: WatchHandle = () => {
|
||||
effect.stop()
|
||||
if (scope) {
|
||||
remove(scope.effects, effect)
|
||||
}
|
||||
}
|
||||
|
||||
if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
|
||||
return unwatch
|
||||
watchHandle.pause = effect.pause.bind(effect)
|
||||
watchHandle.resume = effect.resume.bind(effect)
|
||||
watchHandle.stop = watchHandle
|
||||
|
||||
if (__SSR__ && ssrCleanup) ssrCleanup.push(watchHandle)
|
||||
return watchHandle
|
||||
}
|
||||
|
||||
// this.$watch
|
||||
|
@ -243,7 +248,7 @@ export function instanceWatch(
|
|||
source: string | Function,
|
||||
value: WatchCallback | ObjectWatchOptionItem,
|
||||
options?: WatchOptions,
|
||||
): WatchStopHandle {
|
||||
): WatchHandle {
|
||||
const publicThis = this.proxy as any
|
||||
const getter = isString(source)
|
||||
? source.includes('.')
|
||||
|
@ -265,7 +270,7 @@ export function instanceWatch(
|
|||
|
||||
export function createPathGetter(ctx: any, path: string) {
|
||||
const segments = path.split('.')
|
||||
return () => {
|
||||
return (): any => {
|
||||
let cur = ctx
|
||||
for (let i = 0; i < segments.length && cur; i++) {
|
||||
cur = cur[segments[i]]
|
||||
|
|
|
@ -431,7 +431,7 @@ const warnCount: Record<string, number> = Object.create(null)
|
|||
// test only
|
||||
let warningEnabled = true
|
||||
|
||||
export function toggleDeprecationWarning(flag: boolean) {
|
||||
export function toggleDeprecationWarning(flag: boolean): void {
|
||||
warningEnabled = flag
|
||||
}
|
||||
|
||||
|
@ -439,7 +439,7 @@ export function warnDeprecation(
|
|||
key: DeprecationTypes,
|
||||
instance: ComponentInternalInstance | null,
|
||||
...args: any[]
|
||||
) {
|
||||
): void {
|
||||
if (!__DEV__) {
|
||||
return
|
||||
}
|
||||
|
@ -502,7 +502,7 @@ export const globalCompatConfig: CompatConfig = {
|
|||
MODE: 2,
|
||||
}
|
||||
|
||||
export function configureCompat(config: CompatConfig) {
|
||||
export function configureCompat(config: CompatConfig): void {
|
||||
if (__DEV__) {
|
||||
validateCompatConfig(config)
|
||||
}
|
||||
|
@ -516,7 +516,7 @@ const warnedInvalidKeys: Record<string, boolean> = {}
|
|||
export function validateCompatConfig(
|
||||
config: CompatConfig,
|
||||
instance?: ComponentInternalInstance,
|
||||
) {
|
||||
): void {
|
||||
if (seenConfigObjects.has(config)) {
|
||||
return
|
||||
}
|
||||
|
@ -554,7 +554,7 @@ export function validateCompatConfig(
|
|||
export function getCompatConfigForKey(
|
||||
key: DeprecationTypes | 'MODE',
|
||||
instance: ComponentInternalInstance | null,
|
||||
) {
|
||||
): CompatConfig[DeprecationTypes | 'MODE'] {
|
||||
const instanceConfig =
|
||||
instance && (instance.type as ComponentOptions).compatConfig
|
||||
if (instanceConfig && key in instanceConfig) {
|
||||
|
@ -594,7 +594,7 @@ export function assertCompatEnabled(
|
|||
key: DeprecationTypes,
|
||||
instance: ComponentInternalInstance | null,
|
||||
...args: any[]
|
||||
) {
|
||||
): void {
|
||||
if (!isCompatEnabled(key, instance)) {
|
||||
throw new Error(`${key} compat has been disabled.`)
|
||||
} else if (__DEV__) {
|
||||
|
@ -610,7 +610,7 @@ export function softAssertCompatEnabled(
|
|||
key: DeprecationTypes,
|
||||
instance: ComponentInternalInstance | null,
|
||||
...args: any[]
|
||||
) {
|
||||
): boolean {
|
||||
if (__DEV__) {
|
||||
warnDeprecation(key, instance, ...args)
|
||||
}
|
||||
|
@ -626,7 +626,7 @@ export function checkCompatEnabled(
|
|||
key: DeprecationTypes,
|
||||
instance: ComponentInternalInstance | null,
|
||||
...args: any[]
|
||||
) {
|
||||
): boolean {
|
||||
const enabled = isCompatEnabled(key, instance)
|
||||
if (__DEV__ && enabled) {
|
||||
warnDeprecation(key, instance, ...args)
|
||||
|
|
|
@ -23,7 +23,9 @@ const normalizedAsyncComponentMap = new WeakMap<
|
|||
Component
|
||||
>()
|
||||
|
||||
export function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
|
||||
export function convertLegacyAsyncComponent(
|
||||
comp: LegacyAsyncComponent,
|
||||
): Component {
|
||||
if (normalizedAsyncComponentMap.has(comp)) {
|
||||
return normalizedAsyncComponentMap.get(comp)!
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
|
|||
},
|
||||
}
|
||||
|
||||
export function convertLegacyFunctionalComponent(comp: ComponentOptions) {
|
||||
export function convertLegacyFunctionalComponent(
|
||||
comp: ComponentOptions,
|
||||
): FunctionalComponent {
|
||||
if (normalizedFunctionalComponentMap.has(comp)) {
|
||||
return normalizedFunctionalComponentMap.get(comp)!
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export const compatModelEventPrefix = `onModelCompat:`
|
|||
|
||||
const warnedTypes = new WeakSet()
|
||||
|
||||
export function convertLegacyVModelProps(vnode: VNode) {
|
||||
export function convertLegacyVModelProps(vnode: VNode): void {
|
||||
const { type, shapeFlag, props, dynamicProps } = vnode
|
||||
const comp = type as ComponentOptions
|
||||
if (shapeFlag & ShapeFlags.COMPONENT && props && 'modelValue' in props) {
|
||||
|
@ -68,7 +68,7 @@ export function compatModelEmit(
|
|||
instance: ComponentInternalInstance,
|
||||
event: string,
|
||||
args: any[],
|
||||
) {
|
||||
): void {
|
||||
if (!isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { isPlainObject } from '@vue/shared'
|
||||
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) {
|
||||
const toVal = to[key]
|
||||
const fromVal = from[key]
|
||||
|
|
|
@ -333,7 +333,7 @@ export function installAppCompatProperties(
|
|||
app: App,
|
||||
context: AppContext,
|
||||
render: RootRenderFunction<any>,
|
||||
) {
|
||||
): void {
|
||||
installFilterMethod(app, context)
|
||||
installLegacyOptionMergeStrats(app.config)
|
||||
|
||||
|
@ -548,7 +548,7 @@ function installCompatMount(
|
|||
}
|
||||
|
||||
// clear content before mounting
|
||||
container.innerHTML = ''
|
||||
container.textContent = ''
|
||||
|
||||
// TODO hydration
|
||||
render(vnode, container, namespace)
|
||||
|
|
|
@ -36,7 +36,7 @@ export type LegacyConfig = {
|
|||
}
|
||||
|
||||
// dev only
|
||||
export function installLegacyConfigWarnings(config: AppConfig) {
|
||||
export function installLegacyConfigWarnings(config: AppConfig): void {
|
||||
const legacyConfigOptions: Record<string, DeprecationTypes> = {
|
||||
silent: DeprecationTypes.CONFIG_SILENT,
|
||||
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
|
||||
|
@ -62,7 +62,7 @@ export function installLegacyConfigWarnings(config: AppConfig) {
|
|||
})
|
||||
}
|
||||
|
||||
export function installLegacyOptionMergeStrats(config: AppConfig) {
|
||||
export function installLegacyOptionMergeStrats(config: AppConfig): void {
|
||||
config.optionMergeStrategies = new Proxy({} as any, {
|
||||
get(target, key) {
|
||||
if (key in target) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue