mirror of https://github.com/vuejs/core.git
chore: Merge branch 'main' into minor
This commit is contained in:
commit
f15ba2c2e2
|
|
@ -17,6 +17,27 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
|||
|
||||
## Pull Request Guidelines
|
||||
|
||||
### What kinds of Pull Requests are accepted?
|
||||
|
||||
- Bug fix that addresses a clearly identified bug. **"Clearly identified bug"** means the bug has a proper reproduction either from a related open issue, or is included in the PR itself. Avoid submitting PRs that claim to fix something but do not sufficiently explain what is being fixed.
|
||||
|
||||
- New feature that addresses a clearly explained and widely applicable use case. **"Widely applicable"** means the new feature should provide non-trivial improvements to the majority of the user base. Vue already has a large API surface so we are quite cautious about adding new features - if the use case is niche and can be addressed via userland implementations, it likely isn't suitable to go into core.
|
||||
|
||||
The feature implementation should also consider the trade-off between the added complexity vs. the benefits gained. For example, if a small feature requires significant changes that spreads across the codebase, it is likely not worth it, or the approach should be reconsidered.
|
||||
|
||||
If the feature has a non-trivial API surface addition, or significantly affects the way a common use case is approached by the users, it should go through a discussion first in the [RFC repo](https://github.com/vuejs/rfcs/discussions). PRs of such features without prior discussion make it really difficult to steer / adjust the API design due to coupling with concrete implementations, and can lead to wasted work.
|
||||
|
||||
- Chore: typos, comment improvements, build config, CI config, etc. For typos and comment changes, try to combine multiple of them into a single PR.
|
||||
|
||||
- **It should be noted that we discourage contributors from submitting code refactors that are largely stylistic.** Code refactors are only accepted if it improves performance, or comes with sufficient explanations on why it objectively improves the code quality (e.g. makes a related feature implementation easier).
|
||||
|
||||
The reason is that code readability is subjective. The maintainers of this project have chosen to write the code in its current style based on our preferences, and we do not want to spend time explaining our stylistic preferences. Contributors should just respect the established conventions when contributing code.
|
||||
|
||||
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`.
|
||||
|
||||
- If your pull request is a feature that adds new API surface, it should be submitted against the `minor` branch.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
},
|
||||
{
|
||||
groupName: 'build',
|
||||
matchPackageNames: ['vite', 'terser'],
|
||||
matchPackageNames: ['vite', '@swc/core'],
|
||||
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,4 +31,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@ea32e3a12414e6d3183163c3424a7d7a8631ad84
|
||||
- uses: autofix-ci/action@2891949f3779a1cafafae1523058501de3d4e944
|
||||
|
|
|
|||
|
|
@ -41,3 +41,13 @@ jobs:
|
|||
with:
|
||||
name: size-data
|
||||
path: temp/size
|
||||
|
||||
- name: Save PR number
|
||||
if: ${{github.event_name == 'pull_request'}}
|
||||
run: echo ${{ github.event.number }} > ./pr.txt
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{github.event_name == 'pull_request'}}
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr.txt
|
||||
|
|
|
|||
|
|
@ -35,15 +35,28 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Download PR number
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
name: pr-number
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: /tmp/pr-number
|
||||
|
||||
- name: Read PR Number
|
||||
id: pr-number
|
||||
uses: juliangruber/read-file-action@v1
|
||||
with:
|
||||
path: /tmp/pr-number/pr.txt
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v4
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: temp/size
|
||||
|
||||
- name: Download Previous Size Data
|
||||
uses: dawidd6/action-download-artifact@v4
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
branch: main
|
||||
workflow: size-data.yml
|
||||
|
|
@ -52,7 +65,7 @@ jobs:
|
|||
path: temp/size-prev
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: Compare size
|
||||
- name: Prepare report
|
||||
run: pnpm tsx scripts/size-report.ts > size-report.md
|
||||
|
||||
- name: Read Size Report
|
||||
|
|
@ -65,6 +78,7 @@ jobs:
|
|||
uses: actions-cool/maintain-one-comment@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ steps.pr-number.outputs.content }}
|
||||
body: |
|
||||
${{ steps.size-report.outputs.content }}
|
||||
<!-- VUE_CORE_SIZE -->
|
||||
|
|
|
|||
50
CHANGELOG.md
50
CHANGELOG.md
|
|
@ -1,5 +1,55 @@
|
|||
## [3.4.32](https://github.com/vuejs/core/compare/v3.4.31...v3.4.32) (2024-07-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** use consistent minify options from previous terser config ([789675f](https://github.com/vuejs/core/commit/789675f65d2b72cf979ba6a29bd323f716154a4b))
|
||||
* **compiler-sfc:** correctly resolve type annotation for declared function ([#11279](https://github.com/vuejs/core/issues/11279)) ([b287aee](https://github.com/vuejs/core/commit/b287aeec3ea85f20e4b1fc3d907c901bdc2a0176)), closes [#11266](https://github.com/vuejs/core/issues/11266)
|
||||
* **defineModel:** force local update when setter results in same emitted value ([de174e1](https://github.com/vuejs/core/commit/de174e1aa756508c7542605a448e55a373afb1ed)), closes [#10279](https://github.com/vuejs/core/issues/10279) [#10301](https://github.com/vuejs/core/issues/10301)
|
||||
* **hmr:** hmr reload should work with async component ([#11248](https://github.com/vuejs/core/issues/11248)) ([c8b9794](https://github.com/vuejs/core/commit/c8b97945759e869c997d60c3350d2451c5ff7887))
|
||||
* **hydration:** fix tracking of reactive style objects in production ([c10e40a](https://github.com/vuejs/core/commit/c10e40a217b89ab7e0f7f3515242d4246ecffbdd)), closes [#11372](https://github.com/vuejs/core/issues/11372)
|
||||
* **hydration:** handle consectuvie text nodes during hydration ([f44c3b3](https://github.com/vuejs/core/commit/f44c3b37d446d5f8e34539029dae0d806b25bb47)), closes [#7285](https://github.com/vuejs/core/issues/7285) [#7301](https://github.com/vuejs/core/issues/7301)
|
||||
* **reactivity:** ensure `unref` correctly resolves type for `ShallowRef` ([#11360](https://github.com/vuejs/core/issues/11360)) ([a509e30](https://github.com/vuejs/core/commit/a509e30f059fcdd158f39fdf34670b1019eaf2d1)), closes [#11356](https://github.com/vuejs/core/issues/11356)
|
||||
* **reactivity:** shallowReactive map "unwraps" the nested refs ([#8503](https://github.com/vuejs/core/issues/8503)) ([50ddafe](https://github.com/vuejs/core/commit/50ddafe91b9195cf94124466239f82c9794699fb)), closes [#8501](https://github.com/vuejs/core/issues/8501) [#11249](https://github.com/vuejs/core/issues/11249)
|
||||
* **runtime-core:** avoid recursive warning ([3ee7b4c](https://github.com/vuejs/core/commit/3ee7b4c7b1374c5bdc50a579b49f6bc15022b085)), closes [#8074](https://github.com/vuejs/core/issues/8074)
|
||||
* **runtime-core:** bail manually rendered compiler slot fragments in all cases ([3d34f40](https://github.com/vuejs/core/commit/3d34f406ac7497dafd2f4e62ab23579b78a0e08a)), closes [#10870](https://github.com/vuejs/core/issues/10870)
|
||||
* **runtime-core:** do not emit when defineModel ref is set with same value ([#11162](https://github.com/vuejs/core/issues/11162)) ([f1bb0ae](https://github.com/vuejs/core/commit/f1bb0aef084b5cdd4d49aecfed01ec106d9b6897)), closes [#11125](https://github.com/vuejs/core/issues/11125)
|
||||
* **runtime-core:** errors during component patch should be caught by error handlers ([ee0248a](https://github.com/vuejs/core/commit/ee0248accff589a94688e177e5e3af10c18288cb))
|
||||
* **runtime-core:** force diff slot fallback content and provided content ([d76dd9c](https://github.com/vuejs/core/commit/d76dd9c58de24b273bc55af3a8ed81ba693e9683)), closes [#7256](https://github.com/vuejs/core/issues/7256) [#9200](https://github.com/vuejs/core/issues/9200) [#9308](https://github.com/vuejs/core/issues/9308) [#7266](https://github.com/vuejs/core/issues/7266) [#9213](https://github.com/vuejs/core/issues/9213)
|
||||
* **runtime-core:** more edge case fix for manually rendered compiled slot ([685e3f3](https://github.com/vuejs/core/commit/685e3f381c024b9f4023e60fe0545dc60d90d984)), closes [#11336](https://github.com/vuejs/core/issues/11336)
|
||||
* **runtime-core:** use separate prop caches for components and mixins ([#11350](https://github.com/vuejs/core/issues/11350)) ([b0aa234](https://github.com/vuejs/core/commit/b0aa234e5e7a611c018de68bc31e0cf55518d5ce)), closes [#7998](https://github.com/vuejs/core/issues/7998)
|
||||
* **runtime-dom:** properly handle innerHTML unmount into new children ([#11159](https://github.com/vuejs/core/issues/11159)) ([3e9e32e](https://github.com/vuejs/core/commit/3e9e32ee0a6d0fbf67e9098a66ff0a1ea6647806)), closes [#9135](https://github.com/vuejs/core/issues/9135)
|
||||
* **teleport:** skip teleported nodes when locating patch anchor ([8655ced](https://github.com/vuejs/core/commit/8655ced480ea0fe453ff5fe445cecf97b91ec260)), closes [#9071](https://github.com/vuejs/core/issues/9071) [#9134](https://github.com/vuejs/core/issues/9134) [#9313](https://github.com/vuejs/core/issues/9313) [#9313](https://github.com/vuejs/core/issues/9313)
|
||||
* **v-model:** component v-model modifiers trim and number when cases don't match ([#9609](https://github.com/vuejs/core/issues/9609)) ([7fb6eb8](https://github.com/vuejs/core/commit/7fb6eb882b64bf99a99d00606e54b0e050674206)), closes [#4848](https://github.com/vuejs/core/issues/4848) [#4850](https://github.com/vuejs/core/issues/4850) [#4850](https://github.com/vuejs/core/issues/4850)
|
||||
* **v-once:** properly unmount v-once cached trees ([d343a0d](https://github.com/vuejs/core/commit/d343a0dc01663f91db42b4ddb693e6fffcb45873)), closes [#5154](https://github.com/vuejs/core/issues/5154) [#8809](https://github.com/vuejs/core/issues/8809)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **server-renderer:** avoid unnecessary checks in `createBuffer` ([#11364](https://github.com/vuejs/core/issues/11364)) ([fc205bf](https://github.com/vuejs/core/commit/fc205bf4decde5ce0f4a61394ffa3914b502c287))
|
||||
* **server-renderer:** optimize `unrollBuffer` by avoiding promises ([#11340](https://github.com/vuejs/core/issues/11340)) ([05779a7](https://github.com/vuejs/core/commit/05779a70bd0b567ae458a07636d229bd07c44c4e))
|
||||
|
||||
|
||||
|
||||
## [3.4.31](https://github.com/vuejs/core/compare/v3.4.30...v3.4.31) (2024-06-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** handle inline comments with undefined bindings ([#11217](https://github.com/vuejs/core/issues/11217)) ([746352a](https://github.com/vuejs/core/commit/746352a14d62e9d3d9a38c359d2c54d418c1e0ac)), closes [#11216](https://github.com/vuejs/core/issues/11216)
|
||||
* **shared:** unwrap refs in toDisplayString ([#7306](https://github.com/vuejs/core/issues/7306)) ([0126cff](https://github.com/vuejs/core/commit/0126cfff9d93bcec70e5745519f6378e3cd3f39c)), closes [#5578](https://github.com/vuejs/core/issues/5578) [#5593](https://github.com/vuejs/core/issues/5593) [#11199](https://github.com/vuejs/core/issues/11199) [#11201](https://github.com/vuejs/core/issues/11201)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix(reactivity): avoid infinite loop when render access a side effect computed ([#11135](https://github.com/vuejs/core/issues/11135))" ([e0df985](https://github.com/vuejs/core/commit/e0df985f0317fb65c5b461bf224375c7763f0269))
|
||||
* Revert "fix(reactivity): fix side effect computed dirty level (#11183)" ([6c303ea](https://github.com/vuejs/core/commit/6c303eacd14b7b0de0accc228f6abeb43d706f63)), closes [#11183](https://github.com/vuejs/core/issues/11183)
|
||||
|
||||
|
||||
|
||||
## [3.4.30](https://github.com/vuejs/core/compare/v3.4.29...v3.4.30) (2024-06-22)
|
||||
|
||||
**Note: this release contains a fix (#11150) that requires `vue-tsc` to also be updated in sync to ^2.0.22. See #11196**
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
|
|||
39
package.json
39
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.0-alpha.2",
|
||||
"packageManager": "pnpm@9.3.0",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
|
@ -59,58 +59,53 @@
|
|||
"node": ">=18.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7",
|
||||
"@codspeed/vitest-plugin": "^3.1.0",
|
||||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.8",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.6.1",
|
||||
"@swc/core": "^1.6.13",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vitest/coverage-istanbul": "^1.6.0",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.21.5",
|
||||
"esbuild": "^0.23.0",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.5.0",
|
||||
"eslint-plugin-import-x": "^0.5.1",
|
||||
"eslint": "^9.6.0",
|
||||
"eslint-plugin-import-x": "^0.5.3",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^9.2.0",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^24.1.0",
|
||||
"lint-staged": "^15.2.7",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.10",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^12.0.2",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all2": "^6.2.0",
|
||||
"npm-run-all2": "^6.2.2",
|
||||
"picocolors": "^1.0.1",
|
||||
"prettier": "^3.3.2",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~22.11.0",
|
||||
"rimraf": "^5.0.7",
|
||||
"rollup": "^4.18.0",
|
||||
"puppeteer": "~22.12.1",
|
||||
"rimraf": "^5.0.9",
|
||||
"rollup": "^4.18.1",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.2",
|
||||
"serve": "^14.2.3",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"terser": "^5.31.1",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.15.5",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "~5.4.5",
|
||||
"typescript-eslint": "^7.13.0",
|
||||
"vite": "^5.3.1",
|
||||
"typescript-eslint": "^7.15.0",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"pnpm": {
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ export function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: codegen > CacheExpression w/ isVNode: true 1`] = `
|
||||
exports[`compiler: codegen > CacheExpression w/ isVOnce: true 1`] = `
|
||||
"
|
||||
export function render(_ctx, _cache) {
|
||||
return _cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = foo,
|
||||
(_cache[1] = foo).cacheIndex = 1,
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
|
|
@ -54,7 +54,7 @@ return function render(_ctx, _cache) {
|
|||
[foo + bar]: bar
|
||||
}, [
|
||||
_createElementVNode("p", { "some-key": "foo" })
|
||||
], 16)
|
||||
], 16 /* FULL_PROPS */)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -98,7 +98,7 @@ exports[`compiler: codegen > forNode 1`] = `
|
|||
"
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(), 1))
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(), 1 /* TEXT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ describe('compiler: codegen', () => {
|
|||
disableTracking: true,
|
||||
props: undefined,
|
||||
children: createCallExpression(RENDER_LIST),
|
||||
patchFlag: '1',
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
dynamicProps: undefined,
|
||||
directives: undefined,
|
||||
loc: locStub,
|
||||
|
|
@ -303,7 +303,7 @@ describe('compiler: codegen', () => {
|
|||
disableTracking: false,
|
||||
props: undefined,
|
||||
children: createCallExpression(RENDER_LIST),
|
||||
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
|
||||
patchFlag: PatchFlags.STABLE_FRAGMENT,
|
||||
dynamicProps: undefined,
|
||||
directives: undefined,
|
||||
loc: locStub,
|
||||
|
|
@ -364,7 +364,7 @@ describe('compiler: codegen', () => {
|
|||
),
|
||||
],
|
||||
// flag
|
||||
PatchFlags.FULL_PROPS + '',
|
||||
PatchFlags.FULL_PROPS,
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
|
@ -375,7 +375,7 @@ describe('compiler: codegen', () => {
|
|||
[foo + bar]: bar
|
||||
}, [
|
||||
_${helperNameMap[CREATE_ELEMENT_VNODE]}("p", { "some-key": "foo" })
|
||||
], ${PatchFlags.FULL_PROPS})`)
|
||||
], ${genFlagText(PatchFlags.FULL_PROPS)})`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
|
|
@ -437,7 +437,7 @@ describe('compiler: codegen', () => {
|
|||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('CacheExpression w/ isVNode: true', () => {
|
||||
test('CacheExpression w/ isVOnce: true', () => {
|
||||
const { code } = generate(
|
||||
createRoot({
|
||||
cached: [],
|
||||
|
|
@ -456,7 +456,7 @@ describe('compiler: codegen', () => {
|
|||
`
|
||||
_cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = foo,
|
||||
(_cache[1] = foo).cacheIndex = 1,
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
|
|
@ -666,11 +666,14 @@ describe('compiler: codegen', () => {
|
|||
})
|
||||
|
||||
test('with patchFlag and no children/props', () => {
|
||||
expect(genCode(createVNodeCall(null, `"div"`, undefined, undefined, '1')))
|
||||
.toMatchInlineSnapshot(`
|
||||
"return _createElementVNode("div", null, null, 1)
|
||||
"
|
||||
`)
|
||||
expect(
|
||||
genCode(
|
||||
createVNodeCall(null, `"div"`, undefined, undefined, PatchFlags.TEXT),
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
"return _createElementVNode("div", null, null, 1 /* TEXT */)
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('as block', () => {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import { transformFor } from '../src/transforms/vFor'
|
|||
import { transformElement } from '../src/transforms/transformElement'
|
||||
import { transformSlotOutlet } from '../src/transforms/transformSlotOutlet'
|
||||
import { transformText } from '../src/transforms/transformText'
|
||||
import { genFlagText } from './testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
describe('compiler: transform', () => {
|
||||
|
|
@ -358,7 +357,7 @@ describe('compiler: transform', () => {
|
|||
{ type: NodeTypes.ELEMENT, tag: `div` },
|
||||
{ type: NodeTypes.ELEMENT, tag: `div` },
|
||||
] as any,
|
||||
genFlagText(PatchFlags.STABLE_FRAGMENT),
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
|
@ -374,10 +373,7 @@ describe('compiler: transform', () => {
|
|||
{ type: NodeTypes.ELEMENT, tag: `div` },
|
||||
{ type: NodeTypes.COMMENT },
|
||||
] as any,
|
||||
genFlagText([
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
PatchFlags.DEV_ROOT_FRAGMENT,
|
||||
]),
|
||||
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ return function render(_ctx, _cache) {
|
|||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
||||
_createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
|
@ -25,11 +25,11 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("p", null, [
|
||||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* CACHED */),
|
||||
], -1 /* HOISTED */),
|
||||
_createElementVNode("p", null, [
|
||||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* CACHED */)
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
|
@ -45,7 +45,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", null, [
|
||||
_createCommentVNode("comment")
|
||||
], -1 /* CACHED */)
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
|
@ -59,9 +59,9 @@ return function render(_ctx, _cache) {
|
|||
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||
_createElementVNode("span", null, null, -1 /* HOISTED */),
|
||||
_createTextVNode("foo"),
|
||||
_createElementVNode("div", null, null, -1 /* CACHED */)
|
||||
_createElementVNode("div", null, null, -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
|
@ -75,7 +75,7 @@ return function render(_ctx, _cache) {
|
|||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
|
@ -148,7 +148,7 @@ return function render(_ctx, _cache) {
|
|||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
|
@ -162,7 +162,7 @@ return function render(_ctx, _cache) {
|
|||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
|
@ -178,7 +178,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */)
|
||||
_createElementVNode("span", { class: "hi" }, null, -1 /* HOISTED */)
|
||||
]))]))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
|
|
@ -216,7 +216,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */)
|
||||
]))), [
|
||||
[_directive_foo]
|
||||
])
|
||||
|
|
@ -402,7 +402,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
ok
|
||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
_createElementVNode("span", null, null, -1 /* HOISTED */)
|
||||
])))
|
||||
: _createCommentVNode("v-if", true)
|
||||
]))
|
||||
|
|
@ -423,7 +423,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
_createElementVNode("span", null, null, -1 /* HOISTED */)
|
||||
])))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return _cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"]),
|
||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
|
@ -29,7 +29,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"]),
|
||||
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
|
@ -48,7 +48,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"]),
|
||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
|
@ -67,7 +67,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _renderSlot($slots, "default"),
|
||||
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
|
@ -86,7 +86,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _createElementVNode("div"),
|
||||
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { transformIf } from '../../src/transforms/vIf'
|
|||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { transformBind } from '../../src/transforms/vBind'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
import { transformText } from '../../src/transforms/transformText'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
|
|
@ -348,7 +348,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
id: `[foo]`,
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `_hoisted_1`,
|
||||
|
|
@ -402,7 +402,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
ref: `[foo]`,
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
|
||||
patchFlag: PatchFlags.NEED_PATCH,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
|
@ -424,7 +424,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
content: `_hoisted_1`,
|
||||
},
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
|
||||
patchFlag: PatchFlags.NEED_PATCH,
|
||||
directives: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
|
|
@ -448,7 +448,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
tag: `"div"`,
|
||||
props: { content: `_hoisted_1` },
|
||||
children: { type: NodeTypes.INTERPOLATION },
|
||||
patchFlag: genFlagText(PatchFlags.TEXT),
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
|
@ -520,7 +520,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
},
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
patchFlag: PatchFlags.UNKEYED_FRAGMENT,
|
||||
})
|
||||
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
|
||||
expect(innerBlockCodegen.returns).toMatchObject({
|
||||
|
|
@ -620,7 +620,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
constType: ConstantTypes.NOT_CONSTANT,
|
||||
},
|
||||
},
|
||||
patchFlag: `1 /* TEXT */`,
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -750,7 +750,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
},
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
patchFlag: PatchFlags.UNKEYED_FRAGMENT,
|
||||
})
|
||||
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
|
||||
expect(innerBlockCodegen.returns).toMatchObject({
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import { transformStyle } from '../../../compiler-dom/src/transforms/transformSt
|
|||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { transformBind } from '../../src/transforms/vBind'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
import { transformText } from '../../src/transforms/transformText'
|
||||
import { parseWithForTransform } from './vFor.spec'
|
||||
|
||||
|
|
@ -521,7 +521,7 @@ describe('compiler: element transform', () => {
|
|||
// keep-alive should not compile content to slots
|
||||
children: [{ type: NodeTypes.ELEMENT, tag: 'span' }],
|
||||
// should get a dynamic slots flag to force updates
|
||||
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS),
|
||||
patchFlag: PatchFlags.DYNAMIC_SLOTS,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +588,7 @@ describe('compiler: element transform', () => {
|
|||
})
|
||||
// should factor in props returned by custom directive transforms
|
||||
// in patchFlag analysis
|
||||
expect(node.patchFlag).toMatch(PatchFlags.PROPS + '')
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS)
|
||||
expect(node.dynamicProps).toMatch(`"bar"`)
|
||||
})
|
||||
|
||||
|
|
@ -612,7 +612,7 @@ describe('compiler: element transform', () => {
|
|||
tag: `"div"`,
|
||||
props: undefined,
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.NEED_PATCH), // should generate appropriate flag
|
||||
patchFlag: PatchFlags.NEED_PATCH, // should generate appropriate flag
|
||||
directives: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: [
|
||||
|
|
@ -945,26 +945,26 @@ describe('compiler: element transform', () => {
|
|||
expect(node.patchFlag).toBeUndefined()
|
||||
|
||||
const { node: node2 } = parseWithBind(`<div>{{ foo }}</div>`)
|
||||
expect(node2.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
|
||||
expect(node2.patchFlag).toBe(PatchFlags.TEXT)
|
||||
|
||||
// multiple nodes, merged with optimize text
|
||||
const { node: node3 } = parseWithBind(`<div>foo {{ bar }} baz</div>`)
|
||||
expect(node3.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
|
||||
expect(node3.patchFlag).toBe(PatchFlags.TEXT)
|
||||
})
|
||||
|
||||
test('CLASS', () => {
|
||||
const { node } = parseWithBind(`<div :class="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.CLASS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.CLASS)
|
||||
})
|
||||
|
||||
test('STYLE', () => {
|
||||
const { node } = parseWithBind(`<div :style="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.STYLE))
|
||||
expect(node.patchFlag).toBe(PatchFlags.STYLE)
|
||||
})
|
||||
|
||||
test('PROPS', () => {
|
||||
const { node } = parseWithBind(`<div id="foo" :foo="bar" :baz="qux" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS)
|
||||
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
|
||||
})
|
||||
|
||||
|
|
@ -973,7 +973,7 @@ describe('compiler: element transform', () => {
|
|||
`<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`,
|
||||
)
|
||||
expect(node.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS]),
|
||||
PatchFlags.CLASS | PatchFlags.STYLE | PatchFlags.PROPS,
|
||||
)
|
||||
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
|
||||
})
|
||||
|
|
@ -983,40 +983,40 @@ describe('compiler: element transform', () => {
|
|||
const { node } = parseWithBind(
|
||||
`<Foo :id="foo" :class="cls" :style="styl" />`,
|
||||
)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS)
|
||||
expect(node.dynamicProps).toBe(`["id", "class", "style"]`)
|
||||
})
|
||||
|
||||
test('FULL_PROPS (v-bind)', () => {
|
||||
const { node } = parseWithBind(`<div v-bind="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
|
||||
})
|
||||
|
||||
test('FULL_PROPS (dynamic key)', () => {
|
||||
const { node } = parseWithBind(`<div :[foo]="bar" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
|
||||
})
|
||||
|
||||
test('FULL_PROPS (w/ others)', () => {
|
||||
const { node } = parseWithBind(
|
||||
`<div id="foo" v-bind="bar" :class="cls" />`,
|
||||
)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
|
||||
})
|
||||
|
||||
test('NEED_PATCH (static ref)', () => {
|
||||
const { node } = parseWithBind(`<div ref="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
|
||||
})
|
||||
|
||||
test('NEED_PATCH (dynamic ref)', () => {
|
||||
const { node } = parseWithBind(`<div :ref="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
|
||||
})
|
||||
|
||||
test('NEED_PATCH (custom directives)', () => {
|
||||
const { node } = parseWithBind(`<div v-foo />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
|
||||
})
|
||||
|
||||
test('NEED_PATCH (vnode hooks)', () => {
|
||||
|
|
@ -1025,7 +1025,7 @@ describe('compiler: element transform', () => {
|
|||
cacheHandlers: true,
|
||||
}).ast
|
||||
const node = (root as any).children[0].codegenNode
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
|
||||
})
|
||||
|
||||
test('script setup inline mode template ref (binding exists)', () => {
|
||||
|
|
@ -1120,7 +1120,7 @@ describe('compiler: element transform', () => {
|
|||
},
|
||||
})
|
||||
// should only have props flag
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS)
|
||||
|
||||
const { node: node2 } = parseWithElementTransform(
|
||||
`<div @keyup="foo" />`,
|
||||
|
|
@ -1130,21 +1130,15 @@ describe('compiler: element transform', () => {
|
|||
},
|
||||
},
|
||||
)
|
||||
expect(node2.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
|
||||
)
|
||||
expect(node2.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
|
||||
test('NEED_HYDRATION for v-bind.prop', () => {
|
||||
const { node } = parseWithBind(`<div v-bind:id.prop="id" />`)
|
||||
expect(node.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
|
||||
)
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
|
||||
|
||||
const { node: node2 } = parseWithBind(`<div .id="id" />`)
|
||||
expect(node2.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
|
||||
)
|
||||
expect(node2.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
|
||||
// #5870
|
||||
|
|
@ -1157,9 +1151,7 @@ describe('compiler: element transform', () => {
|
|||
},
|
||||
},
|
||||
)
|
||||
expect(node.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
|
||||
)
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
|
||||
test('should not have PROPS patchflag for constant v-on handlers', () => {
|
||||
|
|
@ -1173,7 +1165,7 @@ describe('compiler: element transform', () => {
|
|||
},
|
||||
})
|
||||
// should only have hydration flag
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -384,6 +384,17 @@ describe('compiler: expression transform', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('should not error', () => {
|
||||
const onError = vi.fn()
|
||||
parseWithExpressionTransform(
|
||||
`<p :id="undefined /* force override the id */"/>`,
|
||||
{
|
||||
onError,
|
||||
},
|
||||
)
|
||||
expect(onError).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('should prefix in assignment', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
`{{ x = 1 }}`,
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import {
|
|||
import { ErrorCodes } from '../../src/errors'
|
||||
import { type CompilerOptions, generate } from '../../src'
|
||||
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
|
||||
export function parseWithForTransform(
|
||||
template: string,
|
||||
|
|
@ -696,10 +696,10 @@ describe('compiler: v-for', () => {
|
|||
tag: FRAGMENT,
|
||||
disableTracking,
|
||||
patchFlag: !disableTracking
|
||||
? genFlagText(PatchFlags.STABLE_FRAGMENT)
|
||||
? PatchFlags.STABLE_FRAGMENT
|
||||
: keyed
|
||||
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
||||
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
? PatchFlags.KEYED_FRAGMENT
|
||||
: PatchFlags.UNKEYED_FRAGMENT,
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
|
|
@ -822,7 +822,7 @@ describe('compiler: v-for', () => {
|
|||
constType: ConstantTypes.NOT_CONSTANT,
|
||||
},
|
||||
},
|
||||
patchFlag: genFlagText(PatchFlags.TEXT),
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
|
@ -846,7 +846,7 @@ describe('compiler: v-for', () => {
|
|||
{ type: NodeTypes.TEXT, content: `hello` },
|
||||
{ type: NodeTypes.ELEMENT, tag: `span` },
|
||||
],
|
||||
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
|
||||
patchFlag: PatchFlags.STABLE_FRAGMENT,
|
||||
},
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
|
@ -950,7 +950,7 @@ describe('compiler: v-for', () => {
|
|||
{ type: NodeTypes.TEXT, content: `hello` },
|
||||
{ type: NodeTypes.ELEMENT, tag: `span` },
|
||||
],
|
||||
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
|
||||
patchFlag: PatchFlags.STABLE_FRAGMENT,
|
||||
},
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
|
@ -971,7 +971,7 @@ describe('compiler: v-for', () => {
|
|||
}),
|
||||
isBlock: true,
|
||||
disableTracking: true,
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
patchFlag: PatchFlags.UNKEYED_FRAGMENT,
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
|
|
@ -1009,7 +1009,7 @@ describe('compiler: v-for', () => {
|
|||
}),
|
||||
isBlock: true,
|
||||
disableTracking: true,
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
patchFlag: PatchFlags.UNKEYED_FRAGMENT,
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
|
|
@ -1048,9 +1048,7 @@ describe('compiler: v-for', () => {
|
|||
const {
|
||||
node: { codegenNode },
|
||||
} = parseWithForTransform('<div v-for="key in keys" :key>test</div>')
|
||||
expect(codegenNode.patchFlag).toBe(
|
||||
`${PatchFlags.KEYED_FRAGMENT} /* ${PatchFlagNames[PatchFlags.KEYED_FRAGMENT]} */`,
|
||||
)
|
||||
expect(codegenNode.patchFlag).toBe(PatchFlags.KEYED_FRAGMENT)
|
||||
})
|
||||
|
||||
test('template v-for key w/ :key shorthand on template injected to the child', () => {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
trackVForSlotScopes,
|
||||
} from '../../src/transforms/vSlot'
|
||||
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeHelpers'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { transformIf } from '../../src/transforms/vIf'
|
||||
|
|
@ -432,7 +432,7 @@ describe('compiler: transform component slots', () => {
|
|||
),
|
||||
// nested slot should be forced dynamic, since scope variables
|
||||
// are not tracked as dependencies of the slot.
|
||||
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS),
|
||||
patchFlag: PatchFlags.DYNAMIC_SLOTS,
|
||||
},
|
||||
},
|
||||
// test scope
|
||||
|
|
@ -474,9 +474,7 @@ describe('compiler: transform component slots', () => {
|
|||
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
|
||||
.codegenNode as any
|
||||
const comp = div.children[0]
|
||||
expect(comp.codegenNode.patchFlag).toBe(
|
||||
genFlagText(PatchFlags.DYNAMIC_SLOTS),
|
||||
)
|
||||
expect(comp.codegenNode.patchFlag).toBe(PatchFlags.DYNAMIC_SLOTS)
|
||||
})
|
||||
|
||||
test('should only force dynamic slots when actually using scope vars w/ prefixIdentifiers: true', () => {
|
||||
|
|
@ -494,7 +492,7 @@ describe('compiler: transform component slots', () => {
|
|||
flag = (innerComp.codegenNode as VNodeCall).patchFlag
|
||||
}
|
||||
if (shouldForce) {
|
||||
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
|
||||
expect(flag).toBe(PatchFlags.DYNAMIC_SLOTS)
|
||||
} else {
|
||||
expect(flag).toBeUndefined()
|
||||
}
|
||||
|
|
@ -581,8 +579,8 @@ describe('compiler: transform component slots', () => {
|
|||
},
|
||||
],
|
||||
})
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||
PatchFlags.DYNAMIC_SLOTS + '',
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
|
@ -630,8 +628,8 @@ describe('compiler: transform component slots', () => {
|
|||
},
|
||||
],
|
||||
})
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||
PatchFlags.DYNAMIC_SLOTS + '',
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||
})
|
||||
|
|
@ -693,8 +691,8 @@ describe('compiler: transform component slots', () => {
|
|||
},
|
||||
],
|
||||
})
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||
PatchFlags.DYNAMIC_SLOTS + '',
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
expect((root as any).children[0].children.length).toBe(3)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
|
@ -744,8 +742,8 @@ describe('compiler: transform component slots', () => {
|
|||
},
|
||||
],
|
||||
})
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||
PatchFlags.DYNAMIC_SLOTS + '',
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -46,13 +46,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/parser": "catalog:",
|
||||
"@vue/shared": "workspace:*",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.0"
|
||||
"estree-walker": "catalog:",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/types": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { isString } from '@vue/shared'
|
||||
import { type PatchFlags, isString } from '@vue/shared'
|
||||
import {
|
||||
CREATE_BLOCK,
|
||||
CREATE_ELEMENT_BLOCK,
|
||||
|
|
@ -332,7 +332,7 @@ export interface VNodeCall extends Node {
|
|||
| SimpleExpressionNode // hoisted
|
||||
| CacheExpression // cached
|
||||
| undefined
|
||||
patchFlag: string | undefined
|
||||
patchFlag: PatchFlags | undefined
|
||||
dynamicProps: string | SimpleExpressionNode | undefined
|
||||
directives: DirectiveArguments | undefined
|
||||
isBlock: boolean
|
||||
|
|
@ -563,7 +563,7 @@ export interface ForCodegenNode extends VNodeCall {
|
|||
tag: typeof FRAGMENT
|
||||
props: undefined
|
||||
children: ForRenderListExpression
|
||||
patchFlag: string
|
||||
patchFlag: PatchFlags
|
||||
disableTracking: boolean
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export function walkIdentifiers(
|
|||
root: Node,
|
||||
onIdentifier: (
|
||||
node: Identifier,
|
||||
parent: Node,
|
||||
parent: Node | null,
|
||||
parentStack: Node[],
|
||||
isReference: boolean,
|
||||
isLocal: boolean,
|
||||
|
|
@ -36,7 +36,7 @@ export function walkIdentifiers(
|
|||
: root
|
||||
|
||||
walk(root, {
|
||||
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
|
||||
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
|
||||
parent && parentStack.push(parent)
|
||||
if (
|
||||
parent &&
|
||||
|
|
@ -47,9 +47,9 @@ export function walkIdentifiers(
|
|||
}
|
||||
if (node.type === 'Identifier') {
|
||||
const isLocal = !!knownIds[node.name]
|
||||
const isRefed = isReferencedIdentifier(node, parent!, parentStack)
|
||||
const isRefed = isReferencedIdentifier(node, parent, parentStack)
|
||||
if (includeAll || (isRefed && !isLocal)) {
|
||||
onIdentifier(node, parent!, parentStack, isRefed, isLocal)
|
||||
onIdentifier(node, parent, parentStack, isRefed, isLocal)
|
||||
}
|
||||
} else if (
|
||||
node.type === 'ObjectProperty' &&
|
||||
|
|
@ -79,7 +79,7 @@ export function walkIdentifiers(
|
|||
}
|
||||
}
|
||||
},
|
||||
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
|
||||
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
|
||||
parent && parentStack.pop()
|
||||
if (node !== rootExp && node.scopeIds) {
|
||||
for (const id of node.scopeIds) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,13 @@ import {
|
|||
isSimpleIdentifier,
|
||||
toValidAssetId,
|
||||
} from './utils'
|
||||
import { isArray, isString, isSymbol } from '@vue/shared'
|
||||
import {
|
||||
PatchFlagNames,
|
||||
type PatchFlags,
|
||||
isArray,
|
||||
isString,
|
||||
isSymbol,
|
||||
} from '@vue/shared'
|
||||
import {
|
||||
CREATE_COMMENT,
|
||||
CREATE_ELEMENT_VNODE,
|
||||
|
|
@ -816,6 +822,28 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
|||
disableTracking,
|
||||
isComponent,
|
||||
} = node
|
||||
|
||||
// add dev annotations to patch flags
|
||||
let patchFlagString
|
||||
if (patchFlag) {
|
||||
if (__DEV__) {
|
||||
if (patchFlag < 0) {
|
||||
// special flags (negative and mutually exclusive)
|
||||
patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
|
||||
} else {
|
||||
// bitwise flags
|
||||
const flagNames = Object.keys(PatchFlagNames)
|
||||
.map(Number)
|
||||
.filter(n => n > 0 && patchFlag & n)
|
||||
.map(n => PatchFlagNames[n as PatchFlags])
|
||||
.join(`, `)
|
||||
patchFlagString = patchFlag + ` /* ${flagNames} */`
|
||||
}
|
||||
} else {
|
||||
patchFlagString = String(patchFlag)
|
||||
}
|
||||
}
|
||||
|
||||
if (directives) {
|
||||
push(helper(WITH_DIRECTIVES) + `(`)
|
||||
}
|
||||
|
|
@ -830,7 +858,7 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
|||
: getVNodeHelper(context.inSSR, isComponent)
|
||||
push(helper(callHelper) + `(`, NewlineType.None, node)
|
||||
genNodeList(
|
||||
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
|
||||
genNullableArgs([tag, props, children, patchFlagString, dynamicProps]),
|
||||
context,
|
||||
)
|
||||
push(`)`)
|
||||
|
|
@ -990,11 +1018,12 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
|||
indent()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
||||
newline()
|
||||
push(`(`)
|
||||
}
|
||||
push(`_cache[${node.index}] = `)
|
||||
genNode(node.value, context)
|
||||
if (needPauseTracking) {
|
||||
push(`,`)
|
||||
push(`).cacheIndex = ${node.index},`)
|
||||
newline()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
|
||||
newline()
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||
helper(FRAGMENT),
|
||||
undefined,
|
||||
root.children,
|
||||
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
|
||||
patchFlag,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
|
|
|
|||
|
|
@ -75,8 +75,7 @@ function walk(
|
|||
: getConstantType(child, context)
|
||||
if (constantType > ConstantTypes.NOT_CONSTANT) {
|
||||
if (constantType >= ConstantTypes.CAN_CACHE) {
|
||||
;(child.codegenNode as VNodeCall).patchFlag =
|
||||
PatchFlags.CACHED + (__DEV__ ? ` /* CACHED */` : ``)
|
||||
;(child.codegenNode as VNodeCall).patchFlag = PatchFlags.CACHED
|
||||
toCache.push(child)
|
||||
continue
|
||||
}
|
||||
|
|
@ -85,9 +84,9 @@ function walk(
|
|||
// hoisting.
|
||||
const codegenNode = child.codegenNode!
|
||||
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||
const flag = getPatchFlag(codegenNode)
|
||||
const flag = codegenNode.patchFlag
|
||||
if (
|
||||
(!flag ||
|
||||
(flag === undefined ||
|
||||
flag === PatchFlags.NEED_PATCH ||
|
||||
flag === PatchFlags.TEXT) &&
|
||||
getGeneratedPropsConstantType(child, context) >=
|
||||
|
|
@ -259,8 +258,7 @@ export function getConstantType(
|
|||
) {
|
||||
return ConstantTypes.NOT_CONSTANT
|
||||
}
|
||||
const flag = getPatchFlag(codegenNode)
|
||||
if (!flag) {
|
||||
if (codegenNode.patchFlag === undefined) {
|
||||
let returnType = ConstantTypes.CAN_STRINGIFY
|
||||
|
||||
// Element itself has no patch flag. However we still need to check:
|
||||
|
|
@ -447,8 +445,3 @@ function getNodeProps(node: PlainElementNode) {
|
|||
return codegenNode.props
|
||||
}
|
||||
}
|
||||
|
||||
function getPatchFlag(node: VNodeCall): number | undefined {
|
||||
const flag = node.patchFlag
|
||||
return flag ? parseInt(flag, 10) : undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import {
|
|||
createVNodeCall,
|
||||
} from '../ast'
|
||||
import {
|
||||
PatchFlagNames,
|
||||
PatchFlags,
|
||||
camelize,
|
||||
capitalize,
|
||||
|
|
@ -101,8 +100,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
|
||||
let vnodeProps: VNodeCall['props']
|
||||
let vnodeChildren: VNodeCall['children']
|
||||
let vnodePatchFlag: VNodeCall['patchFlag']
|
||||
let patchFlag: number = 0
|
||||
let patchFlag: VNodeCall['patchFlag'] | 0 = 0
|
||||
let vnodeDynamicProps: VNodeCall['dynamicProps']
|
||||
let dynamicPropNames: string[] | undefined
|
||||
let vnodeDirectives: VNodeCall['directives']
|
||||
|
|
@ -206,27 +204,8 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
}
|
||||
|
||||
// patchFlag & dynamicPropNames
|
||||
if (patchFlag !== 0) {
|
||||
if (__DEV__) {
|
||||
if (patchFlag < 0) {
|
||||
// special flags (negative and mutually exclusive)
|
||||
vnodePatchFlag =
|
||||
patchFlag + ` /* ${PatchFlagNames[patchFlag as PatchFlags]} */`
|
||||
} else {
|
||||
// bitwise flags
|
||||
const flagNames = Object.keys(PatchFlagNames)
|
||||
.map(Number)
|
||||
.filter(n => n > 0 && patchFlag & n)
|
||||
.map(n => PatchFlagNames[n as PatchFlags])
|
||||
.join(`, `)
|
||||
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
|
||||
}
|
||||
} else {
|
||||
vnodePatchFlag = String(patchFlag)
|
||||
}
|
||||
if (dynamicPropNames && dynamicPropNames.length) {
|
||||
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
|
||||
}
|
||||
if (dynamicPropNames && dynamicPropNames.length) {
|
||||
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
|
||||
}
|
||||
|
||||
node.codegenNode = createVNodeCall(
|
||||
|
|
@ -234,7 +213,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
vnodeTag,
|
||||
vnodeProps,
|
||||
vnodeChildren,
|
||||
vnodePatchFlag,
|
||||
patchFlag === 0 ? undefined : patchFlag,
|
||||
vnodeDynamicProps,
|
||||
vnodeDirectives,
|
||||
!!shouldUseBlock,
|
||||
|
|
|
|||
|
|
@ -116,7 +116,11 @@ export function processExpression(
|
|||
}
|
||||
|
||||
const { inline, bindingMetadata } = context
|
||||
const rewriteIdentifier = (raw: string, parent?: Node, id?: Identifier) => {
|
||||
const rewriteIdentifier = (
|
||||
raw: string,
|
||||
parent?: Node | null,
|
||||
id?: Identifier,
|
||||
) => {
|
||||
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
|
||||
if (inline) {
|
||||
// x = y
|
||||
|
|
@ -313,9 +317,10 @@ export function processExpression(
|
|||
// local scope variable (a v-for alias, or a v-slot prop)
|
||||
if (
|
||||
!(needPrefix && isLocal) &&
|
||||
parent.type !== 'CallExpression' &&
|
||||
parent.type !== 'NewExpression' &&
|
||||
parent.type !== 'MemberExpression'
|
||||
(!parent ||
|
||||
(parent.type !== 'CallExpression' &&
|
||||
parent.type !== 'NewExpression' &&
|
||||
parent.type !== 'MemberExpression'))
|
||||
) {
|
||||
;(node as QualifiedId).isConstant = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ import {
|
|||
} from '../runtimeHelpers'
|
||||
import { processExpression } from './transformExpression'
|
||||
import { validateBrowserExpression } from '../validateExpression'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { transformBindShorthand } from './vBind'
|
||||
|
||||
export const transformFor = createStructuralDirectiveTransform(
|
||||
|
|
@ -109,8 +109,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
helper(FRAGMENT),
|
||||
undefined,
|
||||
renderExp,
|
||||
fragmentFlag +
|
||||
(__DEV__ ? ` /* ${PatchFlagNames[fragmentFlag]} */` : ``),
|
||||
fragmentFlag,
|
||||
undefined,
|
||||
undefined,
|
||||
true /* isBlock */,
|
||||
|
|
@ -169,10 +168,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
helper(FRAGMENT),
|
||||
keyProperty ? createObjectExpression([keyProperty]) : undefined,
|
||||
node.children,
|
||||
PatchFlags.STABLE_FRAGMENT +
|
||||
(__DEV__
|
||||
? ` /* ${PatchFlagNames[PatchFlags.STABLE_FRAGMENT]} */`
|
||||
: ``),
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ function createChildrenCodegenNode(
|
|||
helper(FRAGMENT),
|
||||
createObjectExpression([keyProperty]),
|
||||
children,
|
||||
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
|
||||
patchFlag,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
|
|
|
|||
|
|
@ -40,5 +40,7 @@ describe('decodeHtmlBrowser', () => {
|
|||
true,
|
||||
),
|
||||
).toBe('<strong><strong>&</strong></strong>')
|
||||
expect(decodeHtmlBrowser('"', true)).toBe('"')
|
||||
expect(decodeHtmlBrowser("'", true)).toBe("'")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 })
|
||||
], -1 /* CACHED */)
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -48,7 +48,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("img", { src: _imports_0_ })
|
||||
], -1 /* CACHED */)
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import {
|
|||
} from '@vue/compiler-core'
|
||||
import { transformVHtml } from '../../src/transforms/vHtml'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import {
|
||||
createObjectMatcher,
|
||||
genFlagText,
|
||||
} from '../../../compiler-core/__tests__/testUtils'
|
||||
import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { DOMErrorCodes } from '../../src/errors'
|
||||
|
||||
|
|
@ -34,7 +31,7 @@ describe('compiler: v-html transform', () => {
|
|||
innerHTML: `[test]`,
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: `["innerHTML"]`,
|
||||
})
|
||||
})
|
||||
|
|
@ -53,7 +50,7 @@ describe('compiler: v-html transform', () => {
|
|||
innerHTML: `[test]`,
|
||||
}),
|
||||
children: undefined, // <-- children should have been removed
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: `["innerHTML"]`,
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import { transformOn } from '../../src/transforms/vOn'
|
|||
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
|
||||
import { genFlagText } from '../../../compiler-core/__tests__/testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||
|
|
@ -272,7 +271,7 @@ describe('compiler-dom: transform v-on', () => {
|
|||
// should not treat cached handler as dynamicProp, so it should have no
|
||||
// dynamicProps flags and only the hydration flag
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
genFlagText(PatchFlags.NEED_HYDRATION),
|
||||
PatchFlags.NEED_HYDRATION,
|
||||
)
|
||||
expect(prop).toMatchObject({
|
||||
key: {
|
||||
|
|
@ -300,6 +299,6 @@ describe('compiler-dom: transform v-on', () => {
|
|||
},
|
||||
})
|
||||
// should only have hydration flag
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import {
|
|||
} from '@vue/compiler-core'
|
||||
import { transformVText } from '../../src/transforms/vText'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import {
|
||||
createObjectMatcher,
|
||||
genFlagText,
|
||||
} from '../../../compiler-core/__tests__/testUtils'
|
||||
import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { DOMErrorCodes } from '../../src/errors'
|
||||
|
||||
|
|
@ -36,7 +33,7 @@ describe('compiler: v-text transform', () => {
|
|||
},
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: `["textContent"]`,
|
||||
})
|
||||
})
|
||||
|
|
@ -57,7 +54,7 @@ describe('compiler: v-text transform', () => {
|
|||
},
|
||||
}),
|
||||
children: undefined, // <-- children should have been removed
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: `["textContent"]`,
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -861,7 +861,7 @@ export default {
|
|||
return (_ctx, _cache) => {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_createElementVNode("div", null, _toDisplayString(count.value), 1 /* TEXT */),
|
||||
_cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* CACHED */))
|
||||
_cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ const _hoisted_1 = _imports_0 + '#fragment'
|
|||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */))
|
||||
_cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ const _hoisted_2 = _imports_0 + ' 1x, ' + "/foo/logo.png" + ' 2x'
|
|||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* CACHED */))
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -35,51 +35,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
}, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -92,51 +92,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
}, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -162,51 +162,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_9
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
}, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -635,6 +635,26 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
// #11266
|
||||
test('correctly parse type annotation for declared function', () => {
|
||||
const { props } = resolve(`
|
||||
import { ExtractPropTypes } from 'vue'
|
||||
interface UploadFile<T = any> {
|
||||
xhr?: T
|
||||
}
|
||||
declare function uploadProps<T = any>(): {
|
||||
fileList: {
|
||||
type: PropType<UploadFile<T>[]>
|
||||
default: UploadFile<T>[]
|
||||
}
|
||||
}
|
||||
type UploadProps = ExtractPropTypes<ReturnType<typeof uploadProps>>
|
||||
defineProps<UploadProps>()`)
|
||||
expect(props).toStrictEqual({
|
||||
fileList: ['Array'],
|
||||
})
|
||||
})
|
||||
|
||||
describe('generics', () => {
|
||||
test('generic with type literal', () => {
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -42,26 +42,26 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/parser": "catalog:",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/compiler-ssr": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.10",
|
||||
"postcss": "^8.4.38",
|
||||
"source-map-js": "^1.2.0"
|
||||
"estree-walker": "catalog:",
|
||||
"magic-string": "catalog:",
|
||||
"postcss": "^8.4.39",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.24.7",
|
||||
"@babel/types": "catalog:",
|
||||
"@vue/consolidate": "^1.0.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "^9.0.4",
|
||||
"minimatch": "^9.0.5",
|
||||
"postcss-modules": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.77.5"
|
||||
"sass": "^1.77.8"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -618,7 +618,7 @@ export function compileScript(
|
|||
) {
|
||||
const scope: Statement[][] = [scriptSetupAst.body]
|
||||
walk(node, {
|
||||
enter(child: Node, parent: Node | undefined) {
|
||||
enter(child: Node, parent: Node | null) {
|
||||
if (isFunctionType(child)) {
|
||||
this.skip()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ export function transformDestructuredProps(
|
|||
const ast = ctx.scriptSetupAst!
|
||||
walkScope(ast, true)
|
||||
walk(ast, {
|
||||
enter(node: Node, parent?: Node) {
|
||||
enter(node: Node, parent: Node | null) {
|
||||
parent && parentStack.push(parent)
|
||||
|
||||
// skip type nodes
|
||||
|
|
@ -288,7 +288,7 @@ export function transformDestructuredProps(
|
|||
}
|
||||
}
|
||||
},
|
||||
leave(node: Node, parent?: Node) {
|
||||
leave(node: Node, parent: Node | null) {
|
||||
parent && parentStack.pop()
|
||||
if (
|
||||
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ function innerResolveTypeElements(
|
|||
case 'TSInterfaceDeclaration':
|
||||
return resolveInterfaceMembers(ctx, node, scope, typeParameters)
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TSTypeAnnotation':
|
||||
case 'TSParenthesizedType':
|
||||
return resolveTypeElements(
|
||||
ctx,
|
||||
|
|
|
|||
|
|
@ -452,3 +452,7 @@ describe('toRef <-> toValue', () => {
|
|||
),
|
||||
)
|
||||
})
|
||||
|
||||
// unref
|
||||
declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string>
|
||||
expectType<string>(unref(text))
|
||||
|
|
|
|||
|
|
@ -19,15 +19,6 @@ declare var __FEATURE_PROD_DEVTOOLS__: boolean
|
|||
declare var __FEATURE_SUSPENSE__: boolean
|
||||
declare var __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: boolean
|
||||
|
||||
// for tests
|
||||
declare namespace jest {
|
||||
interface Matchers<R, T> {
|
||||
toHaveBeenWarned(): R
|
||||
toHaveBeenWarnedLast(): R
|
||||
toHaveBeenWarnedTimes(n: number): R
|
||||
}
|
||||
}
|
||||
|
||||
declare module '*.vue' {}
|
||||
|
||||
declare module 'file-saver' {
|
||||
|
|
@ -38,8 +29,8 @@ declare module 'estree-walker' {
|
|||
export function walk<T>(
|
||||
root: T,
|
||||
options: {
|
||||
enter?: (node: T, parent: T | undefined) => any
|
||||
leave?: (node: T, parent: T | undefined) => any
|
||||
enter?: (node: T, parent: T | null) => any
|
||||
leave?: (node: T, parent: T | null) => any
|
||||
exit?: (node: T) => any
|
||||
} & ThisType<{ skip: () => void }>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -904,6 +904,22 @@ describe('reactivity/computed', () => {
|
|||
expect(serializeInner(root)).toBe('11')
|
||||
})
|
||||
|
||||
it('should be recomputed without being affected by side effects', () => {
|
||||
const v = ref(0)
|
||||
const c1 = computed(() => {
|
||||
v.value = 1
|
||||
return 0
|
||||
})
|
||||
const c2 = computed(() => {
|
||||
return v.value + ',' + c1.value
|
||||
})
|
||||
|
||||
expect(c2.value).toBe('0,0')
|
||||
v.value = 1
|
||||
expect(c2.value).toBe('1,0')
|
||||
// expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('debug: onTrigger (ref)', () => {
|
||||
let events: DebuggerEvent[] = []
|
||||
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { isRef, ref } from '../src/ref'
|
|||
import {
|
||||
isProxy,
|
||||
isReactive,
|
||||
isReadonly,
|
||||
isShallow,
|
||||
markRaw,
|
||||
reactive,
|
||||
readonly,
|
||||
|
|
@ -359,4 +361,25 @@ describe('reactivity/reactive', () => {
|
|||
const c = computed(() => {})
|
||||
expect(isProxy(c)).toBe(false)
|
||||
})
|
||||
|
||||
test('The results of the shallow and readonly assignments are the same (Map)', () => {
|
||||
const map = reactive(new Map())
|
||||
map.set('foo', shallowReactive({ a: 2 }))
|
||||
expect(isShallow(map.get('foo'))).toBe(true)
|
||||
|
||||
map.set('bar', readonly({ b: 2 }))
|
||||
expect(isReadonly(map.get('bar'))).toBe(true)
|
||||
})
|
||||
|
||||
test('The results of the shallow and readonly assignments are the same (Set)', () => {
|
||||
const set = reactive(new Set())
|
||||
set.add(shallowReactive({ a: 2 }))
|
||||
set.add(readonly({ b: 2 }))
|
||||
let count = 0
|
||||
for (const i of set) {
|
||||
if (count === 0) expect(isShallow(i)).toBe(true)
|
||||
else expect(isReadonly(i)).toBe(true)
|
||||
count++
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -123,6 +123,29 @@ describe('shallowReactive', () => {
|
|||
shallowSet.forEach(x => expect(isReactive(x)).toBe(false))
|
||||
})
|
||||
|
||||
test('Setting a reactive object on a shallowReactive map', () => {
|
||||
const msg = ref('ads')
|
||||
const bar = reactive({ msg })
|
||||
const foo = shallowReactive(new Map([['foo1', bar]]))
|
||||
foo.set('foo2', bar)
|
||||
|
||||
expect(isReactive(foo.get('foo2'))).toBe(true)
|
||||
expect(isReactive(foo.get('foo1'))).toBe(true)
|
||||
})
|
||||
|
||||
test('Setting a reactive object on a shallowReactive set', () => {
|
||||
const msg = ref(1)
|
||||
const bar = reactive({ msg })
|
||||
const foo = reactive({ msg })
|
||||
|
||||
const deps = shallowReactive(new Set([bar]))
|
||||
deps.add(foo)
|
||||
|
||||
deps.forEach(dep => {
|
||||
expect(isReactive(dep)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// #1210
|
||||
test('onTrack on called on objectSpread', () => {
|
||||
const onTrackFn = vi.fn()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
import { type Target, toRaw, toReactive, toReadonly } from './reactive'
|
||||
import {
|
||||
type Target,
|
||||
isReadonly,
|
||||
isShallow,
|
||||
toRaw,
|
||||
toReactive,
|
||||
toReadonly,
|
||||
} from './reactive'
|
||||
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
|
||||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared'
|
||||
|
|
@ -67,8 +74,10 @@ function size(target: IterableCollections, isReadonly = false) {
|
|||
return Reflect.get(target, 'size', target)
|
||||
}
|
||||
|
||||
function add(this: SetTypes, value: unknown) {
|
||||
value = toRaw(value)
|
||||
function add(this: SetTypes, value: unknown, _isShallow = false) {
|
||||
if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
|
||||
value = toRaw(value)
|
||||
}
|
||||
const target = toRaw(this)
|
||||
const proto = getProto(target)
|
||||
const hadKey = proto.has.call(target, value)
|
||||
|
|
@ -79,8 +88,10 @@ function add(this: SetTypes, value: unknown) {
|
|||
return this
|
||||
}
|
||||
|
||||
function set(this: MapTypes, key: unknown, value: unknown) {
|
||||
value = toRaw(value)
|
||||
function set(this: MapTypes, key: unknown, value: unknown, _isShallow = false) {
|
||||
if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
|
||||
value = toRaw(value)
|
||||
}
|
||||
const target = toRaw(this)
|
||||
const { has, get } = getProto(target)
|
||||
|
||||
|
|
@ -258,8 +269,12 @@ function createInstrumentations() {
|
|||
return size(this as unknown as IterableCollections)
|
||||
},
|
||||
has,
|
||||
add,
|
||||
set,
|
||||
add(this: SetTypes, value: unknown) {
|
||||
return add.call(this, value, true)
|
||||
},
|
||||
set(this: MapTypes, key: unknown, value: unknown) {
|
||||
return set.call(this, key, value, true)
|
||||
},
|
||||
delete: deleteEntry,
|
||||
clear,
|
||||
forEach: createForEach(false, true),
|
||||
|
|
|
|||
|
|
@ -210,7 +210,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>): T {
|
||||
export function unref<T>(ref: MaybeRef<T> | ComputedRef<T> | ShallowRef<T>): T {
|
||||
return isRef(ref) ? ref.value : ref
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +230,9 @@ export function unref<T>(ref: MaybeRef<T> | ComputedRef<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>): T {
|
||||
export function toValue<T>(
|
||||
source: MaybeRefOrGetter<T> | ComputedRef<T> | ShallowRef<T>,
|
||||
): T {
|
||||
return isFunction(source) ? source() : unref(source)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
defineComponent,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
onErrorCaptured,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
|
|
@ -1619,4 +1620,60 @@ describe('api: watch', () => {
|
|||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
expect(foo.value.a).toBe(2)
|
||||
})
|
||||
|
||||
test('watch immediate error in effect scope should be catched by onErrorCaptured', async () => {
|
||||
const warn = vi.spyOn(console, 'warn')
|
||||
warn.mockImplementation(() => {})
|
||||
const ERROR_IN_SCOPE = 'ERROR_IN_SCOPE'
|
||||
const ERROR_OUT_SCOPE = 'ERROR_OUT_SCOPE'
|
||||
|
||||
const errors = ref<string[]>([])
|
||||
const Comp = {
|
||||
setup() {
|
||||
const trigger = ref(0)
|
||||
|
||||
effectScope(true).run(() => {
|
||||
watch(
|
||||
trigger,
|
||||
() => {
|
||||
throw new Error(ERROR_IN_SCOPE)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
throw new Error(ERROR_OUT_SCOPE)
|
||||
})
|
||||
|
||||
return () => ''
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(
|
||||
h(
|
||||
{
|
||||
setup(_, { slots }) {
|
||||
onErrorCaptured(e => {
|
||||
errors.value.push(e.message)
|
||||
return false
|
||||
})
|
||||
|
||||
return () => h('div', slots.default && slots.default())
|
||||
},
|
||||
},
|
||||
null,
|
||||
() => [h(Comp)],
|
||||
),
|
||||
root,
|
||||
)
|
||||
await nextTick()
|
||||
// only watchEffect as ran so far
|
||||
expect(errors.value).toHaveLength(2)
|
||||
expect(errors.value[0]).toBe(ERROR_IN_SCOPE)
|
||||
expect(errors.value[1]).toBe(ERROR_OUT_SCOPE)
|
||||
|
||||
warn.mockRestore()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -356,6 +356,83 @@ describe('component: emit', () => {
|
|||
expect(fn2).toHaveBeenCalledWith('two')
|
||||
})
|
||||
|
||||
test('.trim modifier should work with v-model on component for kebab-cased props and camelCased emit', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
created() {
|
||||
this.$emit('update:firstName', ' one ')
|
||||
},
|
||||
})
|
||||
|
||||
const fn1 = vi.fn()
|
||||
|
||||
const Comp = () =>
|
||||
h(Foo, {
|
||||
'first-name': null,
|
||||
'first-nameModifiers': { trim: true },
|
||||
'onUpdate:first-name': fn1,
|
||||
})
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith('one')
|
||||
})
|
||||
|
||||
test('.trim modifier should work with v-model on component for camelCased props and kebab-cased emit', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
created() {
|
||||
this.$emit('update:model-value', ' one ')
|
||||
this.$emit('update:first-name', ' two ')
|
||||
},
|
||||
})
|
||||
|
||||
const fn1 = vi.fn()
|
||||
const fn2 = vi.fn()
|
||||
|
||||
const Comp = () =>
|
||||
h(Foo, {
|
||||
modelValue: null,
|
||||
modelModifiers: { trim: true },
|
||||
'onUpdate:modelValue': fn1,
|
||||
|
||||
firstName: null,
|
||||
firstNameModifiers: { trim: true },
|
||||
'onUpdate:firstName': fn2,
|
||||
})
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith('one')
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
expect(fn2).toHaveBeenCalledWith('two')
|
||||
})
|
||||
|
||||
test('.trim modifier should work with v-model on component for mixed cased props and emit', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
created() {
|
||||
this.$emit('update:base-URL', ' one ')
|
||||
},
|
||||
})
|
||||
|
||||
const fn1 = vi.fn()
|
||||
|
||||
const Comp = () =>
|
||||
h(Foo, {
|
||||
'base-URL': null,
|
||||
'base-URLModifiers': { trim: true },
|
||||
'onUpdate:base-URL': fn1,
|
||||
})
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith('one')
|
||||
})
|
||||
|
||||
test('.trim and .number modifiers should work with v-model on component', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
|
|
|
|||
|
|
@ -538,6 +538,96 @@ describe('component props', () => {
|
|||
expect(renderProxy.$props).toMatchObject(props)
|
||||
})
|
||||
|
||||
test('merging props from global mixins and extends', () => {
|
||||
let renderProxy: any
|
||||
let extendedRenderProxy: any
|
||||
|
||||
const defaultProp = ' from global'
|
||||
const props = {
|
||||
globalProp: {
|
||||
type: String,
|
||||
default: defaultProp,
|
||||
},
|
||||
}
|
||||
const globalMixin = {
|
||||
props,
|
||||
}
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
renderProxy = this
|
||||
return h('div', ['Comp', this.globalProp])
|
||||
},
|
||||
}
|
||||
const ExtendedComp = {
|
||||
extends: Comp,
|
||||
render(this: any) {
|
||||
extendedRenderProxy = this
|
||||
return h('div', ['ExtendedComp', this.globalProp])
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp(
|
||||
{
|
||||
render: () => [h(ExtendedComp), h(Comp)],
|
||||
},
|
||||
{},
|
||||
)
|
||||
app.mixin(globalMixin)
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
|
||||
expect(serializeInner(root)).toMatch(
|
||||
`<div>ExtendedComp from global</div><div>Comp from global</div>`,
|
||||
)
|
||||
expect(renderProxy.$props).toMatchObject({ globalProp: defaultProp })
|
||||
expect(extendedRenderProxy.$props).toMatchObject({
|
||||
globalProp: defaultProp,
|
||||
})
|
||||
})
|
||||
|
||||
test('merging props for a component that is also used as a mixin', () => {
|
||||
const CompA = {
|
||||
render(this: any) {
|
||||
return this.foo
|
||||
},
|
||||
}
|
||||
|
||||
const mixin = {
|
||||
props: {
|
||||
foo: {
|
||||
default: 'from mixin',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const CompB = {
|
||||
mixins: [mixin, CompA],
|
||||
render(this: any) {
|
||||
return this.foo
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
render() {
|
||||
return [h(CompA), ', ', h(CompB)]
|
||||
},
|
||||
})
|
||||
|
||||
app.mixin({
|
||||
props: {
|
||||
foo: {
|
||||
default: 'from global mixin',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
|
||||
expect(serializeInner(root)).toMatch(`from global mixin, from mixin`)
|
||||
})
|
||||
|
||||
test('props type support BigInt', () => {
|
||||
const Comp = {
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
ref,
|
||||
render,
|
||||
} from '@vue/runtime-test'
|
||||
import { normalizeVNode } from '../src/vnode'
|
||||
import { createBlock, normalizeVNode } from '../src/vnode'
|
||||
import { createSlots } from '../src/helpers/createSlots'
|
||||
|
||||
describe('component: slots', () => {
|
||||
|
|
@ -25,8 +25,21 @@ describe('component: slots', () => {
|
|||
}
|
||||
|
||||
test('initSlots: instance.slots should be set correctly', () => {
|
||||
let instance: any
|
||||
const Comp = {
|
||||
render() {
|
||||
instance = getCurrentInstance()
|
||||
return h('div')
|
||||
},
|
||||
}
|
||||
const slots = { foo: () => {}, _: 1 }
|
||||
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
|
||||
expect(instance.slots).toMatchObject(slots)
|
||||
})
|
||||
|
||||
test('initSlots: instance.slots should remove compiler marker if parent is using manual render function', () => {
|
||||
const { slots } = renderWithSlots({ _: 1 })
|
||||
expect(slots).toMatchObject({ _: 1 })
|
||||
expect(slots).toMatchObject({})
|
||||
})
|
||||
|
||||
test('initSlots: should normalize object slots (when value is null, string, array)', () => {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
serializeInner,
|
||||
withDirectives,
|
||||
} from '@vue/runtime-test'
|
||||
import { Fragment, createVNode } from '../../src/vnode'
|
||||
import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
|
||||
import { compile, render as domRender } from 'vue'
|
||||
|
||||
describe('renderer: teleport', () => {
|
||||
|
|
@ -553,4 +553,71 @@ describe('renderer: teleport', () => {
|
|||
`"<div>teleported</div>"`,
|
||||
)
|
||||
})
|
||||
|
||||
//#9071
|
||||
test('toggle sibling node inside target node', async () => {
|
||||
const root = document.createElement('div')
|
||||
const show = ref(false)
|
||||
const App = defineComponent({
|
||||
setup() {
|
||||
return () => {
|
||||
return show.value
|
||||
? h(Teleport, { to: root }, [h('div', 'teleported')])
|
||||
: h('div', 'foo')
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
domRender(h(App), root)
|
||||
expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
|
||||
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||
'"<!--teleport start--><!--teleport end--><div>teleported</div>"',
|
||||
)
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
|
||||
expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
|
||||
})
|
||||
|
||||
test('unmount previous sibling node inside target node', async () => {
|
||||
const root = document.createElement('div')
|
||||
const parentShow = ref(false)
|
||||
const childShow = ref(true)
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () => h(Teleport, { to: root }, [h('div', 'foo')])
|
||||
},
|
||||
}
|
||||
|
||||
const App = defineComponent({
|
||||
setup() {
|
||||
return () => {
|
||||
return parentShow.value
|
||||
? h(Fragment, { key: 0 }, [
|
||||
childShow.value ? h(Comp) : createCommentVNode('v-if'),
|
||||
])
|
||||
: createCommentVNode('v-if')
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
domRender(h(App), root)
|
||||
expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
|
||||
|
||||
parentShow.value = true
|
||||
await nextTick()
|
||||
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||
'"<!--teleport start--><!--teleport end--><div>foo</div>"',
|
||||
)
|
||||
|
||||
parentShow.value = false
|
||||
await nextTick()
|
||||
expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
type VNode,
|
||||
computed,
|
||||
createApp,
|
||||
defineComponent,
|
||||
h,
|
||||
|
|
@ -11,6 +13,7 @@ import {
|
|||
watch,
|
||||
watchEffect,
|
||||
} from '@vue/runtime-test'
|
||||
import { ErrorCodes, ErrorTypeStrings } from '../src/errorHandling'
|
||||
|
||||
describe('error handling', () => {
|
||||
test('propagation', () => {
|
||||
|
|
@ -609,5 +612,63 @@ describe('error handling', () => {
|
|||
expect(handler).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('errors in scheduler job with owner instance should be caught', async () => {
|
||||
let vnode: VNode
|
||||
const x = ref(0)
|
||||
const app = createApp({
|
||||
render() {
|
||||
return (vnode = vnode || h('div', x.value))
|
||||
},
|
||||
})
|
||||
|
||||
app.config.errorHandler = vi.fn()
|
||||
app.mount(nodeOps.createElement('div'))
|
||||
|
||||
const error = new Error('error')
|
||||
Object.defineProperty(vnode!, 'el', {
|
||||
get() {
|
||||
throw error
|
||||
},
|
||||
})
|
||||
|
||||
x.value++
|
||||
await nextTick()
|
||||
expect(app.config.errorHandler).toHaveBeenCalledWith(
|
||||
error,
|
||||
{},
|
||||
ErrorTypeStrings[ErrorCodes.COMPONENT_UPDATE],
|
||||
)
|
||||
})
|
||||
|
||||
// #11286
|
||||
test('handle error in computed', async () => {
|
||||
const err = new Error()
|
||||
const handler = vi.fn()
|
||||
|
||||
const count = ref(1)
|
||||
const x = computed(() => {
|
||||
if (count.value === 2) throw err
|
||||
return count.value + 1
|
||||
})
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () => x.value
|
||||
},
|
||||
})
|
||||
|
||||
app.config.errorHandler = handler
|
||||
app.mount(nodeOps.createElement('div'))
|
||||
|
||||
count.value = 2
|
||||
|
||||
await nextTick()
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
err,
|
||||
{},
|
||||
ErrorTypeStrings[ErrorCodes.COMPONENT_UPDATE],
|
||||
)
|
||||
})
|
||||
|
||||
// native event handler handling should be tested in respective renderers
|
||||
})
|
||||
|
|
|
|||
|
|
@ -26,13 +26,17 @@ describe('renderSlot', () => {
|
|||
const vnode = renderSlot(
|
||||
{ default: () => [(child = h('child'))] },
|
||||
'default',
|
||||
{ key: 'foo' },
|
||||
)
|
||||
expect(vnode.children).toEqual([child])
|
||||
expect(vnode.key).toBe('foo')
|
||||
})
|
||||
|
||||
it('should render slot fallback', () => {
|
||||
const vnode = renderSlot({}, 'default', {}, () => ['fallback'])
|
||||
const vnode = renderSlot({}, 'default', { key: 'foo' }, () => ['fallback'])
|
||||
expect(vnode.children).toEqual(['fallback'])
|
||||
// should attach fallback key postfix
|
||||
expect(vnode.key).toBe('foo_fb')
|
||||
})
|
||||
|
||||
it('should warn render ssr slot', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
Fragment,
|
||||
type Ref,
|
||||
type TestElement,
|
||||
createApp,
|
||||
createBlock,
|
||||
createElementBlock,
|
||||
|
|
@ -526,4 +527,129 @@ describe('useModel', () => {
|
|||
await nextTick()
|
||||
expect(msg.value).toBe('UGHH')
|
||||
})
|
||||
|
||||
// #10279
|
||||
test('force local update when setter formats value to the same value', async () => {
|
||||
let childMsg: Ref<string>
|
||||
let childModifiers: Record<string, true | undefined>
|
||||
|
||||
const compRender = vi.fn()
|
||||
const parentRender = vi.fn()
|
||||
|
||||
const Comp = defineComponent({
|
||||
props: ['msg', 'msgModifiers'],
|
||||
emits: ['update:msg'],
|
||||
setup(props) {
|
||||
;[childMsg, childModifiers] = useModel(props, 'msg', {
|
||||
set(val) {
|
||||
if (childModifiers.number) {
|
||||
return val.replace(/\D+/g, '')
|
||||
}
|
||||
},
|
||||
})
|
||||
return () => {
|
||||
compRender()
|
||||
return h('input', {
|
||||
// simulate how v-model works
|
||||
onVnodeBeforeMount(vnode) {
|
||||
;(vnode.el as TestElement).props.value = childMsg.value
|
||||
},
|
||||
onVnodeBeforeUpdate(vnode) {
|
||||
;(vnode.el as TestElement).props.value = childMsg.value
|
||||
},
|
||||
onInput(value: any) {
|
||||
childMsg.value = value
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const msg = ref(1)
|
||||
const Parent = defineComponent({
|
||||
setup() {
|
||||
return () => {
|
||||
parentRender()
|
||||
return h(Comp, {
|
||||
msg: msg.value,
|
||||
msgModifiers: { number: true },
|
||||
'onUpdate:msg': val => {
|
||||
msg.value = val
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Parent), root)
|
||||
|
||||
expect(parentRender).toHaveBeenCalledTimes(1)
|
||||
expect(compRender).toHaveBeenCalledTimes(1)
|
||||
expect(serializeInner(root)).toBe('<input value=1></input>')
|
||||
|
||||
const input = root.children[0] as TestElement
|
||||
|
||||
// simulate v-model update
|
||||
input.props.onInput((input.props.value = '2'))
|
||||
await nextTick()
|
||||
expect(msg.value).toBe(2)
|
||||
expect(parentRender).toHaveBeenCalledTimes(2)
|
||||
expect(compRender).toHaveBeenCalledTimes(2)
|
||||
expect(serializeInner(root)).toBe('<input value=2></input>')
|
||||
|
||||
input.props.onInput((input.props.value = '2a'))
|
||||
await nextTick()
|
||||
expect(msg.value).toBe(2)
|
||||
expect(parentRender).toHaveBeenCalledTimes(2)
|
||||
// should force local update
|
||||
expect(compRender).toHaveBeenCalledTimes(3)
|
||||
expect(serializeInner(root)).toBe('<input value=2></input>')
|
||||
|
||||
input.props.onInput((input.props.value = '2a'))
|
||||
await nextTick()
|
||||
expect(parentRender).toHaveBeenCalledTimes(2)
|
||||
// should not force local update if set to the same value
|
||||
expect(compRender).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
test('set no change value', async () => {
|
||||
let changeChildMsg: (() => void) | null = null
|
||||
|
||||
const compRender = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: ['msg'],
|
||||
emits: ['update:msg'],
|
||||
setup(props) {
|
||||
const childMsg = useModel(props, 'msg')
|
||||
changeChildMsg = () => {
|
||||
childMsg.value = childMsg.value
|
||||
}
|
||||
return () => {
|
||||
return childMsg.value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const msg = ref('HI')
|
||||
const Parent = defineComponent({
|
||||
setup() {
|
||||
return () =>
|
||||
h(Comp, {
|
||||
msg: msg.value,
|
||||
'onUpdate:msg': val => {
|
||||
msg.value = val
|
||||
compRender()
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Parent), root)
|
||||
|
||||
expect(compRender).toBeCalledTimes(0)
|
||||
changeChildMsg!()
|
||||
expect(compRender).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ function compileToFunction(template: string) {
|
|||
return render
|
||||
}
|
||||
|
||||
const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
|
||||
|
||||
describe('hot module replacement', () => {
|
||||
test('inject global runtime', () => {
|
||||
expect(createRecord).toBeDefined()
|
||||
|
|
@ -436,18 +438,23 @@ describe('hot module replacement', () => {
|
|||
|
||||
const Parent: ComponentOptions = {
|
||||
setup() {
|
||||
const com = ref()
|
||||
const changeRef = (value: any) => {
|
||||
com.value = value
|
||||
}
|
||||
const com1 = ref()
|
||||
const changeRef1 = (value: any) => (com1.value = value)
|
||||
|
||||
return () => [h(Child, { ref: changeRef }), com.value?.count]
|
||||
const com2 = ref()
|
||||
const changeRef2 = (value: any) => (com2.value = value)
|
||||
|
||||
return () => [
|
||||
h(Child, { ref: changeRef1 }),
|
||||
h(Child, { ref: changeRef2 }),
|
||||
com1.value?.count,
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
render(h(Parent), root)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>0</div>0`)
|
||||
expect(serializeInner(root)).toBe(`<div>0</div><div>0</div>0`)
|
||||
|
||||
reload(childId, {
|
||||
__hmrId: childId,
|
||||
|
|
@ -458,9 +465,9 @@ describe('hot module replacement', () => {
|
|||
render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
|
||||
})
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>1</div>1`)
|
||||
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||
expect(mountSpy).toHaveBeenCalledTimes(1)
|
||||
expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>1`)
|
||||
expect(unmountSpy).toHaveBeenCalledTimes(2)
|
||||
expect(mountSpy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
// #1156 - static nodes should retain DOM element reference across updates
|
||||
|
|
@ -805,4 +812,43 @@ describe('hot module replacement', () => {
|
|||
`<div><div>1<p>3</p></div></div><div><div>1<p>3</p></div></div><p>2</p>`,
|
||||
)
|
||||
})
|
||||
|
||||
// #11248
|
||||
test('reload async component with multiple instances', async () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const childId = 'test-child-id'
|
||||
const Child: ComponentOptions = {
|
||||
__hmrId: childId,
|
||||
data() {
|
||||
return { count: 0 }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
}
|
||||
const Comp = runtimeTest.defineAsyncComponent(() => Promise.resolve(Child))
|
||||
const appId = 'test-app-id'
|
||||
const App: ComponentOptions = {
|
||||
__hmrId: appId,
|
||||
render: () => [h(Comp), h(Comp)],
|
||||
}
|
||||
createRecord(appId, App)
|
||||
|
||||
render(h(App), root)
|
||||
|
||||
await timeout()
|
||||
|
||||
expect(serializeInner(root)).toBe(`<div>0</div><div>0</div>`)
|
||||
|
||||
// change count to 1
|
||||
reload(childId, {
|
||||
__hmrId: childId,
|
||||
data() {
|
||||
return { count: 1 }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
})
|
||||
|
||||
await timeout()
|
||||
|
||||
expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
nextTick,
|
||||
onMounted,
|
||||
openBlock,
|
||||
reactive,
|
||||
ref,
|
||||
renderSlot,
|
||||
useCssVars,
|
||||
|
|
@ -31,7 +32,7 @@ import {
|
|||
withDirectives,
|
||||
} from '@vue/runtime-dom'
|
||||
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { PatchFlags, normalizeStyle } from '@vue/shared'
|
||||
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
||||
import { expect } from 'vitest'
|
||||
|
||||
|
|
@ -148,6 +149,15 @@ describe('SSR hydration', () => {
|
|||
expect(container.innerHTML).toBe(`<div class="bar">bar</div>`)
|
||||
})
|
||||
|
||||
// #7285
|
||||
test('element with multiple continuous text vnodes', async () => {
|
||||
// should no mismatch warning
|
||||
const { container } = mountWithHydration('<div>fooo</div>', () =>
|
||||
h('div', ['fo', createTextVNode('o'), 'o']),
|
||||
)
|
||||
expect(container.textContent).toBe('fooo')
|
||||
})
|
||||
|
||||
test('element with elements children', async () => {
|
||||
const msg = ref('foo')
|
||||
const fn = vi.fn()
|
||||
|
|
@ -239,6 +249,17 @@ describe('SSR hydration', () => {
|
|||
)
|
||||
})
|
||||
|
||||
// #7285
|
||||
test('Fragment (multiple continuous text vnodes)', async () => {
|
||||
// should no mismatch warning
|
||||
const { container } = mountWithHydration('<!--[-->fooo<!--]-->', () => [
|
||||
'fo',
|
||||
createTextVNode('o'),
|
||||
'o',
|
||||
])
|
||||
expect(container.textContent).toBe('fooo')
|
||||
})
|
||||
|
||||
test('Teleport', async () => {
|
||||
const msg = ref('foo')
|
||||
const fn = vi.fn()
|
||||
|
|
@ -1176,6 +1197,38 @@ describe('SSR hydration', () => {
|
|||
expect(text.nodeType).toBe(3)
|
||||
})
|
||||
|
||||
// #11372
|
||||
test('object style value tracking in prod', async () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
const style = reactive({ color: 'red' })
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock(
|
||||
'div',
|
||||
{
|
||||
style: normalizeStyle(style),
|
||||
},
|
||||
null,
|
||||
4 /* STYLE */,
|
||||
)
|
||||
)
|
||||
},
|
||||
}
|
||||
const { container } = mountWithHydration(
|
||||
`<div style="color: red;"></div>`,
|
||||
() => h(Comp),
|
||||
)
|
||||
style.color = 'green'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(`<div style="color: green;"></div>`)
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
test('app.unmount()', async () => {
|
||||
const container = document.createElement('DIV')
|
||||
container.innerHTML = '<button></button>'
|
||||
|
|
@ -1317,76 +1370,83 @@ describe('SSR hydration', () => {
|
|||
// #10607
|
||||
test('update component stable slot (prod + optimized mode)', async () => {
|
||||
__DEV__ = false
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = `<template><div show="false"><!--[--><div><div><!----></div></div><div>0</div><!--]--></div></template>`
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [renderSlot(this.$slots, 'default')])
|
||||
)
|
||||
},
|
||||
}
|
||||
const show = ref(false)
|
||||
const clicked = ref(false)
|
||||
|
||||
const Wrapper = {
|
||||
setup() {
|
||||
const items = ref<number[]>([])
|
||||
onMounted(() => {
|
||||
items.value = [1]
|
||||
})
|
||||
return () => {
|
||||
try {
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = `<template><div show="false"><!--[--><div><div><!----></div></div><div>0</div><!--]--></div></template>`
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Comp, null, {
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
createElementVNode('div', null, [
|
||||
clicked.value
|
||||
? (openBlock(),
|
||||
createElementBlock('div', { key: 0 }, 'foo'))
|
||||
: createCommentVNode('v-if', true),
|
||||
]),
|
||||
]),
|
||||
createElementVNode(
|
||||
'div',
|
||||
null,
|
||||
items.value.length,
|
||||
1 /* TEXT */,
|
||||
),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
createElementBlock('div', null, [
|
||||
renderSlot(this.$slots, 'default'),
|
||||
])
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
createSSRApp({
|
||||
components: { Wrapper },
|
||||
data() {
|
||||
return { show }
|
||||
},
|
||||
template: `<Wrapper :show="show"/>`,
|
||||
}).mount(container)
|
||||
},
|
||||
}
|
||||
const show = ref(false)
|
||||
const clicked = ref(false)
|
||||
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="false"><!--[--><div><div><!----></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
const Wrapper = {
|
||||
setup() {
|
||||
const items = ref<number[]>([])
|
||||
onMounted(() => {
|
||||
items.value = [1]
|
||||
})
|
||||
return () => {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Comp, null, {
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
createElementVNode('div', null, [
|
||||
clicked.value
|
||||
? (openBlock(),
|
||||
createElementBlock('div', { key: 0 }, 'foo'))
|
||||
: createCommentVNode('v-if', true),
|
||||
]),
|
||||
]),
|
||||
createElementVNode(
|
||||
'div',
|
||||
null,
|
||||
items.value.length,
|
||||
1 /* TEXT */,
|
||||
),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
createSSRApp({
|
||||
components: { Wrapper },
|
||||
data() {
|
||||
return { show }
|
||||
},
|
||||
template: `<Wrapper :show="show"/>`,
|
||||
}).mount(container)
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(async () => {
|
||||
clicked.value = true
|
||||
await nextTick()
|
||||
}).not.toThrow("Cannot read properties of null (reading 'insertBefore')")
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="false"><!--[--><div><div><!----></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="true"><!--[--><div><div><div>foo</div></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
__DEV__ = true
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(async () => {
|
||||
clicked.value = true
|
||||
await nextTick()
|
||||
}).not.toThrow("Cannot read properties of null (reading 'insertBefore')")
|
||||
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="true"><!--[--><div><div><div>foo</div></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
} catch (e) {
|
||||
throw e
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
describe('mismatch handling', () => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {
|
|||
createApp,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createElementBlock,
|
||||
createElementVNode,
|
||||
createTextVNode,
|
||||
createVNode,
|
||||
defineComponent,
|
||||
|
|
@ -23,6 +25,7 @@ import {
|
|||
renderList,
|
||||
renderSlot,
|
||||
serialize,
|
||||
setBlockTracking,
|
||||
withCtx,
|
||||
} from '@vue/runtime-test'
|
||||
import { PatchFlags, SlotFlags } from '@vue/shared'
|
||||
|
|
@ -434,7 +437,7 @@ describe('renderer: optimized mode', () => {
|
|||
const App = {
|
||||
setup() {
|
||||
return () => {
|
||||
return createVNode(Comp, null, {
|
||||
return createBlock(Comp, null, {
|
||||
default: withCtx(() => [
|
||||
createVNode('p', null, foo.value, PatchFlags.TEXT),
|
||||
]),
|
||||
|
|
@ -560,6 +563,7 @@ describe('renderer: optimized mode', () => {
|
|||
const state = ref(0)
|
||||
|
||||
const CompA = {
|
||||
name: 'A',
|
||||
setup(props: any, { slots }: SetupContext) {
|
||||
return () => {
|
||||
return (
|
||||
|
|
@ -571,6 +575,7 @@ describe('renderer: optimized mode', () => {
|
|||
}
|
||||
|
||||
const Wrapper = {
|
||||
name: 'Wrapper',
|
||||
setup(props: any, { slots }: SetupContext) {
|
||||
// use the manually written render function to rendering the optimized slots,
|
||||
// which should make subsequent updates exit the optimized mode correctly
|
||||
|
|
@ -581,6 +586,7 @@ describe('renderer: optimized mode', () => {
|
|||
}
|
||||
|
||||
const app = createApp({
|
||||
name: 'App',
|
||||
setup() {
|
||||
return () => {
|
||||
return (
|
||||
|
|
@ -959,4 +965,271 @@ describe('renderer: optimized mode', () => {
|
|||
// should successfully unmount without error
|
||||
expect(inner(root)).toBe(`<!---->`)
|
||||
})
|
||||
|
||||
// #10870
|
||||
test('should bail manually rendered compiler slots for both mount and update', async () => {
|
||||
// only reproducible in prod
|
||||
__DEV__ = false
|
||||
function Outer(_: any, { slots }: any) {
|
||||
return slots.default()
|
||||
}
|
||||
const Mid = {
|
||||
render(ctx: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [renderSlot(ctx.$slots, 'default')])
|
||||
)
|
||||
},
|
||||
}
|
||||
const state1 = ref(true)
|
||||
const state2 = ref(true)
|
||||
const App = {
|
||||
render() {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Outer, null, {
|
||||
default: withCtx(() => [
|
||||
createVNode(
|
||||
Mid,
|
||||
{ foo: state2.value },
|
||||
{
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
createElementVNode('div', null, [
|
||||
state2.value
|
||||
? (openBlock(),
|
||||
createElementBlock(
|
||||
'div',
|
||||
{
|
||||
key: 0,
|
||||
id: 'if',
|
||||
foo: state1.value,
|
||||
},
|
||||
null,
|
||||
8 /* PROPS */,
|
||||
['foo'],
|
||||
))
|
||||
: createCommentVNode('v-if', true),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
},
|
||||
8 /* PROPS */,
|
||||
['foo'],
|
||||
),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp(App)
|
||||
app.config.errorHandler = vi.fn()
|
||||
|
||||
try {
|
||||
app.mount(root)
|
||||
|
||||
state1.value = false
|
||||
await nextTick()
|
||||
|
||||
state2.value = false
|
||||
await nextTick()
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
expect(app.config.errorHandler).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
// #11336
|
||||
test('should bail manually rendered compiler slots for both mount and update (2)', async () => {
|
||||
// only reproducible in prod
|
||||
__DEV__ = false
|
||||
const n = ref(0)
|
||||
function Outer(_: any, { slots }: any) {
|
||||
n.value // track
|
||||
return slots.default()
|
||||
}
|
||||
const Mid = {
|
||||
render(ctx: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [renderSlot(ctx.$slots, 'default')])
|
||||
)
|
||||
},
|
||||
}
|
||||
const show = ref(false)
|
||||
const App = {
|
||||
render() {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Outer, null, {
|
||||
default: withCtx(() => [
|
||||
createVNode(Mid, null, {
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
show.value
|
||||
? (openBlock(),
|
||||
createElementBlock('div', { key: 0 }, '1'))
|
||||
: createCommentVNode('v-if', true),
|
||||
createElementVNode('div', null, '2'),
|
||||
createElementVNode('div', null, '3'),
|
||||
]),
|
||||
createElementVNode('div', null, '4'),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
}),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp(App)
|
||||
app.config.errorHandler = vi.fn()
|
||||
|
||||
try {
|
||||
app.mount(root)
|
||||
|
||||
// force Outer update, which will assign new slots to Mid
|
||||
// we want to make sure the compiled slot flag doesn't accidentally
|
||||
// get assigned again
|
||||
n.value++
|
||||
await nextTick()
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
expect(app.config.errorHandler).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
test('diff slot and slot fallback node', async () => {
|
||||
const Comp = {
|
||||
props: ['show'],
|
||||
setup(props: any, { slots }: SetupContext) {
|
||||
return () => {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [
|
||||
renderSlot(slots, 'default', { hide: !props.show }, () => [
|
||||
(openBlock(),
|
||||
(block = createElementBlock(
|
||||
Fragment,
|
||||
{ key: 0 },
|
||||
[createTextVNode('foo')],
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
))),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const show = ref(true)
|
||||
const app = createApp({
|
||||
render() {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(
|
||||
Comp,
|
||||
{ show: show.value },
|
||||
{
|
||||
default: withCtx(({ hide }: { hide: boolean }) => [
|
||||
!hide
|
||||
? (openBlock(),
|
||||
createElementBlock(
|
||||
Fragment,
|
||||
{ key: 0 },
|
||||
[
|
||||
createCommentVNode('comment'),
|
||||
createElementVNode(
|
||||
'div',
|
||||
null,
|
||||
'bar',
|
||||
PatchFlags.CACHED,
|
||||
),
|
||||
],
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
))
|
||||
: createCommentVNode('v-if', true),
|
||||
]),
|
||||
_: SlotFlags.STABLE,
|
||||
},
|
||||
PatchFlags.PROPS,
|
||||
['show'],
|
||||
)
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
app.mount(root)
|
||||
expect(inner(root)).toBe('<div><!--comment--><div>bar</div></div>')
|
||||
expect(block).toBe(null)
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe('<div>foo</div>')
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe('<div><!--comment--><div>bar</div></div>')
|
||||
})
|
||||
|
||||
test('should not take unmount children fast path if children contain cached nodes', async () => {
|
||||
const show = ref(true)
|
||||
const spyUnmounted = vi.fn()
|
||||
|
||||
const Child = {
|
||||
setup() {
|
||||
onUnmounted(spyUnmounted)
|
||||
return () => createVNode('div', null, 'Child')
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
render(_: any, cache: any) {
|
||||
return show.value
|
||||
? (openBlock(),
|
||||
createBlock('div', null, [
|
||||
createVNode('div', null, [
|
||||
cache[0] ||
|
||||
(setBlockTracking(-1),
|
||||
((cache[0] = createVNode('div', null, [
|
||||
createVNode(Child),
|
||||
])).cacheIndex = 0),
|
||||
setBlockTracking(1),
|
||||
cache[0]),
|
||||
]),
|
||||
]))
|
||||
: createCommentVNode('v-if', true)
|
||||
},
|
||||
})
|
||||
|
||||
app.mount(root)
|
||||
expect(inner(root)).toBe(
|
||||
'<div><div><div><div>Child</div></div></div></div>',
|
||||
)
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe('<!--v-if-->')
|
||||
expect(spyUnmounted).toHaveBeenCalledTimes(1)
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe(
|
||||
'<div><div><div><div>Child</div></div></div></div>',
|
||||
)
|
||||
|
||||
// should unmount again, this verifies previous cache was properly cleared
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe('<!--v-if-->')
|
||||
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
Comment,
|
||||
Fragment,
|
||||
Text,
|
||||
type VNode,
|
||||
cloneVNode,
|
||||
createBlock,
|
||||
createVNode,
|
||||
|
|
@ -633,7 +634,9 @@ describe('vnode', () => {
|
|||
setBlockTracking(1),
|
||||
vnode1,
|
||||
]))
|
||||
expect(vnode.dynamicChildren).toStrictEqual([])
|
||||
const expected: VNode['dynamicChildren'] = []
|
||||
expected.hasOnce = true
|
||||
expect(vnode.dynamicChildren).toStrictEqual(expected)
|
||||
})
|
||||
// #5657
|
||||
test('error of slot function execution should not affect block tracking', () => {
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export type Data = Record<string, unknown>
|
|||
* the usage of `InstanceType<typeof Comp>` which only works for
|
||||
* constructor-based component definition types.
|
||||
*
|
||||
* Exmaple:
|
||||
* @example
|
||||
* ```ts
|
||||
* const MyComp = { ... }
|
||||
* declare const instance: ComponentInstance<typeof MyComp>
|
||||
|
|
@ -787,13 +787,14 @@ export let isInSSRComponentSetup = false
|
|||
export function setupComponent(
|
||||
instance: ComponentInternalInstance,
|
||||
isSSR = false,
|
||||
optimized = false,
|
||||
) {
|
||||
isSSR && setInSSRSetupState(isSSR)
|
||||
|
||||
const { props, children } = instance.vnode
|
||||
const isStateful = isStatefulComponent(instance)
|
||||
initProps(instance, props, isStateful, isSSR)
|
||||
initSlots(instance, children)
|
||||
initSlots(instance, children, optimized)
|
||||
|
||||
const setupResult = isStateful
|
||||
? setupStatefulComponent(instance, isSSR)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
compatModelEventPrefix,
|
||||
} from './compat/componentVModel'
|
||||
import type { ComponentTypeEmits } from './apiSetupHelpers'
|
||||
import { getModelModifiers } from './helpers/useModel'
|
||||
|
||||
export type ObjectEmitsOptions = Record<
|
||||
string,
|
||||
|
|
@ -145,16 +146,12 @@ export function emit(
|
|||
const isModelListener = event.startsWith('update:')
|
||||
|
||||
// for v-model update:xxx events, apply modifiers on args
|
||||
const modelArg = isModelListener && event.slice(7)
|
||||
if (modelArg && modelArg in props) {
|
||||
const modifiersKey = `${
|
||||
modelArg === 'modelValue' ? 'model' : modelArg
|
||||
}Modifiers`
|
||||
const { number, trim } = props[modifiersKey] || EMPTY_OBJ
|
||||
if (trim) {
|
||||
const modifiers = isModelListener && getModelModifiers(props, event.slice(7))
|
||||
if (modifiers) {
|
||||
if (modifiers.trim) {
|
||||
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
||||
}
|
||||
if (number) {
|
||||
if (modifiers.number) {
|
||||
args = rawArgs.map(looseToNumber)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -498,12 +498,15 @@ function resolvePropValue(
|
|||
return value
|
||||
}
|
||||
|
||||
const mixinPropsCache = new WeakMap<ConcreteComponent, NormalizedPropsOptions>()
|
||||
|
||||
export function normalizePropsOptions(
|
||||
comp: ConcreteComponent,
|
||||
appContext: AppContext,
|
||||
asMixin = false,
|
||||
): NormalizedPropsOptions {
|
||||
const cache = appContext.propsCache
|
||||
const cache =
|
||||
__FEATURE_OPTIONS_API__ && asMixin ? mixinPropsCache : appContext.propsCache
|
||||
const cached = cache.get(comp)
|
||||
if (cached) {
|
||||
return cached
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import {
|
|||
ShapeFlags,
|
||||
SlotFlags,
|
||||
def,
|
||||
extend,
|
||||
isArray,
|
||||
isFunction,
|
||||
} from '@vue/shared'
|
||||
|
|
@ -161,17 +160,36 @@ const normalizeVNodeSlots = (
|
|||
instance.slots.default = () => normalized
|
||||
}
|
||||
|
||||
const assignSlots = (
|
||||
slots: InternalSlots,
|
||||
children: Slots,
|
||||
optimized: boolean,
|
||||
) => {
|
||||
for (const key in children) {
|
||||
// #2893
|
||||
// when rendering the optimized slots by manually written render function,
|
||||
// do not copy the `slots._` compiler flag so that `renderSlot` creates
|
||||
// slot Fragment with BAIL patchFlag to force full updates
|
||||
if (optimized || key !== '_') {
|
||||
slots[key] = children[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const initSlots = (
|
||||
instance: ComponentInternalInstance,
|
||||
children: VNodeNormalizedChildren,
|
||||
optimized: boolean,
|
||||
) => {
|
||||
const slots = (instance.slots = createInternalObject())
|
||||
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
||||
const type = (children as RawSlots)._
|
||||
if (type) {
|
||||
extend(slots, children as InternalSlots)
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
// make compiler marker non-enumerable
|
||||
def(slots, '_', type, true)
|
||||
if (optimized) {
|
||||
def(slots, '_', type, true)
|
||||
}
|
||||
} else {
|
||||
normalizeObjectSlots(children as RawSlots, slots, instance)
|
||||
}
|
||||
|
|
@ -195,7 +213,7 @@ export const updateSlots = (
|
|||
if (__DEV__ && isHmrUpdating) {
|
||||
// Parent was HMR updated so slot content may have changed.
|
||||
// force update slots and mark instance for hmr as well
|
||||
extend(slots, children as Slots)
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
trigger(instance, TriggerOpTypes.SET, '$slots')
|
||||
} else if (optimized && type === SlotFlags.STABLE) {
|
||||
// compiled AND stable.
|
||||
|
|
@ -204,14 +222,7 @@ export const updateSlots = (
|
|||
} else {
|
||||
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
|
||||
// normalization.
|
||||
extend(slots, children as Slots)
|
||||
// #2893
|
||||
// when rendering the optimized slots by manually written render function,
|
||||
// we need to delete the `slots._` flag if necessary to make subsequent updates reliable,
|
||||
// i.e. let the `renderSlot` create the bailed Fragment
|
||||
if (!optimized && type === SlotFlags.STABLE) {
|
||||
delete slots._
|
||||
}
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
}
|
||||
} else {
|
||||
needDeletionCheck = !(children as RawSlots).$stable
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ export interface TeleportProps {
|
|||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const TeleportEndKey = Symbol('_vte')
|
||||
|
||||
export const isTeleport = (type: any): boolean => type.__isTeleport
|
||||
|
||||
const isTeleportDisabled = (props: VNode['props']): boolean =>
|
||||
|
|
@ -105,11 +107,16 @@ export const TeleportImpl = {
|
|||
const mainAnchor = (n2.anchor = __DEV__
|
||||
? createComment('teleport end')
|
||||
: createText(''))
|
||||
const target = (n2.target = resolveTarget(n2.props, querySelector))
|
||||
const targetStart = (n2.targetStart = createText(''))
|
||||
const targetAnchor = (n2.targetAnchor = createText(''))
|
||||
insert(placeholder, container, anchor)
|
||||
insert(mainAnchor, container, anchor)
|
||||
const target = (n2.target = resolveTarget(n2.props, querySelector))
|
||||
const targetAnchor = (n2.targetAnchor = createText(''))
|
||||
// attach a special property so we can skip teleported content in
|
||||
// renderer's nextSibling search
|
||||
targetStart[TeleportEndKey] = targetAnchor
|
||||
if (target) {
|
||||
insert(targetStart, target)
|
||||
insert(targetAnchor, target)
|
||||
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
|
||||
if (namespace === 'svg' || isTargetSVG(target)) {
|
||||
|
|
@ -146,6 +153,7 @@ export const TeleportImpl = {
|
|||
} else {
|
||||
// update content
|
||||
n2.el = n1.el
|
||||
n2.targetStart = n1.targetStart
|
||||
const mainAnchor = (n2.anchor = n1.anchor)!
|
||||
const target = (n2.target = n1.target)!
|
||||
const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
|
||||
|
|
@ -253,9 +261,18 @@ export const TeleportImpl = {
|
|||
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
|
||||
doRemove: boolean,
|
||||
) {
|
||||
const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
|
||||
const {
|
||||
shapeFlag,
|
||||
children,
|
||||
anchor,
|
||||
targetStart,
|
||||
targetAnchor,
|
||||
target,
|
||||
props,
|
||||
} = vnode
|
||||
|
||||
if (target) {
|
||||
hostRemove(targetStart!)
|
||||
hostRemove(targetAnchor!)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export function initCustomFormatter() {
|
|||
// custom formatter for Chrome
|
||||
// https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html
|
||||
const formatter = {
|
||||
__vue_custom_formatter: true,
|
||||
header(obj: unknown) {
|
||||
// TODO also format ComponentPublicInstance & ctx.slots/attrs in setup
|
||||
if (!isObject(obj)) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export enum ErrorCodes {
|
|||
FUNCTION_REF,
|
||||
ASYNC_COMPONENT_LOADER,
|
||||
SCHEDULER,
|
||||
COMPONENT_UPDATE,
|
||||
APP_UNMOUNT_CLEANUP,
|
||||
}
|
||||
|
||||
|
|
@ -55,9 +56,8 @@ export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
|
|||
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
||||
[ErrorCodes.FUNCTION_REF]: 'ref function',
|
||||
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
|
||||
[ErrorCodes.SCHEDULER]:
|
||||
'scheduler flush. This is likely a Vue internals bug. ' +
|
||||
'Please open an issue at https://github.com/vuejs/core .',
|
||||
[ErrorCodes.SCHEDULER]: 'scheduler flush',
|
||||
[ErrorCodes.COMPONENT_UPDATE]: 'component update',
|
||||
[ErrorCodes.APP_UNMOUNT_CLEANUP]: 'app unmount cleanup function',
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ export type ErrorTypes = LifecycleHooks | ErrorCodes
|
|||
|
||||
export function callWithErrorHandling(
|
||||
fn: Function,
|
||||
instance: ComponentInternalInstance | null,
|
||||
instance: ComponentInternalInstance | null | undefined,
|
||||
type: ErrorTypes,
|
||||
args?: unknown[],
|
||||
) {
|
||||
|
|
@ -107,11 +107,11 @@ export function callWithAsyncErrorHandling(
|
|||
|
||||
export function handleError(
|
||||
err: unknown,
|
||||
instance: ComponentInternalInstance | null,
|
||||
instance: ComponentInternalInstance | null | undefined,
|
||||
type: ErrorTypes,
|
||||
throwInDev = true,
|
||||
) {
|
||||
const contextVNode = instance && instance.vnode
|
||||
const contextVNode = instance ? instance.vnode : null
|
||||
const { errorHandler, throwUnhandledErrorInProduction } =
|
||||
(instance && instance.appContext.config) || EMPTY_OBJ
|
||||
if (instance) {
|
||||
|
|
|
|||
|
|
@ -65,11 +65,13 @@ export function renderSlot(
|
|||
Fragment,
|
||||
{
|
||||
key:
|
||||
props.key ||
|
||||
// slot content array of a dynamic conditional slot may have a branch
|
||||
// key attached in the `createSlots` helper, respect that
|
||||
(validSlotContent && (validSlotContent as any).key) ||
|
||||
`_${name}`,
|
||||
(props.key ||
|
||||
// slot content array of a dynamic conditional slot may have a branch
|
||||
// key attached in the `createSlots` helper, respect that
|
||||
(validSlotContent && (validSlotContent as any).key) ||
|
||||
`_${name}`) +
|
||||
// #7256 force differentiate fallback content from actual content
|
||||
(!validSlotContent && fallback ? '_fb' : ''),
|
||||
},
|
||||
validSlotContent || (fallback ? fallback() : []),
|
||||
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
|
||||
|
|
|
|||
|
|
@ -29,9 +29,13 @@ export function useModel(
|
|||
|
||||
const camelizedName = camelize(name)
|
||||
const hyphenatedName = hyphenate(name)
|
||||
const modifiers = getModelModifiers(props, name)
|
||||
|
||||
const res = customRef((track, trigger) => {
|
||||
let localValue: any
|
||||
let prevSetValue: any
|
||||
let prevEmittedValue: any
|
||||
|
||||
watchSyncEffect(() => {
|
||||
const propValue = props[name]
|
||||
if (hasChanged(localValue, propValue)) {
|
||||
|
|
@ -39,12 +43,17 @@ export function useModel(
|
|||
trigger()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
get() {
|
||||
track()
|
||||
return options.get ? options.get(localValue) : localValue
|
||||
},
|
||||
|
||||
set(value) {
|
||||
if (!hasChanged(value, localValue)) {
|
||||
return
|
||||
}
|
||||
const rawProps = i.vnode!.props
|
||||
if (
|
||||
!(
|
||||
|
|
@ -56,27 +65,38 @@ export function useModel(
|
|||
(`onUpdate:${name}` in rawProps ||
|
||||
`onUpdate:${camelizedName}` in rawProps ||
|
||||
`onUpdate:${hyphenatedName}` in rawProps)
|
||||
) &&
|
||||
hasChanged(value, localValue)
|
||||
)
|
||||
) {
|
||||
// no v-model, local update
|
||||
localValue = value
|
||||
trigger()
|
||||
}
|
||||
i.emit(`update:${name}`, options.set ? options.set(value) : value)
|
||||
const emittedValue = options.set ? options.set(value) : value
|
||||
i.emit(`update:${name}`, emittedValue)
|
||||
// #10279: if the local value is converted via a setter but the value
|
||||
// emitted to parent was the same, the parent will not trigger any
|
||||
// updates and there will be no prop sync. However the local input state
|
||||
// may be out of sync, so we need to force an update here.
|
||||
if (
|
||||
value !== emittedValue &&
|
||||
value !== prevSetValue &&
|
||||
emittedValue === prevEmittedValue
|
||||
) {
|
||||
trigger()
|
||||
}
|
||||
prevSetValue = value
|
||||
prevEmittedValue = emittedValue
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const modifierKey =
|
||||
name === 'modelValue' ? 'modelModifiers' : `${name}Modifiers`
|
||||
|
||||
// @ts-expect-error
|
||||
res[Symbol.iterator] = () => {
|
||||
let i = 0
|
||||
return {
|
||||
next() {
|
||||
if (i < 2) {
|
||||
return { value: i++ ? props[modifierKey] || {} : res, done: false }
|
||||
return { value: i++ ? modifiers || EMPTY_OBJ : res, done: false }
|
||||
} else {
|
||||
return { done: true }
|
||||
}
|
||||
|
|
@ -86,3 +106,14 @@ export function useModel(
|
|||
|
||||
return res
|
||||
}
|
||||
|
||||
export const getModelModifiers = (
|
||||
props: Record<string, any>,
|
||||
modelName: string,
|
||||
): Record<string, boolean> | undefined => {
|
||||
return modelName === 'modelValue' || modelName === 'model-value'
|
||||
? props.modelModifiers
|
||||
: props[`${modelName}Modifiers`] ||
|
||||
props[`${camelize(modelName)}Modifiers`] ||
|
||||
props[`${hyphenate(modelName)}Modifiers`]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function withMemo(
|
|||
|
||||
// shallow clone
|
||||
ret.memo = memo.slice()
|
||||
ret.memoIndex = index
|
||||
ret.cacheIndex = index
|
||||
|
||||
return (cache[index] = ret)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ type HMRComponent = ComponentOptions | ClassComponent
|
|||
|
||||
export let isHmrUpdating = false
|
||||
|
||||
export const hmrDirtyComponents = new Set<ConcreteComponent>()
|
||||
export const hmrDirtyComponents = new Map<
|
||||
ConcreteComponent,
|
||||
Set<ComponentInternalInstance>
|
||||
>()
|
||||
|
||||
export interface HMRRuntime {
|
||||
createRecord: typeof createRecord
|
||||
|
|
@ -109,18 +112,21 @@ function reload(id: string, newComp: HMRComponent) {
|
|||
// create a snapshot which avoids the set being mutated during updates
|
||||
const instances = [...record.instances]
|
||||
|
||||
for (const instance of instances) {
|
||||
for (let i = 0; i < instances.length; i++) {
|
||||
const instance = instances[i]
|
||||
const oldComp = normalizeClassComponent(instance.type as HMRComponent)
|
||||
|
||||
if (!hmrDirtyComponents.has(oldComp)) {
|
||||
let dirtyInstances = hmrDirtyComponents.get(oldComp)
|
||||
if (!dirtyInstances) {
|
||||
// 1. Update existing comp definition to match new one
|
||||
if (oldComp !== record.initialDef) {
|
||||
updateComponentDef(oldComp, newComp)
|
||||
}
|
||||
// 2. mark definition dirty. This forces the renderer to replace the
|
||||
// component on patch.
|
||||
hmrDirtyComponents.add(oldComp)
|
||||
hmrDirtyComponents.set(oldComp, (dirtyInstances = new Set()))
|
||||
}
|
||||
dirtyInstances.add(instance)
|
||||
|
||||
// 3. invalidate options resolution cache
|
||||
instance.appContext.propsCache.delete(instance.type as any)
|
||||
|
|
@ -130,17 +136,17 @@ function reload(id: string, newComp: HMRComponent) {
|
|||
// 4. actually update
|
||||
if (instance.ceReload) {
|
||||
// custom element
|
||||
hmrDirtyComponents.add(oldComp)
|
||||
dirtyInstances.add(instance)
|
||||
instance.ceReload((newComp as any).styles)
|
||||
hmrDirtyComponents.delete(oldComp)
|
||||
dirtyInstances.delete(instance)
|
||||
} else if (instance.parent) {
|
||||
// 4. Force the parent instance to re-render. This will cause all updated
|
||||
// components to be unmounted and re-mounted. Queue the update so that we
|
||||
// don't end up forcing the same parent to re-render multiple times.
|
||||
queueJob(() => {
|
||||
instance.parent!.update()
|
||||
// #6930 avoid infinite recursion
|
||||
hmrDirtyComponents.delete(oldComp)
|
||||
// #6930, #11248 avoid infinite recursion
|
||||
dirtyInstances.delete(instance)
|
||||
})
|
||||
} else if (instance.appContext.reload) {
|
||||
// root instance mounted via createApp() has a reload method
|
||||
|
|
@ -157,11 +163,7 @@ function reload(id: string, newComp: HMRComponent) {
|
|||
|
||||
// 5. make sure to cleanup dirty hmr components after update
|
||||
queuePostFlushCb(() => {
|
||||
for (const instance of instances) {
|
||||
hmrDirtyComponents.delete(
|
||||
normalizeClassComponent(instance.type as HMRComponent),
|
||||
)
|
||||
}
|
||||
hmrDirtyComponents.clear()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import {
|
|||
} from './components/Suspense'
|
||||
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
|
||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||
import { isReactive } from '@vue/reactivity'
|
||||
|
||||
export type RootHydrateFunction = (
|
||||
vnode: VNode<Node, Element>,
|
||||
|
|
@ -464,15 +465,7 @@ export function createHydrationFunctions(
|
|||
// force hydrate v-bind with .prop modifiers
|
||||
key[0] === '.'
|
||||
) {
|
||||
patchProp(
|
||||
el,
|
||||
key,
|
||||
null,
|
||||
props[key],
|
||||
undefined,
|
||||
undefined,
|
||||
parentComponent,
|
||||
)
|
||||
patchProp(el, key, null, props[key], undefined, parentComponent)
|
||||
}
|
||||
}
|
||||
} else if (props.onClick) {
|
||||
|
|
@ -484,9 +477,13 @@ export function createHydrationFunctions(
|
|||
null,
|
||||
props.onClick,
|
||||
undefined,
|
||||
undefined,
|
||||
parentComponent,
|
||||
)
|
||||
} else if (patchFlag & PatchFlags.STYLE && isReactive(props.style)) {
|
||||
// #11372: object style values are iterated during patch instead of
|
||||
// render/normalization phase, but style patch is skipped during
|
||||
// hydration, so we need to force iterate the object to track deps
|
||||
for (const key in props.style) props.style[key]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -531,7 +528,27 @@ export function createHydrationFunctions(
|
|||
const vnode = optimized
|
||||
? children[i]
|
||||
: (children[i] = normalizeVNode(children[i]))
|
||||
const isText = vnode.type === Text
|
||||
if (node) {
|
||||
if (isText && !optimized) {
|
||||
// #7285 possible consecutive text vnodes from manual render fns or
|
||||
// JSX-compiled fns, but on the client the browser parses only 1 text
|
||||
// node.
|
||||
// look ahead for next possible text vnode
|
||||
let next = children[i + 1]
|
||||
if (next && (next = normalizeVNode(next)).type === Text) {
|
||||
// create an extra TextNode on the client for the next vnode to
|
||||
// adopt
|
||||
insert(
|
||||
createText(
|
||||
(node as Text).data.slice((vnode.children as string).length),
|
||||
),
|
||||
container,
|
||||
nextSibling(node),
|
||||
)
|
||||
;(node as Text).data = vnode.children as string
|
||||
}
|
||||
}
|
||||
node = hydrateNode(
|
||||
node,
|
||||
vnode,
|
||||
|
|
@ -540,7 +557,7 @@ export function createHydrationFunctions(
|
|||
slotScopeIds,
|
||||
optimized,
|
||||
)
|
||||
} else if (vnode.type === Text && !vnode.children) {
|
||||
} else if (isText && !vnode.children) {
|
||||
// #7215 create a TextNode for empty text node
|
||||
// because server rendered HTML won't contain a text node
|
||||
insert((vnode.el = createText('')), container)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ import {
|
|||
type SuspenseImpl,
|
||||
queueEffectWithSuspense,
|
||||
} from './components/Suspense'
|
||||
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
|
||||
import {
|
||||
TeleportEndKey,
|
||||
type TeleportImpl,
|
||||
type TeleportVNode,
|
||||
} from './components/Teleport'
|
||||
import { type KeepAliveContext, isKeepAlive } from './components/KeepAlive'
|
||||
import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr'
|
||||
import { type RootHydrateFunction, createHydrationFunctions } from './hydration'
|
||||
|
|
@ -109,10 +113,7 @@ export interface RendererOptions<
|
|||
prevValue: any,
|
||||
nextValue: any,
|
||||
namespace?: ElementNamespace,
|
||||
prevChildren?: VNode<HostNode, HostElement>[],
|
||||
parentComponent?: ComponentInternalInstance | null,
|
||||
parentSuspense?: SuspenseBoundary | null,
|
||||
unmountChildren?: UnmountChildrenFn,
|
||||
): void
|
||||
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
|
||||
remove(el: HostNode): void
|
||||
|
|
@ -146,7 +147,7 @@ export interface RendererOptions<
|
|||
// functions provided via options, so the internal constraint is really just
|
||||
// a generic object.
|
||||
export interface RendererNode {
|
||||
[key: string]: any
|
||||
[key: string | symbol]: any
|
||||
}
|
||||
|
||||
export interface RendererElement extends RendererNode {}
|
||||
|
|
@ -672,17 +673,7 @@ function baseCreateRenderer(
|
|||
if (props) {
|
||||
for (const key in props) {
|
||||
if (key !== 'value' && !isReservedProp(key)) {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
null,
|
||||
props[key],
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, null, props[key], namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
|
@ -835,6 +826,15 @@ function baseCreateRenderer(
|
|||
dynamicChildren = null
|
||||
}
|
||||
|
||||
// #9135 innerHTML / textContent unset needs to happen before possible
|
||||
// new children mount
|
||||
if (
|
||||
(oldProps.innerHTML && newProps.innerHTML == null) ||
|
||||
(oldProps.textContent && newProps.textContent == null)
|
||||
) {
|
||||
hostSetElementText(el, '')
|
||||
}
|
||||
|
||||
if (dynamicChildren) {
|
||||
patchBlockChildren(
|
||||
n1.dynamicChildren!,
|
||||
|
|
@ -871,15 +871,7 @@ function baseCreateRenderer(
|
|||
// (i.e. at the exact same position in the source template)
|
||||
if (patchFlag & PatchFlags.FULL_PROPS) {
|
||||
// element props contain dynamic keys, full diff needed
|
||||
patchProps(
|
||||
el,
|
||||
n2,
|
||||
oldProps,
|
||||
newProps,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
namespace,
|
||||
)
|
||||
patchProps(el, oldProps, newProps, parentComponent, namespace)
|
||||
} else {
|
||||
// class
|
||||
// this flag is matched when the element has dynamic class bindings.
|
||||
|
|
@ -910,17 +902,7 @@ function baseCreateRenderer(
|
|||
const next = newProps[key]
|
||||
// #1471 force patch value
|
||||
if (next !== prev || key === 'value') {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
prev,
|
||||
next,
|
||||
namespace,
|
||||
n1.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, prev, next, namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -935,15 +917,7 @@ function baseCreateRenderer(
|
|||
}
|
||||
} else if (!optimized && dynamicChildren == null) {
|
||||
// unoptimized, full diff
|
||||
patchProps(
|
||||
el,
|
||||
n2,
|
||||
oldProps,
|
||||
newProps,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
namespace,
|
||||
)
|
||||
patchProps(el, oldProps, newProps, parentComponent, namespace)
|
||||
}
|
||||
|
||||
if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
|
||||
|
|
@ -1000,11 +974,9 @@ function baseCreateRenderer(
|
|||
|
||||
const patchProps = (
|
||||
el: RendererElement,
|
||||
vnode: VNode,
|
||||
oldProps: Data,
|
||||
newProps: Data,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
namespace: ElementNamespace,
|
||||
) => {
|
||||
if (oldProps !== newProps) {
|
||||
|
|
@ -1017,10 +989,7 @@ function baseCreateRenderer(
|
|||
oldProps[key],
|
||||
null,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1032,17 +1001,7 @@ function baseCreateRenderer(
|
|||
const prev = oldProps[key]
|
||||
// defer patching value
|
||||
if (next !== prev && key !== 'value') {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
prev,
|
||||
next,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, prev, next, namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
if ('value' in newProps) {
|
||||
|
|
@ -1235,7 +1194,7 @@ function baseCreateRenderer(
|
|||
if (__DEV__) {
|
||||
startMeasure(instance, `init`)
|
||||
}
|
||||
setupComponent(instance)
|
||||
setupComponent(instance, false, optimized)
|
||||
if (__DEV__) {
|
||||
endMeasure(instance, `init`)
|
||||
}
|
||||
|
|
@ -1586,6 +1545,7 @@ function baseCreateRenderer(
|
|||
|
||||
const update = (instance.update = effect.run.bind(effect))
|
||||
const job: SchedulerJob = (instance.job = effect.runIfDirty.bind(effect))
|
||||
job.i = instance
|
||||
job.id = instance.uid
|
||||
effect.scheduler = () => queueJob(job)
|
||||
|
||||
|
|
@ -1600,7 +1560,6 @@ function baseCreateRenderer(
|
|||
effect.onTrigger = instance.rtg
|
||||
? e => invokeArrayFns(instance.rtg!, e)
|
||||
: void 0
|
||||
job.ownerInstance = instance
|
||||
}
|
||||
|
||||
update()
|
||||
|
|
@ -2110,7 +2069,7 @@ function baseCreateRenderer(
|
|||
shapeFlag,
|
||||
patchFlag,
|
||||
dirs,
|
||||
memoIndex,
|
||||
cacheIndex,
|
||||
} = vnode
|
||||
|
||||
if (patchFlag === PatchFlags.BAIL) {
|
||||
|
|
@ -2123,8 +2082,8 @@ function baseCreateRenderer(
|
|||
}
|
||||
|
||||
// #6593 should clean memo cache when unmount
|
||||
if (memoIndex != null) {
|
||||
parentComponent!.renderCache[memoIndex] = undefined
|
||||
if (cacheIndex != null) {
|
||||
parentComponent!.renderCache[cacheIndex] = undefined
|
||||
}
|
||||
|
||||
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
|
||||
|
|
@ -2165,6 +2124,12 @@ function baseCreateRenderer(
|
|||
)
|
||||
} else if (
|
||||
dynamicChildren &&
|
||||
// #5154
|
||||
// when v-once is used inside a block, setBlockTracking(-1) marks the
|
||||
// parent block with hasOnce: true
|
||||
// so that it doesn't take the fast path during unmount - otherwise
|
||||
// components nested in v-once are never unmounted.
|
||||
!dynamicChildren.hasOnce &&
|
||||
// #1153: fast path should not be taken for non-stable (v-for) fragments
|
||||
(type !== Fragment ||
|
||||
(patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT))
|
||||
|
|
@ -2363,7 +2328,12 @@ function baseCreateRenderer(
|
|||
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
||||
return vnode.suspense!.next()
|
||||
}
|
||||
return hostNextSibling((vnode.anchor || vnode.el)!)
|
||||
const el = hostNextSibling((vnode.anchor || vnode.el)!)
|
||||
// #9071, #9313
|
||||
// teleported content can mess up nextSibling searches during patch so
|
||||
// we need to skip them during nextSibling search
|
||||
const teleportEnd = el && el[TeleportEndKey]
|
||||
return teleportEnd ? hostNextSibling(teleportEnd) : el
|
||||
}
|
||||
|
||||
let isFlushing = false
|
||||
|
|
|
|||
|
|
@ -34,9 +34,8 @@ export interface SchedulerJob extends Function {
|
|||
/**
|
||||
* Attached by renderer.ts when setting up a component's render effect
|
||||
* Used to obtain component information when reporting max recursive updates.
|
||||
* dev only.
|
||||
*/
|
||||
ownerInstance?: ComponentInternalInstance
|
||||
i?: ComponentInternalInstance
|
||||
}
|
||||
|
||||
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
||||
|
|
@ -250,7 +249,11 @@ function flushJobs(seen?: CountMap) {
|
|||
if (__DEV__ && check(job)) {
|
||||
continue
|
||||
}
|
||||
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
|
||||
callWithErrorHandling(
|
||||
job,
|
||||
job.i,
|
||||
job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
|
||||
)
|
||||
job.flags! &= ~SchedulerJobFlags.QUEUED
|
||||
}
|
||||
}
|
||||
|
|
@ -276,7 +279,7 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {
|
|||
} else {
|
||||
const count = seen.get(fn)!
|
||||
if (count > RECURSION_LIMIT) {
|
||||
const instance = fn.ownerInstance
|
||||
const instance = fn.i
|
||||
const componentName = instance && getComponentName(instance.type)
|
||||
handleError(
|
||||
`Maximum recursive updates exceeded${
|
||||
|
|
|
|||
|
|
@ -92,10 +92,22 @@ export type VNodeRef =
|
|||
) => void)
|
||||
|
||||
export type VNodeNormalizedRefAtom = {
|
||||
/**
|
||||
* component instance
|
||||
*/
|
||||
i: ComponentInternalInstance
|
||||
/**
|
||||
* Actual ref
|
||||
*/
|
||||
r: VNodeRef
|
||||
k?: string // setup ref key
|
||||
f?: boolean // refInFor marker
|
||||
/**
|
||||
* setup ref key
|
||||
*/
|
||||
k?: string
|
||||
/**
|
||||
* refInFor marker
|
||||
*/
|
||||
f?: boolean
|
||||
}
|
||||
|
||||
export type VNodeNormalizedRef =
|
||||
|
|
@ -186,6 +198,7 @@ export interface VNode<
|
|||
el: HostNode | null
|
||||
anchor: HostNode | null // fragment anchor
|
||||
target: HostElement | null // teleport target
|
||||
targetStart: HostNode | null // teleport target start anchor
|
||||
targetAnchor: HostNode | null // teleport target anchor
|
||||
/**
|
||||
* number of elements contained in a static vnode
|
||||
|
|
@ -214,7 +227,7 @@ export interface VNode<
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
dynamicChildren: VNode[] | null
|
||||
dynamicChildren: (VNode[] & { hasOnce?: boolean }) | null
|
||||
|
||||
// application root node only
|
||||
appContext: AppContext | null
|
||||
|
|
@ -231,7 +244,7 @@ export interface VNode<
|
|||
/**
|
||||
* @internal index for cleaning v-memo cache
|
||||
*/
|
||||
memoIndex?: number
|
||||
cacheIndex?: number
|
||||
/**
|
||||
* @internal __COMPAT__ only
|
||||
*/
|
||||
|
|
@ -247,8 +260,8 @@ export interface VNode<
|
|||
// can divide a template into nested blocks, and within each block the node
|
||||
// structure would be stable. This allows us to skip most children diffing
|
||||
// and only worry about the dynamic nodes (indicated by patch flags).
|
||||
export const blockStack: (VNode[] | null)[] = []
|
||||
export let currentBlock: VNode[] | null = null
|
||||
export const blockStack: VNode['dynamicChildren'][] = []
|
||||
export let currentBlock: VNode['dynamicChildren'] = null
|
||||
|
||||
/**
|
||||
* Open a block.
|
||||
|
|
@ -299,6 +312,11 @@ export let isBlockTreeEnabled = 1
|
|||
*/
|
||||
export function setBlockTracking(value: number) {
|
||||
isBlockTreeEnabled += value
|
||||
if (value < 0 && currentBlock) {
|
||||
// mark current block so it doesn't take fast path and skip possible
|
||||
// nested components duriung unmount
|
||||
currentBlock.hasOnce = true
|
||||
}
|
||||
}
|
||||
|
||||
function setupBlock(vnode: VNode) {
|
||||
|
|
@ -370,17 +388,16 @@ export function isVNode(value: any): value is VNode {
|
|||
}
|
||||
|
||||
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
|
||||
if (
|
||||
__DEV__ &&
|
||||
n2.shapeFlag & ShapeFlags.COMPONENT &&
|
||||
hmrDirtyComponents.has(n2.type as ConcreteComponent)
|
||||
) {
|
||||
// #7042, ensure the vnode being unmounted during HMR
|
||||
// bitwise operations to remove keep alive flags
|
||||
n1.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
||||
n2.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE
|
||||
// HMR only: if the component has been hot-updated, force a reload.
|
||||
return false
|
||||
if (__DEV__ && n2.shapeFlag & ShapeFlags.COMPONENT && n1.component) {
|
||||
const dirtyInstances = hmrDirtyComponents.get(n2.type as ConcreteComponent)
|
||||
if (dirtyInstances && dirtyInstances.has(n1.component)) {
|
||||
// #7042, ensure the vnode being unmounted during HMR
|
||||
// bitwise operations to remove keep alive flags
|
||||
n1.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
||||
n2.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE
|
||||
// HMR only: if the component has been hot-updated, force a reload.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return n1.type === n2.type && n1.key === n2.key
|
||||
}
|
||||
|
|
@ -461,6 +478,7 @@ function createBaseVNode(
|
|||
el: null,
|
||||
anchor: null,
|
||||
target: null,
|
||||
targetStart: null,
|
||||
targetAnchor: null,
|
||||
staticCount: 0,
|
||||
shapeFlag,
|
||||
|
|
@ -661,6 +679,7 @@ export function cloneVNode<T, U>(
|
|||
? (children as VNode[]).map(deepCloneVNode)
|
||||
: children,
|
||||
target: vnode.target,
|
||||
targetStart: vnode.targetStart,
|
||||
targetAnchor: vnode.targetAnchor,
|
||||
staticCount: vnode.staticCount,
|
||||
shapeFlag: vnode.shapeFlag,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ export function popWarningContext() {
|
|||
stack.pop()
|
||||
}
|
||||
|
||||
let isWarning = false
|
||||
|
||||
export function warn(msg: string, ...args: any[]) {
|
||||
if (isWarning) return
|
||||
isWarning = true
|
||||
|
||||
// avoid props formatting or warn handler tracking deps that might be mutated
|
||||
// during patch, leading to infinite recursion.
|
||||
pauseTracking()
|
||||
|
|
@ -70,6 +75,7 @@ export function warn(msg: string, ...args: any[]) {
|
|||
}
|
||||
|
||||
resetTracking()
|
||||
isWarning = false
|
||||
}
|
||||
|
||||
export function getComponentTrace(): ComponentTraceStack {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { patchProp } from '../src/patchProp'
|
||||
import { h, render } from '../src'
|
||||
import { h, nextTick, ref, render } from '../src'
|
||||
|
||||
describe('runtime-dom: props patching', () => {
|
||||
test('basic', () => {
|
||||
|
|
@ -133,6 +133,25 @@ describe('runtime-dom: props patching', () => {
|
|||
expect(fn).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('patch innerHTML porp', async () => {
|
||||
const root = document.createElement('div')
|
||||
const state = ref(false)
|
||||
const Comp = {
|
||||
render: () => {
|
||||
if (state.value) {
|
||||
return h('div', [h('del', null, 'baz')])
|
||||
} else {
|
||||
return h('div', { innerHTML: 'baz' })
|
||||
}
|
||||
},
|
||||
}
|
||||
render(h(Comp), root)
|
||||
expect(root.innerHTML).toBe(`<div>baz</div>`)
|
||||
state.value = true
|
||||
await nextTick()
|
||||
expect(root.innerHTML).toBe(`<div><del>baz</del></div>`)
|
||||
})
|
||||
|
||||
test('textContent unmount prev children', () => {
|
||||
const fn = vi.fn()
|
||||
const comp = {
|
||||
|
|
|
|||
|
|
@ -10,19 +10,13 @@ export function patchDOMProp(
|
|||
el: any,
|
||||
key: string,
|
||||
value: any,
|
||||
// the following args are passed only due to potential innerHTML/textContent
|
||||
// overriding existing VNodes, in which case the old tree must be properly
|
||||
// unmounted.
|
||||
prevChildren: any,
|
||||
parentComponent: any,
|
||||
parentSuspense: any,
|
||||
unmountChildren: any,
|
||||
) {
|
||||
if (key === 'innerHTML' || key === 'textContent') {
|
||||
if (prevChildren) {
|
||||
unmountChildren(prevChildren, parentComponent, parentSuspense)
|
||||
}
|
||||
el[key] = value == null ? '' : value
|
||||
// null value case is handled in renderer patchElement before patching
|
||||
// children
|
||||
if (value === null) return
|
||||
el[key] = value
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||
prevValue,
|
||||
nextValue,
|
||||
namespace,
|
||||
prevChildren,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
) => {
|
||||
const isSVG = namespace === 'svg'
|
||||
if (key === 'class') {
|
||||
|
|
@ -43,15 +40,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||
? ((key = key.slice(1)), false)
|
||||
: shouldSetAsProp(el, key, nextValue, isSVG)
|
||||
) {
|
||||
patchDOMProp(
|
||||
el,
|
||||
key,
|
||||
nextValue,
|
||||
prevChildren,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
patchDOMProp(el, key, nextValue, parentComponent)
|
||||
// #6007 also set form state as attributes so they work with
|
||||
// <input type="reset"> or libs / extensions that expect attributes
|
||||
// #11163 custom elements may use value as an prop and set it as object
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import { bench, describe } from 'vitest'
|
||||
|
||||
import { createBuffer } from '../src/render'
|
||||
|
||||
describe('createBuffer', () => {
|
||||
let stringBuffer = createBuffer()
|
||||
|
||||
bench(
|
||||
'string only',
|
||||
() => {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
stringBuffer.push('hello')
|
||||
}
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
stringBuffer = createBuffer()
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
let stringNestedBuffer = createBuffer()
|
||||
|
||||
bench(
|
||||
'string with nested',
|
||||
() => {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
if (i % 3 === 0) {
|
||||
stringNestedBuffer.push('hello')
|
||||
} else {
|
||||
const buffer = createBuffer()
|
||||
buffer.push('hello')
|
||||
stringNestedBuffer.push(buffer.getBuffer())
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
stringNestedBuffer = createBuffer()
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
bench(
|
||||
'string with nested async',
|
||||
() => {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
if (i % 3 === 0) {
|
||||
const buffer = createBuffer()
|
||||
buffer.push('hello')
|
||||
stringNestedBuffer.push(Promise.resolve(buffer.getBuffer()))
|
||||
} else {
|
||||
const buffer = createBuffer()
|
||||
buffer.push('hello')
|
||||
stringNestedBuffer.push(buffer.getBuffer())
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
stringNestedBuffer = createBuffer()
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import { bench, describe } from 'vitest'
|
||||
|
||||
import { type SSRBuffer, createBuffer } from '../src/render'
|
||||
import { unrollBuffer } from '../src/renderToString'
|
||||
|
||||
function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
|
||||
const buffer = createBuffer()
|
||||
|
||||
function addItems(buf: ReturnType<typeof createBuffer>, level: number) {
|
||||
for (let i = 1; i <= levels * itemsPerLevel; i++) {
|
||||
buf.push(`sync${level}.${i}`)
|
||||
}
|
||||
if (level < levels) {
|
||||
const subBuffer = createBuffer()
|
||||
addItems(subBuffer, level + 1)
|
||||
buf.push(subBuffer.getBuffer())
|
||||
}
|
||||
}
|
||||
|
||||
addItems(buffer, 1)
|
||||
return buffer.getBuffer()
|
||||
}
|
||||
|
||||
function createMixedBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
|
||||
const buffer = createBuffer()
|
||||
|
||||
function addItems(buf: ReturnType<typeof createBuffer>, level: number) {
|
||||
for (let i = 1; i <= levels * itemsPerLevel; i++) {
|
||||
if (i % 3 === 0) {
|
||||
// @ts-expect-error testing...
|
||||
buf.push(Promise.resolve(`async${level}.${i}`))
|
||||
} else {
|
||||
buf.push(`sync${level}.${i}`)
|
||||
}
|
||||
}
|
||||
if (level < levels) {
|
||||
const subBuffer = createBuffer()
|
||||
addItems(subBuffer, level + 1)
|
||||
buf.push(subBuffer.getBuffer())
|
||||
}
|
||||
}
|
||||
|
||||
addItems(buffer, 1)
|
||||
return buffer.getBuffer()
|
||||
}
|
||||
|
||||
describe('unrollBuffer', () => {
|
||||
let syncBuffer = createBuffer().getBuffer()
|
||||
let mixedBuffer = createBuffer().getBuffer()
|
||||
|
||||
bench(
|
||||
'sync',
|
||||
() => {
|
||||
return unrollBuffer(syncBuffer) as any
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
syncBuffer = createSyncBuffer(5, 3)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
bench(
|
||||
'mixed',
|
||||
() => {
|
||||
return unrollBuffer(mixedBuffer) as any
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
mixedBuffer = createMixedBuffer(5, 3)
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
|
@ -73,9 +73,9 @@ export function createBuffer() {
|
|||
const isStringItem = isString(item)
|
||||
if (appendable && isStringItem) {
|
||||
buffer[buffer.length - 1] += item as string
|
||||
} else {
|
||||
buffer.push(item)
|
||||
return
|
||||
}
|
||||
buffer.push(item)
|
||||
appendable = isStringItem
|
||||
if (isPromise(item) || (isArray(item) && item.hasAsync)) {
|
||||
// promise, or child buffer with async, mark as async.
|
||||
|
|
|
|||
|
|
@ -11,26 +11,46 @@ import { type SSRBuffer, type SSRContext, renderComponentVNode } from './render'
|
|||
|
||||
const { isVNode } = ssrUtils
|
||||
|
||||
async function unrollBuffer(buffer: SSRBuffer): Promise<string> {
|
||||
if (buffer.hasAsync) {
|
||||
let ret = ''
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
let item = buffer[i]
|
||||
if (isPromise(item)) {
|
||||
item = await item
|
||||
}
|
||||
if (isString(item)) {
|
||||
ret += item
|
||||
} else {
|
||||
ret += await unrollBuffer(item)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
} else {
|
||||
// sync buffer can be more efficiently unrolled without unnecessary await
|
||||
// ticks
|
||||
return unrollBufferSync(buffer)
|
||||
function nestedUnrollBuffer(
|
||||
buffer: SSRBuffer,
|
||||
parentRet: string,
|
||||
startIndex: number,
|
||||
): Promise<string> | string {
|
||||
if (!buffer.hasAsync) {
|
||||
return parentRet + unrollBufferSync(buffer)
|
||||
}
|
||||
|
||||
let ret = parentRet
|
||||
for (let i = startIndex; i < buffer.length; i += 1) {
|
||||
const item = buffer[i]
|
||||
if (isString(item)) {
|
||||
ret += item
|
||||
continue
|
||||
}
|
||||
|
||||
if (isPromise(item)) {
|
||||
return item.then(nestedItem => {
|
||||
buffer[i] = nestedItem
|
||||
return nestedUnrollBuffer(buffer, ret, i)
|
||||
})
|
||||
}
|
||||
|
||||
const result = nestedUnrollBuffer(item, ret, 0)
|
||||
if (isPromise(result)) {
|
||||
return result.then(nestedItem => {
|
||||
buffer[i] = nestedItem
|
||||
return nestedUnrollBuffer(buffer, '', i)
|
||||
})
|
||||
}
|
||||
|
||||
ret = result
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
export function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {
|
||||
return nestedUnrollBuffer(buffer, '', 0)
|
||||
}
|
||||
|
||||
function unrollBufferSync(buffer: SSRBuffer): string {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"vite": "^5.3.1"
|
||||
"vite": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.2.1",
|
||||
"@vue/repl": "^4.3.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
|
|
|
|||
|
|
@ -129,7 +129,13 @@ onMounted(() => {
|
|||
:preview-options="{
|
||||
customCode: {
|
||||
importCode: `import { initCustomFormatter } from 'vue'`,
|
||||
useCode: `initCustomFormatter()`,
|
||||
useCode: `if (window.devtoolsFormatters) {
|
||||
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
||||
window.devtoolsFormatters.splice(index, 1)
|
||||
initCustomFormatter()
|
||||
} else {
|
||||
initCustomFormatter()
|
||||
}`,
|
||||
},
|
||||
}"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"vite": "^5.3.1"
|
||||
"vite": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ import fs from 'node:fs'
|
|||
import path from 'node:path'
|
||||
import { type Plugin, defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { execaSync } from 'execa'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
|
||||
const commit = execaSync('git', ['rev-parse', '--short=7', 'HEAD']).stdout
|
||||
const commit = spawnSync('git', ['rev-parse', '--short=7', 'HEAD'])
|
||||
.stdout.toString()
|
||||
.trim()
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
|
|
|
|||
|
|
@ -11,12 +11,28 @@ describe('toDisplayString', () => {
|
|||
})
|
||||
|
||||
test('primitive values', () => {
|
||||
expect(toDisplayString(0)).toBe('0')
|
||||
expect(toDisplayString(1)).toBe('1')
|
||||
expect(toDisplayString(NaN)).toBe('NaN')
|
||||
expect(toDisplayString(true)).toBe('true')
|
||||
expect(toDisplayString(false)).toBe('false')
|
||||
expect(toDisplayString('hello')).toBe('hello')
|
||||
})
|
||||
|
||||
test('primitive values in refs', () => {
|
||||
expect(toDisplayString(ref(0))).toBe('0')
|
||||
expect(toDisplayString(ref(1))).toBe('1')
|
||||
expect(toDisplayString(ref(NaN))).toBe('NaN')
|
||||
expect(toDisplayString(ref(true))).toBe('true')
|
||||
expect(toDisplayString(ref(false))).toBe('false')
|
||||
expect(toDisplayString(ref('hello'))).toBe('hello')
|
||||
})
|
||||
|
||||
test('symbol values', () => {
|
||||
expect(toDisplayString(Symbol('hello'))).toBe('Symbol(hello)')
|
||||
expect(toDisplayString(ref(Symbol('hello')))).toBe('Symbol(hello)')
|
||||
})
|
||||
|
||||
test('Object and Arrays', () => {
|
||||
const obj = { foo: 123 }
|
||||
expect(toDisplayString(obj)).toBe(JSON.stringify(obj, null, 2))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// enums are compiled away via custom transform so no real dependency here
|
||||
import { ReactiveFlags } from '@vue/reactivity'
|
||||
import {
|
||||
isArray,
|
||||
|
|
@ -11,6 +12,11 @@ import {
|
|||
objectToString,
|
||||
} from './general'
|
||||
|
||||
// can't use isRef here since @vue/shared has no deps
|
||||
const isRef = (val: any): val is { value: unknown } => {
|
||||
return !!(val && val[ReactiveFlags.IS_REF] === true)
|
||||
}
|
||||
|
||||
/**
|
||||
* For converting {{ interpolation }} values to displayed strings.
|
||||
* @private
|
||||
|
|
@ -23,13 +29,14 @@ export const toDisplayString = (val: unknown): string => {
|
|||
: isArray(val) ||
|
||||
(isObject(val) &&
|
||||
(val.toString === objectToString || !isFunction(val.toString)))
|
||||
? JSON.stringify(val, replacer, 2)
|
||||
? isRef(val)
|
||||
? toDisplayString(val.value)
|
||||
: JSON.stringify(val, replacer, 2)
|
||||
: String(val)
|
||||
}
|
||||
|
||||
const replacer = (_key: string, val: any): any => {
|
||||
// can't use isRef here since @vue/shared has no deps
|
||||
if (val && val[ReactiveFlags.IS_REF]) {
|
||||
const replacer = (_key: string, val: unknown): any => {
|
||||
if (isRef(val)) {
|
||||
return replacer(_key, val.value)
|
||||
} else if (isMap(val)) {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"enableNonBrowserBranches": true
|
||||
},
|
||||
"dependencies": {
|
||||
"monaco-editor": "^0.49.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/vue-compat#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.0"
|
||||
"@babel/parser": "catalog:",
|
||||
"estree-walker": "catalog:",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "workspace:*"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
- Contains hard-coded prod/dev branches, and the prod build is pre-minified. Use the `*.prod.js` files for production.
|
||||
|
||||
- **`vue(.runtime).esm-browser(.prod).js`**:
|
||||
- For usage via native ES modules imports (in browser via `<script type="module">`.
|
||||
- For usage via native ES modules imports (in browser via `<script type="module">`).
|
||||
- Shares the same runtime compilation, dependency inlining and hard-coded prod/dev behavior with the global build.
|
||||
|
||||
### With a Bundler
|
||||
|
|
|
|||
2032
pnpm-lock.yaml
2032
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -1,2 +1,10 @@
|
|||
packages:
|
||||
- 'packages/*'
|
||||
|
||||
catalog:
|
||||
'@babel/parser': ^7.24.7
|
||||
'@babel/types': ^7.24.7
|
||||
'estree-walker': ^2.0.2
|
||||
'magic-string': ^0.30.10
|
||||
'source-map-js': ^1.2.0
|
||||
'vite': ^5.3.3
|
||||
|
|
|
|||
|
|
@ -374,7 +374,11 @@ function createMinifiedConfig(/** @type {PackageFormat} */ format) {
|
|||
) {
|
||||
const { code, map } = await minifySwc(contents, {
|
||||
module: format === 'es',
|
||||
compress: true,
|
||||
compress: {
|
||||
ecma: 2016,
|
||||
pure_getters: true,
|
||||
},
|
||||
safari10: true,
|
||||
mangle: true,
|
||||
sourceMap: !!sourcemap,
|
||||
inlineSourcesContent: !sourcemapExcludeSources,
|
||||
|
|
|
|||
|
|
@ -17,31 +17,67 @@ nr build core --formats cjs
|
|||
*/
|
||||
|
||||
import fs from 'node:fs/promises'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { parseArgs } from 'node:util'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import minimist from 'minimist'
|
||||
import { brotliCompressSync, gzipSync } from 'node:zlib'
|
||||
import pico from 'picocolors'
|
||||
import { execa, execaSync } from 'execa'
|
||||
import { cpus } from 'node:os'
|
||||
import { createRequire } from 'node:module'
|
||||
import { targets as allTargets, fuzzyMatchTarget } from './utils.js'
|
||||
import { targets as allTargets, exec, fuzzyMatchTarget } from './utils.js'
|
||||
import { scanEnums } from './inline-enums.js'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const args = minimist(process.argv.slice(2))
|
||||
const targets = args._
|
||||
const formats = args.formats || args.f
|
||||
const devOnly = args.devOnly || args.d
|
||||
const prodOnly = !devOnly && (args.prodOnly || args.p)
|
||||
const buildTypes = args.withTypes || args.t
|
||||
const sourceMap = args.sourcemap || args.s
|
||||
const isRelease = args.release
|
||||
/** @type {boolean | undefined} */
|
||||
const buildAllMatching = args.all || args.a
|
||||
const writeSize = args.size
|
||||
const commit = execaSync('git', ['rev-parse', '--short=7', 'HEAD']).stdout
|
||||
const commit = spawnSync('git', ['rev-parse', '--short=7', 'HEAD'])
|
||||
.stdout.toString()
|
||||
.trim()
|
||||
|
||||
const { values, positionals: targets } = parseArgs({
|
||||
allowPositionals: true,
|
||||
options: {
|
||||
formats: {
|
||||
type: 'string',
|
||||
short: 'f',
|
||||
},
|
||||
devOnly: {
|
||||
type: 'boolean',
|
||||
short: 'd',
|
||||
},
|
||||
prodOnly: {
|
||||
type: 'boolean',
|
||||
short: 'p',
|
||||
},
|
||||
withTypes: {
|
||||
type: 'boolean',
|
||||
short: 't',
|
||||
},
|
||||
sourceMap: {
|
||||
type: 'boolean',
|
||||
short: 's',
|
||||
},
|
||||
release: {
|
||||
type: 'boolean',
|
||||
},
|
||||
all: {
|
||||
type: 'boolean',
|
||||
short: 'a',
|
||||
},
|
||||
size: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
formats,
|
||||
all: buildAllMatching,
|
||||
devOnly,
|
||||
prodOnly,
|
||||
withTypes: buildTypes,
|
||||
sourceMap,
|
||||
release: isRelease,
|
||||
size: writeSize,
|
||||
} = values
|
||||
|
||||
const sizeDir = path.resolve('temp/size')
|
||||
|
||||
|
|
@ -57,7 +93,7 @@ async function run() {
|
|||
await buildAll(resolvedTargets)
|
||||
await checkAllSizes(resolvedTargets)
|
||||
if (buildTypes) {
|
||||
await execa(
|
||||
await exec(
|
||||
'pnpm',
|
||||
[
|
||||
'run',
|
||||
|
|
@ -114,6 +150,7 @@ async function runParallel(maxConcurrency, source, iteratorFn) {
|
|||
}
|
||||
return Promise.all(ret)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the target.
|
||||
* @param {string} target - The target to build.
|
||||
|
|
@ -121,7 +158,7 @@ async function runParallel(maxConcurrency, source, iteratorFn) {
|
|||
*/
|
||||
async function build(target) {
|
||||
const pkgDir = path.resolve(`packages/${target}`)
|
||||
const pkg = require(`${pkgDir}/package.json`)
|
||||
const pkg = JSON.parse(readFileSync(`${pkgDir}/package.json`, 'utf-8'))
|
||||
|
||||
// if this is a full build (no specific targets), ignore private packages
|
||||
if ((isRelease || !targets.length) && pkg.private) {
|
||||
|
|
@ -136,7 +173,8 @@ async function build(target) {
|
|||
const env =
|
||||
(pkg.buildOptions && pkg.buildOptions.env) ||
|
||||
(devOnly ? 'development' : 'production')
|
||||
await execa(
|
||||
|
||||
await exec(
|
||||
'rollup',
|
||||
[
|
||||
'-c',
|
||||
|
|
|
|||
|
|
@ -8,16 +8,38 @@ import esbuild from 'esbuild'
|
|||
import { dirname, relative, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { createRequire } from 'node:module'
|
||||
import minimist from 'minimist'
|
||||
import { parseArgs } from 'node:util'
|
||||
import { polyfillNode } from 'esbuild-plugin-polyfill-node'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const args = minimist(process.argv.slice(2))
|
||||
const targets = args._.length ? args._ : ['vue']
|
||||
const format = args.f || 'global'
|
||||
const prod = args.p || false
|
||||
const inlineDeps = args.i || args.inline
|
||||
|
||||
const {
|
||||
values: { format: rawFormat, prod, inline: inlineDeps },
|
||||
positionals,
|
||||
} = parseArgs({
|
||||
allowPositionals: true,
|
||||
options: {
|
||||
format: {
|
||||
type: 'string',
|
||||
short: 'f',
|
||||
default: 'global',
|
||||
},
|
||||
prod: {
|
||||
type: 'boolean',
|
||||
short: 'p',
|
||||
default: false,
|
||||
},
|
||||
inline: {
|
||||
type: 'boolean',
|
||||
short: 'i',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const format = rawFormat || 'global'
|
||||
const targets = positionals.length ? positionals : ['vue']
|
||||
|
||||
// resolve output
|
||||
const outputFormat = format.startsWith('global')
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import { parse } from '@babel/parser'
|
||||
import { execaSync } from 'execa'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import MagicString from 'magic-string'
|
||||
|
||||
/**
|
||||
|
|
@ -49,8 +49,16 @@ export function scanEnums() {
|
|||
const defines = Object.create(null)
|
||||
|
||||
// 1. grep for files with exported enum
|
||||
const { stdout } = execaSync('git', ['grep', `export enum`])
|
||||
const files = [...new Set(stdout.split('\n').map(line => line.split(':')[0]))]
|
||||
const { stdout } = spawnSync('git', ['grep', `export enum`])
|
||||
const files = [
|
||||
...new Set(
|
||||
stdout
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => line.split(':')[0]),
|
||||
),
|
||||
]
|
||||
|
||||
// 2. parse matched files to collect enum info
|
||||
for (const relativeFile of files) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
// @ts-check
|
||||
import minimist from 'minimist'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import pico from 'picocolors'
|
||||
import semver from 'semver'
|
||||
import enquirer from 'enquirer'
|
||||
import { execa } from 'execa'
|
||||
import { createRequire } from 'node:module'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { exec } from './utils.js'
|
||||
import { parseArgs } from 'node:util'
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
|
|
@ -23,12 +23,34 @@ let versionUpdated = false
|
|||
const { prompt } = enquirer
|
||||
const currentVersion = createRequire(import.meta.url)('../package.json').version
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
alias: {
|
||||
skipBuild: 'skip-build',
|
||||
skipTests: 'skip-tests',
|
||||
skipGit: 'skip-git',
|
||||
skipPrompts: 'skip-prompts',
|
||||
|
||||
const { values: args, positionals } = parseArgs({
|
||||
allowPositionals: true,
|
||||
options: {
|
||||
preid: {
|
||||
type: 'string',
|
||||
},
|
||||
dry: {
|
||||
type: 'boolean',
|
||||
},
|
||||
tag: {
|
||||
type: 'string',
|
||||
},
|
||||
canary: {
|
||||
type: 'boolean',
|
||||
},
|
||||
skipBuild: {
|
||||
type: 'boolean',
|
||||
},
|
||||
skipTests: {
|
||||
type: 'boolean',
|
||||
},
|
||||
skipGit: {
|
||||
type: 'boolean',
|
||||
},
|
||||
skipPrompts: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -94,16 +116,16 @@ const versionIncrements = [
|
|||
]
|
||||
|
||||
const inc = (/** @type {import('semver').ReleaseType} */ i) =>
|
||||
semver.inc(currentVersion, i, preId)
|
||||
semver.inc(currentVersion, i, typeof preId === 'string' ? preId : undefined)
|
||||
const run = async (
|
||||
/** @type {string} */ bin,
|
||||
/** @type {ReadonlyArray<string>} */ args,
|
||||
/** @type {import('execa').Options} */ opts = {},
|
||||
) => execa(bin, args, { stdio: 'inherit', ...opts })
|
||||
/** @type {import('node:child_process').SpawnOptions} */ opts = {},
|
||||
) => exec(bin, args, { stdio: 'inherit', ...opts })
|
||||
const dryRun = async (
|
||||
/** @type {string} */ bin,
|
||||
/** @type {ReadonlyArray<string>} */ args,
|
||||
/** @type {import('execa').Options} */ opts = {},
|
||||
/** @type {import('node:child_process').SpawnOptions} */ opts = {},
|
||||
) => console.log(pico.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
|
||||
const runIfNotDry = isDryRun ? dryRun : run
|
||||
const getPkgRoot = (/** @type {string} */ pkg) =>
|
||||
|
|
@ -117,7 +139,7 @@ async function main() {
|
|||
console.log(`${pico.green(`✓`)} commit is up-to-date with remote.\n`)
|
||||
}
|
||||
|
||||
let targetVersion = args._[0]
|
||||
let targetVersion = positionals[0]
|
||||
|
||||
if (isCanary) {
|
||||
// The canary version string format is `3.yyyyMMdd.0` (or `3.yyyyMMdd.0-minor.0` for minor)
|
||||
|
|
@ -392,11 +414,11 @@ async function isInSyncWithRemote() {
|
|||
}
|
||||
|
||||
async function getSha() {
|
||||
return (await execa('git', ['rev-parse', 'HEAD'])).stdout
|
||||
return (await exec('git', ['rev-parse', 'HEAD'])).stdout
|
||||
}
|
||||
|
||||
async function getBranch() {
|
||||
return (await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout
|
||||
return (await exec('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
import type { MockInstance } from 'vitest'
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> extends CustomMatchers<T> {}
|
||||
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
||||
}
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
toHaveBeenWarned(): R
|
||||
toHaveBeenWarnedLast(): R
|
||||
toHaveBeenWarnedTimes(n: number): R
|
||||
}
|
||||
|
||||
vi.stubGlobal('MathMLElement', class MathMLElement {})
|
||||
|
||||
expect.extend({
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { mkdir, writeFile } from 'node:fs/promises'
|
|||
import path from 'node:path'
|
||||
import { rollup } from 'rollup'
|
||||
import nodeResolve from '@rollup/plugin-node-resolve'
|
||||
import { minify } from 'terser'
|
||||
import { minify } from '@swc/core'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
import { brotliCompressSync, gzipSync } from 'node:zlib'
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue