diff --git a/.github/commit-convention.md b/.github/commit-convention.md index d17a8bd4fc9..11a64576a24 100644 --- a/.github/commit-convention.md +++ b/.github/commit-convention.md @@ -44,7 +44,7 @@ This reverts commit 667ecc1654a317a13331b17617d973392f415f02. ### Full Message Format -A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: +A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: ``` (): @@ -74,9 +74,9 @@ The scope could be anything specifying the place of the commit change. For examp The subject contains a succinct description of the change: -* use the imperative, present tense: "change" not "changed" nor "changes" -* don't capitalize the first letter -* no dot (.) at the end +- use the imperative, present tense: "change" not "changed" nor "changes" +- don't capitalize the first letter +- no dot (.) at the end ### Body diff --git a/.github/contributing.md b/.github/contributing.md index da1bd5ec453..2554582b887 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -17,6 +17,26 @@ 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 +81,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. @@ -216,7 +236,7 @@ Tests that test against source code are grouped under `nr test-unit`, while test ### `nr test-dts` -Runs `nr build-dts` first, then verify the type tests in `packages/dts-test` are working correctly against the actual built type declarations. +Runs `nr build-dts` first, then verify the type tests in `packages-private/dts-test` are working correctly against the actual built type declarations. ## Project Structure @@ -315,7 +335,7 @@ Test coverage is continuously deployed at https://coverage.vuejs.org. PRs that i ### Testing Type Definition Correctness -Type tests are located in the `packages/dts-test` directory. To run the dts tests, run `nr test-dts`. Note that the type test requires all relevant `*.d.ts` files to be built first (and the script does it for you). Once the `d.ts` files are built and up-to-date, the tests can be re-run by running `nr test-dts-only`. +Type tests are located in the `packages-private/dts-test` directory. To run the dts tests, run `nr test-dts`. Note that the type test requires all relevant `*.d.ts` files to be built first (and the script does it for you). Once the `d.ts` files are built and up-to-date, the tests can be re-run by running `nr test-dts-only`. ## Financial Contribution diff --git a/.github/maintenance.md b/.github/maintenance.md index 8d4317c6b01..b1fb550dd7a 100644 --- a/.github/maintenance.md +++ b/.github/maintenance.md @@ -80,6 +80,7 @@ Depending on the type of the PR, different considerations need to be taken into - Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`. - Performance + - Be careful about code changes in "hot paths", in particular the Virtual DOM renderer (`runtime-core/src/renderer.ts`) and component instantiation code. - Potential Breakage diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e87294b5b1d..8808b599e62 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -17,8 +17,8 @@ { groupName: 'playground', matchFileNames: [ - 'packages/sfc-playground/package.json', - 'packages/template-explorer/package.json', + 'packages-private/sfc-playground/package.json', + 'packages-private/template-explorer/package.json', ], }, { @@ -28,7 +28,7 @@ }, { groupName: 'build', - matchPackageNames: ['vite', 'terser'], + matchPackageNames: ['vite', '@swc/core'], matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'], }, { @@ -54,5 +54,13 @@ // pinned // https://github.com/vuejs/core/commit/a012e39b373f1b6918e5c89856e8f902e1bfa14d '@rollup/plugin-replace', + + // pinned + // only used in example for e2e tests + 'marked', + + // pinned, 5.0+ has exports issues + // https://github.com/vuejs/core/issues/11603 + 'entities', ], } diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 96152541448..d5c31beffba 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -16,11 +16,12 @@ jobs: - name: Install pnpm 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 diff --git a/.github/workflows/canary-minor.yml b/.github/workflows/canary-minor.yml index a29f8ff94d6..b5d75b9cebb 100644 --- a/.github/workflows/canary-minor.yml +++ b/.github/workflows/canary-minor.yml @@ -19,15 +19,15 @@ jobs: - name: Install pnpm 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' - run: pnpm install - - run: pnpm release --canary --tag minor + - run: pnpm release --canary --publish --tag minor env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index a9432ced059..bb622725aa8 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -26,6 +26,6 @@ jobs: - run: pnpm install - - run: pnpm release --canary + - run: pnpm release --canary --publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d707bae2d3..c8c217f62c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,141 +3,40 @@ on: push: branches: - '**' + tags: + - '!**' pull_request: branches: - main - minor -permissions: - contents: read # to fetch code (actions/checkout) - jobs: - unit-test: - runs-on: ubuntu-latest - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - env: - PUPPETEER_SKIP_DOWNLOAD: 'true' - steps: - - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4.0.0 - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - - run: pnpm install - - - name: Run unit tests - run: pnpm run test-unit - - unit-test-windows: - runs-on: windows-latest - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - env: - PUPPETEER_SKIP_DOWNLOAD: 'true' - steps: - - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4.0.0 - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - - run: pnpm install + test: + if: ${{ ! startsWith(github.event.head_commit.message, 'release:') && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) }} + uses: ./.github/workflows/test.yml - - name: Run compiler unit tests - run: pnpm run test-unit compiler - - - name: Run ssr unit tests - run: pnpm run test-unit server-renderer - - e2e-test: - runs-on: ubuntu-latest - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - steps: - - uses: actions/checkout@v4 - - - name: Setup cache for Chromium binary - uses: actions/cache@v4 - with: - path: ~/.cache/puppeteer - key: chromium-${{ hashFiles('pnpm-lock.yaml') }} - - - name: Install pnpm - uses: pnpm/action-setup@v4.0.0 - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - - run: pnpm install - - run: node node_modules/puppeteer/install.mjs - - - name: Run e2e tests - run: pnpm run test-e2e - - - name: verify treeshaking - run: node scripts/verify-treeshaking.js - - lint-and-test-dts: + continuous-release: + if: github.repository == 'vuejs/core' runs-on: ubuntu-latest - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - env: - PUPPETEER_SKIP_DOWNLOAD: 'true' steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v4.0.0 + uses: pnpm/action-setup@v4 - name: Install Node.js uses: actions/setup-node@v4 with: node-version-file: '.node-version' + registry-url: 'https://registry.npmjs.org' cache: 'pnpm' - - run: pnpm install - - - name: Run eslint - run: pnpm run lint - - - name: Run prettier - run: pnpm run format-check - - - name: Run type declaration tests - run: pnpm run test-dts - - # benchmarks: - # runs-on: ubuntu-latest - # if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - # env: - # PUPPETEER_SKIP_DOWNLOAD: 'true' - # steps: - # - uses: actions/checkout@v4 - - # - name: Install pnpm - # uses: pnpm/action-setup@v3.0.0 - - # - name: Install Node.js - # uses: actions/setup-node@v4 - # with: - # node-version-file: '.node-version' - # cache: 'pnpm' + - name: Install deps + run: pnpm install - # - run: pnpm install + - name: Build + run: pnpm build --withTypes - # - name: Run benchmarks - # uses: CodSpeedHQ/action@v2 - # with: - # run: pnpm vitest bench --run - # token: ${{ secrets.CODSPEED_TOKEN }} + - name: Release + run: pnpx pkg-pr-new publish --compact --pnpm './packages/*' diff --git a/.github/workflows/close-cant-reproduce-issues.yml b/.github/workflows/close-cant-reproduce-issues.yml new file mode 100644 index 00000000000..8fb48f842d8 --- /dev/null +++ b/.github/workflows/close-cant-reproduce-issues.yml @@ -0,0 +1,21 @@ +name: Auto close issues with "can't reproduce" label + +on: + schedule: + - cron: '0 0 * * *' + +permissions: + issues: write + +jobs: + close-issues: + if: github.repository == 'vuejs/core' + runs-on: ubuntu-latest + steps: + - name: can't reproduce + uses: actions-cool/issues-helper@v3 + with: + actions: 'close-issues' + token: ${{ secrets.GITHUB_TOKEN }} + labels: "can't reproduce" + inactive-day: 3 diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index 25adf7c85f4..b3e963ececa 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -9,7 +9,8 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'vuejs/core' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run') steps: - - uses: actions/github-script@v7 + - name: Check user permission + uses: actions/github-script@v7 with: script: | const user = context.payload.sender.login @@ -43,7 +44,8 @@ jobs: }) throw new Error('not allowed') } - - uses: actions/github-script@v7 + - name: Get PR info + uses: actions/github-script@v7 id: get-pr-data with: script: | @@ -56,9 +58,11 @@ jobs: return { num: context.issue.number, branchName: pr.head.ref, - repo: pr.head.repo.full_name + repo: pr.head.repo.full_name, + commit: pr.head.sha } - - uses: actions/github-script@v7 + - name: Trigger run + uses: actions/github-script@v7 id: trigger env: COMMENT: ${{ github.event.comment.body }} @@ -80,6 +84,7 @@ jobs: prNumber: '' + prData.num, branchName: prData.branchName, repo: prData.repo, - suite: suite === '' ? '-' : suite + suite: suite === '' ? '-' : suite, + commit: prData.commit } }) diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml deleted file mode 100644 index d93510607a9..00000000000 --- a/.github/workflows/release-tag.yml +++ /dev/null @@ -1,28 +0,0 @@ -on: - push: - tags: - - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - -name: Create Release - -permissions: {} -jobs: - build: - permissions: - contents: write # to create release (yyx990803/release-tag) - - name: Create Release - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@master - - name: Create Release for Tag - id: release_tag - uses: yyx990803/release-tag@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - body: | - For stable releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details. - For pre-releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/minor/CHANGELOG.md) of the `minor` branch. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..c260a728e71 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,55 @@ +name: Release + +on: + push: + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +jobs: + test: + uses: ./.github/workflows/test.yml + + release: + # prevents this action from running on forks + if: github.repository == 'vuejs/core' + needs: [test] + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + # Use Release environment for deployment protection + environment: Release + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + registry-url: 'https://registry.npmjs.org' + cache: 'pnpm' + + - name: Install deps + run: pnpm install + + - name: Build and publish + id: publish + run: | + pnpm release --publishOnly + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Create GitHub release + id: release_tag + uses: yyx990803/release-tag@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + body: | + For stable releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details. + For pre-releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/minor/CHANGELOG.md) of the `minor` branch. diff --git a/.github/workflows/size-data.yml b/.github/workflows/size-data.yml index 767114e0d9f..7f8bf7b08ca 100644 --- a/.github/workflows/size-data.yml +++ b/.github/workflows/size-data.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - minor pull_request: branches: - main @@ -17,6 +18,7 @@ env: jobs: upload: + if: github.repository == 'vuejs/core' runs-on: ubuntu-latest steps: @@ -36,6 +38,12 @@ jobs: - run: pnpm run size + - name: Save PR number & base branch + if: ${{github.event_name == 'pull_request'}} + run: | + echo ${{ github.event.number }} > ./temp/size/number.txt + echo ${{ github.base_ref }} > ./temp/size/base.txt + - name: Upload Size Data uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/size-report.yml b/.github/workflows/size-report.yml index 016092409c9..f9d2052b69c 100644 --- a/.github/workflows/size-report.yml +++ b/.github/workflows/size-report.yml @@ -18,6 +18,7 @@ jobs: size-report: runs-on: ubuntu-latest if: > + github.repository == 'vuejs/core' && github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' steps: @@ -36,24 +37,36 @@ jobs: run: pnpm install - name: Download Size Data - uses: dawidd6/action-download-artifact@v4 + uses: dawidd6/action-download-artifact@v6 with: name: size-data run_id: ${{ github.event.workflow_run.id }} path: temp/size + - name: Read PR Number + id: pr-number + uses: juliangruber/read-file-action@v1 + with: + path: temp/size/number.txt + + - name: Read base branch + id: pr-base + uses: juliangruber/read-file-action@v1 + with: + path: temp/size/base.txt + - name: Download Previous Size Data - uses: dawidd6/action-download-artifact@v4 + uses: dawidd6/action-download-artifact@v6 with: - branch: main + branch: ${{ steps.pr-base.outputs.content }} workflow: size-data.yml event: push name: size-data path: temp/size-prev if_no_artifact_found: warn - - name: Compare size - run: pnpm tsx scripts/size-report.ts > size-report.md + - name: Prepare report + run: node scripts/size-report.js > size-report.md - name: Read Size Report id: size-report @@ -65,6 +78,7 @@ jobs: uses: actions-cool/maintain-one-comment@v3 with: token: ${{ secrets.GITHUB_TOKEN }} + number: ${{ steps.pr-number.outputs.content }} body: | ${{ steps.size-report.outputs.content }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000000..70dc8224813 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,108 @@ +name: 'test' + +on: workflow_call + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + unit-test: + runs-on: ubuntu-latest + env: + PUPPETEER_SKIP_DOWNLOAD: 'true' + steps: + - uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4.0.0 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + cache: 'pnpm' + + - run: pnpm install + + - name: Run unit tests + run: pnpm run test-unit + + unit-test-windows: + runs-on: windows-latest + env: + PUPPETEER_SKIP_DOWNLOAD: 'true' + steps: + - uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4.0.0 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + cache: 'pnpm' + + - run: pnpm install + + - name: Run compiler unit tests + run: pnpm run test-unit compiler + + - name: Run ssr unit tests + run: pnpm run test-unit server-renderer + + e2e-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup cache for Chromium binary + uses: actions/cache@v4 + with: + path: ~/.cache/puppeteer + key: chromium-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Install pnpm + uses: pnpm/action-setup@v4.0.0 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + cache: 'pnpm' + + - run: pnpm install + - run: node node_modules/puppeteer/install.mjs + + - name: Run e2e tests + run: pnpm run test-e2e + + - name: verify treeshaking + run: node scripts/verify-treeshaking.js + + lint-and-test-dts: + runs-on: ubuntu-latest + env: + PUPPETEER_SKIP_DOWNLOAD: 'true' + steps: + - uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4.0.0 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + cache: 'pnpm' + + - run: pnpm install + + - name: Run eslint + run: pnpm run lint + + - name: Run prettier + run: pnpm run format-check + + - name: Run type declaration tests + run: pnpm run test-dts diff --git a/.prettierignore b/.prettierignore index fbd3dca8ca3..ca3c40849fd 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,3 @@ dist -*.md -*.html pnpm-lock.yaml +CHANGELOG*.md diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000000..91ebd56925c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["vitest.explorer"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index a390f9b0c1c..930c8b590cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,813 +1,473 @@ -## [3.4.27](https://github.com/vuejs/core/compare/v3.4.26...v3.4.27) (2024-05-06) +## [3.5.12](https://github.com/vuejs/core/compare/v3.5.11...v3.5.12) (2024-10-11) ### 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) - - -### Bug Fixes - -* **compat:** fix $options mutation + adjust private API initialization ([d58d133](https://github.com/vuejs/core/commit/d58d133b1cde5085cc5ab0012d544cafd62a6ee6)), closes [#10626](https://github.com/vuejs/core/issues/10626) [#10636](https://github.com/vuejs/core/issues/10636) -* **compile-sfc:** analyze v-bind shorthand usage in template ([#10518](https://github.com/vuejs/core/issues/10518)) ([e5919d4](https://github.com/vuejs/core/commit/e5919d4658cfe0bb18c76611dd3c3432c57f94ab)), closes [#10515](https://github.com/vuejs/core/issues/10515) -* **compiler-core:** fix loc.source for end tags with whitespace before > ([16174da](https://github.com/vuejs/core/commit/16174da21d6c8ac0aae027dd964fc35e221ded0a)), closes [#10694](https://github.com/vuejs/core/issues/10694) [#10695](https://github.com/vuejs/core/issues/10695) -* **compiler-core:** fix v-bind shorthand for component :is ([04af950](https://github.com/vuejs/core/commit/04af9504a720c8e6de26c04b1282cf14fa1bcee3)), closes [#10469](https://github.com/vuejs/core/issues/10469) [#10471](https://github.com/vuejs/core/issues/10471) -* **compiler-sfc:** :is() and :where() in compound selectors ([#10522](https://github.com/vuejs/core/issues/10522)) ([660cadc](https://github.com/vuejs/core/commit/660cadc7aadb909ef33a6055c4374902a82607a4)), closes [#10511](https://github.com/vuejs/core/issues/10511) -* **compiler-sfc:** also search for `.tsx` when type import's extension is omitted ([#10637](https://github.com/vuejs/core/issues/10637)) ([34106bc](https://github.com/vuejs/core/commit/34106bc9c715247211273bb9c64712f04bd4879d)), closes [#10635](https://github.com/vuejs/core/issues/10635) -* **compiler-sfc:** fix defineModel coercion for boolean + string union types ([#9603](https://github.com/vuejs/core/issues/9603)) ([0cef65c](https://github.com/vuejs/core/commit/0cef65cee411356e721bbc90d731fc52fc8fce94)), closes [#9587](https://github.com/vuejs/core/issues/9587) [#10676](https://github.com/vuejs/core/issues/10676) -* **compiler-sfc:** fix universal selector scope ([#10551](https://github.com/vuejs/core/issues/10551)) ([54a6afa](https://github.com/vuejs/core/commit/54a6afa75a546078e901ce0882da53b97420fe94)), closes [#10548](https://github.com/vuejs/core/issues/10548) -* **compiler-sfc:** use options module name if options provide runtimeModuleName options ([#10457](https://github.com/vuejs/core/issues/10457)) ([e76d743](https://github.com/vuejs/core/commit/e76d7430aa7470342f3fe263145a0fa92f5898ca)), closes [#10454](https://github.com/vuejs/core/issues/10454) -* **custom-element:** avoid setting attr to null if it is removed ([#9012](https://github.com/vuejs/core/issues/9012)) ([b49306a](https://github.com/vuejs/core/commit/b49306adff4572d90a42ccd231387f16eb966bbe)), closes [#9006](https://github.com/vuejs/core/issues/9006) [#10324](https://github.com/vuejs/core/issues/10324) -* **hydration:** properly handle optimized mode during hydrate node ([#10638](https://github.com/vuejs/core/issues/10638)) ([2ec06fd](https://github.com/vuejs/core/commit/2ec06fd6c8383e11cdf4efcab1707f973bd6a54c)), closes [#10607](https://github.com/vuejs/core/issues/10607) -* **reactivity:** computed should not be detected as true by isProxy ([#10401](https://github.com/vuejs/core/issues/10401)) ([9da34d7](https://github.com/vuejs/core/commit/9da34d7af81607fddd1f32f21b3b4002402ff1cc)) -* **reactivity:** fix hasOwnProperty key coercion edge cases ([969c5fb](https://github.com/vuejs/core/commit/969c5fb30f4c725757c7385abfc74772514eae4b)) -* **reactivity:** fix tracking when hasOwnProperty is called with non-string value ([c3c5dc9](https://github.com/vuejs/core/commit/c3c5dc93fbccc196771458f0b43cd5b7ad1863f4)), closes [#10455](https://github.com/vuejs/core/issues/10455) [#10464](https://github.com/vuejs/core/issues/10464) -* **runtime-core:** fix errorHandler causes an infinite loop during execution ([#9575](https://github.com/vuejs/core/issues/9575)) ([ab59bed](https://github.com/vuejs/core/commit/ab59bedae4e5e40b28804d88a51305b236d4a873)) -* **runtime-core:** handle invalid values in callWithAsyncErrorHandling ([53d15d3](https://github.com/vuejs/core/commit/53d15d3f76184eed67a18d35e43d9a2062f8e121)) -* **runtime-core:** show hydration mismatch details for non-rectified mismatches too when __PROD_HYDRATION_MISMATCH_DETAILS__ is set ([#10599](https://github.com/vuejs/core/issues/10599)) ([0dea7f9](https://github.com/vuejs/core/commit/0dea7f9a260d93eb6c39aabac8c94c2c9b2042dd)) -* **runtime-dom:** `v-model` string/number coercion for multiselect options ([#10576](https://github.com/vuejs/core/issues/10576)) ([db374e5](https://github.com/vuejs/core/commit/db374e54c9f5e07324728b85c74eca84e28dd352)) -* **runtime-dom:** fix css v-bind for suspensed components ([#8523](https://github.com/vuejs/core/issues/8523)) ([67722ba](https://github.com/vuejs/core/commit/67722ba23b7c36ab8f3fa2d2b4df08e4ddc322e1)), closes [#8520](https://github.com/vuejs/core/issues/8520) -* **runtime-dom:** force update v-model number with leading 0 ([#10506](https://github.com/vuejs/core/issues/10506)) ([15ffe8f](https://github.com/vuejs/core/commit/15ffe8f2c954359770c57e4d9e589b0b622e4a60)), closes [#10503](https://github.com/vuejs/core/issues/10503) [#10615](https://github.com/vuejs/core/issues/10615) -* **runtime-dom:** sanitize wrongly passed string value as event handler ([#8953](https://github.com/vuejs/core/issues/8953)) ([7ccd453](https://github.com/vuejs/core/commit/7ccd453dd004076cad49ec9f56cd5fe97b7b6ed8)), closes [#8818](https://github.com/vuejs/core/issues/8818) -* **ssr:** don't render v-if comments in TransitionGroup ([#6732](https://github.com/vuejs/core/issues/6732)) ([5a96267](https://github.com/vuejs/core/commit/5a9626708e970c6fc0b6f786e3c80c22273d126f)), closes [#6715](https://github.com/vuejs/core/issues/6715) -* **Transition:** ensure the KeepAlive children unmount w/ out-in mode ([#10632](https://github.com/vuejs/core/issues/10632)) ([fc99e4d](https://github.com/vuejs/core/commit/fc99e4d3f01b190ef9fd3c218a668ba9124a32bc)), closes [#10620](https://github.com/vuejs/core/issues/10620) -* **TransitionGroup:** avoid set transition hooks for comment nodes and text nodes ([#9421](https://github.com/vuejs/core/issues/9421)) ([140a768](https://github.com/vuejs/core/commit/140a7681cc3bba22f55d97fd85a5eafe97a1230f)), closes [#4621](https://github.com/vuejs/core/issues/4621) [#4622](https://github.com/vuejs/core/issues/4622) [#5153](https://github.com/vuejs/core/issues/5153) [#5168](https://github.com/vuejs/core/issues/5168) [#7898](https://github.com/vuejs/core/issues/7898) [#9067](https://github.com/vuejs/core/issues/9067) -* **types:** avoid merging object union types when using withDefaults ([#10596](https://github.com/vuejs/core/issues/10596)) ([37ba93c](https://github.com/vuejs/core/commit/37ba93c213a81f99a68a99ef5d4065d61b150ba3)), closes [#10594](https://github.com/vuejs/core/issues/10594) +* **compiler-dom:** avoid stringify option with null value ([#12096](https://github.com/vuejs/core/issues/12096)) ([f6d9926](https://github.com/vuejs/core/commit/f6d99262364b7444ebab8742158599e8cdd79eaa)), closes [#12093](https://github.com/vuejs/core/issues/12093) +* **compiler-sfc:** do not skip TSInstantiationExpression when transforming props destructure ([#12064](https://github.com/vuejs/core/issues/12064)) ([d3ecde8](https://github.com/vuejs/core/commit/d3ecde8a696ff62c8d0ab067fd1d7ee0565b63c5)) +* **compiler-sfc:** use sass modern api if available and avoid deprecation warning ([#11992](https://github.com/vuejs/core/issues/11992)) ([4474c11](https://github.com/vuejs/core/commit/4474c113d1fb1c26298dd6794275d5b5c7cc4d93)) +* **compiler:** clone loc to `ifNode` ([#12131](https://github.com/vuejs/core/issues/12131)) ([cde2c06](https://github.com/vuejs/core/commit/cde2c0671b00d4f6111fcbd7aa76e45872f20b0c)), closes [vuejs/language-tools#4911](https://github.com/vuejs/language-tools/issues/4911) +* **custom-element:** properly remove hyphenated attribute ([#12143](https://github.com/vuejs/core/issues/12143)) ([e16e9a7](https://github.com/vuejs/core/commit/e16e9a7341e7cfb3c443da4e5e5b06e8158712c3)), closes [#12139](https://github.com/vuejs/core/issues/12139) +* **defineModel:** handle kebab-case model correctly ([#12063](https://github.com/vuejs/core/issues/12063)) ([c0418a3](https://github.com/vuejs/core/commit/c0418a3b8fa96a0b108ab71b7aab5d3388f90557)), closes [#12060](https://github.com/vuejs/core/issues/12060) +* **deps:** update dependency monaco-editor to ^0.52.0 ([#12119](https://github.com/vuejs/core/issues/12119)) ([f7cbea2](https://github.com/vuejs/core/commit/f7cbea2111c7770a180b640f36f6a5d4d6abc698)) +* **hydration:** provide compat fallback for idle callback hydration strategy ([#11935](https://github.com/vuejs/core/issues/11935)) ([1ae545a](https://github.com/vuejs/core/commit/1ae545a3786abef983be1c969726489685569c92)) +* **reactivity:** trigger reactivity for Map key `undefined` ([#12055](https://github.com/vuejs/core/issues/12055)) ([7ad289e](https://github.com/vuejs/core/commit/7ad289e1e7fea654524008ff91e43a8b8a55ef22)), closes [#12054](https://github.com/vuejs/core/issues/12054) +* **runtime-core:** allow symbol values for slot prop key ([#12069](https://github.com/vuejs/core/issues/12069)) ([d9d4d4e](https://github.com/vuejs/core/commit/d9d4d4e158cd51a9ddda249f29de8467f60b2792)), closes [#12068](https://github.com/vuejs/core/issues/12068) +* **runtime-core:** fix required prop check false positive for kebab-case edge cases ([#12034](https://github.com/vuejs/core/issues/12034)) ([9da1ac1](https://github.com/vuejs/core/commit/9da1ac156552ac449754e1373aac7e349841becb)), closes [#12011](https://github.com/vuejs/core/issues/12011) +* **runtime-dom:** prevent unnecessary updates in v-model checkbox when value is unchanged ([#12146](https://github.com/vuejs/core/issues/12146)) ([ea943af](https://github.com/vuejs/core/commit/ea943afe404c4ca4b729906c5e8daf7aa2ccde9b)), closes [#12144](https://github.com/vuejs/core/issues/12144) +* **teleport:** handle disabled teleport with updateCssVars ([#12113](https://github.com/vuejs/core/issues/12113)) ([76a8223](https://github.com/vuejs/core/commit/76a8223199c148b79a5c0ea19e235164809760cd)), closes [#12112](https://github.com/vuejs/core/issues/12112) +* **transition/ssr:** make transition appear work with Suspense in SSR ([#12047](https://github.com/vuejs/core/issues/12047)) ([f1a4f67](https://github.com/vuejs/core/commit/f1a4f67aedfe83e440c54222213f070774faa421)), closes [#12046](https://github.com/vuejs/core/issues/12046) +* **types:** ensure `this.$props` type does not include `string` ([#12123](https://github.com/vuejs/core/issues/12123)) ([704173e](https://github.com/vuejs/core/commit/704173e24276706de672cca6c9507e4dd9651197)), closes [#12122](https://github.com/vuejs/core/issues/12122) +* **types:** retain union type narrowing with defaults applied ([#12108](https://github.com/vuejs/core/issues/12108)) ([05685a9](https://github.com/vuejs/core/commit/05685a9d7c42d4cd37169b867833776b91154fed)), closes [#12106](https://github.com/vuejs/core/issues/12106) +* **useId:** ensure useId consistency when using serverPrefetch ([#12128](https://github.com/vuejs/core/issues/12128)) ([b4d3534](https://github.com/vuejs/core/commit/b4d35349d8bc39aa15bd3f1094d230e5928b177c)), closes [#12102](https://github.com/vuejs/core/issues/12102) +* **watch:** watchEffect clean-up with SSR ([#12097](https://github.com/vuejs/core/issues/12097)) ([b094c72](https://github.com/vuejs/core/commit/b094c72b3d40c52c7124f145a9db028509a11202)), closes [#11956](https://github.com/vuejs/core/issues/11956) ### Performance Improvements -* add `__NO_SIDE_EFFECTS__` comments ([#9053](https://github.com/vuejs/core/issues/9053)) ([d46df6b](https://github.com/vuejs/core/commit/d46df6bdb14b0509eb2134b3f85297a306821c61)) -* optimize component props/slots internal object checks ([6af733d](https://github.com/vuejs/core/commit/6af733d68eb400a3d2c5ef5f465fff32b72a324e)) -* **ssr:** avoid calling markRaw on component instance proxy ([4bc9f39](https://github.com/vuejs/core/commit/4bc9f39f028af7313e5cf24c16915a1985d27bf8)) -* **ssr:** optimize setup context creation for ssr in v8 ([ca84316](https://github.com/vuejs/core/commit/ca84316bfb3410efe21333670a6ad5cd21857396)) +* **reactivity:** avoid unnecessary recursion in removeSub ([#12135](https://github.com/vuejs/core/issues/12135)) ([ec917cf](https://github.com/vuejs/core/commit/ec917cfdb9d0169cd0835d3a0e28244242657dc9)) -## [3.4.21](https://github.com/vuejs/core/compare/v3.4.20...v3.4.21) (2024-02-28) +## [3.5.11](https://github.com/vuejs/core/compare/v3.5.10...v3.5.11) (2024-10-03) ### Bug Fixes -* **runtime-dom:** avoid unset option's value ([#10416](https://github.com/vuejs/core/issues/10416)) ([b3f8b5a](https://github.com/vuejs/core/commit/b3f8b5a4e700d4c47a146b6040882287d180f6cb)), closes [#10412](https://github.com/vuejs/core/issues/10412) [#10396](https://github.com/vuejs/core/issues/10396) -* **suspense:** ensure nested suspense patching if in fallback state ([#10417](https://github.com/vuejs/core/issues/10417)) ([7c97778](https://github.com/vuejs/core/commit/7c97778aec1e3513035e5df265e1b8a7801f6106)), closes [#10415](https://github.com/vuejs/core/issues/10415) -* **warning:** stringify args in warn handler ([#10414](https://github.com/vuejs/core/issues/10414)) ([bc37258](https://github.com/vuejs/core/commit/bc37258caa2f6f67f4554ab8587aca3798d92124)), closes [#10409](https://github.com/vuejs/core/issues/10409) +* **compiler-sfc:** do not skip `TSSatisfiesExpression` when transforming props destructure ([#12062](https://github.com/vuejs/core/issues/12062)) ([2328b05](https://github.com/vuejs/core/commit/2328b051f4efa1f1394b7d4e73b7c3f76e430e7c)), closes [#12061](https://github.com/vuejs/core/issues/12061) +* **reactivity:** prevent overwriting `next` property during batch processing ([#12075](https://github.com/vuejs/core/issues/12075)) ([d3f5e6e](https://github.com/vuejs/core/commit/d3f5e6e5319b4ffaa55ca9a2ea3d95d78e76fa58)), closes [#12072](https://github.com/vuejs/core/issues/12072) +* **scheduler:** job ordering when the post queue is flushing ([#12090](https://github.com/vuejs/core/issues/12090)) ([577edca](https://github.com/vuejs/core/commit/577edca8e7795436efd710d1c289ea8ea2642b0e)) +* **types:** correctly infer `TypeProps` when it is `any` ([#12073](https://github.com/vuejs/core/issues/12073)) ([57315ab](https://github.com/vuejs/core/commit/57315ab9688c9741a271d1075bbd28cbe5f71e2f)), closes [#12058](https://github.com/vuejs/core/issues/12058) +* **types:** should not intersect `PublicProps` with `Props` ([#12077](https://github.com/vuejs/core/issues/12077)) ([6f85894](https://github.com/vuejs/core/commit/6f8589437635706f825ccec51800effba1d2bf5f)) +* **types:** infer the first generic type of `Ref` correctly ([#12094](https://github.com/vuejs/core/issues/12094)) ([c97bb84](https://github.com/vuejs/core/commit/c97bb84d0b0a16b012f886b6498e924415ed63e5)) -## [3.4.20](https://github.com/vuejs/core/compare/v3.4.19...v3.4.20) (2024-02-26) +## [3.5.10](https://github.com/vuejs/core/compare/v3.5.9...v3.5.10) (2024-09-27) ### Bug Fixes -* **parser:** should not treat uppercase components as special tags ([e0e0253](https://github.com/vuejs/core/commit/e0e02535cdea1aeb1cfaff0d61d4b2555e555c36)), closes [#10395](https://github.com/vuejs/core/issues/10395) -* **runtime-dom:** avoid always resetting nullish option value ([ff130c4](https://github.com/vuejs/core/commit/ff130c470204086edaa093fb8fdc1247c69cba69)), closes [#10396](https://github.com/vuejs/core/issues/10396) -* **runtime-dom:** fix nested v-show priority regression ([364f890](https://github.com/vuejs/core/commit/364f8902c8657faec7c3a4d70a5b2c856567e92d)), closes [#10338](https://github.com/vuejs/core/issues/10338) -* **runtime-dom:** v-bind style should clear previous css string value ([#10373](https://github.com/vuejs/core/issues/10373)) ([e2d3235](https://github.com/vuejs/core/commit/e2d323538e71d404e729148fd19a08bbc2e3da9b)), closes [#10352](https://github.com/vuejs/core/issues/10352) -* **suspense:** handle suspense switching with nested suspense ([#10184](https://github.com/vuejs/core/issues/10184)) ([0f3da05](https://github.com/vuejs/core/commit/0f3da05ea201761529bb95594df1e2cee20b7107)), closes [#10098](https://github.com/vuejs/core/issues/10098) -* **types:** better typing for direct setup signature of defineComponent ([#10357](https://github.com/vuejs/core/issues/10357)) ([eadce5b](https://github.com/vuejs/core/commit/eadce5b75356656fd2209ebdb406d34823c961b7)), closes [#8604](https://github.com/vuejs/core/issues/8604) [#8855](https://github.com/vuejs/core/issues/8855) +* **custom-element:** properly set kebab-case props on Vue custom elements ([ea3efa0](https://github.com/vuejs/core/commit/ea3efa09e008918c1d9ba7226833a8b1a7a57244)), closes [#12030](https://github.com/vuejs/core/issues/12030) [#12032](https://github.com/vuejs/core/issues/12032) +* **reactivity:** fix nested batch edge case ([93c95dd](https://github.com/vuejs/core/commit/93c95dd4cd416503f43a98a1455f62658d22b0b2)) +* **reactivity:** only clear notified flags for computed in first batch iteration ([aa9ef23](https://github.com/vuejs/core/commit/aa9ef2386a0cd39a174e5a887ec2b1a3525034fc)), closes [#12045](https://github.com/vuejs/core/issues/12045) +* **types/ref:** handle nested refs in UnwrapRef ([#12049](https://github.com/vuejs/core/issues/12049)) ([e2c19c2](https://github.com/vuejs/core/commit/e2c19c20cfee9788519a80c0e53e216b78505994)), closes [#12044](https://github.com/vuejs/core/issues/12044) -## [3.4.19](https://github.com/vuejs/core/compare/v3.4.18...v3.4.19) (2024-02-13) +## [3.5.9](https://github.com/vuejs/core/compare/v3.5.8...v3.5.9) (2024-09-26) ### Bug Fixes -* **deps:** pin lru-cache to avoid hashing error ([b8be990](https://github.com/vuejs/core/commit/b8be99018ceae92d1732dfb414df12b36b90b31f)), closes [#10300](https://github.com/vuejs/core/issues/10300) -* **hydration:** fix css vars hydration mismatch false positive on non-root nodes ([995d2fd](https://github.com/vuejs/core/commit/995d2fdcca485c24849c99f498c1edc163722e04)), closes [#10317](https://github.com/vuejs/core/issues/10317) [#10325](https://github.com/vuejs/core/issues/10325) -* **runtime-dom:** should not trigger transition when v-show value is falsy ([#10311](https://github.com/vuejs/core/issues/10311)) ([e509639](https://github.com/vuejs/core/commit/e50963903d93a7f24003b6e2c03647fdf7454b1e)) - - -### Features - -> Note: this warning is categorized as a feature but released in a patch because it does not affect public APIs. - -* **dx:** warn users when computed is self-triggering ([#10299](https://github.com/vuejs/core/issues/10299)) ([f7ba97f](https://github.com/vuejs/core/commit/f7ba97f9754a9882c1f6b1c07ca1a4040479dd13)) - - -### Performance Improvements +* **reactivity:** fix property dep removal regression ([6001e5c](https://github.com/vuejs/core/commit/6001e5c81a05c894586f9287fbd991677bdd0455)), closes [#12020](https://github.com/vuejs/core/issues/12020) [#12021](https://github.com/vuejs/core/issues/12021) +* **reactivity:** fix recursive sync watcher on computed edge case ([10ff159](https://github.com/vuejs/core/commit/10ff15924053d9bd95ad706f78ce09e288213fcf)), closes [#12033](https://github.com/vuejs/core/issues/12033) [#12037](https://github.com/vuejs/core/issues/12037) +* **runtime-core:** avoid rendering plain object as VNode ([#12038](https://github.com/vuejs/core/issues/12038)) ([cb34b28](https://github.com/vuejs/core/commit/cb34b28a4a9bf868be4785b001c526163eda342e)), closes [#12035](https://github.com/vuejs/core/issues/12035) [vitejs/vite-plugin-vue#353](https://github.com/vitejs/vite-plugin-vue/issues/353) +* **runtime-core:** make useId() always return a string ([a177092](https://github.com/vuejs/core/commit/a177092754642af2f98c33a4feffe8f198c3c950)) +* **types:** correct type inference of union event names ([#12022](https://github.com/vuejs/core/issues/12022)) ([4da6881](https://github.com/vuejs/core/commit/4da688141d9e7c15b622c289deaa81b11845b2c7)) +* **vue:** properly cache runtime compilation ([#12019](https://github.com/vuejs/core/issues/12019)) ([fa0ba24](https://github.com/vuejs/core/commit/fa0ba24b3ace02d7ecab65e57c2bea89a2550dcb)) -* **runtime:** improve `getType()` GC and speed ([#10327](https://github.com/vuejs/core/issues/10327)) ([603a1e1](https://github.com/vuejs/core/commit/603a1e1f5ad587c077f0d974c1bbe856be22ebe9)) - -## [3.4.18](https://github.com/vuejs/core/compare/v3.4.17...v3.4.18) (2024-02-09) +## [3.5.8](https://github.com/vuejs/core/compare/v3.5.7...v3.5.8) (2024-09-22) ### Bug Fixes -* **dx:** warn against reserved keys as prop name ([77a804b](https://github.com/vuejs/core/commit/77a804b1d0d6a3f12fb3674cdceb85ebd6481e02)), closes [#10281](https://github.com/vuejs/core/issues/10281) -* **runtime-dom:** ensure v-show respects display value set via v-bind ([#10297](https://github.com/vuejs/core/issues/10297)) ([c224897](https://github.com/vuejs/core/commit/c224897dd4e189a10ec601a97fe08cb638ebee19)), closes [#10151](https://github.com/vuejs/core/issues/10151) - - - -## [3.4.17](https://github.com/vuejs/core/compare/v3.4.16...v3.4.17) (2024-02-09) - - -### Reverts +* **reactivity:** do not remove dep from depsMap when cleaning up deps of computed ([#11995](https://github.com/vuejs/core/issues/11995)) ([0267a58](https://github.com/vuejs/core/commit/0267a588017eee4951ac2a877fe1ccae84cad905)) -* fix(runtime-dom): ensure v-show respects display value set via v-bind ([#10161](https://github.com/vuejs/core/issues/10161)) ([2cd5b05](https://github.com/vuejs/core/commit/2cd5b05c3bf171be5c0b473c084c01704a058ffa)), closes [#10294](https://github.com/vuejs/core/issues/10294) [#10151](https://github.com/vuejs/core/issues/10151) - -## [3.4.16](https://github.com/vuejs/core/compare/v3.4.15...v3.4.16) (2024-02-08) - - -### Bug Fixes - -* **compiler-core:** handle same-name shorthand edge case for in-DOM templates ([cb87b62](https://github.com/vuejs/core/commit/cb87b6213d7b003fa7280712c285c7c9d9f291ca)), closes [#10280](https://github.com/vuejs/core/issues/10280) -* **compiler-core:** support v-bind shorthand syntax for dynamic slot name ([#10218](https://github.com/vuejs/core/issues/10218)) ([91f058a](https://github.com/vuejs/core/commit/91f058a90cd603492649633d153b120977c4df6b)), closes [#10213](https://github.com/vuejs/core/issues/10213) -* **deps:** update compiler ([#10269](https://github.com/vuejs/core/issues/10269)) ([336bb65](https://github.com/vuejs/core/commit/336bb65820243006efdf990e6ea3610696467508)) -* **hydration:** fix SFC style v-bind hydration mismatch warnings ([#10250](https://github.com/vuejs/core/issues/10250)) ([f0b5f7e](https://github.com/vuejs/core/commit/f0b5f7ed8ddf74f9f5ba47cb65e8300370875291)), closes [#10215](https://github.com/vuejs/core/issues/10215) -* **reactivity:** avoid infinite recursion from side effects in computed getter ([#10232](https://github.com/vuejs/core/issues/10232)) ([0bced13](https://github.com/vuejs/core/commit/0bced13ee5c53a02d5f10e5db76fe38b6e131440)), closes [#10214](https://github.com/vuejs/core/issues/10214) -* **reactivity:** handle `MaybeDirty` recurse ([#10187](https://github.com/vuejs/core/issues/10187)) ([6c7e0bd](https://github.com/vuejs/core/commit/6c7e0bd88f021b0b6365370e97b0c7e243d7d70b)), closes [#10185](https://github.com/vuejs/core/issues/10185) -* **reactivity:** skip non-extensible objects when using `markRaw` ([#10289](https://github.com/vuejs/core/issues/10289)) ([2312184](https://github.com/vuejs/core/commit/2312184bc335e0d32aa4c0c0b49190b6334849b4)), closes [#10288](https://github.com/vuejs/core/issues/10288) -* **runtime-core:** avoid inlining isShallow ([#10238](https://github.com/vuejs/core/issues/10238)) ([53eee72](https://github.com/vuejs/core/commit/53eee72c3a96420db35236b5e8e4d9308a56e1b4)) -* **runtime-core:** support for nested calls to runWithContext ([#10261](https://github.com/vuejs/core/issues/10261)) ([75e02b5](https://github.com/vuejs/core/commit/75e02b5099a08166bdf407127916734c48209ee9)), closes [#10260](https://github.com/vuejs/core/issues/10260) -* **runtime-dom:** ensure v-show respects display value set via v-bind ([#10161](https://github.com/vuejs/core/issues/10161)) ([9b19f09](https://github.com/vuejs/core/commit/9b19f0912104bfccb10c8cf5beab02b21a648935)), closes [#10151](https://github.com/vuejs/core/issues/10151) -* **runtime-dom:** fix option selected update failed ([#10200](https://github.com/vuejs/core/issues/10200)) ([f31d782](https://github.com/vuejs/core/commit/f31d782e4668050a188ac0f11ba8d5b861b913ca)), closes [#10194](https://github.com/vuejs/core/issues/10194) [#10267](https://github.com/vuejs/core/issues/10267) - - -### Reverts - -* perf(templateRef): avoid double render when using template ref on v-for ([eb1b911](https://github.com/vuejs/core/commit/eb1b9116d7cd4a5747e8dadcdc5ba921df011f64)), closes [#9908](https://github.com/vuejs/core/issues/9908) [#10210](https://github.com/vuejs/core/issues/10210) [#10234](https://github.com/vuejs/core/issues/10234) - - - -## [3.4.15](https://github.com/vuejs/core/compare/v3.4.14...v3.4.15) (2024-01-18) +## [3.5.7](https://github.com/vuejs/core/compare/v3.5.6...v3.5.7) (2024-09-20) ### Bug Fixes -* **compiler-sfc:** fix type resolution for symlinked node_modules structure w/ pnpm ([75e866b](https://github.com/vuejs/core/commit/75e866bd4ef368b4e037a4933dbaf188920dc683)), closes [#10121](https://github.com/vuejs/core/issues/10121) -* correct url for production error reference links ([c3087ff](https://github.com/vuejs/core/commit/c3087ff2cce7d96c60a870f8233441311ab4dfb4)) -* **hydration:** fix incorect mismatch warning for option with non-string value and inner text ([d16a213](https://github.com/vuejs/core/commit/d16a2138a33b106b9e1499bbb9e1c67790370c97)) -* **reactivity:** re-fix [#10114](https://github.com/vuejs/core/issues/10114) ([#10123](https://github.com/vuejs/core/issues/10123)) ([c2b274a](https://github.com/vuejs/core/commit/c2b274a887f61deb7e0185d1bef3b77d31e991cc)) -* **runtime-core:** should not warn out-of-render slot fn usage when mounting another app in setup ([#10125](https://github.com/vuejs/core/issues/10125)) ([6fa33e6](https://github.com/vuejs/core/commit/6fa33e67ec42af140a86fbdb86939032c3a1f345)), closes [#10124](https://github.com/vuejs/core/issues/10124) +* **compile-core:** fix v-model with newlines edge case ([#11960](https://github.com/vuejs/core/issues/11960)) ([6224288](https://github.com/vuejs/core/commit/62242886d705ece88dbcad45bb78072ecccad0ca)), closes [#8306](https://github.com/vuejs/core/issues/8306) +* **compiler-sfc:** initialize scope with null prototype object ([#11963](https://github.com/vuejs/core/issues/11963)) ([215e154](https://github.com/vuejs/core/commit/215e15407294bf667261360218f975b88c99c2e5)) +* **hydration:** avoid observing non-Element node ([#11954](https://github.com/vuejs/core/issues/11954)) ([7257e6a](https://github.com/vuejs/core/commit/7257e6a34200409b3fc347d3bb807e11e2785974)), closes [#11952](https://github.com/vuejs/core/issues/11952) +* **reactivity:** do not remove dep from depsMap when unsubbed by computed ([960706e](https://github.com/vuejs/core/commit/960706eebf73f08ebc9d5dd853a05def05e2c153)) +* **reactivity:** fix dev-only memory leak by updating dep.subsHead on sub removal ([5c8b76e](https://github.com/vuejs/core/commit/5c8b76ed6cfbbcee4cbaac0b72beab7291044e4f)), closes [#11956](https://github.com/vuejs/core/issues/11956) +* **reactivity:** fix memory leak from dep instances of garbage collected objects ([235ea47](https://github.com/vuejs/core/commit/235ea4772ed2972914cf142da8b7ac1fb04f7585)), closes [#11979](https://github.com/vuejs/core/issues/11979) [#11971](https://github.com/vuejs/core/issues/11971) +* **reactivity:** fix triggerRef call on ObjectRefImpl returned by toRef ([#11986](https://github.com/vuejs/core/issues/11986)) ([b030c8b](https://github.com/vuejs/core/commit/b030c8bc7327877efb98aa3d9a58eb287a6ff07a)), closes [#11982](https://github.com/vuejs/core/issues/11982) +* **scheduler:** ensure recursive jobs can't be queued twice ([#11955](https://github.com/vuejs/core/issues/11955)) ([d18d6aa](https://github.com/vuejs/core/commit/d18d6aa1b20dc57a8103c51ec4d61e8e53ed936d)) +* **ssr:** don't render comments in TransitionGroup ([#11961](https://github.com/vuejs/core/issues/11961)) ([a2f6ede](https://github.com/vuejs/core/commit/a2f6edeb02faedbb673c4bc5c6a59d9a79a37d07)), closes [#11958](https://github.com/vuejs/core/issues/11958) +* **transition:** respect `duration` setting even when it is `0` ([#11967](https://github.com/vuejs/core/issues/11967)) ([f927a4a](https://github.com/vuejs/core/commit/f927a4ae6f7c453f70ba89498ee0c737dc9866fd)) +* **types:** correct type inference of all-optional props ([#11644](https://github.com/vuejs/core/issues/11644)) ([9eca65e](https://github.com/vuejs/core/commit/9eca65ee9871d1ac878755afa9a3eb1b02030350)), closes [#11733](https://github.com/vuejs/core/issues/11733) [vuejs/language-tools#4704](https://github.com/vuejs/language-tools/issues/4704) ### Performance Improvements -* **templateRef:** avoid double render when using template ref on v-for ([de4d2e2](https://github.com/vuejs/core/commit/de4d2e2143ea8397cebeb1c7a57a60007b283c9f)), closes [#9908](https://github.com/vuejs/core/issues/9908) -* **v-model:** optimize v-model multiple select w/ large lists ([2ffb956](https://github.com/vuejs/core/commit/2ffb956efe692da059f4895669084c5278871351)), closes [#10014](https://github.com/vuejs/core/issues/10014) - +* **hydration:** avoid observer if element is in viewport ([#11639](https://github.com/vuejs/core/issues/11639)) ([e075dfa](https://github.com/vuejs/core/commit/e075dfad5c7649c6045e3711687ec888e7aa1a39)) -## [3.4.14](https://github.com/vuejs/core/compare/v3.4.13...v3.4.14) (2024-01-15) - -### Bug Fixes - -* **compiler-sfc:** enable prefixIdentifiers by default when reparsing on consumed AST ([#10105](https://github.com/vuejs/core/issues/10105)) ([48bf8e4](https://github.com/vuejs/core/commit/48bf8e4c708ec620e4852d71c8713394457108ee)) -* **deps:** update dependency postcss to ^8.4.33 ([#10110](https://github.com/vuejs/core/issues/10110)) ([a557006](https://github.com/vuejs/core/commit/a557006f8e7f110c6f322de38931dceaab8e9cbb)) -* **reactivity:** fix regression for computed with mutation ([#10119](https://github.com/vuejs/core/issues/10119)) ([20f62af](https://github.com/vuejs/core/commit/20f62afaafd422e42b99dde9c16f9a4ebfb9c5f7)), closes [#10114](https://github.com/vuejs/core/issues/10114) - - - -## [3.4.13](https://github.com/vuejs/core/compare/v3.4.12...v3.4.13) (2024-01-13) +## [3.5.6](https://github.com/vuejs/core/compare/v3.5.5...v3.5.6) (2024-09-16) ### Bug Fixes -* **reactivity:** fix dirtyLevel checks for recursive effects ([#10101](https://github.com/vuejs/core/issues/10101)) ([e45a8d2](https://github.com/vuejs/core/commit/e45a8d24b46c174deb46ed952bdaf54c81ad5a85)), closes [#10082](https://github.com/vuejs/core/issues/10082) - - - -## [3.4.12](https://github.com/vuejs/core/compare/v3.4.11...v3.4.12) (2024-01-13) - - -### Reverts - -* fix(reactivity): correct dirty assign in render function ([#10091](https://github.com/vuejs/core/issues/10091)) ([8b18481](https://github.com/vuejs/core/commit/8b1848173b0bc8fd84ce1da1af8d373c044bf073)), closes [#10098](https://github.com/vuejs/core/issues/10098) [#10100](https://github.com/vuejs/core/issues/10100) +* **compile-dom:** should be able to stringify mathML ([#11891](https://github.com/vuejs/core/issues/11891)) ([85c138c](https://github.com/vuejs/core/commit/85c138ced108268f7656b568dfd3036a1e0aae34)) +* **compiler-sfc:** preserve old behavior when using withDefaults with desutructure ([8492c3c](https://github.com/vuejs/core/commit/8492c3c49a922363d6c77ef192c133a8fbce6514)), closes [#11930](https://github.com/vuejs/core/issues/11930) +* **reactivity:** avoid exponential perf cost and reduce call stack depth for deeply chained computeds ([#11944](https://github.com/vuejs/core/issues/11944)) ([c74bb8c](https://github.com/vuejs/core/commit/c74bb8c2dd9e82aaabb0a2a2b368e900929b513b)), closes [#11928](https://github.com/vuejs/core/issues/11928) +* **reactivity:** rely on dirty check only when computed has deps ([#11931](https://github.com/vuejs/core/issues/11931)) ([aa5dafd](https://github.com/vuejs/core/commit/aa5dafd2b55d42d6a29316a3bc91aea85c676a0b)), closes [#11929](https://github.com/vuejs/core/issues/11929) +* **watch:** `once` option should be ignored by watchEffect ([#11884](https://github.com/vuejs/core/issues/11884)) ([49fa673](https://github.com/vuejs/core/commit/49fa673493d93b77ddba2165ab6545bae84fd1ae)) +* **watch:** unwatch should be callable during SSR ([#11925](https://github.com/vuejs/core/issues/11925)) ([2d6adf7](https://github.com/vuejs/core/commit/2d6adf78a047eed091db277ffbd9df0822fb0bdd)), closes [#11924](https://github.com/vuejs/core/issues/11924) -## [3.4.11](https://github.com/vuejs/core/compare/v3.4.10...v3.4.11) (2024-01-12) +## [3.5.5](https://github.com/vuejs/core/compare/v3.5.4...v3.5.5) (2024-09-13) ### Bug Fixes -* **hydration:** improve mismatch when client value is null or undefined ([#10086](https://github.com/vuejs/core/issues/10086)) ([08b60f5](https://github.com/vuejs/core/commit/08b60f5d0d5b57fcf3347ef66cbeab472c475a88)) -* **reactivity:** correct dirty assign in render function ([#10091](https://github.com/vuejs/core/issues/10091)) ([8d04205](https://github.com/vuejs/core/commit/8d042050411fdf04d9d1d6c153287164b12e0255)), closes [#10082](https://github.com/vuejs/core/issues/10082) -* **runtime-core:** filter single root for nested DEV_ROOT_FRAGMENT ([#8593](https://github.com/vuejs/core/issues/8593)) ([d35b877](https://github.com/vuejs/core/commit/d35b87725ab3e2bdc86fb5781ab34939f7ec1029)), closes [#5203](https://github.com/vuejs/core/issues/5203) [#8581](https://github.com/vuejs/core/issues/8581) [#10087](https://github.com/vuejs/core/issues/10087) +* **compiler-core:** fix handling of delimiterOpen in VPre ([#11915](https://github.com/vuejs/core/issues/11915)) ([706d4ac](https://github.com/vuejs/core/commit/706d4ac1d0210b2d9134b3228280187fe02fc971)), closes [#11913](https://github.com/vuejs/core/issues/11913) +* **compiler-dom:** fix stringify static edge for partially eligible chunks in cached parent ([1d99d61](https://github.com/vuejs/core/commit/1d99d61c1bd77f9ea6743f6214a82add8346a121)), closes [#11879](https://github.com/vuejs/core/issues/11879) [#11890](https://github.com/vuejs/core/issues/11890) +* **compiler-dom:** should ignore leading newline in 1`] = ` { - "cached": 0, + "cached": [], "children": [ { "children": [ @@ -4322,7 +4322,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > `, { + parseMode: 'html', + }) + expect((ast3.children[0] as ElementNode).children).toMatchObject([ + { + type: NodeTypes.ELEMENT, + children: [ + { + type: NodeTypes.TEXT, + content: `{{ foo `, + }, + ], + }, + ]) }) test('self-closing v-pre', () => { @@ -2176,6 +2369,7 @@ describe('compiler: parse', () => { test('should remove leading newline character immediately following the pre element start tag', () => { const ast = parse(`
\n  foo  bar  
`, { isPreTag: tag => tag === 'pre', + isIgnoreNewlineTag: tag => tag === 'pre', }) expect(ast.children).toHaveLength(1) const preElement = ast.children[0] as ElementNode diff --git a/packages/compiler-core/__tests__/scopeId.spec.ts b/packages/compiler-core/__tests__/scopeId.spec.ts index 21e7327f2eb..7d1d27e115f 100644 --- a/packages/compiler-core/__tests__/scopeId.spec.ts +++ b/packages/compiler-core/__tests__/scopeId.spec.ts @@ -1,7 +1,4 @@ import { baseCompile } from '../src/compile' -import { POP_SCOPE_ID, PUSH_SCOPE_ID } from '../src/runtimeHelpers' -import { PatchFlags } from '@vue/shared' -import { genFlagText } from './testUtils' /** * Ensure all slot functions are wrapped with _withCtx @@ -57,53 +54,4 @@ describe('scopeId compiler support', () => { expect(code).toMatch(/name: i,\s+fn: _withCtx\(/) expect(code).toMatchSnapshot() }) - - test('should push scopeId for hoisted nodes', () => { - const { ast, code } = baseCompile( - `
hello
{{ foo }}
world
`, - { - mode: 'module', - scopeId: 'test', - hoistStatic: true, - }, - ) - expect(ast.helpers).toContain(PUSH_SCOPE_ID) - expect(ast.helpers).toContain(POP_SCOPE_ID) - expect(ast.hoists.length).toBe(2) - ;[ - `const _withScopeId = n => (_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() - }) - - test('should push typescript-compatible scopeId for hoisted nodes', () => { - const { ast, code } = baseCompile( - `
hello
{{ foo }}
world
`, - { - 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() - }) }) diff --git a/packages/compiler-core/__tests__/testUtils.ts b/packages/compiler-core/__tests__/testUtils.ts index 9618a482add..a2525e0cab9 100644 --- a/packages/compiler-core/__tests__/testUtils.ts +++ b/packages/compiler-core/__tests__/testUtils.ts @@ -3,6 +3,8 @@ import { ElementTypes, Namespaces, NodeTypes, + type Property, + type SimpleExpressionNode, type VNodeCall, locStub, } from '../src' @@ -22,7 +24,10 @@ const bracketsRE = /^\[|\]$/g // e.g. // - createObjectMatcher({ 'foo': '[bar]' }) matches { foo: bar } // - createObjectMatcher({ '[foo]': 'bar' }) matches { [foo]: "bar" } -export function createObjectMatcher(obj: Record) { +export function createObjectMatcher(obj: Record): { + type: NodeTypes + properties: Partial[] +} { return { type: NodeTypes.JS_OBJECT_EXPRESSION, properties: Object.keys(obj).map(key => ({ @@ -31,7 +36,7 @@ export function createObjectMatcher(obj: Record) { type: NodeTypes.SIMPLE_EXPRESSION, content: key.replace(bracketsRE, ''), isStatic: !leadingBracketRE.test(key), - }, + } as SimpleExpressionNode, value: isString(obj[key]) ? { type: NodeTypes.SIMPLE_EXPRESSION, @@ -78,7 +83,7 @@ type Flags = PatchFlags | ShapeFlags export function genFlagText( flag: Flags | Flags[], names: { [k: number]: string } = PatchFlagNames, -) { +): string { if (isArray(flag)) { let f = 0 flag.forEach(ff => { diff --git a/packages/compiler-core/__tests__/transform.spec.ts b/packages/compiler-core/__tests__/transform.spec.ts index a56be51bc5c..0946d364838 100644 --- a/packages/compiler-core/__tests__/transform.spec.ts +++ b/packages/compiler-core/__tests__/transform.spec.ts @@ -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, ), ) }) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap similarity index 67% rename from packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap rename to packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap index ef3e2b8f4d6..375a0c8674a 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap @@ -1,103 +1,87 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`compiler: hoistStatic transform > hoist element with static key 1`] = ` +exports[`compiler: cacheStatic transform > cache element with static key 1`] = ` "const _Vue = Vue -const { createElementVNode: _createElementVNode } = _Vue - -const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */) -const _hoisted_2 = [ - _hoisted_1 -] return function render(_ctx, _cache) { with (_ctx) { const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue - return (_openBlock(), _createElementBlock("div", null, _hoisted_2)) + return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ + _createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */) + ]))) } }" `; -exports[`compiler: hoistStatic transform > hoist nested static tree 1`] = ` +exports[`compiler: cacheStatic transform > cache nested children array 1`] = ` "const _Vue = Vue -const { createElementVNode: _createElementVNode } = _Vue - -const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, [ - /*#__PURE__*/_createElementVNode("span"), - /*#__PURE__*/_createElementVNode("span") -], -1 /* HOISTED */) -const _hoisted_2 = [ - _hoisted_1 -] return function render(_ctx, _cache) { with (_ctx) { const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue - return (_openBlock(), _createElementBlock("div", null, _hoisted_2)) + return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ + _createElementVNode("p", null, [ + _createElementVNode("span"), + _createElementVNode("span") + ], -1 /* HOISTED */), + _createElementVNode("p", null, [ + _createElementVNode("span"), + _createElementVNode("span") + ], -1 /* HOISTED */) + ]))) } }" `; -exports[`compiler: hoistStatic transform > hoist nested static tree with comments 1`] = ` +exports[`compiler: cacheStatic transform > cache nested static tree with comments 1`] = ` "const _Vue = Vue -const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue - -const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [ - /*#__PURE__*/_createCommentVNode("comment") -], -1 /* HOISTED */) -const _hoisted_2 = [ - _hoisted_1 -] return function render(_ctx, _cache) { with (_ctx) { const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue - return (_openBlock(), _createElementBlock("div", null, _hoisted_2)) + return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ + _createElementVNode("div", null, [ + _createCommentVNode("comment") + ], -1 /* HOISTED */) + ]))) } }" `; -exports[`compiler: hoistStatic transform > hoist siblings with common non-hoistable parent 1`] = ` +exports[`compiler: cacheStatic transform > cache siblings including text with common non-hoistable parent 1`] = ` "const _Vue = Vue -const { createElementVNode: _createElementVNode } = _Vue - -const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */) -const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, null, -1 /* HOISTED */) -const _hoisted_3 = [ - _hoisted_1, - _hoisted_2 -] return function render(_ctx, _cache) { with (_ctx) { - const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue - return (_openBlock(), _createElementBlock("div", null, _hoisted_3)) + return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ + _createElementVNode("span", null, null, -1 /* HOISTED */), + _createTextVNode("foo"), + _createElementVNode("div", null, null, -1 /* HOISTED */) + ]))) } }" `; -exports[`compiler: hoistStatic transform > hoist simple element 1`] = ` +exports[`compiler: cacheStatic transform > cache single children array 1`] = ` "const _Vue = Vue -const { createElementVNode: _createElementVNode } = _Vue - -const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */) -const _hoisted_2 = [ - _hoisted_1 -] return function render(_ctx, _cache) { with (_ctx) { const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue - return (_openBlock(), _createElementBlock("div", null, _hoisted_2)) + return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ + _createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */) + ]))) } }" `; -exports[`compiler: hoistStatic transform > hoist static props for elements with directives 1`] = ` +exports[`compiler: cacheStatic transform > hoist static props for elements with directives 1`] = ` "const _Vue = Vue const { createElementVNode: _createElementVNode } = _Vue @@ -118,7 +102,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > hoist static props for elements with dynamic text children 1`] = ` +exports[`compiler: cacheStatic transform > hoist static props for elements with dynamic text children 1`] = ` "const _Vue = Vue const { createElementVNode: _createElementVNode } = _Vue @@ -135,7 +119,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > hoist static props for elements with unhoistable children 1`] = ` +exports[`compiler: cacheStatic transform > hoist static props for elements with unhoistable children 1`] = ` "const _Vue = Vue const { createVNode: _createVNode, createElementVNode: _createElementVNode } = _Vue @@ -156,69 +140,73 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist class with static object value 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static interpolation 1`] = ` "const _Vue = Vue -const { createElementVNode: _createElementVNode } = _Vue - -const _hoisted_1 = { - class: /*#__PURE__*/_normalizeClass({ foo: true }) -} return function render(_ctx, _cache) { with (_ctx) { - const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue - return (_openBlock(), _createElementBlock("div", null, [ - _createElementVNode("span", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */) - ])) + return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ + _createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* HOISTED */) + ]))) } }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static interpolation 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static prop value 1`] = ` "const _Vue = Vue -const { createElementVNode: _createElementVNode } = _Vue - -const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "foo " + /*#__PURE__*/_toDisplayString(1) + " " + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */) -const _hoisted_2 = [ - _hoisted_1 -] return function render(_ctx, _cache) { with (_ctx) { const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue - return (_openBlock(), _createElementBlock("div", null, _hoisted_2)) + return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ + _createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* HOISTED */) + ]))) } }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static prop value 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > clone hoisted array children in v-for + HMR mode 1`] = ` "const _Vue = Vue -const { createElementVNode: _createElementVNode } = _Vue - -const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */) -const _hoisted_2 = [ - _hoisted_1 -] return function render(_ctx, _cache) { with (_ctx) { - const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue - return (_openBlock(), _createElementBlock("div", null, _hoisted_2)) + return (_openBlock(), _createElementBlock("div", null, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => { + return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [ + _createElementVNode("span", { class: "hi" }, null, -1 /* HOISTED */) + ]))])) + }), 256 /* UNKEYED_FRAGMENT */)) + ])) } }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist SVG with directives 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > hoist class with static object value 1`] = ` "const _Vue = Vue const { createElementVNode: _createElementVNode } = _Vue -const _hoisted_1 = /*#__PURE__*/_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */) -const _hoisted_2 = [ - _hoisted_1 -] +const _hoisted_1 = { + class: /*@__PURE__*/_normalizeClass({ foo: true }) +} + +return function render(_ctx, _cache) { + with (_ctx) { + const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_openBlock(), _createElementBlock("div", null, [ + _createElementVNode("span", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */) + ])) + } +}" +`; + +exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache SVG with directives 1`] = ` +"const _Vue = Vue return function render(_ctx, _cache) { with (_ctx) { @@ -227,7 +215,9 @@ return function render(_ctx, _cache) { const _directive_foo = _resolveDirective("foo") return (_openBlock(), _createElementBlock("div", null, [ - _withDirectives((_openBlock(), _createElementBlock("svg", null, _hoisted_2)), [ + _withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [ + _createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */) + ]))), [ [_directive_foo] ]) ])) @@ -235,7 +225,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist elements with cached handlers + other bindings 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers + other bindings 1`] = ` "import { normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" export function render(_ctx, _cache) { @@ -250,7 +240,7 @@ export function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist elements with cached handlers 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers 1`] = ` "import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" export function render(_ctx, _cache) { @@ -264,7 +254,7 @@ export function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables (2) 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (2) 1`] = ` "const _Vue = Vue return function render(_ctx, _cache) { @@ -282,7 +272,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables (v-slot) 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (v-slot) 1`] = ` "const _Vue = Vue return function render(_ctx, _cache) { @@ -301,7 +291,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables 1`] = ` "const _Vue = Vue return function render(_ctx, _cache) { @@ -319,7 +309,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist keyed template v-for with plain element child 1`] = ` +exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache keyed template v-for with plain element child 1`] = ` "const _Vue = Vue return function render(_ctx, _cache) { @@ -335,7 +325,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > should NOT hoist components 1`] = ` +exports[`compiler: cacheStatic transform > should NOT cache components 1`] = ` "const _Vue = Vue return function render(_ctx, _cache) { @@ -351,7 +341,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic key 1`] = ` +exports[`compiler: cacheStatic transform > should NOT cache element with dynamic key 1`] = ` "const _Vue = Vue return function render(_ctx, _cache) { @@ -365,7 +355,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic props (but hoist the props list) 1`] = ` +exports[`compiler: cacheStatic transform > should NOT cache element with dynamic props (but hoist the props list) 1`] = ` "const _Vue = Vue const { createElementVNode: _createElementVNode } = _Vue @@ -382,7 +372,7 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic ref 1`] = ` +exports[`compiler: cacheStatic transform > should NOT cache element with dynamic ref 1`] = ` "const _Vue = Vue return function render(_ctx, _cache) { @@ -396,27 +386,35 @@ return function render(_ctx, _cache) { }" `; -exports[`compiler: hoistStatic transform > should NOT hoist root node 1`] = ` +exports[`compiler: cacheStatic transform > should cache v-if props/children if static 1`] = ` "const _Vue = Vue +const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue + +const _hoisted_1 = { + key: 0, + id: "foo" +} return function render(_ctx, _cache) { with (_ctx) { - const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue - return (_openBlock(), _createElementBlock("div")) + return (_openBlock(), _createElementBlock("div", null, [ + ok + ? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [ + _createElementVNode("span", null, null, -1 /* HOISTED */) + ]))) + : _createCommentVNode("v-if", true) + ])) } }" `; -exports[`compiler: hoistStatic transform > should hoist v-for children if static 1`] = ` +exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = ` "const _Vue = Vue const { createElementVNode: _createElementVNode } = _Vue const _hoisted_1 = { id: "foo" } -const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */) -const _hoisted_3 = [ - _hoisted_2 -] return function render(_ctx, _cache) { with (_ctx) { @@ -424,35 +422,11 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => { - return (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3)) + return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [ + _createElementVNode("span", null, null, -1 /* HOISTED */) + ]))) }), 256 /* UNKEYED_FRAGMENT */)) ])) } }" `; - -exports[`compiler: hoistStatic transform > should hoist v-if props/children if static 1`] = ` -"const _Vue = Vue -const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue - -const _hoisted_1 = { - key: 0, - id: "foo" -} -const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */) -const _hoisted_3 = [ - _hoisted_2 -] - -return function render(_ctx, _cache) { - with (_ctx) { - const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue - - return (_openBlock(), _createElementBlock("div", null, [ - ok - ? (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3)) - : _createCommentVNode("v-if", true) - ])) - } -}" -`; diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap index 9f4406864db..5a94de5a68b 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap @@ -14,44 +14,92 @@ return function render(_ctx, _cache, $props, $setup, $data, $options) { }" `; -exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for loop 1`] = ` +exports[`compiler: expression transform > should allow leak of var declarations in for loop 1`] = ` "const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue -return function render(_ctx, _cache, $props, $setup, $data, $options) { +return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", { onClick: () => { - for (let i = 0; i < _ctx.list.length; i++) { - _ctx.log(i) - } + for (var i = 0; i < _ctx.list.length; i++) { + _ctx.log(i) } + _ctx.error(i) + } }, null, 8 /* PROPS */, ["onClick"])) }" `; -exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...in 1`] = ` +exports[`compiler: expression transform > should not prefix catch block param 1`] = ` "const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue -return function render(_ctx, _cache, $props, $setup, $data, $options) { +return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", { onClick: () => { - for (const x in _ctx.list) { - _ctx.log(x) - } + try {} catch (err) { console.error(err) } + console.log(_ctx.err) + } + }, null, 8 /* PROPS */, ["onClick"])) +}" +`; + +exports[`compiler: expression transform > should not prefix destructured catch block param 1`] = ` +"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue + +return function render(_ctx, _cache) { + return (_openBlock(), _createElementBlock("div", { + onClick: () => { + try { + throw new Error('sup?') + } catch ({ message: { length } }) { + console.error(length) } + console.log(_ctx.length) + } }, null, 8 /* PROPS */, ["onClick"])) }" `; -exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...of 1`] = ` +exports[`compiler: expression transform > should not prefix temp variable of for loop 1`] = ` "const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue -return function render(_ctx, _cache, $props, $setup, $data, $options) { +return function render(_ctx, _cache) { + return (_openBlock(), _createElementBlock("div", { + onClick: () => { + for (let i = 0; i < _ctx.list.length; i++) { + _ctx.log(i) + } + _ctx.error(_ctx.i) + } + }, null, 8 /* PROPS */, ["onClick"])) +}" +`; + +exports[`compiler: expression transform > should not prefix temp variable of for...in 1`] = ` +"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue + +return function render(_ctx, _cache) { + return (_openBlock(), _createElementBlock("div", { + onClick: () => { + for (const x in _ctx.list) { + _ctx.log(x) + } + _ctx.error(_ctx.x) + } + }, null, 8 /* PROPS */, ["onClick"])) +}" +`; + +exports[`compiler: expression transform > should not prefix temp variable of for...of 1`] = ` +"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue + +return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", { onClick: () => { - for (const x of _ctx.list) { - _ctx.log(x) - } + for (const x of _ctx.list) { + _ctx.log(x) } + _ctx.error(_ctx.x) + } }, null, 8 /* PROPS */, ["onClick"])) }" `; diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 1c1203552db..3d13c4066d9 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -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] ) diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts similarity index 50% rename from packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts rename to packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts index d6c46b52eb3..ab5ed7baede 100644 --- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts @@ -21,22 +21,37 @@ 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' -const hoistedChildrenArrayMatcher = (startIndex = 1, length = 1) => ({ - type: NodeTypes.JS_ARRAY_EXPRESSION, - elements: new Array(length).fill(0).map((_, i) => ({ - type: NodeTypes.ELEMENT, - codegenNode: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_hoisted_${startIndex + i}`, - }, - })), +const cachedChildrenArrayMatcher = ( + tags: string[], + needArraySpread = false, +) => ({ + type: NodeTypes.JS_CACHE_EXPRESSION, + needArraySpread, + value: { + type: NodeTypes.JS_ARRAY_EXPRESSION, + elements: tags.map(tag => { + if (tag === '') { + return { + type: NodeTypes.TEXT_CALL, + } + } else { + return { + type: NodeTypes.ELEMENT, + codegenNode: { + type: NodeTypes.VNODE_CALL, + tag: JSON.stringify(tag), + }, + } + } + }), + }, }) -function transformWithHoist(template: string, options: CompilerOptions = {}) { +function transformWithCache(template: string, options: CompilerOptions = {}) { const ast = parse(template) transform(ast, { hoistStatic: true, @@ -60,101 +75,253 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) { return ast } -describe('compiler: hoistStatic transform', () => { - test('should NOT hoist root node', () => { +describe('compiler: cacheStatic transform', () => { + test('should NOT cache root node', () => { // if the whole tree is static, the root still needs to be a block // so that it's patched in optimized mode to skip children - const root = transformWithHoist(`
`) - expect(root.hoists.length).toBe(0) + const root = transformWithCache(`
`) expect(root.codegenNode).toMatchObject({ + type: NodeTypes.VNODE_CALL, tag: `"div"`, }) - expect(generate(root).code).toMatchSnapshot() + expect(root.cached.length).toBe(0) + }) + + test('cache root node children', () => { + // we don't have access to the root codegenNode during the transform + // so we only cache each child individually + const root = transformWithCache( + `hellohello`, + ) + expect(root.codegenNode).toMatchObject({ + type: NodeTypes.VNODE_CALL, + children: [ + { codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } }, + { codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } }, + ], + }) + expect(root.cached.length).toBe(2) }) - test('hoist simple element', () => { - const root = transformWithHoist( + test('cache single children array', () => { + const root = transformWithCache( `
hello
`, ) - expect(root.hoists).toMatchObject([ - { - type: NodeTypes.VNODE_CALL, - tag: `"span"`, - props: createObjectMatcher({ class: 'inline' }), - children: { - type: NodeTypes.TEXT, - content: `hello`, - }, - }, - hoistedChildrenArrayMatcher(), - ]) expect(root.codegenNode).toMatchObject({ tag: `"div"`, props: undefined, - children: { content: `_hoisted_2` }, + children: cachedChildrenArrayMatcher(['span']), }) + expect(root.cached.length).toBe(1) expect(generate(root).code).toMatchSnapshot() }) - test('hoist nested static tree', () => { - const root = transformWithHoist(`

`) - expect(root.hoists).toMatchObject([ - { - type: NodeTypes.VNODE_CALL, - tag: `"p"`, - props: undefined, - children: [ - { type: NodeTypes.ELEMENT, tag: `span` }, - { type: NodeTypes.ELEMENT, tag: `span` }, - ], - }, - hoistedChildrenArrayMatcher(), - ]) + test('cache nested children array', () => { + const root = transformWithCache( + `

`, + ) + expect((root.codegenNode as VNodeCall).children).toMatchObject( + cachedChildrenArrayMatcher(['p', 'p']), + ) + expect(root.cached.length).toBe(1) + expect(generate(root).code).toMatchSnapshot() + }) + + test('cache nested static tree with comments', () => { + const root = transformWithCache(`
`) + expect((root.codegenNode as VNodeCall).children).toMatchObject( + cachedChildrenArrayMatcher(['div']), + ) + expect(root.cached.length).toBe(1) + expect(generate(root).code).toMatchSnapshot() + }) + + test('cache siblings including text with common non-hoistable parent', () => { + const root = transformWithCache(`
foo
`) + expect((root.codegenNode as VNodeCall).children).toMatchObject( + cachedChildrenArrayMatcher(['span', '', 'div']), + ) + expect(root.cached.length).toBe(1) + expect(generate(root).code).toMatchSnapshot() + }) + + test('cache inside default slot', () => { + const root = transformWithCache(`{{x}}`) expect((root.codegenNode as VNodeCall).children).toMatchObject({ - content: '_hoisted_2', + properties: [ + { + key: { content: 'default' }, + value: { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + returns: [ + { + type: NodeTypes.TEXT_CALL, + }, + // first slot child cached + { + type: NodeTypes.ELEMENT, + codegenNode: { + type: NodeTypes.JS_CACHE_EXPRESSION, + }, + }, + ], + }, + }, + { + /* _ slot flag */ + }, + ], }) - expect(generate(root).code).toMatchSnapshot() }) - test('hoist nested static tree with comments', () => { - const root = transformWithHoist(`
`) - expect(root.hoists).toMatchObject([ - { - type: NodeTypes.VNODE_CALL, - tag: `"div"`, - props: undefined, - children: [{ type: NodeTypes.COMMENT, content: `comment` }], - }, - hoistedChildrenArrayMatcher(), - ]) + test('cache default slot as a whole', () => { + const root = transformWithCache(``) expect((root.codegenNode as VNodeCall).children).toMatchObject({ - content: `_hoisted_2`, + properties: [ + { + key: { content: 'default' }, + value: { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + returns: { + type: NodeTypes.JS_CACHE_EXPRESSION, + value: { + type: NodeTypes.JS_ARRAY_EXPRESSION, + elements: [ + { type: NodeTypes.ELEMENT }, + { type: NodeTypes.ELEMENT }, + ], + }, + }, + }, + }, + { + /* _ slot flag */ + }, + ], }) - expect(generate(root).code).toMatchSnapshot() }) - test('hoist siblings with common non-hoistable parent', () => { - const root = transformWithHoist(`
`) - expect(root.hoists).toMatchObject([ - { - type: NodeTypes.VNODE_CALL, - tag: `"span"`, - }, - { - type: NodeTypes.VNODE_CALL, - tag: `"div"`, - }, - hoistedChildrenArrayMatcher(1, 2), - ]) + test('cache inside named slot', () => { + const root = transformWithCache( + ``, + ) expect((root.codegenNode as VNodeCall).children).toMatchObject({ - content: '_hoisted_3', + properties: [ + { + key: { content: 'foo' }, + value: { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + returns: [ + { + type: NodeTypes.TEXT_CALL, + }, + // first slot child cached + { + type: NodeTypes.ELEMENT, + codegenNode: { + type: NodeTypes.JS_CACHE_EXPRESSION, + }, + }, + ], + }, + }, + { + /* _ slot flag */ + }, + ], }) - expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist components', () => { - const root = transformWithHoist(`
`) - expect(root.hoists.length).toBe(0) + test('cache named slot as a whole', () => { + const root = transformWithCache( + ``, + ) + expect((root.codegenNode as VNodeCall).children).toMatchObject({ + properties: [ + { + key: { content: 'foo' }, + value: { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + returns: { + type: NodeTypes.JS_CACHE_EXPRESSION, + value: { + type: NodeTypes.JS_ARRAY_EXPRESSION, + elements: [ + { type: NodeTypes.ELEMENT }, + { type: NodeTypes.ELEMENT }, + ], + }, + }, + }, + }, + { + /* _ slot flag */ + }, + ], + }) + }) + + test('cache dynamically named slot as a whole', () => { + const root = transformWithCache( + ``, + ) + expect((root.codegenNode as VNodeCall).children).toMatchObject({ + properties: [ + { + key: { content: 'foo', isStatic: false }, + value: { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + returns: { + type: NodeTypes.JS_CACHE_EXPRESSION, + value: { + type: NodeTypes.JS_ARRAY_EXPRESSION, + elements: [ + { type: NodeTypes.ELEMENT }, + { type: NodeTypes.ELEMENT }, + ], + }, + }, + }, + }, + { + /* _ slot flag */ + }, + ], + }) + }) + + test('cache dynamically named (expression) slot as a whole', () => { + const root = transformWithCache( + ``, + { prefixIdentifiers: true }, + ) + expect((root.codegenNode as VNodeCall).children).toMatchObject({ + properties: [ + { + key: { type: NodeTypes.COMPOUND_EXPRESSION }, + value: { + type: NodeTypes.JS_FUNCTION_EXPRESSION, + returns: { + type: NodeTypes.JS_CACHE_EXPRESSION, + value: { + type: NodeTypes.JS_ARRAY_EXPRESSION, + elements: [ + { type: NodeTypes.ELEMENT }, + { type: NodeTypes.ELEMENT }, + ], + }, + }, + }, + }, + { + /* _ slot flag */ + }, + ], + }) + }) + + test('should NOT cache components', () => { + const root = transformWithCache(`
`) expect((root.codegenNode as VNodeCall).children).toMatchObject([ { type: NodeTypes.ELEMENT, @@ -164,11 +331,12 @@ describe('compiler: hoistStatic transform', () => { }, }, ]) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist element with dynamic props (but hoist the props list)', () => { - const root = transformWithHoist(`
`) + test('should NOT cache element with dynamic props (but hoist the props list)', () => { + const root = transformWithCache(`
`) expect(root.hoists.length).toBe(1) expect((root.codegenNode as VNodeCall).children).toMatchObject([ { @@ -180,7 +348,7 @@ describe('compiler: hoistStatic transform', () => { id: `[foo]`, }), children: undefined, - patchFlag: genFlagText(PatchFlags.PROPS), + patchFlag: PatchFlags.PROPS, dynamicProps: { type: NodeTypes.SIMPLE_EXPRESSION, content: `_hoisted_1`, @@ -189,31 +357,23 @@ describe('compiler: hoistStatic transform', () => { }, }, ]) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) - test('hoist element with static key', () => { - const root = transformWithHoist(`
`) - expect(root.hoists.length).toBe(2) - expect(root.hoists).toMatchObject([ - { - type: NodeTypes.VNODE_CALL, - tag: `"div"`, - props: createObjectMatcher({ key: 'foo' }), - }, - hoistedChildrenArrayMatcher(), - ]) + test('cache element with static key', () => { + const root = transformWithCache(`
`) expect(root.codegenNode).toMatchObject({ tag: `"div"`, props: undefined, - children: { content: `_hoisted_2` }, + children: cachedChildrenArrayMatcher(['div']), }) + expect(root.cached.length).toBe(1) expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist element with dynamic key', () => { - const root = transformWithHoist(`
`) - expect(root.hoists.length).toBe(0) + test('should NOT cache element with dynamic key', () => { + const root = transformWithCache(`
`) expect((root.codegenNode as VNodeCall).children).toMatchObject([ { type: NodeTypes.ELEMENT, @@ -226,12 +386,12 @@ describe('compiler: hoistStatic transform', () => { }, }, ]) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist element with dynamic ref', () => { - const root = transformWithHoist(`
`) - expect(root.hoists.length).toBe(0) + test('should NOT cache element with dynamic ref', () => { + const root = transformWithCache(`
`) expect((root.codegenNode as VNodeCall).children).toMatchObject([ { type: NodeTypes.ELEMENT, @@ -242,15 +402,16 @@ describe('compiler: hoistStatic transform', () => { ref: `[foo]`, }), children: undefined, - patchFlag: genFlagText(PatchFlags.NEED_PATCH), + patchFlag: PatchFlags.NEED_PATCH, }, }, ]) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) test('hoist static props for elements with directives', () => { - const root = transformWithHoist(`
`) + const root = transformWithCache(`
`) expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })]) expect((root.codegenNode as VNodeCall).children).toMatchObject([ { @@ -263,18 +424,19 @@ describe('compiler: hoistStatic transform', () => { content: `_hoisted_1`, }, children: undefined, - patchFlag: genFlagText(PatchFlags.NEED_PATCH), + patchFlag: PatchFlags.NEED_PATCH, directives: { type: NodeTypes.JS_ARRAY_EXPRESSION, }, }, }, ]) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) test('hoist static props for elements with dynamic text children', () => { - const root = transformWithHoist( + const root = transformWithCache( `
{{ hello }}
`, ) expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })]) @@ -286,15 +448,16 @@ describe('compiler: hoistStatic transform', () => { tag: `"div"`, props: { content: `_hoisted_1` }, children: { type: NodeTypes.INTERPOLATION }, - patchFlag: genFlagText(PatchFlags.TEXT), + patchFlag: PatchFlags.TEXT, }, }, ]) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) test('hoist static props for elements with unhoistable children', () => { - const root = transformWithHoist(`
`) + const root = transformWithCache(`
`) expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })]) expect((root.codegenNode as VNodeCall).children).toMatchObject([ { @@ -307,11 +470,12 @@ describe('compiler: hoistStatic transform', () => { }, }, ]) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) - test('should hoist v-if props/children if static', () => { - const root = transformWithHoist( + test('should cache v-if props/children if static', () => { + const root = transformWithCache( `
`, ) expect(root.hoists).toMatchObject([ @@ -319,40 +483,31 @@ describe('compiler: hoistStatic transform', () => { key: `[0]`, // key injected by v-if branch id: 'foo', }), - { - type: NodeTypes.VNODE_CALL, - tag: `"span"`, - }, - hoistedChildrenArrayMatcher(2), ]) expect( ((root.children[0] as ElementNode).children[0] as IfNode).codegenNode, ).toMatchObject({ type: NodeTypes.JS_CONDITIONAL_EXPRESSION, consequent: { - // blocks should NOT be hoisted + // blocks should NOT be cached type: NodeTypes.VNODE_CALL, tag: `"div"`, props: { content: `_hoisted_1` }, - children: { content: `_hoisted_3` }, + children: cachedChildrenArrayMatcher(['span']), }, }) + expect(root.cached.length).toBe(1) expect(generate(root).code).toMatchSnapshot() }) test('should hoist v-for children if static', () => { - const root = transformWithHoist( + const root = transformWithCache( `
`, ) expect(root.hoists).toMatchObject([ createObjectMatcher({ id: 'foo', }), - { - type: NodeTypes.VNODE_CALL, - tag: `"span"`, - }, - hoistedChildrenArrayMatcher(2), ]) const forBlockCodegen = ( (root.children[0] as ElementNode).children[0] as ForNode @@ -365,85 +520,54 @@ 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({ type: NodeTypes.VNODE_CALL, tag: `"div"`, props: { content: `_hoisted_1` }, - children: { content: `_hoisted_3` }, + children: cachedChildrenArrayMatcher(['span']), }) + expect(root.cached.length).toBe(1) expect(generate(root).code).toMatchSnapshot() }) describe('prefixIdentifiers', () => { - test('hoist nested static tree with static interpolation', () => { - const root = transformWithHoist( + test('cache nested static tree with static interpolation', () => { + const root = transformWithCache( `
foo {{ 1 }} {{ true }}
`, { prefixIdentifiers: true, }, ) - expect(root.hoists).toMatchObject([ - { - type: NodeTypes.VNODE_CALL, - tag: `"span"`, - props: undefined, - children: { - type: NodeTypes.COMPOUND_EXPRESSION, - }, - }, - hoistedChildrenArrayMatcher(), - ]) expect(root.codegenNode).toMatchObject({ tag: `"div"`, props: undefined, - children: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_hoisted_2`, - }, + children: cachedChildrenArrayMatcher(['span']), }) + expect(root.cached.length).toBe(1) expect(generate(root).code).toMatchSnapshot() }) - test('hoist nested static tree with static prop value', () => { - const root = transformWithHoist( + test('cache nested static tree with static prop value', () => { + const root = transformWithCache( `
{{ 1 }}
`, { prefixIdentifiers: true, }, ) - - expect(root.hoists).toMatchObject([ - { - type: NodeTypes.VNODE_CALL, - tag: `"span"`, - props: createObjectMatcher({ foo: `[0]` }), - children: { - type: NodeTypes.INTERPOLATION, - content: { - content: `1`, - isStatic: false, - constType: ConstantTypes.CAN_STRINGIFY, - }, - }, - }, - hoistedChildrenArrayMatcher(), - ]) expect(root.codegenNode).toMatchObject({ tag: `"div"`, props: undefined, - children: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_hoisted_2`, - }, + children: cachedChildrenArrayMatcher(['span']), }) + expect(root.cached.length).toBe(1) expect(generate(root).code).toMatchSnapshot() }) test('hoist class with static object value', () => { - const root = transformWithHoist( + const root = transformWithCache( `
{{ bar }}
`, { prefixIdentifiers: true, @@ -496,7 +620,7 @@ describe('compiler: hoistStatic transform', () => { constType: ConstantTypes.NOT_CONSTANT, }, }, - patchFlag: `1 /* TEXT */`, + patchFlag: PatchFlags.TEXT, }, }, ], @@ -504,44 +628,44 @@ describe('compiler: hoistStatic transform', () => { expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist expressions that refer scope variables', () => { - const root = transformWithHoist( + test('should NOT cache expressions that refer scope variables', () => { + const root = transformWithCache( `

{{ o }}

`, { prefixIdentifiers: true, }, ) - expect(root.hoists.length).toBe(0) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist expressions that refer scope variables (2)', () => { - const root = transformWithHoist( + test('should NOT cache expressions that refer scope variables (2)', () => { + const root = transformWithCache( `

{{ o + 'foo' }}

`, { prefixIdentifiers: true, }, ) - expect(root.hoists.length).toBe(0) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist expressions that refer scope variables (v-slot)', () => { - const root = transformWithHoist( + test('should NOT cache expressions that refer scope variables (v-slot)', () => { + const root = transformWithCache( `{{ foo }}`, { prefixIdentifiers: true, }, ) - expect(root.hoists.length).toBe(0) + expect(root.cached.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist elements with cached handlers', () => { - const root = transformWithHoist( + test('should NOT cache elements with cached handlers', () => { + const root = transformWithCache( `
`, { prefixIdentifiers: true, @@ -549,7 +673,7 @@ describe('compiler: hoistStatic transform', () => { }, ) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.hoists.length).toBe(0) expect( generate(root, { @@ -559,8 +683,8 @@ describe('compiler: hoistStatic transform', () => { ).toMatchSnapshot() }) - test('should NOT hoist elements with cached handlers + other bindings', () => { - const root = transformWithHoist( + test('should NOT cache elements with cached handlers + other bindings', () => { + const root = transformWithCache( `
`, { prefixIdentifiers: true, @@ -568,7 +692,7 @@ describe('compiler: hoistStatic transform', () => { }, ) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.hoists.length).toBe(0) expect( generate(root, { @@ -578,32 +702,66 @@ describe('compiler: hoistStatic transform', () => { ).toMatchSnapshot() }) - test('should NOT hoist keyed template v-for with plain element child', () => { - const root = transformWithHoist( + test('should NOT cache keyed template v-for with plain element child', () => { + const root = transformWithCache( `
`, ) expect(root.hoists.length).toBe(0) expect(generate(root).code).toMatchSnapshot() }) - test('should NOT hoist SVG with directives', () => { - const root = transformWithHoist( + test('should NOT cache SVG with directives', () => { + const root = transformWithCache( `
`, ) - expect(root.hoists.length).toBe(2) + expect(root.cached.length).toBe(1) + expect(root.codegenNode).toMatchObject({ + children: [ + { + tag: 'svg', + // only cache the children, not the svg tag itself + codegenNode: { + children: { + type: NodeTypes.JS_CACHE_EXPRESSION, + }, + }, + }, + ], + }) expect(generate(root).code).toMatchSnapshot() }) - test('clone hoisted array children in HMR mode', () => { - const root = transformWithHoist(`
`, { - hmr: true, - }) - expect(root.hoists.length).toBe(2) - expect(root.codegenNode).toMatchObject({ + test('clone hoisted array children in v-for + HMR mode', () => { + const root = transformWithCache( + `
`, + { + hmr: true, + }, + ) + expect(root.cached.length).toBe(1) + const forBlockCodegen = ( + (root.children[0] as ElementNode).children[0] as ForNode + ).codegenNode + expect(forBlockCodegen).toMatchObject({ + type: NodeTypes.VNODE_CALL, + tag: FRAGMENT, + props: undefined, children: { - content: '[..._hoisted_2]', + type: NodeTypes.JS_CALL_EXPRESSION, + callee: RENDER_LIST, }, + patchFlag: PatchFlags.UNKEYED_FRAGMENT, }) + const innerBlockCodegen = forBlockCodegen!.children.arguments[1] + expect(innerBlockCodegen.returns).toMatchObject({ + type: NodeTypes.VNODE_CALL, + tag: `"div"`, + children: cachedChildrenArrayMatcher( + ['span'], + true /* needArraySpread */, + ), + }) + expect(generate(root).code).toMatchSnapshot() }) }) }) diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 10b9747d1ee..bf3510a052d 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -37,7 +37,7 @@ import { transformStyle } from '../../../compiler-dom/src/transforms/transformSt import { transformOn } from '../../src/transforms/vOn' import { transformBind } from '../../src/transforms/vBind' import { PatchFlags } from '@vue/shared' -import { createObjectMatcher, genFlagText } from '../testUtils' +import { createObjectMatcher } from '../testUtils' import { transformText } from '../../src/transforms/transformText' import { parseWithForTransform } from './vFor.spec' @@ -521,7 +521,7 @@ describe('compiler: element transform', () => { // keep-alive should not compile content to slots children: [{ type: NodeTypes.ELEMENT, tag: 'span' }], // should get a dynamic slots flag to force updates - patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS), + patchFlag: PatchFlags.DYNAMIC_SLOTS, }) } @@ -588,7 +588,7 @@ describe('compiler: element transform', () => { }) // should factor in props returned by custom directive transforms // in patchFlag analysis - expect(node.patchFlag).toMatch(PatchFlags.PROPS + '') + expect(node.patchFlag).toBe(PatchFlags.PROPS) expect(node.dynamicProps).toMatch(`"bar"`) }) @@ -612,7 +612,7 @@ describe('compiler: element transform', () => { tag: `"div"`, props: undefined, children: undefined, - patchFlag: genFlagText(PatchFlags.NEED_PATCH), // should generate appropriate flag + patchFlag: PatchFlags.NEED_PATCH, // should generate appropriate flag directives: { type: NodeTypes.JS_ARRAY_EXPRESSION, elements: [ @@ -945,26 +945,26 @@ describe('compiler: element transform', () => { expect(node.patchFlag).toBeUndefined() const { node: node2 } = parseWithBind(`
{{ foo }}
`) - expect(node2.patchFlag).toBe(genFlagText(PatchFlags.TEXT)) + expect(node2.patchFlag).toBe(PatchFlags.TEXT) // multiple nodes, merged with optimize text const { node: node3 } = parseWithBind(`
foo {{ bar }} baz
`) - expect(node3.patchFlag).toBe(genFlagText(PatchFlags.TEXT)) + expect(node3.patchFlag).toBe(PatchFlags.TEXT) }) test('CLASS', () => { const { node } = parseWithBind(`
`) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.CLASS)) + expect(node.patchFlag).toBe(PatchFlags.CLASS) }) test('STYLE', () => { const { node } = parseWithBind(`
`) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.STYLE)) + expect(node.patchFlag).toBe(PatchFlags.STYLE) }) test('PROPS', () => { const { node } = parseWithBind(`
`) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS)) + expect(node.patchFlag).toBe(PatchFlags.PROPS) expect(node.dynamicProps).toBe(`["foo", "baz"]`) }) @@ -973,7 +973,7 @@ describe('compiler: element transform', () => { `
`, ) expect(node.patchFlag).toBe( - genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS]), + PatchFlags.CLASS | PatchFlags.STYLE | PatchFlags.PROPS, ) expect(node.dynamicProps).toBe(`["foo", "baz"]`) }) @@ -983,40 +983,40 @@ describe('compiler: element transform', () => { const { node } = parseWithBind( ``, ) - 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(`
`) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS)) + expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS) }) test('FULL_PROPS (dynamic key)', () => { const { node } = parseWithBind(`
`) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS)) + expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS) }) test('FULL_PROPS (w/ others)', () => { const { node } = parseWithBind( `
`, ) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS)) + expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS) }) test('NEED_PATCH (static ref)', () => { const { node } = parseWithBind(`
`) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH)) + expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH) }) test('NEED_PATCH (dynamic ref)', () => { const { node } = parseWithBind(`
`) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH)) + expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH) }) test('NEED_PATCH (custom directives)', () => { const { node } = parseWithBind(`
`) - expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH)) + expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH) }) test('NEED_PATCH (vnode hooks)', () => { @@ -1025,7 +1025,7 @@ describe('compiler: element transform', () => { cacheHandlers: true, }).ast const node = (root as any).children[0].codegenNode - expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH)) + expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH) }) test('script setup inline mode template ref (binding exists)', () => { @@ -1120,7 +1120,7 @@ describe('compiler: element transform', () => { }, }) // should only have props flag - expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS)) + expect(node.patchFlag).toBe(PatchFlags.PROPS) const { node: node2 } = parseWithElementTransform( `
`, @@ -1130,21 +1130,15 @@ describe('compiler: element transform', () => { }, }, ) - expect(node2.patchFlag).toBe( - genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]), - ) + expect(node2.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION) }) test('NEED_HYDRATION for v-bind.prop', () => { const { node } = parseWithBind(`
`) - expect(node.patchFlag).toBe( - genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]), - ) + expect(node.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION) const { node: node2 } = parseWithBind(`
`) - expect(node2.patchFlag).toBe( - genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]), - ) + expect(node2.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION) }) // #5870 @@ -1157,9 +1151,7 @@ describe('compiler: element transform', () => { }, }, ) - expect(node.patchFlag).toBe( - genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]), - ) + expect(node.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION) }) test('should not have PROPS patchflag for constant v-on handlers', () => { @@ -1173,7 +1165,7 @@ describe('compiler: element transform', () => { }, }) // should only have hydration flag - expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION)) + expect(node.patchFlag).toBe(PatchFlags.NEED_HYDRATION) }) }) diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index ffd93d791ca..c92814089ef 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -27,6 +27,10 @@ function parseWithExpressionTransform( return ast.children[0] } +function compile(template: string) { + return baseCompile(template, { prefixIdentifiers: true }) +} + describe('compiler: expression transform', () => { test('interpolation (root)', () => { const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode @@ -291,6 +295,7 @@ describe('compiler: expression transform', () => { ], }) }) + test('should not prefix an object property key', () => { const node = parseWithExpressionTransform( `{{ { foo() { baz() }, value: bar } }}`, @@ -384,6 +389,17 @@ describe('compiler: expression transform', () => { ) }) + test('should not error', () => { + const onError = vi.fn() + parseWithExpressionTransform( + `

`, + { + onError, + }, + ) + expect(onError).not.toHaveBeenCalled() + }) + test('should prefix in assignment', () => { const node = parseWithExpressionTransform( `{{ x = 1 }}`, @@ -446,6 +462,90 @@ describe('compiler: expression transform', () => { }) }) + test('should not prefix temp variable of for...in', () => { + const { code } = compile( + `

`, + ) + expect(code).not.toMatch(`log(_ctx.x)`) + expect(code).toMatch(`error(_ctx.x)`) + expect(code).toMatchSnapshot() + }) + + test('should not prefix temp variable of for...of', () => { + const { code } = compile( + `
`, + ) + expect(code).not.toMatch(`log(_ctx.x)`) + expect(code).toMatch(`error(_ctx.x)`) + expect(code).toMatchSnapshot() + }) + + test('should not prefix temp variable of for loop', () => { + const { code } = compile( + `
`, + ) + expect(code).not.toMatch(`log(_ctx.i)`) + expect(code).toMatch(`error(_ctx.i)`) + expect(code).toMatchSnapshot() + }) + + test('should allow leak of var declarations in for loop', () => { + const { code } = compile( + `
`, + ) + expect(code).not.toMatch(`log(_ctx.i)`) + expect(code).not.toMatch(`error(_ctx.i)`) + expect(code).toMatchSnapshot() + }) + + test('should not prefix catch block param', () => { + const { code } = compile( + `
`, + ) + expect(code).not.toMatch(`console.error(_ctx.err)`) + expect(code).toMatch(`console.log(_ctx.err)`) + expect(code).toMatchSnapshot() + }) + + test('should not prefix destructured catch block param', () => { + const { code } = compile( + `
`, + ) + expect(code).not.toMatch(`console.error(_ctx.length)`) + expect(code).toMatch(`console.log(_ctx.length)`) + expect(code).toMatchSnapshot() + }) + describe('ES Proposals support', () => { test('bigInt', () => { const node = parseWithExpressionTransform( @@ -544,42 +644,6 @@ describe('compiler: expression transform', () => { expect(code).toMatchSnapshot() }) - test('should not prefix temp variable of for...in', () => { - const { code } = compileWithBindingMetadata( - `
`, - ) - expect(code).not.toMatch(`_ctx.x`) - expect(code).toMatchSnapshot() - }) - - test('should not prefix temp variable of for...of', () => { - const { code } = compileWithBindingMetadata( - `
`, - ) - expect(code).not.toMatch(`_ctx.x`) - expect(code).toMatchSnapshot() - }) - - test('should not prefix temp variable of for loop', () => { - const { code } = compileWithBindingMetadata( - `
`, - ) - expect(code).not.toMatch(`_ctx.i`) - expect(code).toMatchSnapshot() - }) - test('inline mode', () => { const { code } = compileWithBindingMetadata( `
{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}
`, diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 94f75f2a63b..fead2476ac5 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -13,18 +13,22 @@ import { type ForNode, type InterpolationNode, NodeTypes, + type RootNode, type SimpleExpressionNode, } from '../../src/ast' import { ErrorCodes } from '../../src/errors' import { type CompilerOptions, generate } from '../../src' import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers' -import { PatchFlagNames, PatchFlags } from '@vue/shared' -import { createObjectMatcher, genFlagText } from '../testUtils' +import { PatchFlags } from '@vue/shared' +import { createObjectMatcher } from '../testUtils' export function parseWithForTransform( template: string, options: CompilerOptions = {}, -) { +): { + root: RootNode + node: ForNode & { codegenNode: ForCodegenNode } +} { const ast = parse(template, options) transform(ast, { nodeTransforms: [ @@ -696,10 +700,10 @@ describe('compiler: v-for', () => { tag: FRAGMENT, disableTracking, patchFlag: !disableTracking - ? genFlagText(PatchFlags.STABLE_FRAGMENT) + ? PatchFlags.STABLE_FRAGMENT : keyed - ? genFlagText(PatchFlags.KEYED_FRAGMENT) - : genFlagText(PatchFlags.UNKEYED_FRAGMENT), + ? PatchFlags.KEYED_FRAGMENT + : PatchFlags.UNKEYED_FRAGMENT, children: { type: NodeTypes.JS_CALL_EXPRESSION, callee: RENDER_LIST, @@ -822,7 +826,7 @@ describe('compiler: v-for', () => { constType: ConstantTypes.NOT_CONSTANT, }, }, - patchFlag: genFlagText(PatchFlags.TEXT), + patchFlag: PatchFlags.TEXT, }, }) expect(generate(root).code).toMatchSnapshot() @@ -846,7 +850,7 @@ describe('compiler: v-for', () => { { type: NodeTypes.TEXT, content: `hello` }, { type: NodeTypes.ELEMENT, tag: `span` }, ], - patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT), + patchFlag: PatchFlags.STABLE_FRAGMENT, }, }) expect(generate(root).code).toMatchSnapshot() @@ -950,7 +954,7 @@ describe('compiler: v-for', () => { { type: NodeTypes.TEXT, content: `hello` }, { type: NodeTypes.ELEMENT, tag: `span` }, ], - patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT), + patchFlag: PatchFlags.STABLE_FRAGMENT, }, }) expect(generate(root).code).toMatchSnapshot() @@ -971,7 +975,7 @@ describe('compiler: v-for', () => { }), isBlock: true, disableTracking: true, - patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT), + patchFlag: PatchFlags.UNKEYED_FRAGMENT, children: { type: NodeTypes.JS_CALL_EXPRESSION, callee: RENDER_LIST, @@ -1009,7 +1013,7 @@ describe('compiler: v-for', () => { }), isBlock: true, disableTracking: true, - patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT), + patchFlag: PatchFlags.UNKEYED_FRAGMENT, children: { type: NodeTypes.JS_CALL_EXPRESSION, callee: RENDER_LIST, @@ -1048,9 +1052,7 @@ describe('compiler: v-for', () => { const { node: { codegenNode }, } = parseWithForTransform('
test
') - expect(codegenNode.patchFlag).toBe( - `${PatchFlags.KEYED_FRAGMENT} /* ${PatchFlagNames[PatchFlags.KEYED_FRAGMENT]} */`, - ) + expect(codegenNode.patchFlag).toBe(PatchFlags.KEYED_FRAGMENT) }) test('template v-for key w/ :key shorthand on template injected to the child', () => { diff --git a/packages/compiler-core/__tests__/transforms/vModel.spec.ts b/packages/compiler-core/__tests__/transforms/vModel.spec.ts index 5d94aca2777..82dd4909fd6 100644 --- a/packages/compiler-core/__tests__/transforms/vModel.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vModel.spec.ts @@ -399,7 +399,7 @@ describe('compiler: transform v-model', () => { prefixIdentifiers: true, cacheHandlers: true, }) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) const codegen = (root.children[0] as PlainElementNode) .codegenNode as VNodeCall // should not list cached prop in dynamicProps @@ -417,7 +417,7 @@ describe('compiler: transform v-model', () => { cacheHandlers: true, }, ) - expect(root.cached).toBe(0) + expect(root.cached.length).toBe(0) const codegen = ( (root.children[0] as ForNode).children[0] as PlainElementNode ).codegenNode as VNodeCall @@ -433,7 +433,7 @@ describe('compiler: transform v-model', () => { cacheHandlers: true, }) expect(root.cached).not.toBe(2) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) }) test('should mark update handler dynamic if it refers slot scope variables', () => { diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts index 27d5027533b..9fda0259585 100644 --- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts @@ -285,6 +285,21 @@ describe('compiler: transform v-on', () => { }, ], }) + + const { node: node2 } = parseWithVOn( + `
`, + ) + expect((node2.codegenNode as VNodeCall).props).toMatchObject({ + properties: [ + { + key: { content: `onClick` }, + value: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `(e: (number | string)[]) => foo(e)`, + }, + }, + ], + }) }) test('should NOT wrap as function if expression is already function expression (async)', () => { @@ -505,7 +520,7 @@ describe('compiler: transform v-on', () => { prefixIdentifiers: true, cacheHandlers: true, }) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) const vnodeCall = node.codegenNode as VNodeCall // should not treat cached handler as dynamicProp, so no flags expect(vnodeCall.patchFlag).toBeUndefined() @@ -526,7 +541,7 @@ describe('compiler: transform v-on', () => { prefixIdentifiers: true, cacheHandlers: true, }) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) const vnodeCall = node.codegenNode as VNodeCall // should not treat cached handler as dynamicProp, so no flags expect(vnodeCall.patchFlag).toBeUndefined() @@ -551,7 +566,7 @@ describe('compiler: transform v-on', () => { prefixIdentifiers: true, cacheHandlers: true, }) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) const vnodeCall = node.codegenNode as VNodeCall // should not treat cached handler as dynamicProp, so no flags expect(vnodeCall.patchFlag).toBeUndefined() @@ -588,7 +603,7 @@ describe('compiler: transform v-on', () => { cacheHandlers: true, isNativeTag: tag => tag === 'div', }) - expect(root.cached).toBe(0) + expect(root.cached.length).toBe(0) }) test('should not be cached inside v-once', () => { @@ -599,8 +614,8 @@ describe('compiler: transform v-on', () => { cacheHandlers: true, }, ) - expect(root.cached).not.toBe(2) - expect(root.cached).toBe(1) + expect(root.cached.length).not.toBe(2) + expect(root.cached.length).toBe(1) }) test('unicode identifier should not be cached (v-for)', () => { @@ -611,7 +626,7 @@ describe('compiler: transform v-on', () => { cacheHandlers: true, }, ) - expect(root.cached).toBe(0) + expect(root.cached.length).toBe(0) }) test('inline function expression handler', () => { @@ -619,7 +634,7 @@ describe('compiler: transform v-on', () => { prefixIdentifiers: true, cacheHandlers: true, }) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) const vnodeCall = node.codegenNode as VNodeCall // should not treat cached handler as dynamicProp, so no flags expect(vnodeCall.patchFlag).toBeUndefined() @@ -643,7 +658,7 @@ describe('compiler: transform v-on', () => { cacheHandlers: true, }, ) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) const vnodeCall = node.codegenNode as VNodeCall // should not treat cached handler as dynamicProp, so no flags expect(vnodeCall.patchFlag).toBeUndefined() @@ -668,7 +683,7 @@ describe('compiler: transform v-on', () => { }, ) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) const vnodeCall = node.codegenNode as VNodeCall // should not treat cached handler as dynamicProp, so no flags expect(vnodeCall.patchFlag).toBeUndefined() @@ -700,7 +715,7 @@ describe('compiler: transform v-on', () => { cacheHandlers: true, }, ) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) const vnodeCall = node.codegenNode as VNodeCall // should not treat cached handler as dynamicProp, so no flags expect(vnodeCall.patchFlag).toBeUndefined() @@ -725,8 +740,8 @@ describe('compiler: transform v-on', () => { prefixIdentifiers: true, cacheHandlers: true, }) - expect(root.cached).toBe(1) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) + expect(root.cached.length).toBe(1) const vnodeCall = node.codegenNode as VNodeCall // should not treat cached handler as dynamicProp, so no flags expect(vnodeCall.patchFlag).toBeUndefined() diff --git a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts index 30149c62df6..5f47a0fdbd9 100644 --- a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts @@ -22,7 +22,7 @@ function transformWithOnce(template: string, options: CompilerOptions = {}) { describe('compiler: v-once transform', () => { test('as root node', () => { const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.helpers).toContain(SET_BLOCK_TRACKING) expect(root.codegenNode).toMatchObject({ type: NodeTypes.JS_CACHE_EXPRESSION, @@ -37,7 +37,7 @@ describe('compiler: v-once transform', () => { test('on nested plain element', () => { const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.helpers).toContain(SET_BLOCK_TRACKING) expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ type: NodeTypes.JS_CACHE_EXPRESSION, @@ -52,7 +52,7 @@ describe('compiler: v-once transform', () => { test('on component', () => { const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.helpers).toContain(SET_BLOCK_TRACKING) expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ type: NodeTypes.JS_CACHE_EXPRESSION, @@ -67,7 +67,7 @@ describe('compiler: v-once transform', () => { test('on slot outlet', () => { const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.helpers).toContain(SET_BLOCK_TRACKING) expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ type: NodeTypes.JS_CACHE_EXPRESSION, @@ -84,7 +84,7 @@ describe('compiler: v-once transform', () => { test('inside v-once', () => { const root = transformWithOnce(`
`) expect(root.cached).not.toBe(2) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) }) // cached nodes should be ignored by hoistStatic transform @@ -92,7 +92,7 @@ describe('compiler: v-once transform', () => { const root = transformWithOnce(`
`, { hoistStatic: true, }) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.helpers).toContain(SET_BLOCK_TRACKING) expect(root.hoists.length).toBe(0) expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ @@ -108,7 +108,7 @@ describe('compiler: v-once transform', () => { test('with v-if/else', () => { const root = transformWithOnce(`

`) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.helpers).toContain(SET_BLOCK_TRACKING) expect(root.children[0]).toMatchObject({ type: NodeTypes.IF, @@ -132,7 +132,7 @@ describe('compiler: v-once transform', () => { test('with v-for', () => { const root = transformWithOnce(`

`) - expect(root.cached).toBe(1) + expect(root.cached.length).toBe(1) expect(root.helpers).toContain(SET_BLOCK_TRACKING) expect(root.children[0]).toMatchObject({ type: NodeTypes.FOR, diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index aa7b600ccbe..4766c2ca9d8 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -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() }) diff --git a/packages/compiler-core/__tests__/utils.spec.ts b/packages/compiler-core/__tests__/utils.spec.ts index 506aa86982e..2d377a271ac 100644 --- a/packages/compiler-core/__tests__/utils.spec.ts +++ b/packages/compiler-core/__tests__/utils.spec.ts @@ -1,5 +1,5 @@ -import type { TransformContext } from '../src' -import type { Position } from '../src/ast' +import type { ExpressionNode, TransformContext } from '../src' +import { type Position, createSimpleExpression } from '../src/ast' import { advancePositionWithClone, isMemberExpressionBrowser, @@ -41,7 +41,8 @@ describe('advancePositionWithClone', () => { }) describe('isMemberExpression', () => { - function commonAssertions(fn: (str: string) => boolean) { + function commonAssertions(raw: (exp: ExpressionNode) => boolean) { + const fn = (str: string) => raw(createSimpleExpression(str)) // should work expect(fn('obj.foo')).toBe(true) expect(fn('obj[foo]')).toBe(true) @@ -78,13 +79,16 @@ describe('isMemberExpression', () => { test('browser', () => { commonAssertions(isMemberExpressionBrowser) - expect(isMemberExpressionBrowser('123[a]')).toBe(false) + expect(isMemberExpressionBrowser(createSimpleExpression('123[a]'))).toBe( + false, + ) }) test('node', () => { const ctx = { expressionPlugins: ['typescript'] } as any as TransformContext - const fn = (str: string) => isMemberExpressionNode(str, ctx) - commonAssertions(fn) + const fn = (str: string) => + isMemberExpressionNode(createSimpleExpression(str), ctx) + commonAssertions(exp => isMemberExpressionNode(exp, ctx)) // TS-specific checks expect(fn('foo as string')).toBe(true) diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 34c99e1ee91..a4e941aa43a 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.4.27", + "version": "3.5.12", "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.6", + "@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.6" + "@babel/types": "catalog:" } } diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 91354b1b40b..cfd5fee2569 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -1,4 +1,4 @@ -import { isString } from '@vue/shared' +import { type PatchFlags, isString } from '@vue/shared' import { CREATE_BLOCK, CREATE_ELEMENT_BLOCK, @@ -110,7 +110,7 @@ export interface RootNode extends Node { directives: string[] hoists: (JSChildNode | null)[] imports: ImportItem[] - cached: number + cached: (CacheExpression | null)[] temps: number ssrHelpers?: symbol[] codegenNode?: TemplateChildNode | JSChildNode | BlockStatement @@ -203,7 +203,7 @@ export interface DirectiveNode extends Node { rawName?: string exp: ExpressionNode | undefined arg: ExpressionNode | undefined - modifiers: string[] + modifiers: SimpleExpressionNode[] /** * optional property to cache the expression parse result for v-for */ @@ -218,7 +218,7 @@ export interface DirectiveNode extends Node { export enum ConstantTypes { NOT_CONSTANT = 0, CAN_SKIP_PATCH, - CAN_HOIST, + CAN_CACHE, CAN_STRINGIFY, } @@ -330,8 +330,9 @@ export interface VNodeCall extends Node { | SlotsExpression // component slots | ForRenderListExpression // v-for fragment call | SimpleExpressionNode // hoisted + | CacheExpression // cached | undefined - patchFlag: string | undefined + patchFlag: PatchFlags | undefined dynamicProps: string | SimpleExpressionNode | undefined directives: DirectiveArguments | undefined isBlock: boolean @@ -416,7 +417,8 @@ export interface CacheExpression extends Node { type: NodeTypes.JS_CACHE_EXPRESSION index: number value: JSChildNode - isVNode: boolean + needPauseTracking: boolean + needArraySpread: boolean } export interface MemoExpression extends CallExpression { @@ -511,7 +513,7 @@ export interface SlotsObjectProperty extends Property { } export interface SlotFunctionExpression extends FunctionExpression { - returns: TemplateChildNode[] + returns: TemplateChildNode[] | CacheExpression } // createSlots({ ... }, [ @@ -561,7 +563,7 @@ export interface ForCodegenNode extends VNodeCall { tag: typeof FRAGMENT props: undefined children: ForRenderListExpression - patchFlag: string + patchFlag: PatchFlags disableTracking: boolean } @@ -598,7 +600,7 @@ export function createRoot( directives: [], hoists: [], imports: [], - cached: 0, + cached: [], temps: 0, codegenNode: undefined, loc: locStub, @@ -616,7 +618,7 @@ export function createVNodeCall( isBlock: VNodeCall['isBlock'] = false, disableTracking: VNodeCall['disableTracking'] = false, isComponent: VNodeCall['isComponent'] = false, - loc = locStub, + loc: SourceLocation = locStub, ): VNodeCall { if (context) { if (isBlock) { @@ -771,13 +773,14 @@ export function createConditionalExpression( export function createCacheExpression( index: number, value: JSChildNode, - isVNode: boolean = false, + needPauseTracking: boolean = false, ): CacheExpression { return { type: NodeTypes.JS_CACHE_EXPRESSION, index, value, - isVNode, + needPauseTracking: needPauseTracking, + needArraySpread: false, loc: locStub, } } @@ -848,18 +851,24 @@ export function createReturnStatement( } } -export function getVNodeHelper(ssr: boolean, isComponent: boolean) { +export function getVNodeHelper( + ssr: boolean, + isComponent: boolean, +): typeof CREATE_VNODE | typeof CREATE_ELEMENT_VNODE { return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE } -export function getVNodeBlockHelper(ssr: boolean, isComponent: boolean) { +export function getVNodeBlockHelper( + ssr: boolean, + isComponent: boolean, +): typeof CREATE_BLOCK | typeof CREATE_ELEMENT_BLOCK { return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK } export function convertToBlock( node: VNodeCall, { helper, removeHelper, inSSR }: TransformContext, -) { +): void { if (!node.isBlock) { node.isBlock = true removeHelper(getVNodeHelper(inSSR, node.isComponent)) diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 7482494e17a..52fabeea896 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -2,6 +2,9 @@ // do not import runtime methods import type { BlockStatement, + ForInStatement, + ForOfStatement, + ForStatement, Function, Identifier, Node, @@ -17,7 +20,7 @@ export function walkIdentifiers( root: Node, onIdentifier: ( node: Identifier, - parent: Node, + parent: Node | null, parentStack: Node[], isReference: boolean, isLocal: boolean, @@ -25,7 +28,7 @@ export function walkIdentifiers( includeAll = false, parentStack: Node[] = [], knownIds: Record = Object.create(null), -) { +): void { if (__BROWSER__) { return } @@ -36,7 +39,7 @@ export function walkIdentifiers( : root walk(root, { - enter(node: Node & { scopeIds?: Set }, parent: Node | undefined) { + enter(node: Node & { scopeIds?: Set }, parent: Node | null) { parent && parentStack.push(parent) if ( parent && @@ -47,9 +50,9 @@ export function walkIdentifiers( } if (node.type === 'Identifier') { const isLocal = !!knownIds[node.name] - const isRefed = isReferencedIdentifier(node, parent!, parentStack) + const isRefed = isReferencedIdentifier(node, parent, parentStack) if (includeAll || (isRefed && !isLocal)) { - onIdentifier(node, parent!, parentStack, isRefed, isLocal) + onIdentifier(node, parent, parentStack, isRefed, isLocal) } } else if ( node.type === 'ObjectProperty' && @@ -77,9 +80,17 @@ export function walkIdentifiers( markScopeIdentifier(node, id, knownIds), ) } + } else if (node.type === 'CatchClause' && node.param) { + for (const id of extractIdentifiers(node.param)) { + markScopeIdentifier(node, id, knownIds) + } + } else if (isForStatement(node)) { + walkForStatement(node, false, id => + markScopeIdentifier(node, id, knownIds), + ) } }, - leave(node: Node & { scopeIds?: Set }, parent: Node | undefined) { + leave(node: Node & { scopeIds?: Set }, parent: Node | null) { parent && parentStack.pop() if (node !== rootExp && node.scopeIds) { for (const id of node.scopeIds) { @@ -97,7 +108,7 @@ export function isReferencedIdentifier( id: Identifier, parent: Node | null, parentStack: Node[], -) { +): boolean { if (__BROWSER__) { return false } @@ -166,7 +177,7 @@ export function isInNewExpression(parentStack: Node[]): boolean { export function walkFunctionParams( node: Function, onIdent: (id: Identifier) => void, -) { +): void { for (const p of node.params) { for (const id of extractIdentifiers(p)) { onIdent(id) @@ -177,7 +188,7 @@ export function walkFunctionParams( export function walkBlockDeclarations( block: BlockStatement | Program, onIdent: (node: Identifier) => void, -) { +): void { for (const stmt of block.body) { if (stmt.type === 'VariableDeclaration') { if (stmt.declare) continue @@ -192,18 +203,36 @@ export function walkBlockDeclarations( ) { if (stmt.declare || !stmt.id) continue onIdent(stmt.id) - } else if ( - stmt.type === 'ForOfStatement' || - stmt.type === 'ForInStatement' || - stmt.type === 'ForStatement' - ) { - const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left - if (variable && variable.type === 'VariableDeclaration') { - for (const decl of variable.declarations) { - for (const id of extractIdentifiers(decl.id)) { - onIdent(id) - } - } + } else if (isForStatement(stmt)) { + walkForStatement(stmt, true, onIdent) + } + } +} + +function isForStatement( + stmt: Node, +): stmt is ForStatement | ForOfStatement | ForInStatement { + return ( + stmt.type === 'ForOfStatement' || + stmt.type === 'ForInStatement' || + stmt.type === 'ForStatement' + ) +} + +function walkForStatement( + stmt: ForStatement | ForOfStatement | ForInStatement, + isVar: boolean, + onIdent: (id: Identifier) => void, +) { + const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left + if ( + variable && + variable.type === 'VariableDeclaration' && + (variable.kind === 'var' ? isVar : !isVar) + ) { + for (const decl of variable.declarations) { + for (const id of extractIdentifiers(decl.id)) { + onIdent(id) } } } @@ -284,7 +313,7 @@ export const isStaticProperty = (node: Node): node is ObjectProperty => (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') && !node.computed -export const isStaticPropertyKey = (node: Node, parent: Node) => +export const isStaticPropertyKey = (node: Node, parent: Node): boolean => isStaticProperty(parent) && parent.key === node /** @@ -466,7 +495,7 @@ function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean { return true } -export const TS_NODE_TYPES = [ +export const TS_NODE_TYPES: string[] = [ 'TSAsExpression', // foo as number 'TSTypeAssertion', // (foo) 'TSNonNullExpression', // foo! diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 39170bac5a0..6b6f24b3a30 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -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, @@ -43,8 +49,6 @@ import { CREATE_TEXT, CREATE_VNODE, OPEN_BLOCK, - POP_SCOPE_ID, - PUSH_SCOPE_ID, RESOLVE_COMPONENT, RESOLVE_DIRECTIVE, RESOLVE_FILTER, @@ -95,7 +99,7 @@ interface MappingItem { name: string | null } -const PURE_ANNOTATION = `/*#__PURE__*/` +const PURE_ANNOTATION = `/*@__PURE__*/` const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}` @@ -473,11 +477,6 @@ function genModulePreamble( ssrRuntimeModuleName, } = context - if (genScopeId && ast.hoists.length) { - ast.helpers.add(PUSH_SCOPE_ID) - ast.helpers.add(POP_SCOPE_ID) - } - // generate import statements for helpers if (ast.helpers.size) { const helpers = Array.from(ast.helpers) @@ -566,34 +565,14 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) { return } context.pure = true - const { push, newline, helper, scopeId, mode } = context - const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function' + const { push, newline } = context newline() - // generate inlined withScopeId helper - if (genScopeId) { - const param = context.isTS ? '(n: any)' : 'n' - push( - `const _withScopeId = ${param} => (${helper( - PUSH_SCOPE_ID, - )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`, - ) - newline() - } - for (let i = 0; i < hoists.length; i++) { const exp = hoists[i] if (exp) { - const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL - push( - `const _hoisted_${i + 1} = ${ - needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : `` - }`, - ) + push(`const _hoisted_${i + 1} = `) genNode(exp, context) - if (needScopeIdWrapper) { - push(`)`) - } newline() } } @@ -746,7 +725,7 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) { !__BROWSER__ && genReturnStatement(node, context) break - /* istanbul ignore next */ + /* v8 ignore start */ case NodeTypes.IF_BRANCH: // noop break @@ -757,6 +736,7 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) { const exhaustiveCheck: never = node return exhaustiveCheck } + /* v8 ignore stop */ } } @@ -843,6 +823,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) + `(`) } @@ -857,7 +859,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,16 +1010,21 @@ function genConditionalExpression( function genCacheExpression(node: CacheExpression, context: CodegenContext) { const { push, helper, indent, deindent, newline } = context + const { needPauseTracking, needArraySpread } = node + if (needArraySpread) { + push(`[...(`) + } push(`_cache[${node.index}] || (`) - if (node.isVNode) { + if (needPauseTracking) { indent() push(`${helper(SET_BLOCK_TRACKING)}(-1),`) newline() + push(`(`) } push(`_cache[${node.index}] = `) genNode(node.value, context) - if (node.isVNode) { - push(`,`) + if (needPauseTracking) { + push(`).cacheIndex = ${node.index},`) newline() push(`${helper(SET_BLOCK_TRACKING)}(1),`) newline() @@ -1025,6 +1032,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) { deindent() } push(`)`) + if (needArraySpread) { + push(`)]`) + } } function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) { diff --git a/packages/compiler-core/src/compat/compatConfig.ts b/packages/compiler-core/src/compat/compatConfig.ts index bf06f4aae0f..58c5d9611f2 100644 --- a/packages/compiler-core/src/compat/compatConfig.ts +++ b/packages/compiler-core/src/compat/compatConfig.ts @@ -106,7 +106,7 @@ function getCompatValue( export function isCompatEnabled( key: CompilerDeprecationTypes, context: MergedParserOptions | TransformContext, -) { +): boolean { const mode = getCompatValue('MODE', context) const value = getCompatValue(key, context) // in v3 mode, only enable if explicitly set to true @@ -132,7 +132,7 @@ export function warnDeprecation( context: MergedParserOptions | TransformContext, loc: SourceLocation | null, ...args: any[] -) { +): void { const val = getCompatValue(key, context) if (val === 'suppress-warning') { return diff --git a/packages/compiler-core/src/compat/transformFilter.ts b/packages/compiler-core/src/compat/transformFilter.ts index 52b381567d5..4791e67543e 100644 --- a/packages/compiler-core/src/compat/transformFilter.ts +++ b/packages/compiler-core/src/compat/transformFilter.ts @@ -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 && diff --git a/packages/compiler-core/src/compile.ts b/packages/compiler-core/src/compile.ts index 854c2bc6d69..a697c9d22e6 100644 --- a/packages/compiler-core/src/compile.ts +++ b/packages/compiler-core/src/compile.ts @@ -68,7 +68,7 @@ export function baseCompile( ): CodegenResult { const onError = options.onError || defaultOnError const isModuleMode = options.mode === 'module' - /* istanbul ignore if */ + /* v8 ignore start */ if (__BROWSER__) { if (options.prefixIdentifiers === true) { onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED)) @@ -76,6 +76,7 @@ export function baseCompile( onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED)) } } + /* v8 ignore stop */ const prefixIdentifiers = !__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode) diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index a536392b9a3..58e113ab19e 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -9,11 +9,11 @@ export interface CoreCompilerError extends CompilerError { code: ErrorCodes } -export function defaultOnError(error: CompilerError) { +export function defaultOnError(error: CompilerError): never { throw error } -export function defaultOnWarn(msg: CompilerError) { +export function defaultOnWarn(msg: CompilerError): void { __DEV__ && console.warn(`[Vue warn] ${msg.message}`) } diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 47628b66979..29e5f681300 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -67,7 +67,7 @@ export { type PropsExpression, } from './transforms/transformElement' export { processSlotOutlet } from './transforms/transformSlotOutlet' -export { getConstantType } from './transforms/hoistStatic' +export { getConstantType } from './transforms/cacheStatic' export { generateCodeFrame } from '@vue/shared' // v2 compat only diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index 5a8cd0079b9..1de865f42eb 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -52,6 +52,11 @@ export interface ParserOptions * e.g. elements that should preserve whitespace inside, e.g. `
`
    */
   isPreTag?: (tag: string) => boolean
+  /**
+   * Elements that should ignore the first newline token per parinsg spec
+   * e.g. `', parserOptions)
+      const element = ast.children[0] as ElementNode
+      const text = element.children[0] as TextNode
+      expect(element.children.length).toBe(1)
+      expect(text).toStrictEqual({
+        type: NodeTypes.TEXT,
+        content: 'hello',
+        loc: {
+          start: { offset: 10, line: 1, column: 11 },
+          end: { offset: 16, line: 2, column: 6 },
+          source: '\nhello',
+        },
+      })
+    })
+
     test('should not treat Uppercase component as special tag', () => {
       const ast = parse(
         '',
diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
index 57d880a03f6..a863eb32e61 100644
--- a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
+++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
@@ -1,96 +1,157 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
-exports[`stringify static html > should bail for 
`, ) // should be optimized now - expect(ast.hoists).toMatchObject([ - { - type: NodeTypes.JS_CALL_EXPRESSION, - callee: CREATE_STATIC, - arguments: [ - JSON.stringify( - ``, - ), - '1', - ], - }, - { - type: NodeTypes.JS_ARRAY_EXPRESSION, - }, + expect(ast.cached).toMatchObject([ + cachedArrayStaticNodeMatcher( + ``, + 1, + ), ]) expect(code).toMatchSnapshot() }) @@ -522,14 +466,32 @@ describe('stringify static html', () => { StringifyThresholds.ELEMENT_WITH_BINDING_COUNT, )}
`, ) - expect(ast.hoists).toMatchObject([ - { - type: NodeTypes.VNODE_CALL, - }, - { - type: NodeTypes.JS_ARRAY_EXPRESSION, - }, - ]) + expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()]) + expect(code).toMatchSnapshot() + }) + + test('should bail for