mirror of https://github.com/vuejs/core.git
Merge branch 'main' into fix/7966
This commit is contained in:
commit
a94eb54365
|
@ -1,4 +0,0 @@
|
|||
node_modules
|
||||
dist
|
||||
temp
|
||||
coverage
|
140
.eslintrc.cjs
140
.eslintrc.cjs
|
@ -1,140 +0,0 @@
|
|||
const { builtinModules } = require('node:module')
|
||||
const DOMGlobals = ['window', 'document']
|
||||
const NodeGlobals = ['module', 'require']
|
||||
|
||||
const banConstEnum = {
|
||||
selector: 'TSEnumDeclaration[const=true]',
|
||||
message:
|
||||
'Please use non-const enums. This project automatically inlines enums.',
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('eslint-define-config').ESLintConfig}
|
||||
*/
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['jest', 'import', '@typescript-eslint'],
|
||||
rules: {
|
||||
'no-debugger': 'error',
|
||||
'no-console': ['error', { allow: ['warn', 'error', 'info'] }],
|
||||
// most of the codebase are expected to be env agnostic
|
||||
'no-restricted-globals': ['error', ...DOMGlobals, ...NodeGlobals],
|
||||
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
banConstEnum,
|
||||
{
|
||||
selector: 'ObjectPattern > RestElement',
|
||||
message:
|
||||
'Our output target is ES2016, and object rest spread results in ' +
|
||||
'verbose helpers and should be avoided.',
|
||||
},
|
||||
{
|
||||
selector: 'ObjectExpression > SpreadElement',
|
||||
message:
|
||||
'esbuild transpiles object spread into very verbose inline helpers.\n' +
|
||||
'Please use the `extend` helper from @vue/shared instead.',
|
||||
},
|
||||
{
|
||||
selector: 'AwaitExpression',
|
||||
message:
|
||||
'Our output target is ES2016, so async/await syntax should be avoided.',
|
||||
},
|
||||
],
|
||||
'sort-imports': ['error', { ignoreDeclarationSort: true }],
|
||||
|
||||
'import/no-nodejs-modules': [
|
||||
'error',
|
||||
{ allow: builtinModules.map(mod => `node:${mod}`) },
|
||||
],
|
||||
// This rule enforces the preference for using '@ts-expect-error' comments in TypeScript
|
||||
// code to indicate intentional type errors, improving code clarity and maintainability.
|
||||
'@typescript-eslint/prefer-ts-expect-error': 'error',
|
||||
// Enforce the use of 'import type' for importing types
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
fixStyle: 'inline-type-imports',
|
||||
disallowTypeAnnotations: false,
|
||||
},
|
||||
],
|
||||
// Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers
|
||||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
||||
},
|
||||
overrides: [
|
||||
// tests, no restrictions (runs in Node / jest with jsdom)
|
||||
{
|
||||
files: ['**/__tests__/**', 'packages/dts-test/**'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'jest/no-disabled-tests': 'error',
|
||||
'jest/no-focused-tests': 'error',
|
||||
},
|
||||
},
|
||||
// shared, may be used in any env
|
||||
{
|
||||
files: ['packages/shared/**', '.eslintrc.cjs'],
|
||||
rules: {
|
||||
'no-restricted-globals': 'off',
|
||||
},
|
||||
},
|
||||
// Packages targeting DOM
|
||||
{
|
||||
files: ['packages/{vue,vue-compat,runtime-dom}/**'],
|
||||
rules: {
|
||||
'no-restricted-globals': ['error', ...NodeGlobals],
|
||||
},
|
||||
},
|
||||
// Packages targeting Node
|
||||
{
|
||||
files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'],
|
||||
rules: {
|
||||
'no-restricted-globals': ['error', ...DOMGlobals],
|
||||
'no-restricted-syntax': ['error', banConstEnum],
|
||||
},
|
||||
},
|
||||
// Private package, browser only + no syntax restrictions
|
||||
{
|
||||
files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
|
||||
rules: {
|
||||
'no-restricted-globals': ['error', ...NodeGlobals],
|
||||
'no-restricted-syntax': ['error', banConstEnum],
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
// JavaScript files
|
||||
{
|
||||
files: ['*.js', '*.cjs'],
|
||||
rules: {
|
||||
// We only do `no-unused-vars` checks for js files, TS files are checked by TypeScript itself.
|
||||
'no-unused-vars': ['error', { vars: 'all', args: 'none' }],
|
||||
},
|
||||
},
|
||||
// Node scripts
|
||||
{
|
||||
files: [
|
||||
'scripts/**',
|
||||
'./*.{js,ts}',
|
||||
'packages/*/*.js',
|
||||
'packages/vue/*/*.js',
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': ['error', banConstEnum],
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
// Import nodejs modules in compiler-sfc
|
||||
{
|
||||
files: ['packages/compiler-sfc/src/**'],
|
||||
rules: {
|
||||
'import/no-nodejs-modules': ['error', { allow: builtinModules }],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
Messages must be matched by the following regex:
|
||||
|
||||
``` js
|
||||
```regexp
|
||||
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/
|
||||
```
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -61,7 +82,7 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
|||
|
||||
## Development Setup
|
||||
|
||||
You will need [Node.js](https://nodejs.org) **version 18.12+**, and [PNPM](https://pnpm.io) **version 8+**.
|
||||
You will need [Node.js](https://nodejs.org) with minimum version as specified in the [`.node-version`](https://github.com/vuejs/core/blob/main/.node-version) file, and [PNPM](https://pnpm.io) with minimum version as specified in the [`"packageManager"` field in `package.json`](https://github.com/vuejs/core/blob/main/package.json#L4).
|
||||
|
||||
We also recommend installing [@antfu/ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier.
|
||||
|
||||
|
|
|
@ -28,13 +28,13 @@
|
|||
},
|
||||
{
|
||||
groupName: 'build',
|
||||
matchPackageNames: ['vite', 'terser'],
|
||||
matchPackageNames: ['vite', '@swc/core'],
|
||||
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
|
||||
},
|
||||
{
|
||||
groupName: 'lint',
|
||||
matchPackageNames: ['simple-git-hooks', 'lint-staged'],
|
||||
matchPackagePrefixes: ['@typescript-eslint', 'eslint', 'prettier'],
|
||||
matchPackagePrefixes: ['typescript-eslint', 'eslint', 'prettier'],
|
||||
},
|
||||
],
|
||||
ignoreDeps: [
|
||||
|
|
|
@ -14,13 +14,14 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Set node version to 18
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
|
@ -30,4 +31,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@ea32e3a12414e6d3183163c3424a7d7a8631ad84
|
||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
||||
|
|
|
@ -17,12 +17,12 @@ jobs:
|
|||
ref: minor
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Set node version to 18
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -43,7 +43,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -72,7 +72,7 @@ jobs:
|
|||
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -98,7 +98,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -24,4 +24,5 @@ jobs:
|
|||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
body: |
|
||||
Please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
|
||||
For stable releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
|
||||
For pre-releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/minor/CHANGELOG.md) of the `minor` branch.
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -36,26 +36,27 @@ jobs:
|
|||
run: pnpm install
|
||||
|
||||
- name: Download PR number
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
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: ./pr.txt
|
||||
path: /tmp/pr-number/pr.txt
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
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@v3
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
branch: main
|
||||
workflow: size-data.yml
|
||||
|
@ -64,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
|
||||
|
|
236
CHANGELOG.md
236
CHANGELOG.md
|
@ -1,3 +1,239 @@
|
|||
## [3.4.35](https://github.com/vuejs/core/compare/v3.4.34...v3.4.35) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **teleport/ssr:** fix Teleport hydration regression due to targetStart anchor addition ([7b18cdb](https://github.com/vuejs/core/commit/7b18cdb0b53a94007ca6a3675bf41b5d3153fec6))
|
||||
* **teleport/ssr:** ensure targetAnchor and targetStart not null during hydration ([#11456](https://github.com/vuejs/core/issues/11456)) ([12667da](https://github.com/vuejs/core/commit/12667da4879f980dcf2c50e36f3642d085a87d71)), closes [#11400](https://github.com/vuejs/core/issues/11400)
|
||||
* **types/ref:** allow getter and setter types to be unrelated ([#11442](https://github.com/vuejs/core/issues/11442)) ([e0b2975](https://github.com/vuejs/core/commit/e0b2975ef65ae6a0be0aa0a0df43fb887c665251))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **runtime-core:** improve efficiency of normalizePropsOptions ([#11409](https://github.com/vuejs/core/issues/11409)) ([5680142](https://github.com/vuejs/core/commit/5680142e68096c42e66da9f4c6220d040d7c56ba)), closes [#9739](https://github.com/vuejs/core/issues/9739)
|
||||
|
||||
|
||||
|
||||
## [3.4.34](https://github.com/vuejs/core/compare/v3.4.33...v3.4.34) (2024-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **defineModel:** correct update with multiple changes in same tick ([#11430](https://github.com/vuejs/core/issues/11430)) ([a18f1ec](https://github.com/vuejs/core/commit/a18f1ecf05842337f1eb39a6871adb8cb4024093)), closes [#11429](https://github.com/vuejs/core/issues/11429)
|
||||
|
||||
|
||||
|
||||
## [3.4.33](https://github.com/vuejs/core/compare/v3.4.32...v3.4.33) (2024-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **runtime-dom:** handle undefined values in v-html ([#11403](https://github.com/vuejs/core/issues/11403)) ([5df67e3](https://github.com/vuejs/core/commit/5df67e36756639ea7b923d1b139d6cb14450123b))
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
* **compiler-core:** should not remove slot node with `v-else` ([#11150](https://github.com/vuejs/core/issues/11150)) ([e102670](https://github.com/vuejs/core/commit/e102670bde00417c3a5b0262c855b297c0e4169e))
|
||||
* **hydration:** fix css vars hydration mismatch false positive on attr-fallthrough ([#11190](https://github.com/vuejs/core/issues/11190)) ([7ad67ce](https://github.com/vuejs/core/commit/7ad67ced26e5f53a47cb42f4834496e4958cb53b)), closes [#11188](https://github.com/vuejs/core/issues/11188)
|
||||
* **hydration:** skip prop mismatch check for directives that mutate DOM in created ([3169c91](https://github.com/vuejs/core/commit/3169c914939d02a013b2938aff30dac8525923f8)), closes [#11189](https://github.com/vuejs/core/issues/11189)
|
||||
* **reactivity:** fix side effect computed dirty level ([#11183](https://github.com/vuejs/core/issues/11183)) ([3bd79e3](https://github.com/vuejs/core/commit/3bd79e3e5ed960fc42cbf77bc61a97d2c03557c0)), closes [#11181](https://github.com/vuejs/core/issues/11181) [#11169](https://github.com/vuejs/core/issues/11169)
|
||||
* **runtime-core:** ensure unmount dynamic components in optimized mode ([#11171](https://github.com/vuejs/core/issues/11171)) ([220fe24](https://github.com/vuejs/core/commit/220fe247484209e62c7f4991902c5335e29c5007)), closes [#11168](https://github.com/vuejs/core/issues/11168)
|
||||
* **runtime-core:** update devtool __vnode on patch, avoid memory leak during dev ([a959781](https://github.com/vuejs/core/commit/a959781dd6f609dcb6f16dd7fa47d3b16895e5ca)), closes [#11192](https://github.com/vuejs/core/issues/11192)
|
||||
* **runtime-dom:** ensure only symbols are explicitly stringified during attribute patching ([#11182](https://github.com/vuejs/core/issues/11182)) ([a2e35d6](https://github.com/vuejs/core/commit/a2e35d682db15a592f4270bb0cde70a0e7bdc4a6)), closes [#11177](https://github.com/vuejs/core/issues/11177)
|
||||
* **runtime-dom:** prevent setting state as attribute for custom elements ([#11165](https://github.com/vuejs/core/issues/11165)) ([8ae4c29](https://github.com/vuejs/core/commit/8ae4c293adcec28f18114cb6016230a86787e6a9)), closes [#11163](https://github.com/vuejs/core/issues/11163)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **reactivity:** cache tracking value ([#11145](https://github.com/vuejs/core/issues/11145)) ([7936dae](https://github.com/vuejs/core/commit/7936daebceab2ae9461c3b8f256e51020fb7d3ed))
|
||||
|
||||
|
||||
|
||||
## [3.4.29](https://github.com/vuejs/core/compare/v3.4.28...v3.4.29) (2024-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** fix accidental inclusion of runtime-core in server-renderer cjs build ([11cc12b](https://github.com/vuejs/core/commit/11cc12b915edfe0e4d3175e57464f73bc2c1cb04)), closes [#11137](https://github.com/vuejs/core/issues/11137)
|
||||
* **compiler-sfc:** fix missing scope for extends error message ([4ec387b](https://github.com/vuejs/core/commit/4ec387b100985b008cdcc4cd883a5b6328c05766))
|
||||
* **compiler-sfc:** fix parsing of mts, d.mts, and mtsx files ([a476692](https://github.com/vuejs/core/commit/a476692ed2d7308f2742d8ff3554cf97a392b0b7))
|
||||
* **compiler-sfc:** support [@vue-ignore](https://github.com/vue-ignore) comment on more type sources ([a23e99b](https://github.com/vuejs/core/commit/a23e99bedf1d65841d162951f10ce35b907a5680))
|
||||
* **custom-element:** support same direct setup function signature in defineCustomElement ([7c8b126](https://github.com/vuejs/core/commit/7c8b12620aad4969b8dc4944d4fc486d16c3033c)), closes [#11116](https://github.com/vuejs/core/issues/11116)
|
||||
* **reactivity:** avoid infinite loop when render access a side effect computed ([#11135](https://github.com/vuejs/core/issues/11135)) ([8296e19](https://github.com/vuejs/core/commit/8296e19855e369a7826f5ea26540a6da01dc7093)), closes [#11121](https://github.com/vuejs/core/issues/11121)
|
||||
|
||||
|
||||
|
||||
## [3.4.28](https://github.com/vuejs/core/compare/v3.4.27...v3.4.28) (2024-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compat:** correctly transform non-identifier expressions in legacy filter syntax ([#10896](https://github.com/vuejs/core/issues/10896)) ([07b3c4b](https://github.com/vuejs/core/commit/07b3c4b7860009e19446f3d78571556c5737d82a)), closes [#10852](https://github.com/vuejs/core/issues/10852)
|
||||
* **compat:** ensure proper handling of render fuction from SFC using Vue.extend ([#7781](https://github.com/vuejs/core/issues/7781)) ([c73847f](https://github.com/vuejs/core/commit/c73847f2becc20f03cb9c68748eea92455e688ee)), closes [#7766](https://github.com/vuejs/core/issues/7766)
|
||||
* **compat:** only warn ATTR_FALSE_VALUE when enabled ([04729ba](https://github.com/vuejs/core/commit/04729ba2163d840f0ca7866bc964696eb5557804)), closes [#11126](https://github.com/vuejs/core/issues/11126)
|
||||
* **compile-sfc:** register props destructure rest id as setup bindings ([#10888](https://github.com/vuejs/core/issues/10888)) ([b2b5f57](https://github.com/vuejs/core/commit/b2b5f57c2c945edd0eebc1b545ec1b7568e51484)), closes [#10885](https://github.com/vuejs/core/issues/10885)
|
||||
* **compile-sfc:** Support project reference with folder, ([#10908](https://github.com/vuejs/core/issues/10908)) ([bdeac37](https://github.com/vuejs/core/commit/bdeac377c7b85888193b49ac187e927636cc40bc)), closes [#10907](https://github.com/vuejs/core/issues/10907)
|
||||
* **compiler-core:** allow unicode to appear in simple identifiers ([#6765](https://github.com/vuejs/core/issues/6765)) ([3ea9644](https://github.com/vuejs/core/commit/3ea964473d3ac0ba3e7b0b2c22d71f23d0f69123)), closes [#6367](https://github.com/vuejs/core/issues/6367)
|
||||
* **compiler-core:** change v-for key type to match Object.keys ([#10963](https://github.com/vuejs/core/issues/10963)) ([9fead52](https://github.com/vuejs/core/commit/9fead5234320848f8be82275c6b5dd0a290f2cca)), closes [#8819](https://github.com/vuejs/core/issues/8819)
|
||||
* **compiler-core:** emit TS-compatible function declaration when requested ([#9363](https://github.com/vuejs/core/issues/9363)) ([5d25850](https://github.com/vuejs/core/commit/5d258502a0faffc8a451b8701f13a31b2566d068))
|
||||
* **compiler-core:** fix :key shorthand on v-for ([#10942](https://github.com/vuejs/core/issues/10942)) ([29425df](https://github.com/vuejs/core/commit/29425df1acb9e520c6ae894d06bcff73fde90edd)), closes [#10882](https://github.com/vuejs/core/issues/10882) [#10939](https://github.com/vuejs/core/issues/10939)
|
||||
* **compiler-core:** make `ForIteratorExpression`'s `returns` property optional ([#11011](https://github.com/vuejs/core/issues/11011)) ([5b8c1af](https://github.com/vuejs/core/commit/5b8c1afb74e39045fcb53a011420d26e3f67eab4))
|
||||
* **compiler-core:** should set `<math>` tag as block to retain MathML namespace after patching ([#10891](https://github.com/vuejs/core/issues/10891)) ([87c5443](https://github.com/vuejs/core/commit/87c54430448005294c41803f07f517fef848f917))
|
||||
* **compiler-core:** v-for expression missing source with spaces should emit error ([#5821](https://github.com/vuejs/core/issues/5821)) ([b9ca202](https://github.com/vuejs/core/commit/b9ca202f477be595477e182972ee9bae3f2b9f74)), closes [#5819](https://github.com/vuejs/core/issues/5819)
|
||||
* **compiler-sfc:** improve type resolving for the keyof operator ([#10921](https://github.com/vuejs/core/issues/10921)) ([293cf4e](https://github.com/vuejs/core/commit/293cf4e131b6d4606e1de2cd7ea87814e2544952)), closes [#10920](https://github.com/vuejs/core/issues/10920) [#11002](https://github.com/vuejs/core/issues/11002)
|
||||
* **compiler-sfc:** support as keyword with template literal types ([#11100](https://github.com/vuejs/core/issues/11100)) ([2594b1d](https://github.com/vuejs/core/commit/2594b1df57f672ac6621ac2880645e975fea581c)), closes [#10962](https://github.com/vuejs/core/issues/10962)
|
||||
* **compiler-sfc:** support type resolve for keyof for intersection & union types ([#11132](https://github.com/vuejs/core/issues/11132)) ([495263a](https://github.com/vuejs/core/commit/495263a9cb356861e58a4364f2570608265486b5)), closes [#11129](https://github.com/vuejs/core/issues/11129)
|
||||
* **compiler-sfc:** throw error when import macro as alias ([#11041](https://github.com/vuejs/core/issues/11041)) ([34a97ed](https://github.com/vuejs/core/commit/34a97edd2c8273c213599c44770accdb0846da8e))
|
||||
* correct the type of `<details>`'s `onToggle` event handler ([#10938](https://github.com/vuejs/core/issues/10938)) ([fd18ce7](https://github.com/vuejs/core/commit/fd18ce70b1a260a2485c9cd7faa30193da4b79f5)), closes [#10928](https://github.com/vuejs/core/issues/10928)
|
||||
* **custom-element:** disconnect MutationObserver in nextTick in case that custom elements are moved ([#10613](https://github.com/vuejs/core/issues/10613)) ([bbb5be2](https://github.com/vuejs/core/commit/bbb5be299b500a00e60c757118c846c3b5ddd8e0)), closes [#10610](https://github.com/vuejs/core/issues/10610)
|
||||
* **custom-elements:** compatibility of createElement in older versions of Chrome ([#9615](https://github.com/vuejs/core/issues/9615)) ([a88295d](https://github.com/vuejs/core/commit/a88295dc076ee867939d8b0ee2225e63c5ffb0ca)), closes [#9614](https://github.com/vuejs/core/issues/9614)
|
||||
* **hmr:** avoid infinite recursion when reloading hmr components ([#6936](https://github.com/vuejs/core/issues/6936)) ([36bd9b0](https://github.com/vuejs/core/commit/36bd9b0a1fb83e61731fb80d66e265dccbedcfa8)), closes [#6930](https://github.com/vuejs/core/issues/6930)
|
||||
* **hydration:** log hydration error even when using async components ([#9403](https://github.com/vuejs/core/issues/9403)) ([5afc76c](https://github.com/vuejs/core/commit/5afc76c229f9ad30eef07f34c7b65e8fe427e637)), closes [#9369](https://github.com/vuejs/core/issues/9369)
|
||||
* **KeepAlive:** properly cache nested Suspense subtree ([#10912](https://github.com/vuejs/core/issues/10912)) ([07764fe](https://github.com/vuejs/core/commit/07764fe330692fadf0fc9fb9e92cb5b111df33be))
|
||||
* **npm:** explicitly add `@vue/reactivity` as dependency of `@vue/runtime-dom` ([#10468](https://github.com/vuejs/core/issues/10468)) ([ec424f6](https://github.com/vuejs/core/commit/ec424f6cd96b7e6ba74fc244c484c00fa5590aac))
|
||||
* **reactivity:** pass oldValue in debug info when triggering refs ([#8210](https://github.com/vuejs/core/issues/8210)) ([3b0a56a](https://github.com/vuejs/core/commit/3b0a56a9c4d162ec3bd725a4f2dfd776b045e727)), closes [vuejs/pinia#2061](https://github.com/vuejs/pinia/issues/2061)
|
||||
* **runtime-core:** avoid traversing static children for vnodes w/ PatchFlags.BAIL ([#11115](https://github.com/vuejs/core/issues/11115)) ([b557d3f](https://github.com/vuejs/core/commit/b557d3fb8ae1e4e926c4ad0fbb2fa7abe50fd661)), closes [#10547](https://github.com/vuejs/core/issues/10547)
|
||||
* **runtime-core:** do not fire mount/activated hooks if unmounted before mounted ([#9370](https://github.com/vuejs/core/issues/9370)) ([aa156ed](https://github.com/vuejs/core/commit/aa156ed5c4dc0d33ff37e201a7e89d5e0e29160e)), closes [#8898](https://github.com/vuejs/core/issues/8898) [#9264](https://github.com/vuejs/core/issues/9264) [#9617](https://github.com/vuejs/core/issues/9617)
|
||||
* **runtime-core:** ensure suspense creates dep component's render effect with correct optimized flag ([#7689](https://github.com/vuejs/core/issues/7689)) ([c521f95](https://github.com/vuejs/core/commit/c521f956e1697cda36a7f1b913599e5e2004f7ba)), closes [#7688](https://github.com/vuejs/core/issues/7688)
|
||||
* **runtime-core:** fix missed updates when passing text vnode to `<component :is>` ([#8304](https://github.com/vuejs/core/issues/8304)) ([b310ec3](https://github.com/vuejs/core/commit/b310ec389d9738247e5b0f01711186216eb49955)), closes [#8298](https://github.com/vuejs/core/issues/8298)
|
||||
* **runtime-core:** fix stale v-memo after v-if toggle ([#6606](https://github.com/vuejs/core/issues/6606)) ([edf2638](https://github.com/vuejs/core/commit/edf263847eddc910f4d2de68287d84b8c66c3860)), closes [#6593](https://github.com/vuejs/core/issues/6593)
|
||||
* **runtime-core:** fix Transition for components with root-level v-if ([#7678](https://github.com/vuejs/core/issues/7678)) ([ef2e737](https://github.com/vuejs/core/commit/ef2e737577de42ea38771403f8a4dee8c892daa5)), closes [#7649](https://github.com/vuejs/core/issues/7649)
|
||||
* **runtime-dom:** also set attribute for form element state ([537a571](https://github.com/vuejs/core/commit/537a571f8cf09dfe0a020e9e8891ecdd351fc3e4)), closes [#6007](https://github.com/vuejs/core/issues/6007) [#6012](https://github.com/vuejs/core/issues/6012)
|
||||
* **runtime-dom:** support Symbol for input value bindings ([#10608](https://github.com/vuejs/core/issues/10608)) ([188f3ae](https://github.com/vuejs/core/commit/188f3ae533fd340603068a516a8fecc5d57426c5)), closes [#10597](https://github.com/vuejs/core/issues/10597)
|
||||
* **shared:** ensure invokeArrayFns handles undefined arguments ([#10869](https://github.com/vuejs/core/issues/10869)) ([9b40d0f](https://github.com/vuejs/core/commit/9b40d0f25da868a83b0d6bf99dbbdb3ca68bb700)), closes [#10863](https://github.com/vuejs/core/issues/10863)
|
||||
* **ssr:** directive binding.instance should respect exposed during ssr ([df686ab](https://github.com/vuejs/core/commit/df686abb4f0ac9d898e4fd93751e860f8cbbdbea)), closes [#7499](https://github.com/vuejs/core/issues/7499) [#7502](https://github.com/vuejs/core/issues/7502)
|
||||
* **ssr:** fix hydration for node with empty text node ([#7216](https://github.com/vuejs/core/issues/7216)) ([d1011c0](https://github.com/vuejs/core/commit/d1011c07a957d858cb37725b13bc8e4d7a395490))
|
||||
* **ssr:** fix the bug that multi slot scope id does not work on component ([#6100](https://github.com/vuejs/core/issues/6100)) ([4c74302](https://github.com/vuejs/core/commit/4c74302aae64c118752db7fc2a2c229a11ebaead)), closes [#6093](https://github.com/vuejs/core/issues/6093)
|
||||
* **teleport:** do not throw target warning when teleport is disabled ([#9818](https://github.com/vuejs/core/issues/9818)) ([15ee43f](https://github.com/vuejs/core/commit/15ee43f66ad2485ac212b02b444345d867b3c060))
|
||||
* **transition:** ensure Transition enterHooks are updated after clone ([#11066](https://github.com/vuejs/core/issues/11066)) ([671cf29](https://github.com/vuejs/core/commit/671cf297a550d15b19fa3fecce1b30e26cad8154)), closes [#11061](https://github.com/vuejs/core/issues/11061)
|
||||
* **types/apiWatch:** correct type inference for reactive array ([#11036](https://github.com/vuejs/core/issues/11036)) ([aae2d78](https://github.com/vuejs/core/commit/aae2d78875daa476280a45e71c2f38292964efae)), closes [#9416](https://github.com/vuejs/core/issues/9416)
|
||||
* **types:** improve `app.provide` type checking ([#10603](https://github.com/vuejs/core/issues/10603)) ([612bbf0](https://github.com/vuejs/core/commit/612bbf0507cbe39d701acc5dff11824802078063)), closes [#10602](https://github.com/vuejs/core/issues/10602)
|
||||
* **types:** support generic argument in setup context expose method ([#8507](https://github.com/vuejs/core/issues/8507)) ([635a59b](https://github.com/vuejs/core/commit/635a59b96fe6be445525c6595ca27da7ef7c1feb))
|
||||
* **v-model:** fix the lazy modifier is not reset by other modifications ([#8547](https://github.com/vuejs/core/issues/8547)) ([a52a02f](https://github.com/vuejs/core/commit/a52a02f43fdf73d8aaad99c9cafed07f12ee422a)), closes [#8546](https://github.com/vuejs/core/issues/8546) [#6564](https://github.com/vuejs/core/issues/6564) [#6773](https://github.com/vuejs/core/issues/6773)
|
||||
* **watch:** support traversing symbol properties in deep watcher ([#10969](https://github.com/vuejs/core/issues/10969)) ([a3e8aaf](https://github.com/vuejs/core/commit/a3e8aafbcc82003a66caded61143eb64c4ef02cd)), closes [#402](https://github.com/vuejs/core/issues/402)
|
||||
|
||||
|
||||
|
||||
## [3.4.27](https://github.com/vuejs/core/compare/v3.4.26...v3.4.27) (2024-05-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compat:** include legacy scoped slots ([#10868](https://github.com/vuejs/core/issues/10868)) ([8366126](https://github.com/vuejs/core/commit/83661264a4ced3cb2ff6800904a86dd9e82bbfe2)), closes [#8869](https://github.com/vuejs/core/issues/8869)
|
||||
* **compiler-core:** add support for arrow aysnc function with unbracketed ([#5789](https://github.com/vuejs/core/issues/5789)) ([ca7d421](https://github.com/vuejs/core/commit/ca7d421e8775f6813f8943d32ab485e0c542f98b)), closes [#5788](https://github.com/vuejs/core/issues/5788)
|
||||
* **compiler-dom:** restrict createStaticVNode usage with option elements ([#10846](https://github.com/vuejs/core/issues/10846)) ([0e3d617](https://github.com/vuejs/core/commit/0e3d6178b02d0386d779720ae2cc4eac1d1ec990)), closes [#6568](https://github.com/vuejs/core/issues/6568) [#7434](https://github.com/vuejs/core/issues/7434)
|
||||
* **compiler-sfc:** handle keyof operator ([#10874](https://github.com/vuejs/core/issues/10874)) ([10d34a5](https://github.com/vuejs/core/commit/10d34a5624775f20437ccad074a97270ef74c3fb)), closes [#10871](https://github.com/vuejs/core/issues/10871)
|
||||
* **hydration:** handle edge case of style mismatch without style attribute ([f2c1412](https://github.com/vuejs/core/commit/f2c1412e46a8fad3e13403bfa78335c4f704f21c)), closes [#10786](https://github.com/vuejs/core/issues/10786)
|
||||
|
||||
|
||||
|
||||
## [3.4.26](https://github.com/vuejs/core/compare/v3.4.25...v3.4.26) (2024-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** fix bail constant for globals ([fefce06](https://github.com/vuejs/core/commit/fefce06b41e3b75de3d748dc6399628ec5056e78))
|
||||
* **compiler-core:** remove unnecessary constant bail check ([09b4df8](https://github.com/vuejs/core/commit/09b4df809e59ef5f4bc91acfc56dc8f82a8e243a)), closes [#10807](https://github.com/vuejs/core/issues/10807)
|
||||
* **runtime-core:** attrs should be readonly in functional components ([#10767](https://github.com/vuejs/core/issues/10767)) ([e8fd644](https://github.com/vuejs/core/commit/e8fd6446d14a6899e5e8ab1ee394d90088e01844))
|
||||
* **runtime-core:** ensure slot compiler marker writable ([#10825](https://github.com/vuejs/core/issues/10825)) ([9c2de62](https://github.com/vuejs/core/commit/9c2de6244cd44bc5fbfd82b5850c710ce725044f)), closes [#10818](https://github.com/vuejs/core/issues/10818)
|
||||
* **runtime-core:** properly handle inherit transition during clone VNode ([#10809](https://github.com/vuejs/core/issues/10809)) ([638a79f](https://github.com/vuejs/core/commit/638a79f64a7e184f2a2c65e21d764703f4bda561)), closes [#3716](https://github.com/vuejs/core/issues/3716) [#10497](https://github.com/vuejs/core/issues/10497) [#4091](https://github.com/vuejs/core/issues/4091)
|
||||
* **Transition:** re-fix [#10620](https://github.com/vuejs/core/issues/10620) ([#10832](https://github.com/vuejs/core/issues/10832)) ([accf839](https://github.com/vuejs/core/commit/accf8396ae1c9dd49759ba0546483f1d2c70c9bc)), closes [#10632](https://github.com/vuejs/core/issues/10632) [#10827](https://github.com/vuejs/core/issues/10827)
|
||||
|
||||
|
||||
|
||||
## [3.4.25](https://github.com/vuejs/core/compare/v3.4.24...v3.4.25) (2024-04-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **defineModel:** align prod mode runtime type generation with defineProps ([4253a57](https://github.com/vuejs/core/commit/4253a57f1703a7f1ac701d77e0a235689203461d)), closes [#10769](https://github.com/vuejs/core/issues/10769)
|
||||
* **runtime-core:** properly get keepAlive child ([#10772](https://github.com/vuejs/core/issues/10772)) ([3724693](https://github.com/vuejs/core/commit/3724693a25c3f2dd13d70a8a1af760b03a4fb783)), closes [#10771](https://github.com/vuejs/core/issues/10771)
|
||||
* **runtime-core:** use normal object as internal prototype for attrs and slots ([064e82f](https://github.com/vuejs/core/commit/064e82f5855f30fe0b77fe9b5e4dd22700fd634d)), closes [/github.com/vuejs/core/commit/6df53d85a207986128159d88565e6e7045db2add#r141304923](https://github.com//github.com/vuejs/core/commit/6df53d85a207986128159d88565e6e7045db2add/issues/r141304923)
|
||||
|
||||
|
||||
|
||||
## [3.4.24](https://github.com/vuejs/core/compare/v3.4.23...v3.4.24) (2024-04-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** handle template ref bound via v-bind object on v-for ([#10706](https://github.com/vuejs/core/issues/10706)) ([da7adef](https://github.com/vuejs/core/commit/da7adefa844265eecc9c336abfc727bc05b4f16e)), closes [#10696](https://github.com/vuejs/core/issues/10696)
|
||||
* **compiler-core:** properly parse await expressions in edge cases ([b92c25f](https://github.com/vuejs/core/commit/b92c25f53dff0fc1687f57ca4033d0ac25218940)), closes [#10754](https://github.com/vuejs/core/issues/10754)
|
||||
* **compiler-sfc:** handle readonly operator and ReadonlyArray/Map/Set types ([5cef52a](https://github.com/vuejs/core/commit/5cef52a5c23ba8ba3239e6def03b8ff008d3cc72)), closes [#10726](https://github.com/vuejs/core/issues/10726)
|
||||
* **compiler-ssr:** fix hydration mismatch for conditional slot in transition ([f12c81e](https://github.com/vuejs/core/commit/f12c81efca3fcf9a7ce478af2261ad6ab9b0bfd7)), closes [#10743](https://github.com/vuejs/core/issues/10743)
|
||||
* **compiler-ssr:** fix v-html SSR for nullish values ([1ff4076](https://github.com/vuejs/core/commit/1ff407676f9495883b459779a9b0370d7588b51f)), closes [#10725](https://github.com/vuejs/core/issues/10725)
|
||||
* **deps:** update compiler ([#10760](https://github.com/vuejs/core/issues/10760)) ([15df5c1](https://github.com/vuejs/core/commit/15df5c1b261b9b471eb811fd47ab7b3cfc41cf83))
|
||||
* **runtime-core:** fix edge case of KeepAlive inside Transition with slot children ([#10719](https://github.com/vuejs/core/issues/10719)) ([e51ca61](https://github.com/vuejs/core/commit/e51ca61ca060b2772e967d169548fc2f58fce6d1)), closes [#10708](https://github.com/vuejs/core/issues/10708)
|
||||
* **runtime-core:** further fix slots _ctx check ([cde7f05](https://github.com/vuejs/core/commit/cde7f05787d16dbb93d9419ef5331adf992816fd)), closes [#10724](https://github.com/vuejs/core/issues/10724)
|
||||
* **runtime-core:** props should be readonly via direct template access ([b93f264](https://github.com/vuejs/core/commit/b93f26464785de227b88c51a88328ae80e80d804)), closes [#8216](https://github.com/vuejs/core/issues/8216) [#10736](https://github.com/vuejs/core/issues/10736)
|
||||
* **transition:** transition is breaking/flickering when enter is canceled ([#10688](https://github.com/vuejs/core/issues/10688)) ([65109a7](https://github.com/vuejs/core/commit/65109a70f187473edae8cf4df11af3c33345e6f6))
|
||||
|
||||
|
||||
|
||||
## [3.4.23](https://github.com/vuejs/core/compare/v3.4.22...v3.4.23) (2024-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **runtime-core:** fix regression for $attrs tracking in slots ([6930e60](https://github.com/vuejs/core/commit/6930e60787e4905a50417190263ae7dd46cf5409)), closes [#10710](https://github.com/vuejs/core/issues/10710)
|
||||
* **runtime-core:** use same internal object mechanism for slots ([6df53d8](https://github.com/vuejs/core/commit/6df53d85a207986128159d88565e6e7045db2add)), closes [#10709](https://github.com/vuejs/core/issues/10709)
|
||||
|
||||
|
||||
|
||||
## [3.4.22](https://github.com/vuejs/core/compare/v3.4.21...v3.4.22) (2024-04-15)
|
||||
|
||||
|
||||
|
|
|
@ -5,3 +5,11 @@ To report a vulnerability, please email security@vuejs.org.
|
|||
While the discovery of new vulnerabilities is rare, we also recommend always using the latest versions of Vue and its official companion libraries to ensure your application remains as secure as possible.
|
||||
|
||||
Please note that we do not consider XSS via template expressions a valid attack vector, because it can only happen if the user intentionally uses untrusted content as template compilation source. This is similar to knowingly pasting untrusted scripts into a browser console. We explicitly warn users against using untrusted content as template compilation source in our documentation.
|
||||
|
||||
## Security Hall of Fame
|
||||
|
||||
We would like to thank the following security researchers for responsibly disclosing security issues to us.
|
||||
|
||||
- Jeet Pal - [@jeetpal2007](https://github.com/jeetpal2007) | [Email](jeetpal2007@gmail.com) | [LinkedIn](https://in.linkedin.com/in/jeet-pal-22601a290 )
|
||||
- Mix - [@mnixry](https://github.com/mnixry)
|
||||
- Aviv Keller - [@RedYetiDev](https://github.com/redyetidev) | [LinkedIn](https://www.linkedin.com/in/redyetidev) <redyetidev@gmail.com>
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
import importX from 'eslint-plugin-import-x'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import vitest from 'eslint-plugin-vitest'
|
||||
import { builtinModules } from 'node:module'
|
||||
|
||||
const DOMGlobals = ['window', 'document']
|
||||
const NodeGlobals = ['module', 'require']
|
||||
|
||||
const banConstEnum = {
|
||||
selector: 'TSEnumDeclaration[const=true]',
|
||||
message:
|
||||
'Please use non-const enums. This project automatically inlines enums.',
|
||||
}
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
|
||||
extends: [tseslint.configs.base],
|
||||
plugins: {
|
||||
'import-x': importX,
|
||||
},
|
||||
rules: {
|
||||
'no-debugger': 'error',
|
||||
'no-console': ['error', { allow: ['warn', 'error', 'info'] }],
|
||||
// most of the codebase are expected to be env agnostic
|
||||
'no-restricted-globals': ['error', ...DOMGlobals, ...NodeGlobals],
|
||||
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
banConstEnum,
|
||||
{
|
||||
selector: 'ObjectPattern > RestElement',
|
||||
message:
|
||||
'Our output target is ES2016, and object rest spread results in ' +
|
||||
'verbose helpers and should be avoided.',
|
||||
},
|
||||
{
|
||||
selector: 'ObjectExpression > SpreadElement',
|
||||
message:
|
||||
'esbuild transpiles object spread into very verbose inline helpers.\n' +
|
||||
'Please use the `extend` helper from @vue/shared instead.',
|
||||
},
|
||||
{
|
||||
selector: 'AwaitExpression',
|
||||
message:
|
||||
'Our output target is ES2016, so async/await syntax should be avoided.',
|
||||
},
|
||||
{
|
||||
selector: 'ChainExpression',
|
||||
message:
|
||||
'Our output target is ES2016, and optional chaining results in ' +
|
||||
'verbose helpers and should be avoided.',
|
||||
},
|
||||
],
|
||||
'sort-imports': ['error', { ignoreDeclarationSort: true }],
|
||||
|
||||
'import-x/no-nodejs-modules': [
|
||||
'error',
|
||||
{ allow: builtinModules.map(mod => `node:${mod}`) },
|
||||
],
|
||||
// This rule enforces the preference for using '@ts-expect-error' comments in TypeScript
|
||||
// code to indicate intentional type errors, improving code clarity and maintainability.
|
||||
'@typescript-eslint/prefer-ts-expect-error': 'error',
|
||||
// Enforce the use of 'import type' for importing types
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
fixStyle: 'inline-type-imports',
|
||||
disallowTypeAnnotations: false,
|
||||
},
|
||||
],
|
||||
// Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers
|
||||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// tests, no restrictions (runs in Node / Vitest with jsdom)
|
||||
{
|
||||
files: ['**/__tests__/**', 'packages/dts-test/**'],
|
||||
plugins: { vitest },
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...vitest.environments.env.globals,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'vitest/no-disabled-tests': 'error',
|
||||
'vitest/no-focused-tests': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// shared, may be used in any env
|
||||
{
|
||||
files: ['packages/shared/**', 'eslint.config.js'],
|
||||
rules: {
|
||||
'no-restricted-globals': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// Packages targeting DOM
|
||||
{
|
||||
files: ['packages/{vue,vue-compat,runtime-dom}/**'],
|
||||
rules: {
|
||||
'no-restricted-globals': ['error', ...NodeGlobals],
|
||||
},
|
||||
},
|
||||
|
||||
// Packages targeting Node
|
||||
{
|
||||
files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'],
|
||||
rules: {
|
||||
'no-restricted-globals': ['error', ...DOMGlobals],
|
||||
'no-restricted-syntax': ['error', banConstEnum],
|
||||
},
|
||||
},
|
||||
|
||||
// Private package, browser only + no syntax restrictions
|
||||
{
|
||||
files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
|
||||
rules: {
|
||||
'no-restricted-globals': ['error', ...NodeGlobals],
|
||||
'no-restricted-syntax': ['error', banConstEnum],
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// JavaScript files
|
||||
{
|
||||
files: ['*.js'],
|
||||
rules: {
|
||||
// We only do `no-unused-vars` checks for js files, TS files are checked by TypeScript itself.
|
||||
'no-unused-vars': ['error', { vars: 'all', args: 'none' }],
|
||||
},
|
||||
},
|
||||
|
||||
// Node scripts
|
||||
{
|
||||
files: [
|
||||
'eslint.config.js',
|
||||
'rollup*.config.js',
|
||||
'scripts/**',
|
||||
'./*.{js,ts}',
|
||||
'packages/*/*.js',
|
||||
'packages/vue/*/*.js',
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': ['error', banConstEnum],
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// Import nodejs modules in compiler-sfc
|
||||
{
|
||||
files: ['packages/compiler-sfc/src/**'],
|
||||
rules: {
|
||||
'import-x/no-nodejs-modules': ['error', { allow: builtinModules }],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
ignores: [
|
||||
'**/dist/',
|
||||
'**/temp/',
|
||||
'**/coverage/',
|
||||
'.idea/',
|
||||
'explorations/',
|
||||
'dts-build/packages',
|
||||
],
|
||||
},
|
||||
)
|
86
package.json
86
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.4.22",
|
||||
"packageManager": "pnpm@8.15.6",
|
||||
"version": "3.4.35",
|
||||
"packageManager": "pnpm@9.6.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
@ -13,7 +13,7 @@
|
|||
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
|
||||
"size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler",
|
||||
"check": "tsc --incremental --noEmit",
|
||||
"lint": "eslint --cache --ext .js,.ts,.tsx . --ignore-path .gitignore",
|
||||
"lint": "eslint --cache .",
|
||||
"format": "prettier --write --cache .",
|
||||
"format-check": "prettier --check --cache .",
|
||||
"test": "vitest",
|
||||
|
@ -59,60 +59,64 @@
|
|||
"node": ">=18.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@babel/types": "^7.24.0",
|
||||
"@codspeed/vitest-plugin": "^3.1.0",
|
||||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@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",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@swc/core": "^1.7.3",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.12.5",
|
||||
"@types/node": "^20.14.13",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"@vitest/coverage-istanbul": "^1.4.0",
|
||||
"@vitest/coverage-istanbul": "^1.6.0",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"conventional-changelog-cli": "^5.0.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.20.2",
|
||||
"esbuild": "^0.23.0",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-define-config": "^2.1.0",
|
||||
"eslint-plugin-import": "npm:eslint-plugin-i@^2.29.1",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^8.0.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-plugin-import-x": "^3.1.0",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^24.1.1",
|
||||
"lint-staged": "^15.2.7",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.8",
|
||||
"magic-string": "^0.30.10",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^12.0.1",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all2": "^6.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"marked": "^12.0.2",
|
||||
"npm-run-all2": "^6.2.2",
|
||||
"picocolors": "^1.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~22.6.3",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~22.14.0",
|
||||
"rimraf": "^5.0.9",
|
||||
"rollup": "^4.19.1",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.0",
|
||||
"serve": "^14.2.1",
|
||||
"semver": "^7.6.3",
|
||||
"serve": "^14.2.3",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"terser": "^5.30.1",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.7.2",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "~5.4.5",
|
||||
"vite": "^5.2.7",
|
||||
"vitest": "^1.4.0"
|
||||
"typescript-eslint": "^7.17.0",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
"typescript-eslint>eslint": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin>eslint": "^9.0.0",
|
||||
"@typescript-eslint/parser>eslint": "^9.0.0",
|
||||
"@typescript-eslint/type-utils>eslint": "^9.0.0",
|
||||
"@typescript-eslint/utils>eslint": "^9.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -16,6 +16,22 @@ export function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`scopeId compiler support > should push typescript-compatible scopeId for hoisted nodes 1`] = `
|
||||
"import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"
|
||||
|
||||
const _withScopeId = (n: any) => (_pushScopeId("test"),n=n(),_popScopeId(),n)
|
||||
const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", -1 /* HOISTED */))
|
||||
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", -1 /* HOISTED */))
|
||||
|
||||
export function render(_ctx: any,_cache: any) {
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_hoisted_1,
|
||||
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
|
||||
_hoisted_2
|
||||
]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`scopeId compiler support > should wrap default slot 1`] = `
|
||||
"import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from "vue"
|
||||
|
||||
|
|
|
@ -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: 1,
|
||||
|
@ -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', () => {
|
||||
|
|
|
@ -16,8 +16,6 @@ import {
|
|||
import { baseParse } from '../src/parser'
|
||||
import type { Program } from '@babel/types'
|
||||
|
||||
/* eslint jest/no-disabled-tests: "off" */
|
||||
|
||||
describe('compiler: parse', () => {
|
||||
describe('Text', () => {
|
||||
test('simple text', () => {
|
||||
|
|
|
@ -81,4 +81,29 @@ describe('scopeId compiler support', () => {
|
|||
].forEach(c => expect(code).toMatch(c))
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should push typescript-compatible scopeId for hoisted nodes', () => {
|
||||
const { ast, code } = baseCompile(
|
||||
`<div><div>hello</div>{{ foo }}<div>world</div></div>`,
|
||||
{
|
||||
mode: 'module',
|
||||
scopeId: 'test',
|
||||
hoistStatic: true,
|
||||
isTS: true,
|
||||
},
|
||||
)
|
||||
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
|
||||
expect(ast.helpers).toContain(POP_SCOPE_ID)
|
||||
expect(ast.hoists.length).toBe(2)
|
||||
;[
|
||||
`const _withScopeId = (n: any) => (_pushScopeId("test"),n=n(),_popScopeId(),n)`,
|
||||
`const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText(
|
||||
PatchFlags.HOISTED,
|
||||
)}))`,
|
||||
`const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText(
|
||||
PatchFlags.HOISTED,
|
||||
)}))`,
|
||||
].forEach(c => expect(code).toMatch(c))
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`compiler: v-for > codegen > basic v-for 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||
return (_openBlock(), _createElementBlock("span"))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > keyed template v-for 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, { key: item }, [
|
||||
"hello",
|
||||
_createElementVNode("span")
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}), 128 /* KEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > keyed v-for 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||
return (_openBlock(), _createElementBlock("span", { key: item }))
|
||||
}), 128 /* KEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > skipped key 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, __, index) => {
|
||||
return (_openBlock(), _createElementBlock("span"))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > skipped value & key 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, __, index) => {
|
||||
return (_openBlock(), _createElementBlock("span"))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > skipped value 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, key, index) => {
|
||||
return (_openBlock(), _createElementBlock("span"))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > template v-for 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
"hello",
|
||||
_createElementVNode("span")
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > template v-for key injection with single child 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||
return (_openBlock(), _createElementBlock("span", {
|
||||
key: item.id,
|
||||
id: item.id
|
||||
}, null, 8 /* PROPS */, ["id"]))
|
||||
}), 128 /* KEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > template v-for w/ <slot/> 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||
return _renderSlot($slots, "default")
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > v-for on <slot/> 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||
return _renderSlot($slots, "default")
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > v-for on element with custom directive 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, resolveDirective: _resolveDirective, withDirectives: _withDirectives } = _Vue
|
||||
|
||||
const _directive_foo = _resolveDirective("foo")
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
return _withDirectives((_openBlock(), _createElementBlock("div", null, null, 512 /* NEED_PATCH */)), [
|
||||
[_directive_foo]
|
||||
])
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > v-for with constant expression 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (item) => {
|
||||
return _createElementVNode("p", null, _toDisplayString(item), 1 /* TEXT */)
|
||||
}), 64 /* STABLE_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > v-if + v-for 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||
|
||||
return ok
|
||||
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div"))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
: _createCommentVNode("v-if", true)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > v-if + v-for on <template> 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||
|
||||
return ok
|
||||
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [], 64 /* STABLE_FRAGMENT */))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
: _createCommentVNode("v-if", true)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > codegen > value + key + index 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, key, index) => {
|
||||
return (_openBlock(), _createElementBlock("span"))
|
||||
}), 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'
|
||||
|
||||
|
@ -180,7 +180,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||
id: `[foo]`,
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `_hoisted_1`,
|
||||
|
@ -242,7 +242,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||
ref: `[foo]`,
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
|
||||
patchFlag: PatchFlags.NEED_PATCH,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -263,7 +263,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||
content: `_hoisted_1`,
|
||||
},
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
|
||||
patchFlag: PatchFlags.NEED_PATCH,
|
||||
directives: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
|
@ -286,7 +286,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||
tag: `"div"`,
|
||||
props: { content: `_hoisted_1` },
|
||||
children: { type: NodeTypes.INTERPOLATION },
|
||||
patchFlag: genFlagText(PatchFlags.TEXT),
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -365,7 +365,7 @@ describe('compiler: hoistStatic 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({
|
||||
|
@ -496,7 +496,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||
constType: ConstantTypes.NOT_CONSTANT,
|
||||
},
|
||||
},
|
||||
patchFlag: `1 /* TEXT */`,
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -37,8 +37,9 @@ 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'
|
||||
|
||||
function parseWithElementTransform(
|
||||
template: string,
|
||||
|
@ -520,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,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -587,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"`)
|
||||
})
|
||||
|
||||
|
@ -611,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: [
|
||||
|
@ -944,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"]`)
|
||||
})
|
||||
|
||||
|
@ -972,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"]`)
|
||||
})
|
||||
|
@ -982,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)', () => {
|
||||
|
@ -1024,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)', () => {
|
||||
|
@ -1119,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" />`,
|
||||
|
@ -1129,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
|
||||
|
@ -1156,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', () => {
|
||||
|
@ -1172,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)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1283,6 +1276,18 @@ describe('compiler: element transform', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('<math> should be forced into blocks', () => {
|
||||
const ast = parse(`<div><math/></div>`)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformElement],
|
||||
})
|
||||
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"math"`,
|
||||
isBlock: true,
|
||||
})
|
||||
})
|
||||
|
||||
test('force block for runtime custom directive w/ children', () => {
|
||||
const { node } = parseWithElementTransform(`<div v-foo>hello</div>`)
|
||||
expect(node.isBlock).toBe(true)
|
||||
|
@ -1338,4 +1343,42 @@ describe('compiler: element transform', () => {
|
|||
isBlock: false,
|
||||
})
|
||||
})
|
||||
|
||||
test('ref_for marker on static ref', () => {
|
||||
const { node } = parseWithForTransform(`<div v-for="i in l" ref="x"/>`)
|
||||
expect((node.children[0] as any).codegenNode.props).toMatchObject(
|
||||
createObjectMatcher({
|
||||
ref_for: `[true]`,
|
||||
ref: 'x',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test('ref_for marker on dynamic ref', () => {
|
||||
const { node } = parseWithForTransform(`<div v-for="i in l" :ref="x"/>`)
|
||||
expect((node.children[0] as any).codegenNode.props).toMatchObject(
|
||||
createObjectMatcher({
|
||||
ref_for: `[true]`,
|
||||
ref: '[x]',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test('ref_for marker on v-bind', () => {
|
||||
const { node } = parseWithForTransform(`<div v-for="i in l" v-bind="x" />`)
|
||||
expect((node.children[0] as any).codegenNode.props).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: MERGE_PROPS,
|
||||
arguments: [
|
||||
createObjectMatcher({
|
||||
ref_for: `[true]`,
|
||||
}),
|
||||
{
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'x',
|
||||
isStatic: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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 }}`,
|
||||
|
@ -421,6 +432,31 @@ describe('compiler: expression transform', () => {
|
|||
})
|
||||
})
|
||||
|
||||
// #10807
|
||||
test('should not bail constant on strings w/ ()', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
`{{ { foo: 'ok()' } }}`,
|
||||
) as InterpolationNode
|
||||
expect(node.content).toMatchObject({
|
||||
constType: ConstantTypes.CAN_STRINGIFY,
|
||||
})
|
||||
})
|
||||
|
||||
test('should bail constant for global identifiers w/ new or call expressions', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
`{{ new Date().getFullYear() }}`,
|
||||
) as InterpolationNode
|
||||
expect(node.content).toMatchObject({
|
||||
children: [
|
||||
'new ',
|
||||
{ constType: ConstantTypes.NOT_CONSTANT },
|
||||
'().',
|
||||
{ constType: ConstantTypes.NOT_CONSTANT },
|
||||
'()',
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
describe('ES Proposals support', () => {
|
||||
test('bigInt', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
|
@ -598,5 +634,33 @@ describe('compiler: expression transform', () => {
|
|||
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
|
||||
)
|
||||
})
|
||||
|
||||
// #10754
|
||||
test('await expression in right hand of assignment, inline mode', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
`{{ (async () => { x = await bar })() }}`,
|
||||
{
|
||||
inline: true,
|
||||
bindingMetadata: {
|
||||
x: BindingTypes.SETUP_LET,
|
||||
bar: BindingTypes.SETUP_CONST,
|
||||
},
|
||||
},
|
||||
) as InterpolationNode
|
||||
expect(node.content).toMatchObject({
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
`(async () => { `,
|
||||
{
|
||||
content: `_isRef(x) ? x.value = await bar : x`,
|
||||
},
|
||||
` = await `,
|
||||
{
|
||||
content: `bar`,
|
||||
},
|
||||
` })()`,
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -19,9 +19,9 @@ import { ErrorCodes } from '../../src/errors'
|
|||
import { type CompilerOptions, generate } from '../../src'
|
||||
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
|
||||
function parseWithForTransform(
|
||||
export function parseWithForTransform(
|
||||
template: string,
|
||||
options: CompilerOptions = {},
|
||||
) {
|
||||
|
@ -202,6 +202,18 @@ describe('compiler: v-for', () => {
|
|||
expect(forNode.valueAlias).toBeUndefined()
|
||||
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
|
||||
})
|
||||
|
||||
test('source containing string expression with spaces', () => {
|
||||
const { node: forNode } = parseWithForTransform(
|
||||
`<span v-for="item in state ['my items']" />`,
|
||||
)
|
||||
expect(forNode.keyAlias).toBeUndefined()
|
||||
expect(forNode.objectIndexAlias).toBeUndefined()
|
||||
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('item')
|
||||
expect((forNode.source as SimpleExpressionNode).content).toBe(
|
||||
"state ['my items']",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('errors', () => {
|
||||
|
@ -253,6 +265,18 @@ describe('compiler: v-for', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('missing source and have multiple spaces with', () => {
|
||||
const onError = vi.fn()
|
||||
parseWithForTransform('<span v-for="item in " />', { onError })
|
||||
|
||||
expect(onError).toHaveBeenCalledTimes(1)
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test('missing value', () => {
|
||||
const onError = vi.fn()
|
||||
parseWithForTransform('<span v-for="in items" />', { onError })
|
||||
|
@ -672,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,
|
||||
|
@ -798,7 +822,7 @@ describe('compiler: v-for', () => {
|
|||
constType: ConstantTypes.NOT_CONSTANT,
|
||||
},
|
||||
},
|
||||
patchFlag: genFlagText(PatchFlags.TEXT),
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
@ -822,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()
|
||||
|
@ -926,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()
|
||||
|
@ -947,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,
|
||||
|
@ -985,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,
|
||||
|
@ -1019,5 +1043,31 @@ describe('compiler: v-for', () => {
|
|||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('template v-for key w/ :key shorthand on div', () => {
|
||||
const {
|
||||
node: { codegenNode },
|
||||
} = parseWithForTransform('<div v-for="key in keys" :key>test</div>')
|
||||
expect(codegenNode.patchFlag).toBe(PatchFlags.KEYED_FRAGMENT)
|
||||
})
|
||||
|
||||
test('template v-for key w/ :key shorthand on template injected to the child', () => {
|
||||
const {
|
||||
node: { codegenNode },
|
||||
} = parseWithForTransform(
|
||||
'<template v-for="key in keys" :key><div>test</div></template>',
|
||||
)
|
||||
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
|
||||
source: { content: `keys` },
|
||||
params: [{ content: `key` }],
|
||||
innerVNodeCall: {
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
props: createObjectMatcher({
|
||||
key: '[key]',
|
||||
}),
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
baseParse as parse,
|
||||
transform,
|
||||
} from '../../src'
|
||||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||
|
@ -17,7 +18,7 @@ import { transformExpression } from '../../src/transforms/transformExpression'
|
|||
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template, options)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformExpression, transformElement],
|
||||
nodeTransforms: [transformExpression, transformElement, transformFor],
|
||||
directiveTransforms: {
|
||||
on: transformOn,
|
||||
},
|
||||
|
@ -286,6 +287,23 @@ describe('compiler: transform v-on', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('should NOT wrap as function if expression is already function expression (async)', () => {
|
||||
const { node } = parseWithVOn(
|
||||
`<div @click="async $event => await foo($event)"/>`,
|
||||
)
|
||||
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||
properties: [
|
||||
{
|
||||
key: { content: `onClick` },
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `async $event => await foo($event)`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
test('should NOT wrap as function if expression is already function expression (with newlines)', () => {
|
||||
const { node } = parseWithVOn(
|
||||
`<div @click="
|
||||
|
@ -585,6 +603,17 @@ describe('compiler: transform v-on', () => {
|
|||
expect(root.cached).toBe(1)
|
||||
})
|
||||
|
||||
test('unicode identifier should not be cached (v-for)', () => {
|
||||
const { root } = parseWithVOn(
|
||||
`<div v-for="项 in items" :key="value"><div v-on:click="foo(项)"/></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
},
|
||||
)
|
||||
expect(root.cached).toBe(0)
|
||||
})
|
||||
|
||||
test('inline function expression handler', () => {
|
||||
const { root, node } = parseWithVOn(`<div v-on:click="() => foo()" />`, {
|
||||
prefixIdentifiers: true,
|
||||
|
@ -630,6 +659,39 @@ describe('compiler: transform v-on', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('inline async arrow function with no bracket expression handler', () => {
|
||||
const { root, node } = parseWithVOn(
|
||||
`<div v-on:click="async e => await foo(e)" />`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
},
|
||||
)
|
||||
|
||||
expect(root.cached).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
expect(
|
||||
(vnodeCall.props as ObjectExpression).properties[0].value,
|
||||
).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index: 0,
|
||||
value: {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
`async `,
|
||||
{ content: `e` },
|
||||
` => await `,
|
||||
{ content: `_ctx.foo` },
|
||||
`(`,
|
||||
{ content: `e` },
|
||||
`)`,
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('inline async function expression handler', () => {
|
||||
const { root, node } = parseWithVOn(
|
||||
`<div v-on:click="async function () { await foo() } " />`,
|
||||
|
|
|
@ -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,9 +691,10 @@ 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()
|
||||
})
|
||||
|
||||
|
@ -743,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()
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.4.22",
|
||||
"version": "3.4.35",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
@ -46,13 +46,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@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.0"
|
||||
"@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,
|
||||
|
@ -331,7 +331,7 @@ export interface VNodeCall extends Node {
|
|||
| ForRenderListExpression // v-for fragment call
|
||||
| SimpleExpressionNode // hoisted
|
||||
| undefined
|
||||
patchFlag: string | undefined
|
||||
patchFlag: PatchFlags | undefined
|
||||
dynamicProps: string | SimpleExpressionNode | undefined
|
||||
directives: DirectiveArguments | undefined
|
||||
isBlock: boolean
|
||||
|
@ -416,7 +416,7 @@ export interface CacheExpression extends Node {
|
|||
type: NodeTypes.JS_CACHE_EXPRESSION
|
||||
index: number
|
||||
value: JSChildNode
|
||||
isVNode: boolean
|
||||
isVOnce: boolean
|
||||
}
|
||||
|
||||
export interface MemoExpression extends CallExpression {
|
||||
|
@ -561,7 +561,7 @@ export interface ForCodegenNode extends VNodeCall {
|
|||
tag: typeof FRAGMENT
|
||||
props: undefined
|
||||
children: ForRenderListExpression
|
||||
patchFlag: string
|
||||
patchFlag: PatchFlags
|
||||
disableTracking: boolean
|
||||
}
|
||||
|
||||
|
@ -571,7 +571,7 @@ export interface ForRenderListExpression extends CallExpression {
|
|||
}
|
||||
|
||||
export interface ForIteratorExpression extends FunctionExpression {
|
||||
returns: BlockCodegenNode
|
||||
returns?: BlockCodegenNode
|
||||
}
|
||||
|
||||
// AST Utilities ---------------------------------------------------------------
|
||||
|
@ -771,13 +771,13 @@ export function createConditionalExpression(
|
|||
export function createCacheExpression(
|
||||
index: number,
|
||||
value: JSChildNode,
|
||||
isVNode: boolean = false,
|
||||
isVOnce: boolean = false,
|
||||
): CacheExpression {
|
||||
return {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index,
|
||||
value,
|
||||
isVNode,
|
||||
isVOnce,
|
||||
loc: locStub,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,14 @@ import type {
|
|||
} from '@babel/types'
|
||||
import { walk } from 'estree-walker'
|
||||
|
||||
/**
|
||||
* Return value indicates whether the AST walked can be a constant
|
||||
*/
|
||||
export function walkIdentifiers(
|
||||
root: Node,
|
||||
onIdentifier: (
|
||||
node: Identifier,
|
||||
parent: Node,
|
||||
parent: Node | null,
|
||||
parentStack: Node[],
|
||||
isReference: boolean,
|
||||
isLocal: boolean,
|
||||
|
@ -33,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 &&
|
||||
|
@ -44,12 +47,13 @@ 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' &&
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
parent?.type === 'ObjectPattern'
|
||||
) {
|
||||
// mark property in destructure pattern
|
||||
|
@ -75,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) {
|
||||
|
@ -404,6 +408,7 @@ function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
|
|||
// no: export { NODE as foo } from "foo";
|
||||
case 'ExportSpecifier':
|
||||
// @ts-expect-error
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
if (grandparent?.source) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -572,8 +578,9 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
|
|||
|
||||
// generate inlined withScopeId helper
|
||||
if (genScopeId) {
|
||||
const param = context.isTS ? '(n: any)' : 'n'
|
||||
push(
|
||||
`const _withScopeId = n => (${helper(
|
||||
`const _withScopeId = ${param} => (${helper(
|
||||
PUSH_SCOPE_ID,
|
||||
)}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
|
||||
)
|
||||
|
@ -842,6 +849,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) + `(`)
|
||||
}
|
||||
|
@ -856,7 +885,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(`)`)
|
||||
|
@ -1008,15 +1037,16 @@ function genConditionalExpression(
|
|||
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
||||
const { push, helper, indent, deindent, newline } = context
|
||||
push(`_cache[${node.index}] || (`)
|
||||
if (node.isVNode) {
|
||||
if (node.isVOnce) {
|
||||
indent()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
||||
newline()
|
||||
push(`(`)
|
||||
}
|
||||
push(`_cache[${node.index}] = `)
|
||||
genNode(node.value, context)
|
||||
if (node.isVNode) {
|
||||
push(`,`)
|
||||
if (node.isVOnce) {
|
||||
push(`).cacheIndex = ${node.index},`)
|
||||
newline()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
|
||||
newline()
|
||||
|
|
|
@ -25,9 +25,7 @@ export const transformFilter: NodeTransform = (node, context) => {
|
|||
// filter rewrite is applied before expression transform so only
|
||||
// simple expressions are possible at this stage
|
||||
rewriteFilter(node.content, context)
|
||||
}
|
||||
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
} else if (node.type === NodeTypes.ELEMENT) {
|
||||
node.props.forEach((prop: AttributeNode | DirectiveNode) => {
|
||||
if (
|
||||
prop.type === NodeTypes.DIRECTIVE &&
|
||||
|
@ -168,6 +166,8 @@ function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
|
|||
expression = wrapFilter(expression, filters[i], context)
|
||||
}
|
||||
node.content = expression
|
||||
// reset ast since the content is replaced
|
||||
node.ast = undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -179,7 +179,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
const name = currentOpenTag!.tag
|
||||
currentOpenTag!.isSelfClosing = true
|
||||
endOpenTag(end)
|
||||
if (stack[0]?.tag === name) {
|
||||
if (stack[0] && stack[0].tag === name) {
|
||||
onCloseTag(stack.shift()!, end)
|
||||
}
|
||||
},
|
||||
|
@ -587,14 +587,14 @@ function endOpenTag(end: number) {
|
|||
|
||||
function onText(content: string, start: number, end: number) {
|
||||
if (__BROWSER__) {
|
||||
const tag = stack[0]?.tag
|
||||
const tag = stack[0] && stack[0].tag
|
||||
if (tag !== 'script' && tag !== 'style' && content.includes('&')) {
|
||||
content = currentOptions.decodeEntities!(content, false)
|
||||
}
|
||||
}
|
||||
const parent = stack[0] || currentRoot
|
||||
const lastNode = parent.children[parent.children.length - 1]
|
||||
if (lastNode?.type === NodeTypes.TEXT) {
|
||||
if (lastNode && lastNode.type === NodeTypes.TEXT) {
|
||||
// merge
|
||||
lastNode.content += content
|
||||
setLocEnd(lastNode.loc, end)
|
||||
|
@ -771,7 +771,8 @@ function isComponent({ tag, props }: ElementNode): boolean {
|
|||
tag === 'component' ||
|
||||
isUpperCase(tag.charCodeAt(0)) ||
|
||||
isCoreComponent(tag) ||
|
||||
currentOptions.isBuiltInComponent?.(tag) ||
|
||||
(currentOptions.isBuiltInComponent &&
|
||||
currentOptions.isBuiltInComponent(tag)) ||
|
||||
(currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
|
||||
) {
|
||||
return true
|
||||
|
@ -828,8 +829,8 @@ function condenseWhitespace(
|
|||
if (node.type === NodeTypes.TEXT) {
|
||||
if (!inPre) {
|
||||
if (isAllWhitespace(node.content)) {
|
||||
const prev = nodes[i - 1]?.type
|
||||
const next = nodes[i + 1]?.type
|
||||
const prev = nodes[i - 1] && nodes[i - 1].type
|
||||
const next = nodes[i + 1] && nodes[i + 1].type
|
||||
// Remove if:
|
||||
// - the whitespace is the first or last node, or:
|
||||
// - (condense mode) the whitespace is between two comments, or:
|
||||
|
@ -1063,7 +1064,7 @@ export function baseParse(input: string, options?: ParserOptions): RootNode {
|
|||
currentOptions.ns === Namespaces.SVG ||
|
||||
currentOptions.ns === Namespaces.MATH_ML
|
||||
|
||||
const delimiters = options?.delimiters
|
||||
const delimiters = options && options.delimiters
|
||||
if (delimiters) {
|
||||
tokenizer.delimiterOpen = toCharCodes(delimiters[0])
|
||||
tokenizer.delimiterClose = toCharCodes(delimiters[1])
|
||||
|
|
|
@ -102,6 +102,9 @@ export interface TransformContext
|
|||
vOnce: number
|
||||
}
|
||||
parent: ParentNode | null
|
||||
// we could use a stack but in practice we've only ever needed two layers up
|
||||
// so this is more efficient
|
||||
grandParent: ParentNode | null
|
||||
childIndex: number
|
||||
currentNode: RootNode | TemplateChildNode | null
|
||||
inVOnce: boolean
|
||||
|
@ -193,6 +196,7 @@ export function createTransformContext(
|
|||
vOnce: 0,
|
||||
},
|
||||
parent: null,
|
||||
grandParent: null,
|
||||
currentNode: root,
|
||||
childIndex: 0,
|
||||
inVOnce: false,
|
||||
|
@ -378,7 +382,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||
helper(FRAGMENT),
|
||||
undefined,
|
||||
root.children,
|
||||
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
|
||||
patchFlag,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
|
@ -401,6 +405,7 @@ export function traverseChildren(
|
|||
for (; i < parent.children.length; i++) {
|
||||
const child = parent.children[i]
|
||||
if (isString(child)) continue
|
||||
context.grandParent = context.parent
|
||||
context.parent = parent
|
||||
context.childIndex = i
|
||||
context.onNodeRemoved = nodeRemoved
|
||||
|
|
|
@ -70,8 +70,7 @@ function walk(
|
|||
: getConstantType(child, context)
|
||||
if (constantType > ConstantTypes.NOT_CONSTANT) {
|
||||
if (constantType >= ConstantTypes.CAN_HOIST) {
|
||||
;(child.codegenNode as VNodeCall).patchFlag =
|
||||
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
|
||||
;(child.codegenNode as VNodeCall).patchFlag = PatchFlags.HOISTED
|
||||
child.codegenNode = context.hoist(child.codegenNode!)
|
||||
hoistedCount++
|
||||
continue
|
||||
|
@ -81,9 +80,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) >=
|
||||
|
@ -174,12 +173,12 @@ export function getConstantType(
|
|||
if (
|
||||
codegenNode.isBlock &&
|
||||
node.tag !== 'svg' &&
|
||||
node.tag !== 'foreignObject'
|
||||
node.tag !== 'foreignObject' &&
|
||||
node.tag !== 'math'
|
||||
) {
|
||||
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:
|
||||
|
@ -364,8 +363,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']
|
||||
|
@ -117,7 +115,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
||||
// This is technically web-specific, but splitting the logic out of core
|
||||
// leads to too much unnecessary complexity.
|
||||
(tag === 'svg' || tag === 'foreignObject'))
|
||||
(tag === 'svg' || tag === 'foreignObject' || tag === 'math'))
|
||||
|
||||
// props
|
||||
if (props.length > 0) {
|
||||
|
@ -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,
|
||||
|
@ -433,6 +412,18 @@ export function buildProps(
|
|||
if (arg) mergeArgs.push(arg)
|
||||
}
|
||||
|
||||
// mark template ref on v-for
|
||||
const pushRefVForMarker = () => {
|
||||
if (context.scopes.vFor > 0) {
|
||||
properties.push(
|
||||
createObjectProperty(
|
||||
createSimpleExpression('ref_for', true),
|
||||
createSimpleExpression('true'),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const analyzePatchFlag = ({ key, value }: Property) => {
|
||||
if (isStaticExp(key)) {
|
||||
const name = key.content
|
||||
|
@ -502,14 +493,7 @@ export function buildProps(
|
|||
let isStatic = true
|
||||
if (name === 'ref') {
|
||||
hasRef = true
|
||||
if (context.scopes.vFor > 0) {
|
||||
properties.push(
|
||||
createObjectProperty(
|
||||
createSimpleExpression('ref_for', true),
|
||||
createSimpleExpression('true'),
|
||||
),
|
||||
)
|
||||
}
|
||||
pushRefVForMarker()
|
||||
// in inline mode there is no setupState object, so we can't use string
|
||||
// keys to set the ref. Instead, we need to transform it to pass the
|
||||
// actual ref instead.
|
||||
|
@ -601,13 +585,8 @@ export function buildProps(
|
|||
shouldUseBlock = true
|
||||
}
|
||||
|
||||
if (isVBind && isStaticArgOf(arg, 'ref') && context.scopes.vFor > 0) {
|
||||
properties.push(
|
||||
createObjectProperty(
|
||||
createSimpleExpression('ref_for', true),
|
||||
createSimpleExpression('true'),
|
||||
),
|
||||
)
|
||||
if (isVBind && isStaticArgOf(arg, 'ref')) {
|
||||
pushRefVForMarker()
|
||||
}
|
||||
|
||||
// special case for v-bind and v-on with no argument
|
||||
|
@ -615,6 +594,8 @@ export function buildProps(
|
|||
hasDynamicKeys = true
|
||||
if (exp) {
|
||||
if (isVBind) {
|
||||
// #10696 in case a v-bind object contains ref
|
||||
pushRefVForMarker()
|
||||
// have to merge early for compat build check
|
||||
pushMergeArg()
|
||||
if (__COMPAT__) {
|
||||
|
|
|
@ -40,16 +40,12 @@ import type {
|
|||
UpdateExpression,
|
||||
} from '@babel/types'
|
||||
import { validateBrowserExpression } from '../validateExpression'
|
||||
import { parse } from '@babel/parser'
|
||||
import { parseExpression } from '@babel/parser'
|
||||
import { IS_REF, UNREF } from '../runtimeHelpers'
|
||||
import { BindingTypes } from '../options'
|
||||
|
||||
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||
|
||||
// a heuristic safeguard to bail constant expressions on presence of
|
||||
// likely function invocation and member access
|
||||
const constantBailRE = /\w\s*\(|\.[^\d]/
|
||||
|
||||
export const transformExpression: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.INTERPOLATION) {
|
||||
node.content = processExpression(
|
||||
|
@ -120,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
|
||||
|
@ -226,8 +226,6 @@ export function processExpression(
|
|||
|
||||
// fast path if expression is a simple identifier.
|
||||
const rawExp = node.content
|
||||
// bail constant on parens (function invocation) and dot (member access)
|
||||
const bailConstant = constantBailRE.test(rawExp)
|
||||
|
||||
let ast = node.ast
|
||||
|
||||
|
@ -272,9 +270,10 @@ export function processExpression(
|
|||
? ` ${rawExp} `
|
||||
: `(${rawExp})${asParams ? `=>{}` : ``}`
|
||||
try {
|
||||
ast = parse(source, {
|
||||
ast = parseExpression(source, {
|
||||
sourceType: 'module',
|
||||
plugins: context.expressionPlugins,
|
||||
}).program
|
||||
})
|
||||
} catch (e: any) {
|
||||
context.onError(
|
||||
createCompilerError(
|
||||
|
@ -316,7 +315,13 @@ export function processExpression(
|
|||
} else {
|
||||
// The identifier is considered constant unless it's pointing to a
|
||||
// local scope variable (a v-for alias, or a v-slot prop)
|
||||
if (!(needPrefix && isLocal) && !bailConstant) {
|
||||
if (
|
||||
!(needPrefix && isLocal) &&
|
||||
(!parent ||
|
||||
(parent.type !== 'CallExpression' &&
|
||||
parent.type !== 'NewExpression' &&
|
||||
parent.type !== 'MemberExpression'))
|
||||
) {
|
||||
;(node as QualifiedId).isConstant = true
|
||||
}
|
||||
// also generate sub-expressions for other identifiers for better
|
||||
|
@ -370,9 +375,7 @@ export function processExpression(
|
|||
ret.ast = ast
|
||||
} else {
|
||||
ret = node
|
||||
ret.constType = bailConstant
|
||||
? ConstantTypes.NOT_CONSTANT
|
||||
: ConstantTypes.CAN_STRINGIFY
|
||||
ret.constType = ConstantTypes.CAN_STRINGIFY
|
||||
}
|
||||
ret.identifiers = Object.keys(knownIds)
|
||||
return ret
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { DirectiveTransform } from '../transform'
|
||||
import type { DirectiveTransform, TransformContext } from '../transform'
|
||||
import {
|
||||
type DirectiveNode,
|
||||
type ExpressionNode,
|
||||
NodeTypes,
|
||||
type SimpleExpressionNode,
|
||||
|
@ -56,11 +57,8 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
}
|
||||
}
|
||||
|
||||
const propName = camelize((arg as SimpleExpressionNode).content)
|
||||
exp = dir.exp = createSimpleExpression(propName, false, arg.loc)
|
||||
if (!__BROWSER__) {
|
||||
exp = dir.exp = processExpression(exp, context)
|
||||
}
|
||||
transformBindShorthand(dir, context)
|
||||
exp = dir.exp!
|
||||
}
|
||||
|
||||
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
||||
|
@ -98,6 +96,19 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const transformBindShorthand = (
|
||||
dir: DirectiveNode,
|
||||
context: TransformContext,
|
||||
) => {
|
||||
const arg = dir.arg!
|
||||
|
||||
const propName = camelize((arg as SimpleExpressionNode).content)
|
||||
dir.exp = createSimpleExpression(propName, false, arg.loc)
|
||||
if (!__BROWSER__) {
|
||||
dir.exp = processExpression(dir.exp, context)
|
||||
}
|
||||
}
|
||||
|
||||
const injectPrefix = (arg: ExpressionNode, prefix: string) => {
|
||||
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
if (arg.isStatic) {
|
||||
|
|
|
@ -46,7 +46,8 @@ 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(
|
||||
'for',
|
||||
|
@ -60,13 +61,20 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
]) as ForRenderListExpression
|
||||
const isTemplate = isTemplateNode(node)
|
||||
const memo = findDir(node, 'memo')
|
||||
const keyProp = findProp(node, `key`)
|
||||
const keyProp = findProp(node, `key`, false, true)
|
||||
if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) {
|
||||
// resolve :key shorthand #10882
|
||||
transformBindShorthand(keyProp, context)
|
||||
}
|
||||
const keyExp =
|
||||
keyProp &&
|
||||
(keyProp.type === NodeTypes.ATTRIBUTE
|
||||
? createSimpleExpression(keyProp.value!.content, true)
|
||||
: keyProp.exp!)
|
||||
const keyProperty = keyProp ? createObjectProperty(`key`, keyExp!) : null
|
||||
? keyProp.value
|
||||
? createSimpleExpression(keyProp.value.content, true)
|
||||
: undefined
|
||||
: keyProp.exp)
|
||||
const keyProperty =
|
||||
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
||||
|
||||
if (!__BROWSER__ && isTemplate) {
|
||||
// #2085 / #5288 process :key and v-memo expressions need to be
|
||||
|
@ -101,8 +109,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
helper(FRAGMENT),
|
||||
undefined,
|
||||
renderExp,
|
||||
fragmentFlag +
|
||||
(__DEV__ ? ` /* ${PatchFlagNames[fragmentFlag]} */` : ``),
|
||||
fragmentFlag,
|
||||
undefined,
|
||||
undefined,
|
||||
true /* isBlock */,
|
||||
|
@ -161,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,
|
||||
|
|
|
@ -17,7 +17,7 @@ import { hasScopeRef, isMemberExpression } from '../utils'
|
|||
import { TO_HANDLER_KEY } from '../runtimeHelpers'
|
||||
|
||||
const fnExpRE =
|
||||
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
||||
/^\s*(async\s*)?(\([^)]*?\)|[\w$_]+)\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
||||
|
||||
export interface VOnDirectiveNode extends DirectiveNode {
|
||||
// v-on without arg is handled directly in ./transformElements.ts due to it affecting
|
||||
|
|
|
@ -226,10 +226,7 @@ export function buildSlots(
|
|||
break
|
||||
}
|
||||
}
|
||||
if (prev && isTemplateNode(prev) && findDir(prev, 'if')) {
|
||||
// remove node
|
||||
children.splice(i, 1)
|
||||
i--
|
||||
if (prev && isTemplateNode(prev) && findDir(prev, /^(else-)?if$/)) {
|
||||
__TEST__ && assert(dynamicSlots.length > 0)
|
||||
// attach this slot to previous conditional
|
||||
let conditional = dynamicSlots[
|
||||
|
|
|
@ -62,7 +62,7 @@ export function isCoreComponent(tag: string): symbol | void {
|
|||
}
|
||||
}
|
||||
|
||||
const nonIdentifierRE = /^\d|[^\$\w]/
|
||||
const nonIdentifierRE = /^\d|[^\$\w\xA0-\uFFFF]/
|
||||
export const isSimpleIdentifier = (name: string): boolean =>
|
||||
!nonIdentifierRE.test(name)
|
||||
|
||||
|
@ -499,4 +499,4 @@ export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) {
|
|||
}
|
||||
}
|
||||
|
||||
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/
|
||||
|
|
|
@ -40,5 +40,7 @@ describe('decodeHtmlBrowser', () => {
|
|||
true,
|
||||
),
|
||||
).toBe('<strong><strong>&</strong></strong>')
|
||||
expect(decodeHtmlBrowser('"', true)).toBe('"')
|
||||
expect(decodeHtmlBrowser("'", true)).toBe("'")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,5 +1,24 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`stringify static html > should bail for <option> elements with number values 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("select", null, [
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 })
|
||||
], -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
|
@ -20,6 +39,19 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should work for <option> elements with string values 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
|
|
|
@ -485,4 +485,51 @@ describe('stringify static html', () => {
|
|||
expect(code).toMatch(`<code>text1</code>`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should work for <option> elements with string values', () => {
|
||||
const { ast, code } = compileWithStringify(
|
||||
`<div><select>${repeat(
|
||||
`<option value="1" />`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</select></div>`,
|
||||
)
|
||||
// should be optimized now
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<select>${repeat(
|
||||
`<option value="1"></option>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</select>`,
|
||||
),
|
||||
'1',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should bail for <option> elements with number values', () => {
|
||||
const { ast, code } = compileWithStringify(
|
||||
`<div><select>${repeat(
|
||||
`<option :value="1" />`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</select></div>`,
|
||||
)
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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"]`,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.4.22",
|
||||
"version": "3.4.35",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
type TextCallNode,
|
||||
type TransformContext,
|
||||
createCallExpression,
|
||||
isStaticArgOf,
|
||||
} from '@vue/compiler-core'
|
||||
import {
|
||||
escapeHtml,
|
||||
|
@ -200,6 +201,7 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
|||
// probably only need to check for most common case
|
||||
// i.e. non-phrasing-content tags inside `<p>`
|
||||
function walk(node: ElementNode): boolean {
|
||||
const isOptionTag = node.tag === 'option' && node.ns === Namespaces.HTML
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const p = node.props[i]
|
||||
// bail on non-attr bindings
|
||||
|
@ -225,6 +227,16 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
|||
) {
|
||||
return bail()
|
||||
}
|
||||
// <option :value="1"> cannot be safely stringified
|
||||
if (
|
||||
isOptionTag &&
|
||||
isStaticArgOf(p.arg, 'value') &&
|
||||
p.exp &&
|
||||
p.exp.ast &&
|
||||
p.exp.ast.type !== 'StringLiteral'
|
||||
) {
|
||||
return bail()
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
|
|
|
@ -226,3 +226,43 @@ return { modelValue, fn, fnWithDefault, str, optional }
|
|||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > w/ types, production mode, boolean + multiple types 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: [Boolean, String, Object] },
|
||||
"modelModifiers": {},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const modelValue = _useModel<boolean | string | {}>(__props, "modelValue")
|
||||
|
||||
return { modelValue }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > w/ types, production mode, function + runtime opts + multiple types 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: [Number, Function], ...{ default: () => 1 } },
|
||||
"modelModifiers": {},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const modelValue = _useModel<number | (() => number)>(__props, "modelValue")
|
||||
|
||||
return { modelValue }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
|
|
@ -304,3 +304,19 @@ return () => {}
|
|||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > rest spread non-inline 1`] = `
|
||||
"import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
|
||||
|
||||
export default {
|
||||
props: ['foo', 'bar'],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const rest = _createPropsRestProxy(__props, ["foo"])
|
||||
|
||||
return { rest }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -161,6 +161,34 @@ describe('defineModel()', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('w/ types, production mode, boolean + multiple types', () => {
|
||||
const { content } = compile(
|
||||
`
|
||||
<script setup lang="ts">
|
||||
const modelValue = defineModel<boolean | string | {}>()
|
||||
</script>
|
||||
`,
|
||||
{ isProd: true },
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch('"modelValue": { type: [Boolean, String, Object] }')
|
||||
})
|
||||
|
||||
test('w/ types, production mode, function + runtime opts + multiple types', () => {
|
||||
const { content } = compile(
|
||||
`
|
||||
<script setup lang="ts">
|
||||
const modelValue = defineModel<number | (() => number)>({ default: () => 1 })
|
||||
</script>
|
||||
`,
|
||||
{ isProd: true },
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(
|
||||
'"modelValue": { type: [Number, Function], ...{ default: () => 1 } }',
|
||||
)
|
||||
})
|
||||
|
||||
test('get / set transformers', () => {
|
||||
const { content } = compile(
|
||||
`
|
||||
|
|
|
@ -265,6 +265,27 @@ describe('sfc reactive props destructure', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('rest spread non-inline', () => {
|
||||
const { content, bindings } = compile(
|
||||
`
|
||||
<script setup>
|
||||
const { foo, ...rest } = defineProps(['foo', 'bar'])
|
||||
</script>
|
||||
<template>{{ rest.bar }}</template>
|
||||
`,
|
||||
{ inlineTemplate: false },
|
||||
)
|
||||
expect(content).toMatch(
|
||||
`const rest = _createPropsRestProxy(__props, ["foo"])`,
|
||||
)
|
||||
assertCode(content)
|
||||
expect(bindings).toStrictEqual({
|
||||
foo: BindingTypes.PROPS,
|
||||
bar: BindingTypes.PROPS,
|
||||
rest: BindingTypes.SETUP_REACTIVE_CONST,
|
||||
})
|
||||
})
|
||||
|
||||
// #6960
|
||||
test('computed static key', () => {
|
||||
const { content, bindings } = compile(`
|
||||
|
|
|
@ -9,8 +9,9 @@ import {
|
|||
registerTS,
|
||||
resolveTypeElements,
|
||||
} from '../../src/script/resolveType'
|
||||
|
||||
import { UNKNOWN_TYPE } from '../../src/script/utils'
|
||||
import ts from 'typescript'
|
||||
|
||||
registerTS(() => ts)
|
||||
|
||||
describe('resolveType', () => {
|
||||
|
@ -128,7 +129,7 @@ describe('resolveType', () => {
|
|||
defineProps<{ self: any } & Foo & Bar & Baz>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
self: ['Unknown'],
|
||||
self: [UNKNOWN_TYPE],
|
||||
foo: ['Number'],
|
||||
// both Bar & Baz has 'bar', but Baz['bar] is wider so it should be
|
||||
// preferred
|
||||
|
@ -136,6 +137,18 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('intersection type with ignore', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Foo = { foo: number }
|
||||
type Bar = { bar: string }
|
||||
defineProps<Foo & /* @vue-ignore */ Bar>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['Number'],
|
||||
})
|
||||
})
|
||||
|
||||
// #7553
|
||||
test('union type', () => {
|
||||
expect(
|
||||
|
@ -265,6 +278,27 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('utility type: ReadonlyArray', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
defineProps<{ foo: ReadonlyArray<string> }>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['Array'],
|
||||
})
|
||||
})
|
||||
|
||||
test('utility type: ReadonlyMap & Readonly Set', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
defineProps<{ foo: ReadonlyMap<string, unknown>, bar: ReadonlySet<string> }>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['Map'],
|
||||
bar: ['Set'],
|
||||
})
|
||||
})
|
||||
|
||||
test('indexed access type (literal)', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
|
@ -416,6 +450,152 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('readonly', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
defineProps<{ foo: readonly unknown[] }>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['Array'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof', () => {
|
||||
const files = {
|
||||
'/foo.ts': `export type IMP = { ${1}: 1 };`,
|
||||
}
|
||||
|
||||
const { props } = resolve(
|
||||
`
|
||||
import { IMP } from './foo'
|
||||
interface Foo { foo: 1, ${1}: 1 }
|
||||
type Bar = { bar: 1 }
|
||||
declare const obj: Bar
|
||||
declare const set: Set<any>
|
||||
declare const arr: Array<any>
|
||||
|
||||
defineProps<{
|
||||
imp: keyof IMP,
|
||||
foo: keyof Foo,
|
||||
bar: keyof Bar,
|
||||
obj: keyof typeof obj,
|
||||
set: keyof typeof set,
|
||||
arr: keyof typeof arr
|
||||
}>()
|
||||
`,
|
||||
files,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
imp: ['Number'],
|
||||
foo: ['String', 'Number'],
|
||||
bar: ['String'],
|
||||
obj: ['String'],
|
||||
set: ['String'],
|
||||
arr: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: index signature', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
declare const num: number;
|
||||
interface Foo {
|
||||
[key: symbol]: 1
|
||||
[key: string]: 1
|
||||
[key: typeof num]: 1,
|
||||
}
|
||||
|
||||
type Test<T> = T
|
||||
type Bar = {
|
||||
[key: string]: 1
|
||||
[key: Test<number>]: 1
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
foo: keyof Foo
|
||||
bar: keyof Bar
|
||||
}>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['Symbol', 'String', 'Number'],
|
||||
bar: [UNKNOWN_TYPE],
|
||||
})
|
||||
})
|
||||
|
||||
// #11129
|
||||
test('keyof: intersection type', () => {
|
||||
const { props } = resolve(`
|
||||
type A = { name: string }
|
||||
type B = A & { [key: number]: string }
|
||||
defineProps<{
|
||||
foo: keyof B
|
||||
}>()`)
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: union type', () => {
|
||||
const { props } = resolve(`
|
||||
type A = { name: string }
|
||||
type B = A | { [key: number]: string }
|
||||
defineProps<{
|
||||
foo: keyof B
|
||||
}>()`)
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: utility type', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
type Foo = Record<symbol | string, any>
|
||||
type Bar = { [key: string]: any }
|
||||
type AnyRecord = Record<keyof any, any>
|
||||
type Baz = { a: 1, ${1}: 2, b: 3}
|
||||
|
||||
defineProps<{
|
||||
record: keyof Foo,
|
||||
anyRecord: keyof AnyRecord
|
||||
partial: keyof Partial<Bar>,
|
||||
required: keyof Required<Bar>,
|
||||
readonly: keyof Readonly<Bar>,
|
||||
pick: keyof Pick<Baz, 'a' | 1>
|
||||
extract: keyof Extract<keyof Baz, 'a' | 1>
|
||||
}>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
record: ['Symbol', 'String'],
|
||||
anyRecord: ['String', 'Number', 'Symbol'],
|
||||
partial: ['String'],
|
||||
required: ['String'],
|
||||
readonly: ['String'],
|
||||
pick: ['String', 'Number'],
|
||||
extract: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: fallback to Unknown', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
interface Barr {}
|
||||
interface Bar extends Barr {}
|
||||
type Foo = keyof Bar
|
||||
defineProps<{ foo: Foo }>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
foo: [UNKNOWN_TYPE],
|
||||
})
|
||||
})
|
||||
|
||||
test('ExtractPropTypes (element-plus)', () => {
|
||||
const { props, raw } = resolve(
|
||||
`
|
||||
|
@ -455,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(
|
||||
|
@ -879,6 +1079,53 @@ describe('resolveType', () => {
|
|||
expect(deps && [...deps]).toStrictEqual(['/user.ts'])
|
||||
})
|
||||
|
||||
test('ts module resolve w/ project reference folder', () => {
|
||||
const files = {
|
||||
'/tsconfig.json': JSON.stringify({
|
||||
references: [
|
||||
{
|
||||
path: './web',
|
||||
},
|
||||
{
|
||||
path: './empty',
|
||||
},
|
||||
{
|
||||
path: './noexists-should-ignore',
|
||||
},
|
||||
],
|
||||
}),
|
||||
'/web/tsconfig.json': JSON.stringify({
|
||||
include: ['../**/*.ts', '../**/*.vue'],
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
paths: {
|
||||
bar: ['../user.ts'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
// tsconfig with no include / paths defined, should match nothing
|
||||
'/empty/tsconfig.json': JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
},
|
||||
}),
|
||||
'/user.ts': 'export type User = { bar: string }',
|
||||
}
|
||||
|
||||
const { props, deps } = resolve(
|
||||
`
|
||||
import { User } from 'bar'
|
||||
defineProps<User>()
|
||||
`,
|
||||
files,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
bar: ['String'],
|
||||
})
|
||||
expect(deps && [...deps]).toStrictEqual(['/user.ts'])
|
||||
})
|
||||
|
||||
test('ts module resolve w/ path aliased vue file', () => {
|
||||
const files = {
|
||||
'/tsconfig.json': JSON.stringify({
|
||||
|
@ -1055,6 +1302,37 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('template literals', () => {
|
||||
test('mapped types with string type', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type X = 'a' | 'b'
|
||||
defineProps<{[K in X as \`\${K}_foo\`]: string}>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
a_foo: ['String'],
|
||||
b_foo: ['String'],
|
||||
})
|
||||
})
|
||||
|
||||
// #10962
|
||||
test('mapped types with generic parameters', () => {
|
||||
const { props } = resolve(`
|
||||
type Breakpoints = 'sm' | 'md' | 'lg'
|
||||
type BreakpointFactory<T extends string, V> = {
|
||||
[K in Breakpoints as \`\${T}\${Capitalize<K>}\`]: V
|
||||
}
|
||||
type ColsBreakpoints = BreakpointFactory<'cols', number>
|
||||
defineProps<ColsBreakpoints>()
|
||||
`)
|
||||
expect(props).toStrictEqual({
|
||||
colsSm: ['Number'],
|
||||
colsMd: ['Number'],
|
||||
colsLg: ['Number'],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function resolve(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
|
||||
import { parse as babelParse } from '@babel/parser'
|
||||
import {
|
||||
type SFCTemplateCompileOptions,
|
||||
compileTemplate,
|
||||
|
@ -452,6 +453,36 @@ test('prefixing edge case for reused AST ssr mode', () => {
|
|||
).not.toThrowError()
|
||||
})
|
||||
|
||||
// #10852
|
||||
test('non-identifier expression in legacy filter syntax', () => {
|
||||
const src = `
|
||||
<template>
|
||||
<div>
|
||||
Today is
|
||||
{{ new Date() | formatDate }}
|
||||
</div>
|
||||
</template>
|
||||
`
|
||||
|
||||
const { descriptor } = parse(src)
|
||||
const compilationResult = compileTemplate({
|
||||
id: 'xxx',
|
||||
filename: 'test.vue',
|
||||
ast: descriptor.template!.ast,
|
||||
source: descriptor.template!.content,
|
||||
ssr: false,
|
||||
compilerOptions: {
|
||||
compatConfig: {
|
||||
MODE: 2,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(() => {
|
||||
babelParse(compilationResult.code, { sourceType: 'module' })
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
interface Pos {
|
||||
line: number
|
||||
column: number
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.4.22",
|
||||
"version": "3.4.35",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -42,26 +42,26 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@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.8",
|
||||
"postcss": "^8.4.38",
|
||||
"source-map-js": "^1.2.0"
|
||||
"estree-walker": "catalog:",
|
||||
"magic-string": "catalog:",
|
||||
"postcss": "^8.4.40",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.24.0",
|
||||
"@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.0.16",
|
||||
"pug": "^3.0.2",
|
||||
"sass": "^1.74.1"
|
||||
"postcss-selector-parser": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.77.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ import {
|
|||
} from './script/defineEmits'
|
||||
import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
|
||||
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
||||
import { processDefineSlots } from './script/defineSlots'
|
||||
import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
|
||||
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
||||
import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
|
||||
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
||||
|
@ -135,6 +135,16 @@ export interface ImportBinding {
|
|||
isUsedInTemplate: boolean
|
||||
}
|
||||
|
||||
const MACROS = [
|
||||
DEFINE_PROPS,
|
||||
DEFINE_EMITS,
|
||||
DEFINE_EXPOSE,
|
||||
DEFINE_OPTIONS,
|
||||
DEFINE_SLOTS,
|
||||
DEFINE_MODEL,
|
||||
WITH_DEFAULTS,
|
||||
]
|
||||
|
||||
/**
|
||||
* Compile `<script setup>`
|
||||
* It requires the whole SFC descriptor because we need to handle and merge
|
||||
|
@ -317,15 +327,18 @@ export function compileScript(
|
|||
const imported = getImportedName(specifier)
|
||||
const source = node.source.value
|
||||
const existing = ctx.userImports[local]
|
||||
if (
|
||||
source === 'vue' &&
|
||||
(imported === DEFINE_PROPS ||
|
||||
imported === DEFINE_EMITS ||
|
||||
imported === DEFINE_EXPOSE)
|
||||
) {
|
||||
warnOnce(
|
||||
`\`${imported}\` is a compiler macro and no longer needs to be imported.`,
|
||||
)
|
||||
if (source === 'vue' && MACROS.includes(imported)) {
|
||||
if (local === imported) {
|
||||
warnOnce(
|
||||
`\`${imported}\` is a compiler macro and no longer needs to be imported.`,
|
||||
)
|
||||
} else {
|
||||
ctx.error(
|
||||
`\`${imported}\` is a compiler macro and cannot be aliased to ` +
|
||||
`a different name.`,
|
||||
specifier,
|
||||
)
|
||||
}
|
||||
removeSpecifier(i)
|
||||
} else if (existing) {
|
||||
if (existing.source === source && existing.imported === imported) {
|
||||
|
@ -523,8 +536,14 @@ export function compileScript(
|
|||
)
|
||||
}
|
||||
|
||||
// defineProps / defineEmits
|
||||
// defineProps
|
||||
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
||||
if (ctx.propsDestructureRestId) {
|
||||
setupBindings[ctx.propsDestructureRestId] =
|
||||
BindingTypes.SETUP_REACTIVE_CONST
|
||||
}
|
||||
|
||||
// defineEmits
|
||||
const isDefineEmits =
|
||||
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
||||
!isDefineEmits &&
|
||||
|
@ -597,7 +616,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()
|
||||
}
|
||||
|
@ -1048,13 +1067,16 @@ function walkDeclaration(
|
|||
// export const foo = ...
|
||||
for (const { id, init: _init } of node.declarations) {
|
||||
const init = _init && unwrapTSNode(_init)
|
||||
const isDefineCall = !!(
|
||||
const isConstMacroCall =
|
||||
isConst &&
|
||||
isCallOf(
|
||||
init,
|
||||
c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS,
|
||||
c =>
|
||||
c === DEFINE_PROPS ||
|
||||
c === DEFINE_EMITS ||
|
||||
c === WITH_DEFAULTS ||
|
||||
c === DEFINE_SLOTS,
|
||||
)
|
||||
)
|
||||
if (id.type === 'Identifier') {
|
||||
let bindingType
|
||||
const userReactiveBinding = userImportAliases['reactive']
|
||||
|
@ -1071,7 +1093,7 @@ function walkDeclaration(
|
|||
} else if (
|
||||
// if a declaration is a const literal, we can mark it so that
|
||||
// the generated render fn code doesn't need to unref() it
|
||||
isDefineCall ||
|
||||
isConstMacroCall ||
|
||||
(isConst && canNeverBeRef(init!, userReactiveBinding))
|
||||
) {
|
||||
bindingType = isCallOf(init, DEFINE_PROPS)
|
||||
|
@ -1103,9 +1125,9 @@ function walkDeclaration(
|
|||
continue
|
||||
}
|
||||
if (id.type === 'ObjectPattern') {
|
||||
walkObjectPattern(id, bindings, isConst, isDefineCall)
|
||||
walkObjectPattern(id, bindings, isConst, isConstMacroCall)
|
||||
} else if (id.type === 'ArrayPattern') {
|
||||
walkArrayPattern(id, bindings, isConst, isDefineCall)
|
||||
walkArrayPattern(id, bindings, isConst, isConstMacroCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,11 +191,11 @@ function doCompileTemplate({
|
|||
if (ssr && !ssrCssVars) {
|
||||
warnOnce(
|
||||
`compileTemplate is called with \`ssr: true\` but no ` +
|
||||
`corresponding \`cssVars\` option.\`.`,
|
||||
`corresponding \`cssVars\` option.`,
|
||||
)
|
||||
}
|
||||
if (!id) {
|
||||
warnOnce(`compileTemplate now requires the \`id\` option.\`.`)
|
||||
warnOnce(`compileTemplate now requires the \`id\` option.`)
|
||||
id = ''
|
||||
}
|
||||
|
||||
|
|
|
@ -175,14 +175,14 @@ export function resolveParserPlugins(
|
|||
) {
|
||||
plugins.push('importAttributes')
|
||||
}
|
||||
if (lang === 'jsx' || lang === 'tsx') {
|
||||
if (lang === 'jsx' || lang === 'tsx' || lang === 'mtsx') {
|
||||
plugins.push('jsx')
|
||||
} else if (userPlugins) {
|
||||
// If don't match the case of adding jsx
|
||||
// should remove the jsx from user options
|
||||
userPlugins = userPlugins.filter(p => p !== 'jsx')
|
||||
}
|
||||
if (lang === 'ts' || lang === 'tsx') {
|
||||
if (lang === 'ts' || lang === 'mts' || lang === 'tsx' || lang === 'mtsx') {
|
||||
plugins.push(['typescript', { dts }], 'explicitResourceManagement')
|
||||
if (!userPlugins || !userPlugins.includes('decorators')) {
|
||||
plugins.push('decorators-legacy')
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import type { LVal, Node, TSType } from '@babel/types'
|
||||
import type { ScriptCompileContext } from './context'
|
||||
import { inferRuntimeType } from './resolveType'
|
||||
import {
|
||||
UNKNOWN_TYPE,
|
||||
concatStrings,
|
||||
isCallOf,
|
||||
toRuntimeTypeString,
|
||||
} from './utils'
|
||||
import { UNKNOWN_TYPE, isCallOf, toRuntimeTypeString } from './utils'
|
||||
import { BindingTypes, unwrapTSNode } from '@vue/compiler-dom'
|
||||
|
||||
export const DEFINE_MODEL = 'defineModel'
|
||||
|
@ -124,44 +119,50 @@ export function genModelProps(ctx: ScriptCompileContext) {
|
|||
|
||||
const isProd = !!ctx.options.isProd
|
||||
let modelPropsDecl = ''
|
||||
for (const [name, { type, options }] of Object.entries(ctx.modelDecls)) {
|
||||
for (const [name, { type, options: runtimeOptions }] of Object.entries(
|
||||
ctx.modelDecls,
|
||||
)) {
|
||||
let skipCheck = false
|
||||
|
||||
let codegenOptions = ``
|
||||
let runtimeTypes = type && inferRuntimeType(ctx, type)
|
||||
if (runtimeTypes) {
|
||||
const hasBoolean = runtimeTypes.includes('Boolean')
|
||||
const hasFunction = runtimeTypes.includes('Function')
|
||||
const hasUnknownType = runtimeTypes.includes(UNKNOWN_TYPE)
|
||||
|
||||
if (isProd || hasUnknownType) {
|
||||
runtimeTypes = runtimeTypes.filter(
|
||||
t =>
|
||||
t === 'Boolean' ||
|
||||
(hasBoolean && t === 'String') ||
|
||||
(t === 'Function' && options),
|
||||
)
|
||||
if (hasUnknownType) {
|
||||
if (hasBoolean || hasFunction) {
|
||||
runtimeTypes = runtimeTypes.filter(t => t !== UNKNOWN_TYPE)
|
||||
skipCheck = true
|
||||
} else {
|
||||
runtimeTypes = ['null']
|
||||
}
|
||||
}
|
||||
|
||||
skipCheck = !isProd && hasUnknownType && runtimeTypes.length > 0
|
||||
if (!isProd) {
|
||||
codegenOptions =
|
||||
`type: ${toRuntimeTypeString(runtimeTypes)}` +
|
||||
(skipCheck ? ', skipCheck: true' : '')
|
||||
} else if (hasBoolean || (runtimeOptions && hasFunction)) {
|
||||
// preserve types if contains boolean, or
|
||||
// function w/ runtime options that may contain default
|
||||
codegenOptions = `type: ${toRuntimeTypeString(runtimeTypes)}`
|
||||
} else {
|
||||
// able to drop types in production
|
||||
}
|
||||
}
|
||||
|
||||
let runtimeType =
|
||||
(runtimeTypes &&
|
||||
runtimeTypes.length > 0 &&
|
||||
toRuntimeTypeString(runtimeTypes)) ||
|
||||
undefined
|
||||
|
||||
const codegenOptions = concatStrings([
|
||||
runtimeType && `type: ${runtimeType}`,
|
||||
skipCheck && 'skipCheck: true',
|
||||
])
|
||||
|
||||
let decl: string
|
||||
if (runtimeType && options) {
|
||||
if (codegenOptions && runtimeOptions) {
|
||||
decl = ctx.isTS
|
||||
? `{ ${codegenOptions}, ...${options} }`
|
||||
: `Object.assign({ ${codegenOptions} }, ${options})`
|
||||
? `{ ${codegenOptions}, ...${runtimeOptions} }`
|
||||
: `Object.assign({ ${codegenOptions} }, ${runtimeOptions})`
|
||||
} else if (codegenOptions) {
|
||||
decl = `{ ${codegenOptions} }`
|
||||
} else if (runtimeOptions) {
|
||||
decl = runtimeOptions
|
||||
} else {
|
||||
decl = options || (runtimeType ? `{ ${codegenOptions} }` : '{}')
|
||||
decl = `{}`
|
||||
}
|
||||
modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},`
|
||||
|
||||
|
|
|
@ -37,10 +37,23 @@ export function processDefineOptions(
|
|||
(prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
|
||||
prop.key.type === 'Identifier'
|
||||
) {
|
||||
if (prop.key.name === 'props') propsOption = prop
|
||||
if (prop.key.name === 'emits') emitsOption = prop
|
||||
if (prop.key.name === 'expose') exposeOption = prop
|
||||
if (prop.key.name === 'slots') slotsOption = prop
|
||||
switch (prop.key.name) {
|
||||
case 'props':
|
||||
propsOption = prop
|
||||
break
|
||||
|
||||
case 'emits':
|
||||
emitsOption = prop
|
||||
break
|
||||
|
||||
case 'expose':
|
||||
exposeOption = prop
|
||||
break
|
||||
|
||||
case 'slots':
|
||||
slotsOption = prop
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,7 +239,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
|
||||
|
@ -294,7 +294,7 @@ export function transformDestructuredProps(
|
|||
}
|
||||
}
|
||||
},
|
||||
leave(node: Node, parent?: Node) {
|
||||
leave(node: Node, parent: Node | null) {
|
||||
parent && parentStack.pop()
|
||||
if (
|
||||
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
||||
|
|
|
@ -165,12 +165,19 @@ function innerResolveTypeElements(
|
|||
scope: TypeScope,
|
||||
typeParameters?: Record<string, Node>,
|
||||
): ResolvedElements {
|
||||
if (
|
||||
node.leadingComments &&
|
||||
node.leadingComments.some(c => c.value.includes('@vue-ignore'))
|
||||
) {
|
||||
return { props: {} }
|
||||
}
|
||||
switch (node.type) {
|
||||
case 'TSTypeLiteral':
|
||||
return typeElementsToMap(ctx, node.members, scope, typeParameters)
|
||||
case 'TSInterfaceDeclaration':
|
||||
return resolveInterfaceMembers(ctx, node, scope, typeParameters)
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TSTypeAnnotation':
|
||||
case 'TSParenthesizedType':
|
||||
return resolveTypeElements(
|
||||
ctx,
|
||||
|
@ -188,7 +195,7 @@ function innerResolveTypeElements(
|
|||
node.type,
|
||||
)
|
||||
case 'TSMappedType':
|
||||
return resolveMappedType(ctx, node, scope)
|
||||
return resolveMappedType(ctx, node, scope, typeParameters)
|
||||
case 'TSIndexedAccessType': {
|
||||
const types = resolveIndexType(ctx, node, scope)
|
||||
return mergeElements(
|
||||
|
@ -414,12 +421,6 @@ function resolveInterfaceMembers(
|
|||
)
|
||||
if (node.extends) {
|
||||
for (const ext of node.extends) {
|
||||
if (
|
||||
ext.leadingComments &&
|
||||
ext.leadingComments.some(c => c.value.includes('@vue-ignore'))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
const { props, calls } = resolveTypeElements(ctx, ext, scope)
|
||||
for (const key in props) {
|
||||
|
@ -439,6 +440,7 @@ function resolveInterfaceMembers(
|
|||
`Note: both in 3.2 or with the ignore, the properties in the base ` +
|
||||
`type are treated as fallthrough attrs at runtime.`,
|
||||
ext,
|
||||
scope,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -450,9 +452,18 @@ function resolveMappedType(
|
|||
ctx: TypeResolveContext,
|
||||
node: TSMappedType,
|
||||
scope: TypeScope,
|
||||
typeParameters?: Record<string, Node>,
|
||||
): ResolvedElements {
|
||||
const res: ResolvedElements = { props: {} }
|
||||
const keys = resolveStringType(ctx, node.typeParameter.constraint!, scope)
|
||||
let keys: string[]
|
||||
if (node.nameType) {
|
||||
const { name, constraint } = node.typeParameter
|
||||
scope = createChildScope(scope)
|
||||
Object.assign(scope.types, { ...typeParameters, [name]: constraint })
|
||||
keys = resolveStringType(ctx, node.nameType, scope)
|
||||
} else {
|
||||
keys = resolveStringType(ctx, node.typeParameter.constraint!, scope)
|
||||
}
|
||||
for (const key of keys) {
|
||||
res.props[key] = createProperty(
|
||||
{
|
||||
|
@ -903,7 +914,7 @@ function importSourceToScope(
|
|||
|
||||
const filename = osSpecificJoinFn(dirname(scope.filename), source)
|
||||
resolved = resolveExt(filename, fs)
|
||||
} else if (source.startsWith('.')) {
|
||||
} else if (source[0] === '.') {
|
||||
// relative import - fast path
|
||||
const filename = joinPaths(dirname(scope.filename), source)
|
||||
resolved = resolveExt(filename, fs)
|
||||
|
@ -1005,11 +1016,11 @@ function resolveWithTS(
|
|||
(c.config.options.pathsBasePath as string) ||
|
||||
dirname(c.config.options.configFilePath as string),
|
||||
)
|
||||
const included: string[] = c.config.raw?.include
|
||||
const excluded: string[] = c.config.raw?.exclude
|
||||
const included: string[] | undefined = c.config.raw?.include
|
||||
const excluded: string[] | undefined = c.config.raw?.exclude
|
||||
if (
|
||||
(!included && (!base || containingFile.startsWith(base))) ||
|
||||
included.some(p => isMatch(containingFile, joinPaths(base, p)))
|
||||
included?.some(p => isMatch(containingFile, joinPaths(base, p)))
|
||||
) {
|
||||
if (
|
||||
excluded &&
|
||||
|
@ -1080,8 +1091,12 @@ function loadTSConfig(
|
|||
const res = [config]
|
||||
if (config.projectReferences) {
|
||||
for (const ref of config.projectReferences) {
|
||||
tsConfigRefMap.set(ref.path, configPath)
|
||||
res.unshift(...loadTSConfig(ref.path, ts, fs))
|
||||
const refPath = ts.resolveProjectReferencePath(ref)
|
||||
if (!fs.fileExists(refPath)) {
|
||||
continue
|
||||
}
|
||||
tsConfigRefMap.set(refPath, configPath)
|
||||
res.unshift(...loadTSConfig(refPath, ts, fs))
|
||||
}
|
||||
}
|
||||
return res
|
||||
|
@ -1125,12 +1140,12 @@ function parseFile(
|
|||
parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'],
|
||||
): Statement[] {
|
||||
const ext = extname(filename)
|
||||
if (ext === '.ts' || ext === '.tsx') {
|
||||
if (ext === '.ts' || ext === '.mts' || ext === '.tsx' || ext === '.mtsx') {
|
||||
return babelParse(content, {
|
||||
plugins: resolveParserPlugins(
|
||||
ext.slice(1),
|
||||
parserPlugins,
|
||||
filename.endsWith('.d.ts'),
|
||||
/\.d\.m?ts$/.test(filename),
|
||||
),
|
||||
sourceType: 'module',
|
||||
}).program.body
|
||||
|
@ -1448,6 +1463,7 @@ export function inferRuntimeType(
|
|||
ctx: TypeResolveContext,
|
||||
node: Node & MaybeWithScope,
|
||||
scope = node._ownerScope || ctxToScope(ctx),
|
||||
isKeyOf = false,
|
||||
): string[] {
|
||||
try {
|
||||
switch (node.type) {
|
||||
|
@ -1467,8 +1483,29 @@ export function inferRuntimeType(
|
|||
const types = new Set<string>()
|
||||
const members =
|
||||
node.type === 'TSTypeLiteral' ? node.members : node.body.body
|
||||
|
||||
for (const m of members) {
|
||||
if (
|
||||
if (isKeyOf) {
|
||||
if (
|
||||
m.type === 'TSPropertySignature' &&
|
||||
m.key.type === 'NumericLiteral'
|
||||
) {
|
||||
types.add('Number')
|
||||
} else if (m.type === 'TSIndexSignature') {
|
||||
const annotation = m.parameters[0].typeAnnotation
|
||||
if (annotation && annotation.type !== 'Noop') {
|
||||
const type = inferRuntimeType(
|
||||
ctx,
|
||||
annotation.typeAnnotation,
|
||||
scope,
|
||||
)[0]
|
||||
if (type === UNKNOWN_TYPE) return [UNKNOWN_TYPE]
|
||||
types.add(type)
|
||||
}
|
||||
} else {
|
||||
types.add('String')
|
||||
}
|
||||
} else if (
|
||||
m.type === 'TSCallSignatureDeclaration' ||
|
||||
m.type === 'TSConstructSignatureDeclaration'
|
||||
) {
|
||||
|
@ -1477,7 +1514,10 @@ export function inferRuntimeType(
|
|||
types.add('Object')
|
||||
}
|
||||
}
|
||||
return types.size ? Array.from(types) : ['Object']
|
||||
|
||||
return types.size
|
||||
? Array.from(types)
|
||||
: [isKeyOf ? UNKNOWN_TYPE : 'Object']
|
||||
}
|
||||
case 'TSPropertySignature':
|
||||
if (node.typeAnnotation) {
|
||||
|
@ -1512,71 +1552,132 @@ export function inferRuntimeType(
|
|||
case 'TSTypeReference': {
|
||||
const resolved = resolveTypeReference(ctx, node, scope)
|
||||
if (resolved) {
|
||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
|
||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||
}
|
||||
|
||||
if (node.typeName.type === 'Identifier') {
|
||||
switch (node.typeName.name) {
|
||||
case 'Array':
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
return [node.typeName.name]
|
||||
if (isKeyOf) {
|
||||
switch (node.typeName.name) {
|
||||
case 'String':
|
||||
case 'Array':
|
||||
case 'ArrayLike':
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
case 'ReadonlyArray':
|
||||
return ['String', 'Number']
|
||||
|
||||
// TS built-in utility types
|
||||
// https://www.typescriptlang.org/docs/handbook/utility-types.html
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
case 'Record':
|
||||
case 'Pick':
|
||||
case 'Omit':
|
||||
case 'InstanceType':
|
||||
return ['Object']
|
||||
// TS built-in utility types
|
||||
case 'Record':
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
true,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Pick':
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
return ['String']
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
case 'ReadonlyMap':
|
||||
case 'ReadonlySet':
|
||||
return ['String']
|
||||
}
|
||||
} else {
|
||||
switch (node.typeName.name) {
|
||||
case 'Array':
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
return [node.typeName.name]
|
||||
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
return ['Array']
|
||||
// TS built-in utility types
|
||||
// https://www.typescriptlang.org/docs/handbook/utility-types.html
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
case 'Record':
|
||||
case 'Pick':
|
||||
case 'Omit':
|
||||
case 'InstanceType':
|
||||
return ['Object']
|
||||
|
||||
case 'NonNullable':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
).filter(t => t !== 'null')
|
||||
}
|
||||
break
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Exclude':
|
||||
case 'OmitThisParameter':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
return ['String']
|
||||
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
case 'ReadonlyArray':
|
||||
return ['Array']
|
||||
|
||||
case 'ReadonlyMap':
|
||||
return ['Map']
|
||||
case 'ReadonlySet':
|
||||
return ['Set']
|
||||
|
||||
case 'NonNullable':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
).filter(t => t !== 'null')
|
||||
}
|
||||
break
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Exclude':
|
||||
case 'OmitThisParameter':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// cannot infer, fallback to UNKNOWN: ThisParameterType
|
||||
|
@ -1587,9 +1688,9 @@ export function inferRuntimeType(
|
|||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||
|
||||
case 'TSUnionType':
|
||||
return flattenTypes(ctx, node.types, scope)
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf)
|
||||
case 'TSIntersectionType': {
|
||||
return flattenTypes(ctx, node.types, scope).filter(
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
|
||||
t => t !== UNKNOWN_TYPE,
|
||||
)
|
||||
}
|
||||
|
@ -1628,11 +1729,28 @@ export function inferRuntimeType(
|
|||
// typeof only support identifier in local scope
|
||||
const matched = scope.declares[id.name]
|
||||
if (matched) {
|
||||
return inferRuntimeType(ctx, matched, matched._ownerScope)
|
||||
return inferRuntimeType(ctx, matched, matched._ownerScope, isKeyOf)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// e.g. readonly
|
||||
case 'TSTypeOperator': {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeAnnotation,
|
||||
scope,
|
||||
node.operator === 'keyof',
|
||||
)
|
||||
}
|
||||
|
||||
case 'TSAnyKeyword': {
|
||||
if (isKeyOf) {
|
||||
return ['String', 'Number', 'Symbol']
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// always soft fail on failed runtime type inference
|
||||
|
@ -1644,14 +1762,15 @@ function flattenTypes(
|
|||
ctx: TypeResolveContext,
|
||||
types: TSType[],
|
||||
scope: TypeScope,
|
||||
isKeyOf: boolean = false,
|
||||
): string[] {
|
||||
if (types.length === 1) {
|
||||
return inferRuntimeType(ctx, types[0], scope)
|
||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf)
|
||||
}
|
||||
return [
|
||||
...new Set(
|
||||
([] as string[]).concat(
|
||||
...types.map(t => inferRuntimeType(ctx, t, scope)),
|
||||
...types.map(t => inferRuntimeType(ctx, t, scope, isKeyOf)),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -25,7 +25,7 @@ describe('ssr: element', () => {
|
|||
test('v-html', () => {
|
||||
expect(getCompiledString(`<div v-html="foo"/>`)).toMatchInlineSnapshot(`
|
||||
"\`<div>\${
|
||||
_ctx.foo
|
||||
(_ctx.foo) ?? ''
|
||||
}</div>\`"
|
||||
`)
|
||||
})
|
||||
|
|
|
@ -143,4 +143,20 @@ describe('ssr: <slot>', () => {
|
|||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('with v-if inside transition', () => {
|
||||
const { code } = compile(`<transition><slot v-if="true"/></transition>`)
|
||||
expect(code).toMatch(ssrHelpers[SSR_RENDER_SLOT_INNER])
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderSlotInner: _ssrRenderSlotInner } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
if (true) {
|
||||
_ssrRenderSlotInner(_ctx.$slots, "default", {}, null, _push, _parent, null, true)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.4.22",
|
||||
"version": "3.4.35",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
createAssignmentExpression,
|
||||
createCallExpression,
|
||||
createCompilerError,
|
||||
createCompoundExpression,
|
||||
createConditionalExpression,
|
||||
createInterpolation,
|
||||
createSequenceExpression,
|
||||
|
@ -188,7 +189,10 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||
// special cases with children override
|
||||
if (prop.type === NodeTypes.DIRECTIVE) {
|
||||
if (prop.name === 'html' && prop.exp) {
|
||||
rawChildrenMap.set(node, prop.exp)
|
||||
rawChildrenMap.set(
|
||||
node,
|
||||
createCompoundExpression([`(`, prop.exp, `) ?? ''`]),
|
||||
)
|
||||
} else if (prop.name === 'text' && prop.exp) {
|
||||
node.children = [createInterpolation(prop.exp, prop.loc)]
|
||||
} else if (prop.name === 'slot') {
|
||||
|
|
|
@ -40,24 +40,30 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
|
|||
|
||||
// #3989, #9933
|
||||
// check if this is a single slot inside a transition wrapper - since
|
||||
// transition/transition-group will unwrap the slot fragment into vnode(s) at runtime,
|
||||
// we need to avoid rendering the slot as a fragment.
|
||||
const parent = context.parent
|
||||
let componentType
|
||||
if (
|
||||
parent &&
|
||||
parent.type === NodeTypes.ELEMENT &&
|
||||
parent.tagType === ElementTypes.COMPONENT &&
|
||||
((componentType = resolveComponentType(parent, context, true)) ===
|
||||
TRANSITION ||
|
||||
componentType === TRANSITION_GROUP) &&
|
||||
parent.children.filter(c => c.type === NodeTypes.ELEMENT).length === 1
|
||||
) {
|
||||
method = SSR_RENDER_SLOT_INNER
|
||||
if (!(context.scopeId && context.slotted !== false)) {
|
||||
args.push('null')
|
||||
// transition/transition-group will unwrap the slot fragment into vnode(s)
|
||||
// at runtime, we need to avoid rendering the slot as a fragment.
|
||||
let parent = context.parent!
|
||||
if (parent) {
|
||||
const children = parent.children
|
||||
// #10743 <slot v-if> in <Transition>
|
||||
if (parent.type === NodeTypes.IF_BRANCH) {
|
||||
parent = context.grandParent!
|
||||
}
|
||||
let componentType
|
||||
if (
|
||||
parent.type === NodeTypes.ELEMENT &&
|
||||
parent.tagType === ElementTypes.COMPONENT &&
|
||||
((componentType = resolveComponentType(parent, context, true)) ===
|
||||
TRANSITION ||
|
||||
componentType === TRANSITION_GROUP) &&
|
||||
children.filter(c => c.type === NodeTypes.ELEMENT).length === 1
|
||||
) {
|
||||
method = SSR_RENDER_SLOT_INNER
|
||||
if (!(context.scopeId && context.slotted !== false)) {
|
||||
args.push('null')
|
||||
}
|
||||
args.push('true')
|
||||
}
|
||||
args.push('true')
|
||||
}
|
||||
|
||||
node.ssrCodegenNode = createCallExpression(context.helper(method), args)
|
||||
|
|
|
@ -1501,7 +1501,7 @@ describe('should work when props type is incompatible with setup returned type '
|
|||
|
||||
describe('withKeys and withModifiers as pro', () => {
|
||||
const onKeydown = withKeys(e => {}, [''])
|
||||
const onClick = withModifiers(e => {}, [''])
|
||||
const onClick = withModifiers(e => {}, [])
|
||||
;<input onKeydown={onKeydown} onClick={onClick} />
|
||||
})
|
||||
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { type InjectionKey, type Ref, inject, provide, ref } from 'vue'
|
||||
import {
|
||||
type InjectionKey,
|
||||
type Ref,
|
||||
createApp,
|
||||
inject,
|
||||
provide,
|
||||
ref,
|
||||
} from 'vue'
|
||||
import { expectType } from './utils'
|
||||
|
||||
// non-symbol keys
|
||||
|
@ -40,3 +47,8 @@ provide<Cube>(injectionKeyRef, { size: 123 })
|
|||
provide<Cube>('cube', { size: 'foo' })
|
||||
// @ts-expect-error
|
||||
provide<Cube>(123, { size: 'foo' })
|
||||
|
||||
// #10602
|
||||
const app = createApp({})
|
||||
// @ts-expect-error
|
||||
app.provide(injectionKeyRef, ref({}))
|
||||
|
|
|
@ -120,3 +120,13 @@ describe('should unwrap extended Set correctly', () => {
|
|||
expectType<string>(eset1.foo)
|
||||
expectType<number>(eset1.bar)
|
||||
})
|
||||
|
||||
describe('should not error when assignment', () => {
|
||||
const arr = reactive([''])
|
||||
let record: Record<number, string>
|
||||
record = arr
|
||||
expectType<string>(record[0])
|
||||
let record2: { [key: number]: string }
|
||||
record2 = arr
|
||||
expectType<string>(record2[0])
|
||||
})
|
||||
|
|
|
@ -172,6 +172,16 @@ describe('ref with generic', <T extends { name: string }>() => {
|
|||
expectType<string>(ss.value.name)
|
||||
})
|
||||
|
||||
describe('allow getter and setter types to be unrelated', <T>() => {
|
||||
const a = { b: ref(0) }
|
||||
const c = ref(a)
|
||||
c.value = a
|
||||
|
||||
const d = {} as T
|
||||
const e = ref(d)
|
||||
e.value = d
|
||||
})
|
||||
|
||||
// shallowRef
|
||||
type Status = 'initial' | 'ready' | 'invalidating'
|
||||
const shallowStatus = shallowRef<Status>('initial')
|
||||
|
@ -452,3 +462,7 @@ describe('toRef <-> toValue', () => {
|
|||
),
|
||||
)
|
||||
})
|
||||
|
||||
// unref
|
||||
declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string>
|
||||
expectType<string>(unref(text))
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import {
|
||||
type ComputedRef,
|
||||
type MaybeRef,
|
||||
type Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
defineModel,
|
||||
reactive,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch,
|
||||
|
@ -12,10 +16,17 @@ const source = ref('foo')
|
|||
const source2 = computed(() => source.value)
|
||||
const source3 = () => 1
|
||||
|
||||
type Bar = Ref<string> | ComputedRef<string> | (() => number)
|
||||
type Foo = readonly [Ref<string>, ComputedRef<string>, () => number]
|
||||
type OnCleanup = (fn: () => void) => void
|
||||
|
||||
const readonlyArr: Foo = [source, source2, source3]
|
||||
|
||||
// lazy watcher will have consistent types for oldValue.
|
||||
watch(source, (value, oldValue) => {
|
||||
watch(source, (value, oldValue, onCleanup) => {
|
||||
expectType<string>(value)
|
||||
expectType<string>(oldValue)
|
||||
expectType<OnCleanup>(onCleanup)
|
||||
})
|
||||
|
||||
watch([source, source2, source3], (values, oldValues) => {
|
||||
|
@ -29,6 +40,29 @@ watch([source, source2, source3] as const, (values, oldValues) => {
|
|||
expectType<Readonly<[string, string, number]>>(oldValues)
|
||||
})
|
||||
|
||||
// reactive array
|
||||
watch(reactive([source, source2, source3]), (value, oldValues) => {
|
||||
expectType<Bar[]>(value)
|
||||
expectType<Bar[]>(oldValues)
|
||||
})
|
||||
|
||||
// reactive w/ readonly tuple
|
||||
watch(reactive([source, source2, source3] as const), (value, oldValues) => {
|
||||
expectType<Foo>(value)
|
||||
expectType<Foo>(oldValues)
|
||||
})
|
||||
|
||||
// readonly array
|
||||
watch(readonlyArr, (values, oldValues) => {
|
||||
expectType<Readonly<[string, string, number]>>(values)
|
||||
expectType<Readonly<[string, string, number]>>(oldValues)
|
||||
})
|
||||
|
||||
// no type error, case from vueuse
|
||||
declare const aAny: any
|
||||
watch(aAny, (v, ov) => {})
|
||||
watch(aAny, (v, ov) => {}, { immediate: true })
|
||||
|
||||
// immediate watcher's oldValue will be undefined on first run.
|
||||
watch(
|
||||
source,
|
||||
|
@ -62,6 +96,34 @@ watch(
|
|||
{ immediate: true },
|
||||
)
|
||||
|
||||
// reactive array
|
||||
watch(
|
||||
reactive([source, source2, source3]),
|
||||
(value, oldVals) => {
|
||||
expectType<Bar[]>(value)
|
||||
expectType<Bar[] | undefined>(oldVals)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// reactive w/ readonly tuple
|
||||
watch(reactive([source, source2, source3] as const), (value, oldVals) => {
|
||||
expectType<Foo>(value)
|
||||
expectType<Foo | undefined>(oldVals)
|
||||
})
|
||||
|
||||
// readonly array
|
||||
watch(
|
||||
readonlyArr,
|
||||
(values, oldValues) => {
|
||||
expectType<Readonly<[string, string, number]>>(values)
|
||||
expectType<
|
||||
Readonly<[string | undefined, string | undefined, number | undefined]>
|
||||
>(oldValues)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// should provide correct ref.value inner type to callbacks
|
||||
const nestedRefSource = ref({
|
||||
foo: ref(1),
|
||||
|
@ -92,9 +154,10 @@ defineComponent({
|
|||
created() {
|
||||
this.$watch(
|
||||
() => this.a,
|
||||
(v, ov) => {
|
||||
(v, ov, onCleanup) => {
|
||||
expectType<number>(v)
|
||||
expectType<number>(ov)
|
||||
expectType<OnCleanup>(onCleanup)
|
||||
},
|
||||
)
|
||||
},
|
||||
|
@ -141,3 +204,10 @@ defineComponent({
|
|||
expectType<{ foo: string }>(value)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const css: MaybeRef<string> = ''
|
||||
watch(ref(css), value => {
|
||||
expectType<string>(value)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 }>,
|
||||
)
|
||||
|
|
|
@ -258,7 +258,7 @@ describe('reactivity/computed', () => {
|
|||
])
|
||||
})
|
||||
|
||||
it('debug: onTrigger', () => {
|
||||
it('debug: onTrigger (reactive)', () => {
|
||||
let events: DebuggerEvent[] = []
|
||||
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
||||
events.push(e)
|
||||
|
@ -618,4 +618,45 @@ describe('reactivity/computed', () => {
|
|||
expect(serializeInner(root)).toBe('Hello World World World World')
|
||||
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
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) => {
|
||||
events.push(e)
|
||||
})
|
||||
const obj = ref(1)
|
||||
const c = computed(() => obj.value, { onTrigger })
|
||||
|
||||
// computed won't trigger compute until accessed
|
||||
c.value
|
||||
|
||||
obj.value++
|
||||
|
||||
expect(c.value).toBe(2)
|
||||
expect(onTrigger).toHaveBeenCalledTimes(1)
|
||||
expect(events[0]).toEqual({
|
||||
effect: c.effect,
|
||||
target: toRaw(obj),
|
||||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
oldValue: 1,
|
||||
newValue: 2,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -252,6 +252,22 @@ describe('reactivity/effect', () => {
|
|||
expect(dummy).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should not observe well-known symbol keyed properties in has operation', () => {
|
||||
const key = Symbol.isConcatSpreadable
|
||||
const obj = reactive({
|
||||
[key]: true,
|
||||
}) as any
|
||||
|
||||
const spy = vi.fn(() => {
|
||||
key in obj
|
||||
})
|
||||
effect(spy)
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
|
||||
obj[key] = false
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should support manipulating an array while observing symbol keyed properties', () => {
|
||||
const key = Symbol()
|
||||
let dummy
|
||||
|
@ -830,6 +846,31 @@ describe('reactivity/effect', () => {
|
|||
expect(dummy).toBe(3)
|
||||
})
|
||||
|
||||
it('stop with multiple dependencies', () => {
|
||||
let dummy1, dummy2
|
||||
const obj1 = reactive({ prop: 1 })
|
||||
const obj2 = reactive({ prop: 1 })
|
||||
const runner = effect(() => {
|
||||
dummy1 = obj1.prop
|
||||
dummy2 = obj2.prop
|
||||
})
|
||||
|
||||
obj1.prop = 2
|
||||
expect(dummy1).toBe(2)
|
||||
|
||||
obj2.prop = 3
|
||||
expect(dummy2).toBe(3)
|
||||
|
||||
stop(runner)
|
||||
|
||||
obj1.prop = 4
|
||||
obj2.prop = 5
|
||||
|
||||
// Check that both dependencies have been cleared
|
||||
expect(dummy1).toBe(2)
|
||||
expect(dummy2).toBe(3)
|
||||
})
|
||||
|
||||
it('events: onStop', () => {
|
||||
const onStop = vi.fn()
|
||||
const runner = effect(() => {}, {
|
||||
|
|
|
@ -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++
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -450,7 +450,7 @@ describe('reactivity/readonly', () => {
|
|||
bar: markRaw({ b: 2 }),
|
||||
})
|
||||
expect(isReadonly(obj.foo)).toBe(true)
|
||||
expect(isReactive(obj.bar)).toBe(false)
|
||||
expect(isReadonly(obj.bar)).toBe(false)
|
||||
})
|
||||
|
||||
test('should make ref readonly', () => {
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
ref,
|
||||
toRef,
|
||||
toRefs,
|
||||
toValue,
|
||||
} from '../src/index'
|
||||
import { computed } from '@vue/runtime-dom'
|
||||
import { customRef, shallowRef, triggerRef, unref } from '../src/ref'
|
||||
|
@ -251,6 +252,18 @@ describe('reactivity/ref', () => {
|
|||
x: 1,
|
||||
})
|
||||
const x = toRef(a, 'x')
|
||||
|
||||
const b = ref({ y: 1 })
|
||||
|
||||
const c = toRef(b)
|
||||
|
||||
const d = toRef({ z: 1 })
|
||||
|
||||
expect(isRef(d)).toBe(true)
|
||||
expect(d.value.z).toBe(1)
|
||||
|
||||
expect(c).toBe(b)
|
||||
|
||||
expect(isRef(x)).toBe(true)
|
||||
expect(x.value).toBe(1)
|
||||
|
||||
|
@ -442,4 +455,16 @@ describe('reactivity/ref', () => {
|
|||
expect(a.value).toBe(rr)
|
||||
expect(a.value).not.toBe(r)
|
||||
})
|
||||
|
||||
test('toValue', () => {
|
||||
const a = ref(1)
|
||||
const b = computed(() => a.value + 1)
|
||||
const c = () => a.value + 2
|
||||
const d = 4
|
||||
|
||||
expect(toValue(a)).toBe(1)
|
||||
expect(toValue(b)).toBe(2)
|
||||
expect(toValue(c)).toBe(3)
|
||||
expect(toValue(d)).toBe(4)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.4.22",
|
||||
"version": "3.4.35",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { toRaw, toReactive, toReadonly } from './reactive'
|
||||
import {
|
||||
isReadonly,
|
||||
isShallow,
|
||||
toRaw,
|
||||
toReactive,
|
||||
toReadonly,
|
||||
} from './reactive'
|
||||
import {
|
||||
ITERATE_KEY,
|
||||
MAP_KEY_ITERATE_KEY,
|
||||
|
@ -72,8 +78,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)
|
||||
|
@ -84,8 +92,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)
|
||||
|
||||
|
@ -163,19 +173,6 @@ function createForEach(isReadonly: boolean, isShallow: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
interface Iterable {
|
||||
[Symbol.iterator](): Iterator
|
||||
}
|
||||
|
||||
interface Iterator {
|
||||
next(value?: any): IterationResult
|
||||
}
|
||||
|
||||
interface IterationResult {
|
||||
value: any
|
||||
done: boolean
|
||||
}
|
||||
|
||||
function createIterableMethod(
|
||||
method: string | symbol,
|
||||
isReadonly: boolean,
|
||||
|
@ -184,7 +181,7 @@ function createIterableMethod(
|
|||
return function (
|
||||
this: IterableCollections,
|
||||
...args: unknown[]
|
||||
): Iterable & Iterator {
|
||||
): Iterable<unknown> & Iterator<unknown> {
|
||||
const target = (this as any)[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const targetIsMap = isMap(rawTarget)
|
||||
|
@ -263,8 +260,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),
|
||||
|
|
|
@ -128,7 +128,7 @@ export class ReactiveEffect<T = any> {
|
|||
if (this.active) {
|
||||
preCleanupEffect(this)
|
||||
postCleanupEffect(this)
|
||||
this.onStop?.()
|
||||
this.onStop && this.onStop()
|
||||
this.active = false
|
||||
}
|
||||
}
|
||||
|
@ -281,6 +281,7 @@ export function trackEffect(
|
|||
effect._depsLength++
|
||||
}
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!))
|
||||
}
|
||||
}
|
||||
|
@ -309,6 +310,7 @@ export function triggerEffects(
|
|||
(tracking ??= dep.get(effect) === effect._trackId)
|
||||
) {
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
|
||||
}
|
||||
effect.trigger()
|
||||
|
|
|
@ -35,6 +35,8 @@ export {
|
|||
type DeepReadonly,
|
||||
type ShallowReactive,
|
||||
type UnwrapNestedRefs,
|
||||
type Reactive,
|
||||
type ReactiveMarker,
|
||||
} from './reactive'
|
||||
export {
|
||||
computed,
|
||||
|
|
|
@ -58,6 +58,15 @@ function getTargetType(value: Target) {
|
|||
// only unwrap nested ref
|
||||
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
|
||||
|
||||
declare const ReactiveMarkerSymbol: unique symbol
|
||||
|
||||
export declare class ReactiveMarker {
|
||||
private [ReactiveMarkerSymbol]?: void
|
||||
}
|
||||
|
||||
export type Reactive<T> = UnwrapNestedRefs<T> &
|
||||
(T extends readonly any[] ? ReactiveMarker : {})
|
||||
|
||||
/**
|
||||
* Returns a reactive proxy of the object.
|
||||
*
|
||||
|
@ -73,7 +82,7 @@ export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
|
|||
* @param target - The source object.
|
||||
* @see {@link https://vuejs.org/api/reactivity-core.html#reactive}
|
||||
*/
|
||||
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
|
||||
export function reactive<T extends object>(target: T): Reactive<T>
|
||||
export function reactive(target: object) {
|
||||
// if trying to observe a readonly proxy, return the readonly version.
|
||||
if (isReadonly(target)) {
|
||||
|
@ -135,7 +144,7 @@ export function shallowReactive<T extends object>(
|
|||
}
|
||||
|
||||
type Primitive = string | number | boolean | bigint | symbol | undefined | null
|
||||
type Builtin = Primitive | Function | Date | Error | RegExp
|
||||
export type Builtin = Primitive | Function | Date | Error | RegExp
|
||||
export type DeepReadonly<T> = T extends Builtin
|
||||
? T
|
||||
: T extends Map<infer K, infer V>
|
||||
|
@ -248,7 +257,11 @@ function createReactiveObject(
|
|||
) {
|
||||
if (!isObject(target)) {
|
||||
if (__DEV__) {
|
||||
warn(`value cannot be made reactive: ${String(target)}`)
|
||||
warn(
|
||||
`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
|
||||
target,
|
||||
)}`,
|
||||
)
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
|
|
@ -145,6 +145,7 @@ export function trigger(
|
|||
resetScheduling()
|
||||
}
|
||||
|
||||
export function getDepFromReactive(object: any, key: string | number | symbol) {
|
||||
return targetMap.get(object)?.get(key)
|
||||
export function getDepFromReactive(object: any, key: PropertyKey) {
|
||||
const depsMap = targetMap.get(object)
|
||||
return depsMap && depsMap.get(key)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
toRaw,
|
||||
toReactive,
|
||||
} from './reactive'
|
||||
import type { ShallowReactiveMarker } from './reactive'
|
||||
import type { Builtin, ShallowReactiveMarker } from './reactive'
|
||||
import { type Dep, createDep } from './dep'
|
||||
import { ComputedRefImpl } from './computed'
|
||||
import { getDepFromReactive } from './reactiveEffect'
|
||||
|
@ -30,8 +30,9 @@ import { warn } from './warning'
|
|||
declare const RefSymbol: unique symbol
|
||||
export declare const RawSymbol: unique symbol
|
||||
|
||||
export interface Ref<T = any> {
|
||||
value: T
|
||||
export interface Ref<T = any, S = T> {
|
||||
get value(): T
|
||||
set value(_: S)
|
||||
/**
|
||||
* Type differentiator only.
|
||||
* We need this to be in public d.ts but don't want it to show up in IDE
|
||||
|
@ -69,6 +70,7 @@ export function triggerRefValue(
|
|||
ref: RefBase<any>,
|
||||
dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
|
||||
newVal?: any,
|
||||
oldVal?: any,
|
||||
) {
|
||||
ref = toRaw(ref)
|
||||
const dep = ref.dep
|
||||
|
@ -82,6 +84,7 @@ export function triggerRefValue(
|
|||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
newValue: newVal,
|
||||
oldValue: oldVal,
|
||||
}
|
||||
: void 0,
|
||||
)
|
||||
|
@ -106,7 +109,7 @@ export function isRef(r: any): r is Ref {
|
|||
* @param value - The object to wrap in the ref.
|
||||
* @see {@link https://vuejs.org/api/reactivity-core.html#ref}
|
||||
*/
|
||||
export function ref<T>(value: T): Ref<UnwrapRef<T>>
|
||||
export function ref<T>(value: T): Ref<UnwrapRef<T>, UnwrapRef<T> | T>
|
||||
export function ref<T = any>(): Ref<T | undefined>
|
||||
export function ref(value?: unknown) {
|
||||
return createRef(value, false)
|
||||
|
@ -177,9 +180,10 @@ class RefImpl<T> {
|
|||
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
|
||||
newVal = useDirectValue ? newVal : toRaw(newVal)
|
||||
if (hasChanged(newVal, this._rawValue)) {
|
||||
const oldVal = this._rawValue
|
||||
this._rawValue = newVal
|
||||
this._value = useDirectValue ? newVal : toReactive(newVal)
|
||||
triggerRefValue(this, DirtyLevels.Dirty, newVal)
|
||||
triggerRefValue(this, DirtyLevels.Dirty, newVal, oldVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +236,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
|
||||
}
|
||||
|
||||
|
@ -252,7 +256,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)
|
||||
}
|
||||
|
||||
|
@ -475,11 +481,6 @@ function propertyToRef(
|
|||
: (new ObjectRefImpl(source, key, defaultValue) as any)
|
||||
}
|
||||
|
||||
// corner case when use narrows type
|
||||
// Ex. type RelativePath = string & { __brand: unknown }
|
||||
// RelativePath extends object -> true
|
||||
type BaseTypes = string | number | boolean
|
||||
|
||||
/**
|
||||
* This is a special exported interface for other packages to declare
|
||||
* additional types that should bail out for ref unwrapping. For example
|
||||
|
@ -496,10 +497,10 @@ type BaseTypes = string | number | boolean
|
|||
export interface RefUnwrapBailTypes {}
|
||||
|
||||
export type ShallowUnwrapRef<T> = {
|
||||
[K in keyof T]: DistrubuteRef<T[K]>
|
||||
[K in keyof T]: DistributeRef<T[K]>
|
||||
}
|
||||
|
||||
type DistrubuteRef<T> = T extends Ref<infer V> ? V : T
|
||||
type DistributeRef<T> = T extends Ref<infer V> ? V : T
|
||||
|
||||
export type UnwrapRef<T> =
|
||||
T extends ShallowRef<infer V>
|
||||
|
@ -509,8 +510,7 @@ export type UnwrapRef<T> =
|
|||
: UnwrapRefSimple<T>
|
||||
|
||||
export type UnwrapRefSimple<T> = T extends
|
||||
| Function
|
||||
| BaseTypes
|
||||
| Builtin
|
||||
| Ref
|
||||
| RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
|
||||
| { [RawSymbol]?: true }
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
} from '../src/index'
|
||||
import { createApp, nodeOps, render, serialize } from '@vue/runtime-test'
|
||||
|
||||
// reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject
|
||||
describe('api: provide/inject', () => {
|
||||
it('string keys', () => {
|
||||
const Provider = {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import {
|
||||
KeepAlive,
|
||||
TrackOpTypes,
|
||||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
onActivated,
|
||||
onBeforeMount,
|
||||
onBeforeUnmount,
|
||||
onBeforeUpdate,
|
||||
|
@ -22,8 +24,6 @@ import {
|
|||
TriggerOpTypes,
|
||||
} from '@vue/reactivity'
|
||||
|
||||
// reference: https://vue-composition-api-rfc.netlify.com/api.html#lifecycle-hooks
|
||||
|
||||
describe('api: lifecycle hooks', () => {
|
||||
it('onBeforeMount', () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
|
@ -40,6 +40,8 @@ describe('api: lifecycle hooks', () => {
|
|||
}
|
||||
render(h(Comp), root)
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
// #10863
|
||||
expect(fn).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('onMounted', () => {
|
||||
|
@ -405,4 +407,60 @@ describe('api: lifecycle hooks', () => {
|
|||
await nextTick()
|
||||
expect(fn).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
it('immediately trigger unmount during rendering', async () => {
|
||||
const fn = vi.fn()
|
||||
const toggle = ref(false)
|
||||
|
||||
const Child = {
|
||||
setup() {
|
||||
onMounted(fn)
|
||||
// trigger unmount immediately
|
||||
toggle.value = false
|
||||
return () => h('div')
|
||||
},
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () => (toggle.value ? [h(Child)] : null)
|
||||
},
|
||||
}
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
toggle.value = true
|
||||
await nextTick()
|
||||
expect(fn).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('immediately trigger unmount during rendering(with KeepAlive)', async () => {
|
||||
const mountedSpy = vi.fn()
|
||||
const activeSpy = vi.fn()
|
||||
const toggle = ref(false)
|
||||
|
||||
const Child = {
|
||||
setup() {
|
||||
onMounted(mountedSpy)
|
||||
onActivated(activeSpy)
|
||||
|
||||
// trigger unmount immediately
|
||||
toggle.value = false
|
||||
return () => h('div')
|
||||
},
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () => h(KeepAlive, [toggle.value ? h(Child) : null])
|
||||
},
|
||||
}
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
toggle.value = true
|
||||
await nextTick()
|
||||
expect(mountedSpy).toHaveBeenCalledTimes(0)
|
||||
expect(activeSpy).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -12,8 +12,6 @@ import {
|
|||
watchEffect,
|
||||
} from '@vue/runtime-test'
|
||||
|
||||
// reference: https://vue-composition-api-rfc.netlify.com/api.html#setup
|
||||
|
||||
describe('api: setup context', () => {
|
||||
it('should expose return values to template render context', () => {
|
||||
const Comp = defineComponent({
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
defineComponent,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
onErrorCaptured,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
|
@ -35,8 +36,6 @@ import {
|
|||
triggerRef,
|
||||
} from '@vue/reactivity'
|
||||
|
||||
// reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
|
||||
|
||||
describe('api: watch', () => {
|
||||
it('effect', async () => {
|
||||
const state = reactive({ count: 0 })
|
||||
|
@ -96,6 +95,30 @@ describe('api: watch', () => {
|
|||
expect(spy).toBeCalledWith([1], [1], expect.anything())
|
||||
})
|
||||
|
||||
it('should not call functions inside a reactive source array', () => {
|
||||
const spy1 = vi.fn()
|
||||
const array = reactive([spy1])
|
||||
const spy2 = vi.fn()
|
||||
watch(array, spy2, { immediate: true })
|
||||
expect(spy1).toBeCalledTimes(0)
|
||||
expect(spy2).toBeCalledWith([spy1], undefined, expect.anything())
|
||||
})
|
||||
|
||||
it('should not unwrap refs in a reactive source array', async () => {
|
||||
const val = ref({ foo: 1 })
|
||||
const array = reactive([val])
|
||||
const spy = vi.fn()
|
||||
watch(array, spy, { immediate: true })
|
||||
expect(spy).toBeCalledTimes(1)
|
||||
expect(spy).toBeCalledWith([val], undefined, expect.anything())
|
||||
|
||||
// deep by default
|
||||
val.value.foo++
|
||||
await nextTick()
|
||||
expect(spy).toBeCalledTimes(2)
|
||||
expect(spy).toBeCalledWith([val], [val], expect.anything())
|
||||
})
|
||||
|
||||
it('should not fire if watched getter result did not change', async () => {
|
||||
const spy = vi.fn()
|
||||
const n = ref(0)
|
||||
|
@ -186,6 +209,24 @@ describe('api: watch', () => {
|
|||
expect(dummy).toBe(1)
|
||||
})
|
||||
|
||||
it('directly watching reactive array with explicit deep: false', async () => {
|
||||
const val = ref(1)
|
||||
const array: any[] = reactive([val])
|
||||
const spy = vi.fn()
|
||||
watch(array, spy, { immediate: true, deep: false })
|
||||
expect(spy).toBeCalledTimes(1)
|
||||
expect(spy).toBeCalledWith([val], undefined, expect.anything())
|
||||
|
||||
val.value++
|
||||
await nextTick()
|
||||
expect(spy).toBeCalledTimes(1)
|
||||
|
||||
array[1] = 2
|
||||
await nextTick()
|
||||
expect(spy).toBeCalledTimes(2)
|
||||
expect(spy).toBeCalledWith([val, 2], [val, 2], expect.anything())
|
||||
})
|
||||
|
||||
// #9916
|
||||
it('watching shallow reactive array with deep: false', async () => {
|
||||
class foo {
|
||||
|
@ -890,6 +931,52 @@ describe('api: watch', () => {
|
|||
expect(dummy).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it('deep with symbols', async () => {
|
||||
const symbol1 = Symbol()
|
||||
const symbol2 = Symbol()
|
||||
const symbol3 = Symbol()
|
||||
const symbol4 = Symbol()
|
||||
|
||||
const raw: any = {
|
||||
[symbol1]: {
|
||||
[symbol2]: 1,
|
||||
},
|
||||
}
|
||||
|
||||
Object.defineProperty(raw, symbol3, {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
value: 1,
|
||||
})
|
||||
|
||||
const state = reactive(raw)
|
||||
const spy = vi.fn()
|
||||
|
||||
watch(() => state, spy, { deep: true })
|
||||
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(0)
|
||||
|
||||
state[symbol1][symbol2] = 2
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Non-enumerable properties don't trigger deep watchers
|
||||
state[symbol3] = 3
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Adding a new symbol property
|
||||
state[symbol4] = 1
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(2)
|
||||
|
||||
// Removing a symbol property
|
||||
delete state[symbol4]
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('immediate', async () => {
|
||||
const count = ref(0)
|
||||
const cb = vi.fn()
|
||||
|
@ -1474,4 +1561,76 @@ describe('api: watch', () => {
|
|||
unwatch!()
|
||||
expect(scope.effects.length).toBe(0)
|
||||
})
|
||||
|
||||
test('circular reference', async () => {
|
||||
const obj = { a: 1 }
|
||||
// @ts-expect-error
|
||||
obj.b = obj
|
||||
const foo = ref(obj)
|
||||
const spy = vi.fn()
|
||||
|
||||
watch(foo, spy, { deep: true })
|
||||
|
||||
// @ts-expect-error
|
||||
foo.value.b.a = 2
|
||||
await nextTick()
|
||||
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() {},
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
getCurrentInstance,
|
||||
h,
|
||||
inject,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
provide,
|
||||
ref,
|
||||
|
@ -19,7 +20,7 @@ import {
|
|||
toRefs,
|
||||
watch,
|
||||
} from '@vue/runtime-test'
|
||||
import { render as domRender, nextTick } from 'vue'
|
||||
import { render as domRender } from 'vue'
|
||||
|
||||
describe('component props', () => {
|
||||
test('stateful', () => {
|
||||
|
@ -537,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: {
|
||||
|
@ -748,4 +839,39 @@ describe('component props', () => {
|
|||
expect(`Invalid prop name: "ref"`).toHaveBeenWarned()
|
||||
expect(`Invalid prop name: "$foo"`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
// #5517
|
||||
test('events should not be props when component updating', async () => {
|
||||
let props: any
|
||||
function eventHandler() {}
|
||||
const foo = ref(1)
|
||||
|
||||
const Child = defineComponent({
|
||||
setup(_props) {
|
||||
props = _props
|
||||
},
|
||||
emits: ['event'],
|
||||
props: ['foo'],
|
||||
template: `<div>{{ foo }}</div>`,
|
||||
})
|
||||
|
||||
const Comp = defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
foo,
|
||||
eventHandler,
|
||||
}
|
||||
},
|
||||
components: { Child },
|
||||
template: `<Child @event="eventHandler" :foo="foo" />`,
|
||||
})
|
||||
|
||||
const root = document.createElement('div')
|
||||
domRender(h(Comp), root)
|
||||
expect(props).not.toHaveProperty('onEvent')
|
||||
|
||||
foo.value++
|
||||
await nextTick()
|
||||
expect(props).not.toHaveProperty('onEvent')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)', () => {
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
onUnmounted,
|
||||
ref,
|
||||
render,
|
||||
serialize,
|
||||
|
@ -769,42 +768,6 @@ describe('BaseTransition', () => {
|
|||
test('w/ KeepAlive', async () => {
|
||||
await runTestWithKeepAlive(testOutIn)
|
||||
})
|
||||
|
||||
test('w/ KeepAlive + unmount innerChild', async () => {
|
||||
const unmountSpy = vi.fn()
|
||||
const includeRef = ref(['TrueBranch'])
|
||||
const trueComp = {
|
||||
name: 'TrueBranch',
|
||||
setup() {
|
||||
onUnmounted(unmountSpy)
|
||||
const count = ref(0)
|
||||
return () => h('div', count.value)
|
||||
},
|
||||
}
|
||||
|
||||
const toggle = ref(true)
|
||||
const { props } = mockProps({ mode: 'out-in' }, true /*withKeepAlive*/)
|
||||
const root = nodeOps.createElement('div')
|
||||
const App = {
|
||||
render() {
|
||||
return h(BaseTransition, props, () => {
|
||||
return h(
|
||||
KeepAlive,
|
||||
{ include: includeRef.value },
|
||||
toggle.value ? h(trueComp) : h('div'),
|
||||
)
|
||||
})
|
||||
},
|
||||
}
|
||||
render(h(App), root)
|
||||
|
||||
// trigger toggle
|
||||
toggle.value = false
|
||||
includeRef.value = []
|
||||
|
||||
await nextTick()
|
||||
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// #6835
|
||||
|
@ -1230,4 +1193,9 @@ describe('BaseTransition', () => {
|
|||
await runTestWithKeepAlive(testInOutBeforeFinish)
|
||||
})
|
||||
})
|
||||
|
||||
// #10719
|
||||
test('should not error on KeepAlive w/ function children', () => {
|
||||
expect(() => mount({}, () => () => h('div'), true)).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue