Merge tag 'v3.5.0-beta.1'

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

View File

@ -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

View File

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

View File

@ -80,6 +80,7 @@ Depending on the type of the PR, different considerations need to be taken into
- Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`.
- 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

View File

@ -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

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

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

View File

@ -4,6 +4,7 @@ on:
push:
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

View File

@ -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

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

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,6 @@ Please note that we do not consider XSS via template expressions a valid attack
We would like to thank the following security researchers for responsibly disclosing security issues to us.
- 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>

1008
changelogs/CHANGELOG-3.4.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"private": true,
"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"
},

View File

@ -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"]))
}"

View File

@ -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>`,

View File

@ -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:"
},

View File

@ -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))

View File

@ -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!

View File

@ -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

View File

@ -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}`)
}

View File

@ -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,

View File

@ -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]
})

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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)

View File

@ -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) {

View File

@ -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())

View File

@ -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]*)/

View File

@ -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

View File

@ -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",

View File

@ -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`,

View File

@ -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.`,
)
})
})
})

View File

@ -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",

View File

@ -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,
}

View File

@ -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 (

View File

@ -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 ||

View File

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

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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}`,
)

View File

@ -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`,
)

View File

@ -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>\`"
`)
})
})

View File

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

View File

@ -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",

View File

@ -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`,

View File

@ -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)

View File

@ -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)

View File

@ -163,6 +163,25 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
]),
]
}
} else if (directives.length && !node.children.length) {
const tempId = `_temp${context.temps++}`
propsExp.arguments = [
createAssignmentExpression(
createSimpleExpression(tempId, false),
mergedProps,
),
]
rawChildrenMap.set(
node,
createConditionalExpression(
createSimpleExpression(`"textContent" in ${tempId}`, false),
createCallExpression(context.helper(SSR_INTERPOLATE), [
createSimpleExpression(`${tempId}.textContent`, false),
]),
createSimpleExpression(`${tempId}.innerHTML ?? ''`, false),
false,
),
)
}
if (needTagForRuntime) {
@ -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++) {

View File

@ -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

View File

@ -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) {

View File

@ -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(

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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!,

View File

@ -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],

View File

@ -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
})
})

View File

@ -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' },
},
})

View File

@ -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')

View File

@ -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

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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",

View File

@ -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)

View File

@ -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
}

View File

@ -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,

View File

@ -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)
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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> &

View File

@ -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())
}
}

View File

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

View File

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

View File

@ -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(() => {

View File

@ -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

View File

@ -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: {

View File

@ -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) {

View File

@ -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')

View File

@ -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)
})
})

View File

@ -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()
})
})

View File

@ -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 = {

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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(

View File

@ -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]]

View File

@ -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)

View File

@ -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)!
}

View File

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

View File

@ -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
}

View File

@ -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]

View File

@ -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)

View File

@ -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