Merge branch 'main' into fix/8466
This commit is contained in:
commit
5bf9068082
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable no-restricted-globals */
|
||||
|
||||
const DOMGlobals = ['window', 'document']
|
||||
const NodeGlobals = ['module', 'require']
|
||||
|
||||
|
@ -9,12 +11,6 @@ module.exports = {
|
|||
plugins: ['jest'],
|
||||
rules: {
|
||||
'no-debugger': 'error',
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
// we are only using this rule to check for unused arguments since TS
|
||||
// catches unused variables but not args.
|
||||
{ varsIgnorePattern: '.*', args: 'none' }
|
||||
],
|
||||
// most of the codebase are expected to be env agnostic
|
||||
'no-restricted-globals': ['error', ...DOMGlobals, ...NodeGlobals],
|
||||
|
||||
|
@ -72,6 +68,14 @@ module.exports = {
|
|||
'no-restricted-syntax': 'off'
|
||||
}
|
||||
},
|
||||
// JavaScript files
|
||||
{
|
||||
files: ['*.js', '*.cjs'],
|
||||
rules: {
|
||||
// We only do `no-unused-vars` checks for js files, TS files are checked by TypeScript itself.
|
||||
'no-unused-vars': ['error', { vars: 'all', args: 'none' }]
|
||||
}
|
||||
},
|
||||
// Node scripts
|
||||
{
|
||||
files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'],
|
||||
|
|
|
@ -17,7 +17,11 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
|||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- Checkout a topic branch from a base branch, e.g. `main`, and merge back against that branch.
|
||||
- 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.
|
||||
|
||||
- Otherwise, it should be submitted against the `main` branch.
|
||||
|
||||
- [Make sure to tick the "Allow edits from maintainers" box](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). This allows us to directly make minor edits / refactors and saves a lot of time.
|
||||
|
||||
|
@ -181,7 +185,7 @@ Shortcut for starting the SFC Playground in local dev mode. This provides the fa
|
|||
|
||||
### `nr dev-esm`
|
||||
|
||||
Builds and watches `vue/dist/vue-runtime.esm-bundler.js` with all deps inlined using esbuild. This is useful when debugging the ESM build in a reproductions that require real build setups: link `packages/vue` globally, then link it into the project being debugged.
|
||||
Builds and watches `vue/dist/vue-runtime.esm-bundler.js` with all deps inlined using esbuild. This is useful when debugging the ESM build in a reproduction that requires real build setups: link `packages/vue` globally, then link it into the project being debugged.
|
||||
|
||||
### `nr dev-compiler`
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
Binary file not shown.
After Width: | Height: | Size: 262 KiB |
|
@ -0,0 +1,122 @@
|
|||
# Vue Core Maintenance Handbook
|
||||
|
||||
Unlike [contributing.md](./contributing.md), which targets external contributors, this document is mainly intended for team members responsible for maintaining the project. It provides guidelines on how to triage issues, review & merge PRs, and publish releases. However, it should also be valuable to external contributors even if you are not a maintainer, as it gives you a better idea of how the maintainers operate, and how you can better collaborate with them. And who knows - maybe one day you will join as a maintainer as well!
|
||||
|
||||
- [Issue Triage Workflow](#issue-triage-workflow)
|
||||
- [Pull Request Review Guidelines](#pull-request-review-guidelines)
|
||||
- [Reviewing a Fix](#reviewing-a-fix)
|
||||
- [Reviewing a Refactor](#reviewing-a-refactor)
|
||||
- [Reviewing a Feature](#reviewing-a-feature)
|
||||
- [Common Considerations for All PRs](#common-considerations-for-all-prs)
|
||||
- [PR Merge Rules for Team Members](#pr-merge-rules-for-team-members)
|
||||
- [Git Branch and Release Workflow](#git-branch-and-release-workflow)
|
||||
|
||||
## Issue Triage Workflow
|
||||
|
||||

|
||||
|
||||
## Pull Request Review Guidelines
|
||||
|
||||
The first step of reviewing a PR is to identify its purpose. We can usually put a PR in one of these categories:
|
||||
|
||||
- **Fix**: fixes some wrong behavior. Usually associated with an issue that has a reproduction of the behavior being fixed.
|
||||
- **Refactor**: improves performance or code quality, but does not affect behavior.
|
||||
- **Feature**: implements something that increases the public API surface.
|
||||
|
||||
Depending on the type of the PR, different considerations need to be taken into account.
|
||||
|
||||
### Reviewing a Fix
|
||||
|
||||
- Is the PR fixing a well defined issue / bug report?
|
||||
- If not, ask to clarify context / provide reproduction or failing test case
|
||||
- In most cases, a fix PR should include a test case that fails without the fix.
|
||||
- Is it the right fix?
|
||||
- If not, guide user to rework the PR.
|
||||
- If the needed change is small and obvious, can directly push to the PR or add inline suggestions to reduce the back-and-forth.
|
||||
- Is the cost justified?
|
||||
- Sometimes the fix for a rare edge case might be introducing disproportionately large overhead (perf or code size). We should try our best to reduce the overhead to make the fix a reasonable tradeoff.
|
||||
- If the reviewer is not sure about a fix, try to leave a comment explaining the concerns / reservations so the contributor at least gets some feedback.
|
||||
|
||||
#### Verifying a Fix
|
||||
|
||||
- **Always locally verify that the fix indeed fixes the original behavior, either through a reproduction or a failing test case.**
|
||||
- We will run [ecosystem-ci](https://github.com/vuejs/ecosystem-ci) before every release, but if you are concerned about the potential impact of a change, it never hurts to manually run ecosystem-ci by leaving a `/ecosystem-ci run` comment (only works for team members).
|
||||
- Take extra caution with snapshot tests! The CI can be "passing" even if the code generated in the snapshot contains bugs. It's best to always accompany a snapshot test with extra `expect(code).toMatch(...)` assertions.
|
||||
|
||||
### Reviewing a Refactor
|
||||
|
||||
- Performance: if a refactor PR claims to improve performance, there should be benchmarks showcasing said performance unless the improvement is self-explanatory.
|
||||
|
||||
- Code quality / stylistic PRs: we should be conservative on merging this type PRs because (1) they can be subjective in many cases, and (2) they often come with large git diffs, causing merge conflicts with other pending PRs, and leading to unwanted noise when tracing changes through git history. Use your best judgement on this type of PRs on whether they are worth it.
|
||||
|
||||
- For PRs in this category that are approved, do not merge immediately. Group them before releasing a new minor, after all feature-oriented PRs are merged.
|
||||
|
||||
### Reviewing a Feature
|
||||
|
||||
- Feature PRs should always have clear context and explanation on why the feature should be added, ideally in the form of an RFC. If the PR doesn't explain what real-world problem it is solving, ask the contributor to clarify.
|
||||
|
||||
- Decide if the feature should require an RFC process. The line isn't always clear, but a rough criteria is whether it is augmenting an existing API vs. adding a new API. Some examples:
|
||||
|
||||
- Adding a new built-in component or directive is "significant" and definitely requires an RFC.
|
||||
- Template syntax additions like adding a new `v-on` modifier or a new `v-bind` syntax sugar are "substantial". It would be nice to have an RFC for it, but a detailed explanation on the use case and reasoning behind the design directly in the PR itself can be acceptable.
|
||||
- Small, low-impact additions like exposing a new utility type or adding a new app config option can be self-explanatory, but should still provide enough context in the PR.
|
||||
|
||||
- Always ask if the use case can be solved with existing APIs. Vue already has a pretty large API surface, so we want to make sure every new addition either solves something that wasn't possible before, or significantly improves the DX of a common task.
|
||||
|
||||
### Common Considerations for All PRs
|
||||
|
||||
- Scope: a PR should only contain changes directly related to the problem being addressed. It should not contain unnecessary code style changes.
|
||||
|
||||
- Implementation: code style should be consistent with the rest of the codebase, follow common best practices. Prefer code that is boring but easy to understand over "clever" code.
|
||||
|
||||
- Size: bundle size matters. We have a GitHub action that compares the size change for every PR. We should always aim to realize the desired changes with the smallest amount of code size increase.
|
||||
|
||||
- Sometimes we need to compare the size increase vs. perceived benefits to decide whether a change is justifiable. Also take extra care to make sure added code can be tree-shaken if not needed.
|
||||
|
||||
- Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable.
|
||||
|
||||
- Runtime code is more sensitive to size increase than compiler code.
|
||||
|
||||
- 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
|
||||
- avoiding runtime behavior breakage is the highest priority
|
||||
- if not sure, use `ecosystem-ci` to verify!
|
||||
- some fix inevitably cause behavior change, these must be discussed case-by-case
|
||||
- type level breakage (e.g upgrading TS) is possible between minors
|
||||
|
||||
## PR Merge Rules for Team Members
|
||||
|
||||
Given that the PR meets the review requirements:
|
||||
|
||||
- Chore / dependencies bumps: can merge directly.
|
||||
- Fixes / refactors: can merge with two or more approvals from team members.
|
||||
- If you believe a PR looks good but you are not 100% confident to merge, label with "ready for merge" and Evan will provide a final review before merging.
|
||||
- Features: if approved by two or more team members, label with "ready to merge". Evan will review periodically, or they can be raised and discussed at team meetings.
|
||||
|
||||
## Git Branch and Release Workflow
|
||||
|
||||
We use two primary work branches: `main` and `minor`.
|
||||
|
||||
- The `main` branch is for stable releases. Changes that are bug fixes or refactors that do not affect the public API surface should land in this branch. We periodically release patch releases from the `main` branch.
|
||||
|
||||
- The `minor` branch is the WIP branch for the next minor release. Changes that are new features or those that affect public API behavior should land in this branch. We will periodically release pre-releases (alpha / beta) for the next minor from this branch.
|
||||
|
||||
Before each release, we merge latest `main` into `minor` so it would include the latest bug fixes.
|
||||
|
||||
When the minor is ready, we do a final merge of `main` into `minor`, and then release a stable minor from this branch (e.g. `3.4.0`). After that, the `main` branch is fast-forwarded to the release commit, so the two branches are synced at each stable minor release.
|
||||
|
||||

|
||||
|
||||
### Reasoning Behind the Workflow
|
||||
|
||||
The reason behind this workflow is to allow merging and releasing of fixes and features in parallel. In the past, we used a linear trunk-based development model. While the linear model results in a clean git history, the downside is that we need to be careful about when to merge patches vs. features.
|
||||
|
||||
Vue typically groups a number of features with the same scope in a minor release. We don't want to release a minor just because we happened to merge a feature PR along with a bunch of small bug fixes. So we usually "wait" until we feel we are ready to start working on a minor release before merging feature PRs.
|
||||
|
||||
But in reality, there are always bugs to fix and patch release to work on - this caused the intervals between minors to drag on longer than we had hoped, and many feature PRs were left waiting for a long period of time.
|
||||
|
||||
This is why we decided to separate bug fixes and feature PRs into separate branches. With this two-branch model, we are able to merge and release both types of changes in parallel.
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
|
@ -30,4 +30,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@d3e591514b99d0fca6779455ff8338516663f7cc
|
||||
- uses: autofix-ci/action@bee19d72e71787c12ca0f29de72f2833e437e4c9
|
||||
|
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
@ -45,7 +45,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
@ -74,13 +74,13 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
- run: node node_modules/puppeteer/install.js
|
||||
- run: node node_modules/puppeteer/install.mjs
|
||||
|
||||
- name: Run e2e tests
|
||||
run: pnpm run test-e2e
|
||||
|
@ -97,7 +97,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -9,7 +9,7 @@ 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@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const user = context.payload.sender.login
|
||||
|
@ -43,7 +43,7 @@ jobs:
|
|||
})
|
||||
throw new Error('not allowed')
|
||||
}
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
id: get-pr-data
|
||||
with:
|
||||
script: |
|
||||
|
@ -58,7 +58,7 @@ jobs:
|
|||
branchName: pr.head.ref,
|
||||
repo: pr.head.repo.full_name
|
||||
}
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
id: trigger
|
||||
env:
|
||||
COMMENT: ${{ github.event.comment.body }}
|
||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
if: github.repository == 'vuejs/core'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-inactive-days: '14'
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: pnpm
|
||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: pnpm
|
||||
|
|
89
CHANGELOG.md
89
CHANGELOG.md
|
@ -1,3 +1,92 @@
|
|||
## [3.3.9](https://github.com/vuejs/core/compare/v3.3.8...v3.3.9) (2023-11-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** avoid rewriting scope variables in inline for loops ([#7245](https://github.com/vuejs/core/issues/7245)) ([a2d810e](https://github.com/vuejs/core/commit/a2d810eb40cef631f61991ca68b426ee9546aba0)), closes [#7238](https://github.com/vuejs/core/issues/7238)
|
||||
* **compiler-core:** fix `resolveParserPlugins` decorators check ([#9566](https://github.com/vuejs/core/issues/9566)) ([9d0eba9](https://github.com/vuejs/core/commit/9d0eba916f3bf6fb5c03222400edae1a2db7444f)), closes [#9560](https://github.com/vuejs/core/issues/9560)
|
||||
* **compiler-sfc:** consistently escape type-only prop names ([#8654](https://github.com/vuejs/core/issues/8654)) ([3e08d24](https://github.com/vuejs/core/commit/3e08d246dfd8523c54fb8e7a4a6fd5506ffb1bcc)), closes [#8635](https://github.com/vuejs/core/issues/8635) [#8910](https://github.com/vuejs/core/issues/8910) [vitejs/vite-plugin-vue#184](https://github.com/vitejs/vite-plugin-vue/issues/184)
|
||||
* **compiler-sfc:** malformed filename on windows using path.posix.join() ([#9478](https://github.com/vuejs/core/issues/9478)) ([f18a174](https://github.com/vuejs/core/commit/f18a174979626b3429db93c5d5b7ae5448917c70)), closes [#8671](https://github.com/vuejs/core/issues/8671) [#9583](https://github.com/vuejs/core/issues/9583) [#9446](https://github.com/vuejs/core/issues/9446) [#9473](https://github.com/vuejs/core/issues/9473)
|
||||
* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([3227e50](https://github.com/vuejs/core/commit/3227e50b32105f8893f7dff2f29278c5b3a9f621))
|
||||
* **compiler-sfc:** support resolve extends interface for defineEmits ([#8470](https://github.com/vuejs/core/issues/8470)) ([9e1b74b](https://github.com/vuejs/core/commit/9e1b74bcd5fa4151f5d1bc02c69fbbfa4762f577)), closes [#8465](https://github.com/vuejs/core/issues/8465)
|
||||
* **hmr/transition:** fix kept-alive component inside transition disappearing after hmr ([#7126](https://github.com/vuejs/core/issues/7126)) ([d11e978](https://github.com/vuejs/core/commit/d11e978fc98dcc83526c167e603b8308f317f786)), closes [#7121](https://github.com/vuejs/core/issues/7121)
|
||||
* **hydration:** force hydration for v-bind with .prop modifier ([364f319](https://github.com/vuejs/core/commit/364f319d214226770d97c98d8fcada80c9e8dde3)), closes [#7490](https://github.com/vuejs/core/issues/7490)
|
||||
* **hydration:** properly hydrate indeterminate prop ([34b5a5d](https://github.com/vuejs/core/commit/34b5a5da4ae9c9faccac237acd7acc8e7e017571)), closes [#7476](https://github.com/vuejs/core/issues/7476)
|
||||
* **reactivity:** clear method on readonly collections should return undefined ([#7316](https://github.com/vuejs/core/issues/7316)) ([657476d](https://github.com/vuejs/core/commit/657476dcdb964be4fbb1277c215c073f3275728e))
|
||||
* **reactivity:** onCleanup also needs to be cleaned ([#8655](https://github.com/vuejs/core/issues/8655)) ([73fd810](https://github.com/vuejs/core/commit/73fd810eebdd383a2b4629f67736c4db1f428abd)), closes [#5151](https://github.com/vuejs/core/issues/5151) [#7695](https://github.com/vuejs/core/issues/7695)
|
||||
* **ssr:** hydration `__vnode` missing for devtools ([#9328](https://github.com/vuejs/core/issues/9328)) ([5156ac5](https://github.com/vuejs/core/commit/5156ac5b38cfa80d3db26f2c9bf40cb22a7521cb))
|
||||
* **types:** allow falsy value types in `StyleValue` ([#7954](https://github.com/vuejs/core/issues/7954)) ([17aa92b](https://github.com/vuejs/core/commit/17aa92b79b31d8bb8b5873ddc599420cb9806db8)), closes [#7955](https://github.com/vuejs/core/issues/7955)
|
||||
* **types:** defineCustomElement using defineComponent return type with emits ([#7937](https://github.com/vuejs/core/issues/7937)) ([5d932a8](https://github.com/vuejs/core/commit/5d932a8e6d14343c9d7fc7c2ecb58ac618b2f938)), closes [#7782](https://github.com/vuejs/core/issues/7782)
|
||||
* **types:** fix `unref` and `toValue` when input union type contains ComputedRef ([#8748](https://github.com/vuejs/core/issues/8748)) ([176d476](https://github.com/vuejs/core/commit/176d47671271b1abc21b1508e9a493c7efca6451)), closes [#8747](https://github.com/vuejs/core/issues/8747) [#8857](https://github.com/vuejs/core/issues/8857)
|
||||
* **types:** fix instance type when props type is incompatible with setup returned type ([#7338](https://github.com/vuejs/core/issues/7338)) ([0e1e8f9](https://github.com/vuejs/core/commit/0e1e8f919e5a74cdaadf9c80ee135088b25e7fa3)), closes [#5885](https://github.com/vuejs/core/issues/5885)
|
||||
* **types:** fix shallowRef return type with union value type ([#7853](https://github.com/vuejs/core/issues/7853)) ([7c44800](https://github.com/vuejs/core/commit/7c448000b0def910c2cfabfdf7ff20a3d6bc844f)), closes [#7852](https://github.com/vuejs/core/issues/7852)
|
||||
* **types:** more precise types for class bindings ([#8012](https://github.com/vuejs/core/issues/8012)) ([46e3374](https://github.com/vuejs/core/commit/46e33744c890bd49482c5e5c5cdea44e00ec84d5))
|
||||
* **types:** remove optional properties from defineProps return type ([#6421](https://github.com/vuejs/core/issues/6421)) ([94c049d](https://github.com/vuejs/core/commit/94c049d930d922069e38ea8700d7ff0970f71e61)), closes [#6420](https://github.com/vuejs/core/issues/6420)
|
||||
* **types:** return type of withDefaults should be readonly ([#8601](https://github.com/vuejs/core/issues/8601)) ([f15debc](https://github.com/vuejs/core/commit/f15debc01acb22d23f5acee97e6f02db88cef11a))
|
||||
* **types:** revert class type restrictions ([5d077c8](https://github.com/vuejs/core/commit/5d077c8754cc14f85d2d6d386df70cf8c0d93842)), closes [#8012](https://github.com/vuejs/core/issues/8012)
|
||||
* **types:** update jsx type definitions ([#8607](https://github.com/vuejs/core/issues/8607)) ([58e2a94](https://github.com/vuejs/core/commit/58e2a94871ae06a909c5f8bad07fb401193e6a38))
|
||||
* **types:** widen ClassValue type ([2424013](https://github.com/vuejs/core/commit/242401305944422d0c361b16101a4d18908927af))
|
||||
* **v-model:** avoid overwriting number input with same value ([#7004](https://github.com/vuejs/core/issues/7004)) ([40f4b77](https://github.com/vuejs/core/commit/40f4b77bb570868cb6e47791078767797e465989)), closes [#7003](https://github.com/vuejs/core/issues/7003)
|
||||
* **v-model:** unnecessary value binding error should apply to dynamic instead of static binding ([2859b65](https://github.com/vuejs/core/commit/2859b653c9a22460e60233cac10fe139e359b046)), closes [#3596](https://github.com/vuejs/core/issues/3596)
|
||||
|
||||
|
||||
|
||||
## [3.3.8](https://github.com/vuejs/core/compare/v3.3.7...v3.3.8) (2023-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compile-sfc:** support `Error` type in `defineProps` ([#5955](https://github.com/vuejs/core/issues/5955)) ([a989345](https://github.com/vuejs/core/commit/a9893458ec519aae442e1b99e64e6d74685cd22c))
|
||||
* **compiler-core:** known global should be shadowed by local variables in expression rewrite ([#9492](https://github.com/vuejs/core/issues/9492)) ([a75d1c5](https://github.com/vuejs/core/commit/a75d1c5c6242e91a73cc5ba01e6da620dea0b3d9)), closes [#9482](https://github.com/vuejs/core/issues/9482)
|
||||
* **compiler-sfc:** fix dynamic directive arguments usage check for slots ([#9495](https://github.com/vuejs/core/issues/9495)) ([b39fa1f](https://github.com/vuejs/core/commit/b39fa1f8157647859331ce439c42ae016a49b415)), closes [#9493](https://github.com/vuejs/core/issues/9493)
|
||||
* **deps:** update dependency @vue/repl to ^2.6.2 ([#9536](https://github.com/vuejs/core/issues/9536)) ([5cef325](https://github.com/vuejs/core/commit/5cef325f41e3b38657c72fa1a38dedeee1c7a60a))
|
||||
* **deps:** update dependency @vue/repl to ^2.6.3 ([#9540](https://github.com/vuejs/core/issues/9540)) ([176d590](https://github.com/vuejs/core/commit/176d59058c9aecffe9da4d4311e98496684f06d4))
|
||||
* **hydration:** fix tagName access eeror on comment/text node hydration mismatch ([dd8a0cf](https://github.com/vuejs/core/commit/dd8a0cf5dcde13d2cbd899262a0e07f16e14e489)), closes [#9531](https://github.com/vuejs/core/issues/9531)
|
||||
* **types:** avoid exposing lru-cache types in generated dts ([462aeb3](https://github.com/vuejs/core/commit/462aeb3b600765e219ded2ee9a0ed1e74df61de0)), closes [#9521](https://github.com/vuejs/core/issues/9521)
|
||||
* **warn:** avoid warning on empty children with Suspense ([#3962](https://github.com/vuejs/core/issues/3962)) ([405f345](https://github.com/vuejs/core/commit/405f34587a63a5f1e3d147b9848219ea98acc22d))
|
||||
|
||||
|
||||
|
||||
## [3.3.7](https://github.com/vuejs/core/compare/v3.3.6...v3.3.7) (2023-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-sfc:** avoid gen useCssVars when targeting SSR ([#6979](https://github.com/vuejs/core/issues/6979)) ([c568778](https://github.com/vuejs/core/commit/c568778ea3265d8e57f788b00864c9509bf88a4e)), closes [#6926](https://github.com/vuejs/core/issues/6926)
|
||||
* **compiler-ssr:** proper scope analysis for ssr vnode slot fallback ([#7184](https://github.com/vuejs/core/issues/7184)) ([e09c26b](https://github.com/vuejs/core/commit/e09c26bc9bc4394c2c2d928806d382515c2676f3)), closes [#7095](https://github.com/vuejs/core/issues/7095)
|
||||
* correctly resolve types from relative paths on Windows ([#9446](https://github.com/vuejs/core/issues/9446)) ([089d36d](https://github.com/vuejs/core/commit/089d36d167dc7834065b03ca689f9b6a44eead8a)), closes [#8671](https://github.com/vuejs/core/issues/8671)
|
||||
* **hmr:** fix hmr error for hoisted children array in v-for ([7334376](https://github.com/vuejs/core/commit/733437691f70ebca8dd6cc3bc8356f5b57d4d5d8)), closes [#6978](https://github.com/vuejs/core/issues/6978) [#7114](https://github.com/vuejs/core/issues/7114)
|
||||
* **reactivity:** assigning array.length while observing a symbol property ([#7568](https://github.com/vuejs/core/issues/7568)) ([e9e2778](https://github.com/vuejs/core/commit/e9e2778e9ec5cca07c1df5f0c9b7b3595a1a3244))
|
||||
* **scheduler:** ensure jobs are in the correct order ([#7748](https://github.com/vuejs/core/issues/7748)) ([a8f6638](https://github.com/vuejs/core/commit/a8f663867b8cd2736b82204bc58756ef02441276)), closes [#7576](https://github.com/vuejs/core/issues/7576)
|
||||
* **ssr:** fix hydration mismatch for disabled teleport at component root ([#9399](https://github.com/vuejs/core/issues/9399)) ([d8990fc](https://github.com/vuejs/core/commit/d8990fc6182d1c2cf0a8eab7b35a9d04df668507)), closes [#6152](https://github.com/vuejs/core/issues/6152)
|
||||
* **Suspense:** calling hooks before the transition finishes ([#9388](https://github.com/vuejs/core/issues/9388)) ([00de3e6](https://github.com/vuejs/core/commit/00de3e61ed7a55e7d6c2e1987551d66ad0f909ff)), closes [#5844](https://github.com/vuejs/core/issues/5844) [#5952](https://github.com/vuejs/core/issues/5952)
|
||||
* **transition/ssr:** make transition appear work with SSR ([#8859](https://github.com/vuejs/core/issues/8859)) ([5ea8a8a](https://github.com/vuejs/core/commit/5ea8a8a4fab4e19a71e123e4d27d051f5e927172)), closes [#6951](https://github.com/vuejs/core/issues/6951)
|
||||
* **types:** fix ComponentCustomProps augmentation ([#9468](https://github.com/vuejs/core/issues/9468)) ([7374e93](https://github.com/vuejs/core/commit/7374e93f0281f273b90ab5a6724cc47332a01d6c)), closes [#8376](https://github.com/vuejs/core/issues/8376)
|
||||
* **types:** improve `h` overload to support union of string and component ([#5432](https://github.com/vuejs/core/issues/5432)) ([16ecb44](https://github.com/vuejs/core/commit/16ecb44c89cd8299a3b8de33cccc2e2cc36f065b)), closes [#5431](https://github.com/vuejs/core/issues/5431)
|
||||
|
||||
|
||||
|
||||
## [3.3.6](https://github.com/vuejs/core/compare/v3.3.5...v3.3.6) (2023-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-sfc:** model name conflict ([#8798](https://github.com/vuejs/core/issues/8798)) ([df81da8](https://github.com/vuejs/core/commit/df81da8be97c8a1366563c7e3e01076ef02eb8f7))
|
||||
* **compiler-sfc:** support asset paths containing spaces ([#8752](https://github.com/vuejs/core/issues/8752)) ([36c99a9](https://github.com/vuejs/core/commit/36c99a9c6bb6bc306be054c3c8a85ff8ce50605a))
|
||||
* **compiler-ssr:** fix missing scopeId on server-rendered TransitionGroup ([#7557](https://github.com/vuejs/core/issues/7557)) ([61c1357](https://github.com/vuejs/core/commit/61c135742795aa5e3189a79c7dec6afa21bbc8d9)), closes [#7554](https://github.com/vuejs/core/issues/7554)
|
||||
* **compiler-ssr:** fix ssr compile error for select with non-option children ([#9442](https://github.com/vuejs/core/issues/9442)) ([cdb2e72](https://github.com/vuejs/core/commit/cdb2e725e7ea297f1f4180fb04889a3b757bc84e)), closes [#9440](https://github.com/vuejs/core/issues/9440)
|
||||
* **runtime-core:** delete stale slots which are present but undefined ([#6484](https://github.com/vuejs/core/issues/6484)) ([75b8722](https://github.com/vuejs/core/commit/75b872213574cb37e2c9e8a15f65613f867ca9a6)), closes [#9109](https://github.com/vuejs/core/issues/9109)
|
||||
* **runtime-core:** fix error when using cssvars with disabled teleport ([#7341](https://github.com/vuejs/core/issues/7341)) ([8f0472c](https://github.com/vuejs/core/commit/8f0472c9abedb337dc256143b69d8ab8759dbf5c)), closes [#7342](https://github.com/vuejs/core/issues/7342)
|
||||
* **teleport:** ensure descendent component would be unmounted correctly ([#6529](https://github.com/vuejs/core/issues/6529)) ([4162311](https://github.com/vuejs/core/commit/4162311efdb0db5ca458542e1604b19efa2fae0e)), closes [#6347](https://github.com/vuejs/core/issues/6347)
|
||||
* **types:** support contenteditable="plaintext-only" ([#8796](https://github.com/vuejs/core/issues/8796)) ([26ca89e](https://github.com/vuejs/core/commit/26ca89e5cf734fbef81e182050d2a215ec8a437b))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* replace Map/Set with WeakMap/WeakSet ([#8549](https://github.com/vuejs/core/issues/8549)) ([712f96d](https://github.com/vuejs/core/commit/712f96d6ac4d3d984732cba448cb84624daba850))
|
||||
|
||||
|
||||
|
||||
## [3.3.5](https://github.com/vuejs/core/compare/v3.3.4...v3.3.5) (2023-10-20)
|
||||
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ Vue.js is an MIT-licensed open source project with its ongoing development made
|
|||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://vuejs.org/sponsor/#current-sponsors">
|
||||
<img alt="sponsors" src="https://sponsors.vuejs.org/sponsors.svg?v2">
|
||||
<img alt="sponsors" src="https://sponsors.vuejs.org/sponsors.svg?v3">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
|
44
package.json
44
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.3.5",
|
||||
"packageManager": "pnpm@8.9.2",
|
||||
"version": "3.3.9",
|
||||
"packageManager": "pnpm@8.11.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
@ -27,9 +27,9 @@
|
|||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||
"dev-compiler": "run-p \"dev template-explorer\" serve",
|
||||
"dev-sfc": "run-s dev-sfc-prepare dev-sfc-run",
|
||||
"dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-compiler-cjs",
|
||||
"dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-all-cjs",
|
||||
"dev-sfc-serve": "vite packages/sfc-playground --host",
|
||||
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
|
||||
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev vue -ipf esm-browser-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
|
||||
"serve": "serve",
|
||||
"open": "open http://localhost:3000/packages/template-explorer/local.html",
|
||||
"build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
|
||||
|
@ -57,42 +57,42 @@
|
|||
"node": ">=18.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@babel/parser": "^7.23.4",
|
||||
"@babel/types": "^7.23.4",
|
||||
"@rollup/plugin-alias": "^5.0.1",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.4",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/hash-sum": "^1.0.1",
|
||||
"@types/node": "^18.18.6",
|
||||
"@typescript-eslint/parser": "^6.8.0",
|
||||
"@vitest/coverage-istanbul": "^0.34.4",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^20.10.0",
|
||||
"@typescript-eslint/parser": "^6.13.0",
|
||||
"@vitest/coverage-istanbul": "^0.34.6",
|
||||
"@vue/consolidate": "0.17.3",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.19.5",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-plugin-jest": "^27.4.2",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-jest": "^27.6.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^8.0.1",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.0.2",
|
||||
"lint-staged": "^15.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.5",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^9.1.2",
|
||||
"marked": "^9.1.6",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier": "^3.1.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~21.2.1",
|
||||
"puppeteer": "~21.5.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^3.29.4",
|
||||
"rollup": "^4.1.4",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-esbuild": "^6.1.0",
|
||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||
|
@ -100,11 +100,11 @@
|
|||
"serve": "^14.2.1",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"terser": "^5.22.0",
|
||||
"todomvc-app-css": "^2.4.2",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^3.14.0",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.3.0",
|
||||
"vitest": "^0.34.4"
|
||||
"tsx": "^4.5.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.0",
|
||||
"vitest": "^0.34.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`compiler: expression transform > bindingMetadata > inline mode 1`] = `
|
||||
"(_ctx, _cache) => {
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.props) + \\" \\" + _toDisplayString(_unref(setup)) + \\" \\" + _toDisplayString(setupConst) + \\" \\" + _toDisplayString(_ctx.data) + \\" \\" + _toDisplayString(_ctx.options), 1 /* TEXT */))
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.props) + \\" \\" + _toDisplayString(_unref(setup)) + \\" \\" + _toDisplayString(setupConst) + \\" \\" + _toDisplayString(_ctx.data) + \\" \\" + _toDisplayString(_ctx.options) + \\" \\" + _toDisplayString(isNaN.value), 1 /* TEXT */))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -10,6 +10,48 @@ exports[`compiler: expression transform > bindingMetadata > non-inline mode 1`]
|
|||
"const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString($props.props) + \\" \\" + _toDisplayString($setup.setup) + \\" \\" + _toDisplayString($data.data) + \\" \\" + _toDisplayString($options.options), 1 /* TEXT */))
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString($props.props) + \\" \\" + _toDisplayString($setup.setup) + \\" \\" + _toDisplayString($data.data) + \\" \\" + _toDisplayString($options.options) + \\" \\" + _toDisplayString($setup.isNaN), 1 /* TEXT */))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: expression transform > bindingMetadata > 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 (_openBlock(), _createElementBlock(\\"div\\", {
|
||||
onClick: () => {
|
||||
for (let i = 0; i < _ctx.list.length; i++) {
|
||||
_ctx.log(i)
|
||||
}
|
||||
}
|
||||
}, null, 8 /* PROPS */, [\\"onClick\\"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...in 1`] = `
|
||||
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", {
|
||||
onClick: () => {
|
||||
for (const x in _ctx.list) {
|
||||
_ctx.log(x)
|
||||
}
|
||||
}
|
||||
}, null, 8 /* PROPS */, [\\"onClick\\"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...of 1`] = `
|
||||
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", {
|
||||
onClick: () => {
|
||||
for (const x of _ctx.list) {
|
||||
_ctx.log(x)
|
||||
}
|
||||
}
|
||||
}, null, 8 /* PROPS */, [\\"onClick\\"]))
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock(\\"input\\", {
|
||||
\\"foo-value\\": model,
|
||||
\\"onUpdate:fooValue\\": $event => ((model) = $event)
|
||||
}, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
|
||||
}, null, 40 /* PROPS, NEED_HYDRATION */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -593,5 +593,17 @@ describe('compiler: hoistStatic transform', () => {
|
|||
expect(root.hoists.length).toBe(2)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('clone hoisted array children in HMR mode', () => {
|
||||
const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
|
||||
hmr: true
|
||||
})
|
||||
expect(root.hoists.length).toBe(2)
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
children: {
|
||||
content: '[..._hoisted_2]'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -152,6 +152,28 @@ describe('compiler: element transform', () => {
|
|||
expect(node.tag).toBe(`Foo.Example`)
|
||||
})
|
||||
|
||||
test('resolve namespaced component from props bindings (inline)', () => {
|
||||
const { root, node } = parseWithElementTransform(`<Foo.Example/>`, {
|
||||
inline: true,
|
||||
bindingMetadata: {
|
||||
Foo: BindingTypes.PROPS
|
||||
}
|
||||
})
|
||||
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
|
||||
expect(node.tag).toBe(`_unref(__props["Foo"]).Example`)
|
||||
})
|
||||
|
||||
test('resolve namespaced component from props bindings (non-inline)', () => {
|
||||
const { root, node } = parseWithElementTransform(`<Foo.Example/>`, {
|
||||
inline: false,
|
||||
bindingMetadata: {
|
||||
Foo: BindingTypes.PROPS
|
||||
}
|
||||
})
|
||||
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
|
||||
expect(node.tag).toBe('_unref($props["Foo"]).Example')
|
||||
})
|
||||
|
||||
test('do not resolve component from non-script-setup bindings', () => {
|
||||
const bindingMetadata = {
|
||||
Example: BindingTypes.SETUP_MAYBE_REF
|
||||
|
@ -1089,7 +1111,7 @@ describe('compiler: element transform', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('HYDRATE_EVENTS', () => {
|
||||
test('NEED_HYDRATION for v-on', () => {
|
||||
// ignore click events (has dedicated fast path)
|
||||
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
|
||||
directiveTransforms: {
|
||||
|
@ -1108,12 +1130,24 @@ describe('compiler: element transform', () => {
|
|||
}
|
||||
)
|
||||
expect(node2.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||
)
|
||||
})
|
||||
|
||||
test('NEED_HYDRATION for v-bind.prop', () => {
|
||||
const { node } = parseWithBind(`<div v-bind:id.prop="id" />`)
|
||||
expect(node.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||
)
|
||||
|
||||
const { node: node2 } = parseWithBind(`<div .id="id" />`)
|
||||
expect(node2.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||
)
|
||||
})
|
||||
|
||||
// #5870
|
||||
test('HYDRATE_EVENTS on dynamic component', () => {
|
||||
test('NEED_HYDRATION on dynamic component', () => {
|
||||
const { node } = parseWithElementTransform(
|
||||
`<component :is="foo" @input="foo" />`,
|
||||
{
|
||||
|
@ -1123,9 +1157,23 @@ describe('compiler: element transform', () => {
|
|||
}
|
||||
)
|
||||
expect(node.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||
)
|
||||
})
|
||||
|
||||
test('should not have PROPS patchflag for constant v-on handlers', () => {
|
||||
const { node } = parseWithElementTransform(`<div @keydown="foo" />`, {
|
||||
prefixIdentifiers: true,
|
||||
bindingMetadata: {
|
||||
foo: BindingTypes.SETUP_CONST
|
||||
},
|
||||
directiveTransforms: {
|
||||
on: transformOn
|
||||
}
|
||||
})
|
||||
// should only have hydration flag
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic component', () => {
|
||||
|
|
|
@ -506,7 +506,8 @@ describe('compiler: expression transform', () => {
|
|||
data: BindingTypes.DATA,
|
||||
options: BindingTypes.OPTIONS,
|
||||
reactive: BindingTypes.SETUP_REACTIVE_CONST,
|
||||
literal: BindingTypes.LITERAL_CONST
|
||||
literal: BindingTypes.LITERAL_CONST,
|
||||
isNaN: BindingTypes.SETUP_REF
|
||||
}
|
||||
|
||||
function compileWithBindingMetadata(
|
||||
|
@ -522,19 +523,56 @@ describe('compiler: expression transform', () => {
|
|||
|
||||
test('non-inline mode', () => {
|
||||
const { code } = compileWithBindingMetadata(
|
||||
`<div>{{ props }} {{ setup }} {{ data }} {{ options }}</div>`
|
||||
`<div>{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}</div>`
|
||||
)
|
||||
expect(code).toMatch(`$props.props`)
|
||||
expect(code).toMatch(`$setup.setup`)
|
||||
expect(code).toMatch(`$setup.isNaN`)
|
||||
expect(code).toMatch(`$data.data`)
|
||||
expect(code).toMatch(`$options.options`)
|
||||
expect(code).toMatch(`_ctx, _cache, $props, $setup, $data, $options`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should not prefix temp variable of for...in', () => {
|
||||
const { code } = compileWithBindingMetadata(
|
||||
`<div @click="() => {
|
||||
for (const x in list) {
|
||||
log(x)
|
||||
}
|
||||
}"/>`
|
||||
)
|
||||
expect(code).not.toMatch(`_ctx.x`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should not prefix temp variable of for...of', () => {
|
||||
const { code } = compileWithBindingMetadata(
|
||||
`<div @click="() => {
|
||||
for (const x of list) {
|
||||
log(x)
|
||||
}
|
||||
}"/>`
|
||||
)
|
||||
expect(code).not.toMatch(`_ctx.x`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should not prefix temp variable of for loop', () => {
|
||||
const { code } = compileWithBindingMetadata(
|
||||
`<div @click="() => {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
log(i)
|
||||
}
|
||||
}"/>`
|
||||
)
|
||||
expect(code).not.toMatch(`_ctx.i`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('inline mode', () => {
|
||||
const { code } = compileWithBindingMetadata(
|
||||
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }}</div>`,
|
||||
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`,
|
||||
{ inline: true }
|
||||
)
|
||||
expect(code).toMatch(`__props.props`)
|
||||
|
@ -542,6 +580,7 @@ describe('compiler: expression transform', () => {
|
|||
expect(code).toMatch(`_toDisplayString(setupConst)`)
|
||||
expect(code).toMatch(`_ctx.data`)
|
||||
expect(code).toMatch(`_ctx.options`)
|
||||
expect(code).toMatch(`isNaN.value`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
|
|
|
@ -674,8 +674,8 @@ describe('compiler: v-for', () => {
|
|||
patchFlag: !disableTracking
|
||||
? genFlagText(PatchFlags.STABLE_FRAGMENT)
|
||||
: keyed
|
||||
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
||||
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
||||
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.3.5",
|
||||
"version": "3.3.9",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
@ -32,12 +32,12 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/shared": "3.3.5",
|
||||
"@babel/parser": "^7.23.4",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.23.0"
|
||||
"@babel/types": "^7.23.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,6 +165,19 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -448,8 +448,8 @@ function genAssets(
|
|||
__COMPAT__ && type === 'filter'
|
||||
? RESOLVE_FILTER
|
||||
: type === 'component'
|
||||
? RESOLVE_COMPONENT
|
||||
: RESOLVE_DIRECTIVE
|
||||
? RESOLVE_COMPONENT
|
||||
: RESOLVE_DIRECTIVE
|
||||
)
|
||||
for (let i = 0; i < assets.length; i++) {
|
||||
let id = assets[i]
|
||||
|
|
|
@ -41,8 +41,8 @@ export function getBaseTransformPreset(
|
|||
transformExpression
|
||||
]
|
||||
: __BROWSER__ && __DEV__
|
||||
? [transformExpression]
|
||||
: []),
|
||||
? [transformExpression]
|
||||
: []),
|
||||
transformSlotOutlet,
|
||||
transformElement,
|
||||
trackSlotScopes,
|
||||
|
|
|
@ -256,6 +256,12 @@ export interface TransformOptions
|
|||
* needed to render inline CSS variables on component root
|
||||
*/
|
||||
ssrCssVars?: string
|
||||
/**
|
||||
* Whether to compile the template assuming it needs to handle HMR.
|
||||
* Some edge cases may need to generate different code for HMR to work
|
||||
* correctly, e.g. #6938, #7138
|
||||
*/
|
||||
hmr?: boolean
|
||||
}
|
||||
|
||||
export interface CodegenOptions extends SharedTransformCodegenOptions {
|
||||
|
|
|
@ -811,8 +811,8 @@ function parseAttribute(
|
|||
(isPropShorthand || startsWith(name, ':')
|
||||
? 'bind'
|
||||
: startsWith(name, '@')
|
||||
? 'on'
|
||||
: 'slot')
|
||||
? 'on'
|
||||
: 'slot')
|
||||
let arg: ExpressionNode | undefined
|
||||
|
||||
if (match[2]) {
|
||||
|
@ -1063,7 +1063,7 @@ function parseTextData(
|
|||
) {
|
||||
return rawText
|
||||
} else {
|
||||
// DATA or RCDATA containing "&"". Entity decoding required.
|
||||
// DATA or RCDATA containing "&". Entity decoding is required.
|
||||
return context.options.decodeEntities(
|
||||
rawText,
|
||||
mode === TextModes.ATTRIBUTE_VALUE
|
||||
|
|
|
@ -117,7 +117,7 @@ export interface TransformContext
|
|||
removeIdentifiers(exp: ExpressionNode | string): void
|
||||
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
||||
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
||||
constantCache: Map<TemplateChildNode, ConstantTypes>
|
||||
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
||||
|
||||
// 2.x Compat only
|
||||
filters?: Set<string>
|
||||
|
@ -129,6 +129,7 @@ export function createTransformContext(
|
|||
filename = '',
|
||||
prefixIdentifiers = false,
|
||||
hoistStatic = false,
|
||||
hmr = false,
|
||||
cacheHandlers = false,
|
||||
nodeTransforms = [],
|
||||
directiveTransforms = {},
|
||||
|
@ -155,6 +156,7 @@ export function createTransformContext(
|
|||
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
|
||||
prefixIdentifiers,
|
||||
hoistStatic,
|
||||
hmr,
|
||||
cacheHandlers,
|
||||
nodeTransforms,
|
||||
directiveTransforms,
|
||||
|
@ -181,7 +183,7 @@ export function createTransformContext(
|
|||
directives: new Set(),
|
||||
hoists: [],
|
||||
imports: [],
|
||||
constantCache: new Map(),
|
||||
constantCache: new WeakMap(),
|
||||
temps: 0,
|
||||
cached: 0,
|
||||
identifiers: Object.create(null),
|
||||
|
@ -236,8 +238,8 @@ export function createTransformContext(
|
|||
const removalIndex = node
|
||||
? list.indexOf(node)
|
||||
: context.currentNode
|
||||
? context.childIndex
|
||||
: -1
|
||||
? context.childIndex
|
||||
: -1
|
||||
/* istanbul ignore if */
|
||||
if (__DEV__ && removalIndex < 0) {
|
||||
throw new Error(`node being removed is not a child of current parent`)
|
||||
|
|
|
@ -140,9 +140,16 @@ function walk(
|
|||
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||
isArray(node.codegenNode.children)
|
||||
) {
|
||||
node.codegenNode.children = context.hoist(
|
||||
const hoisted = context.hoist(
|
||||
createArrayExpression(node.codegenNode.children)
|
||||
)
|
||||
// #6978, #7138, #7114
|
||||
// a hoisted children array inside v-for can caused HMR errors since
|
||||
// it might be mutated when mounting the v-for list
|
||||
if (context.hmr) {
|
||||
hoisted.content = `[...${hoisted.content}]`
|
||||
}
|
||||
node.codegenNode.children = hoisted
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ import {
|
|||
TemplateTextChildNode,
|
||||
DirectiveArguments,
|
||||
createVNodeCall,
|
||||
ConstantTypes
|
||||
ConstantTypes,
|
||||
JSChildNode
|
||||
} from '../ast'
|
||||
import {
|
||||
PatchFlags,
|
||||
|
@ -385,6 +386,13 @@ function resolveSetupReference(name: string, context: TransformContext) {
|
|||
`${context.helperString(UNREF)}(${fromMaybeRef})`
|
||||
: `$setup[${JSON.stringify(fromMaybeRef)}]`
|
||||
}
|
||||
|
||||
const fromProps = checkType(BindingTypes.PROPS)
|
||||
if (fromProps) {
|
||||
return `${context.helperString(UNREF)}(${
|
||||
context.inline ? '__props' : '$props'
|
||||
}[${JSON.stringify(fromProps)}])`
|
||||
}
|
||||
}
|
||||
|
||||
export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
||||
|
@ -452,6 +460,12 @@ export function buildProps(
|
|||
hasVnodeHook = true
|
||||
}
|
||||
|
||||
if (isEventHandler && value.type === NodeTypes.JS_CALL_EXPRESSION) {
|
||||
// handler wrapped with internal helper e.g. withModifiers(fn)
|
||||
// extract the actual expression
|
||||
value = value.arguments[0] as JSChildNode
|
||||
}
|
||||
|
||||
if (
|
||||
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
|
||||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
||||
|
@ -550,7 +564,7 @@ export function buildProps(
|
|||
)
|
||||
} else {
|
||||
// directives
|
||||
const { name, arg, exp, loc } = prop
|
||||
const { name, arg, exp, loc, modifiers } = prop
|
||||
const isVBind = name === 'bind'
|
||||
const isVOn = name === 'on'
|
||||
|
||||
|
@ -678,6 +692,11 @@ export function buildProps(
|
|||
continue
|
||||
}
|
||||
|
||||
// force hydration for v-bind with .prop modifier
|
||||
if (isVBind && modifiers.includes('prop')) {
|
||||
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||
}
|
||||
|
||||
const directiveTransform = context.directiveTransforms[name]
|
||||
if (directiveTransform) {
|
||||
// has built-in directive transform.
|
||||
|
@ -743,12 +762,12 @@ export function buildProps(
|
|||
patchFlag |= PatchFlags.PROPS
|
||||
}
|
||||
if (hasHydrationEventBinding) {
|
||||
patchFlag |= PatchFlags.HYDRATE_EVENTS
|
||||
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||
}
|
||||
}
|
||||
if (
|
||||
!shouldUseBlock &&
|
||||
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
|
||||
(patchFlag === 0 || patchFlag === PatchFlags.NEED_HYDRATION) &&
|
||||
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
|
||||
) {
|
||||
patchFlag |= PatchFlags.NEED_PATCH
|
||||
|
|
|
@ -227,10 +227,15 @@ export function processExpression(
|
|||
const isScopeVarReference = context.identifiers[rawExp]
|
||||
const isAllowedGlobal = isGloballyAllowed(rawExp)
|
||||
const isLiteral = isLiteralWhitelisted(rawExp)
|
||||
if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) {
|
||||
if (
|
||||
!asParams &&
|
||||
!isScopeVarReference &&
|
||||
!isLiteral &&
|
||||
(!isAllowedGlobal || bindingMetadata[rawExp])
|
||||
) {
|
||||
// const bindings exposed from setup can be skipped for patching but
|
||||
// cannot be hoisted to module scope
|
||||
if (isConst(bindingMetadata[node.content])) {
|
||||
if (isConst(bindingMetadata[rawExp])) {
|
||||
node.constType = ConstantTypes.CAN_SKIP_PATCH
|
||||
}
|
||||
node.content = rewriteIdentifier(rawExp)
|
||||
|
|
|
@ -37,7 +37,8 @@ import {
|
|||
isTemplateNode,
|
||||
isSlotOutlet,
|
||||
injectProp,
|
||||
findDir
|
||||
findDir,
|
||||
forAliasRE
|
||||
} from '../utils'
|
||||
import {
|
||||
RENDER_LIST,
|
||||
|
@ -94,8 +95,8 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
const fragmentFlag = isStableFragment
|
||||
? PatchFlags.STABLE_FRAGMENT
|
||||
: keyProp
|
||||
? PatchFlags.KEYED_FRAGMENT
|
||||
: PatchFlags.UNKEYED_FRAGMENT
|
||||
? PatchFlags.KEYED_FRAGMENT
|
||||
: PatchFlags.UNKEYED_FRAGMENT
|
||||
|
||||
forNode.codegenNode = createVNodeCall(
|
||||
context,
|
||||
|
@ -140,10 +141,10 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
const slotOutlet = isSlotOutlet(node)
|
||||
? node
|
||||
: isTemplate &&
|
||||
node.children.length === 1 &&
|
||||
isSlotOutlet(node.children[0])
|
||||
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
||||
: null
|
||||
node.children.length === 1 &&
|
||||
isSlotOutlet(node.children[0])
|
||||
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
||||
: null
|
||||
|
||||
if (slotOutlet) {
|
||||
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
||||
|
@ -308,7 +309,6 @@ export function processFor(
|
|||
}
|
||||
}
|
||||
|
||||
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||
// This regex doesn't cover the case if key or index aliases have destructuring,
|
||||
// but those do not make sense in the first place, so this works in practice.
|
||||
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||
|
|
|
@ -100,11 +100,12 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
|
|||
|
||||
export type SlotFnBuilder = (
|
||||
slotProps: ExpressionNode | undefined,
|
||||
vForExp: ExpressionNode | undefined,
|
||||
slotChildren: TemplateChildNode[],
|
||||
loc: SourceLocation
|
||||
) => FunctionExpression
|
||||
|
||||
const buildClientSlotFn: SlotFnBuilder = (props, children, loc) =>
|
||||
const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
|
||||
createFunctionExpression(
|
||||
props,
|
||||
children,
|
||||
|
@ -149,7 +150,7 @@ export function buildSlots(
|
|||
slotsProperties.push(
|
||||
createObjectProperty(
|
||||
arg || createSimpleExpression('default', true),
|
||||
buildSlotFn(exp, children, loc)
|
||||
buildSlotFn(exp, undefined, children, loc)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -201,11 +202,17 @@ export function buildSlots(
|
|||
hasDynamicSlots = true
|
||||
}
|
||||
|
||||
const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc)
|
||||
const vFor = findDir(slotElement, 'for')
|
||||
const slotFunction = buildSlotFn(
|
||||
slotProps,
|
||||
vFor?.exp,
|
||||
slotChildren,
|
||||
slotLoc
|
||||
)
|
||||
|
||||
// check if this slot is conditional (v-if/v-for)
|
||||
let vIf: DirectiveNode | undefined
|
||||
let vElse: DirectiveNode | undefined
|
||||
let vFor: DirectiveNode | undefined
|
||||
if ((vIf = findDir(slotElement, 'if'))) {
|
||||
hasDynamicSlots = true
|
||||
dynamicSlots.push(
|
||||
|
@ -257,7 +264,7 @@ export function buildSlots(
|
|||
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
|
||||
)
|
||||
}
|
||||
} else if ((vFor = findDir(slotElement, 'for'))) {
|
||||
} else if (vFor) {
|
||||
hasDynamicSlots = true
|
||||
const parseResult =
|
||||
vFor.parseResult ||
|
||||
|
@ -306,7 +313,7 @@ export function buildSlots(
|
|||
props: ExpressionNode | undefined,
|
||||
children: TemplateChildNode[]
|
||||
) => {
|
||||
const fn = buildSlotFn(props, children, loc)
|
||||
const fn = buildSlotFn(props, undefined, children, loc)
|
||||
if (__COMPAT__ && context.compatConfig) {
|
||||
fn.isNonScopedSlot = true
|
||||
}
|
||||
|
@ -342,8 +349,8 @@ export function buildSlots(
|
|||
const slotFlag = hasDynamicSlots
|
||||
? SlotFlags.DYNAMIC
|
||||
: hasForwardedSlots(node.children)
|
||||
? SlotFlags.FORWARDED
|
||||
: SlotFlags.STABLE
|
||||
? SlotFlags.FORWARDED
|
||||
: SlotFlags.STABLE
|
||||
|
||||
let slots = createObjectExpression(
|
||||
slotsProperties.concat(
|
||||
|
|
|
@ -519,3 +519,5 @@ export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) {
|
|||
return node
|
||||
}
|
||||
}
|
||||
|
||||
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||
|
|
|
@ -20,10 +20,7 @@ describe('stringify static html', () => {
|
|||
}
|
||||
|
||||
function repeat(code: string, n: number): string {
|
||||
return new Array(n)
|
||||
.fill(0)
|
||||
.map(() => code)
|
||||
.join('')
|
||||
return code.repeat(n)
|
||||
}
|
||||
|
||||
test('should bail on non-eligible static trees', () => {
|
||||
|
|
|
@ -137,6 +137,27 @@ describe('compiler: transform v-model', () => {
|
|||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('should error on dynamic value binding alongside v-model', () => {
|
||||
const onError = vi.fn()
|
||||
transformWithModel(`<input v-model="test" :value="test" />`, {
|
||||
onError
|
||||
})
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// #3596
|
||||
test('should NOT error on static value binding alongside v-model', () => {
|
||||
const onError = vi.fn()
|
||||
transformWithModel(`<input v-model="test" value="test" />`, {
|
||||
onError
|
||||
})
|
||||
expect(onError).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('modifiers', () => {
|
||||
|
|
|
@ -7,7 +7,8 @@ import {
|
|||
NodeTypes,
|
||||
ObjectExpression,
|
||||
transform,
|
||||
VNodeCall
|
||||
VNodeCall,
|
||||
BindingTypes
|
||||
} from '@vue/compiler-core'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers'
|
||||
|
@ -25,12 +26,11 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
|||
},
|
||||
...options
|
||||
})
|
||||
const node = (ast.children[0] as ElementNode).codegenNode as VNodeCall
|
||||
return {
|
||||
root: ast,
|
||||
props: (
|
||||
((ast.children[0] as ElementNode).codegenNode as VNodeCall)
|
||||
.props as ObjectExpression
|
||||
).properties
|
||||
node,
|
||||
props: (node.props as ObjectExpression).properties
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ describe('compiler-dom: transform v-on', () => {
|
|||
// should not treat cached handler as dynamicProp, so it should have no
|
||||
// dynamicProps flags and only the hydration flag
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
genFlagText(PatchFlags.HYDRATE_EVENTS)
|
||||
genFlagText(PatchFlags.NEED_HYDRATION)
|
||||
)
|
||||
expect(prop).toMatchObject({
|
||||
key: {
|
||||
|
@ -288,4 +288,18 @@ describe('compiler-dom: transform v-on', () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('should not have PROPS patchFlag for constant v-on handlers with modifiers', () => {
|
||||
const { node } = parseWithVOn(`<div @keydown.up="foo" />`, {
|
||||
prefixIdentifiers: true,
|
||||
bindingMetadata: {
|
||||
foo: BindingTypes.SETUP_CONST
|
||||
},
|
||||
directiveTransforms: {
|
||||
on: transformOn
|
||||
}
|
||||
})
|
||||
// should only have hydration flag
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.3.5",
|
||||
"version": "3.3.9",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.3.5",
|
||||
"@vue/compiler-core": "3.3.5"
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/compiler-core": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,8 +153,8 @@ const isStringifiableAttr = (name: string, ns: DOMNamespaces) => {
|
|||
(ns === DOMNamespaces.HTML
|
||||
? isKnownHtmlAttr(name)
|
||||
: ns === DOMNamespaces.SVG
|
||||
? isKnownSvgAttr(name)
|
||||
: false) || dataAriaRE.test(name)
|
||||
? isKnownSvgAttr(name)
|
||||
: false) || dataAriaRE.test(name)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@ import {
|
|||
ElementTypes,
|
||||
findProp,
|
||||
NodeTypes,
|
||||
hasDynamicKeyVBind
|
||||
hasDynamicKeyVBind,
|
||||
findDir,
|
||||
isStaticArgOf
|
||||
} from '@vue/compiler-core'
|
||||
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
||||
import {
|
||||
|
@ -32,8 +34,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||
}
|
||||
|
||||
function checkDuplicatedValue() {
|
||||
const value = findProp(node, 'value')
|
||||
if (value) {
|
||||
const value = findDir(node, 'bind')
|
||||
if (value && isStaticArgOf(value.arg, 'value')) {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
|
||||
|
|
|
@ -96,14 +96,14 @@ const transformClick = (key: ExpressionNode, event: string) => {
|
|||
return isStaticClick
|
||||
? createSimpleExpression(event, true)
|
||||
: key.type !== NodeTypes.SIMPLE_EXPRESSION
|
||||
? createCompoundExpression([
|
||||
`(`,
|
||||
key,
|
||||
`) === "onClick" ? "${event}" : (`,
|
||||
key,
|
||||
`)`
|
||||
])
|
||||
: key
|
||||
? createCompoundExpression([
|
||||
`(`,
|
||||
key,
|
||||
`) === "onClick" ? "${event}" : (`,
|
||||
key,
|
||||
`)`
|
||||
])
|
||||
: key
|
||||
}
|
||||
|
||||
export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
**Note: as of 3.2.13+, this package is included as a dependency of the main `vue` package and can be accessed as `vue/compiler-sfc`. This means you no longer need to explicitly install this package and ensure its version match that of `vue`'s. Just use the main `vue/compiler-sfc` deep import instead.**
|
||||
|
||||
This package contains lower level utilities that you can use if you are writing a plugin / transform for a bundler or module system that compiles Vue Single File Components (SFCs) into JavaScript. It is used in [vue-loader](https://github.com/vuejs/vue-loader), [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue) and [vite](https://github.com/vitejs/vite).
|
||||
This package contains lower level utilities that you can use if you are writing a plugin / transform for a bundler or module system that compiles Vue Single File Components (SFCs) into JavaScript. It is used in [vue-loader](https://github.com/vuejs/vue-loader) and [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue).
|
||||
|
||||
## API
|
||||
|
||||
|
@ -77,4 +77,4 @@ export default script
|
|||
|
||||
Options needed for these APIs can be passed via the query string.
|
||||
|
||||
For detailed API references and options, check out the source type definitions. For actual usage of these APIs, check out [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue/tree/next) or [vue-loader](https://github.com/vuejs/vue-loader/tree/next).
|
||||
For detailed API references and options, check out the source type definitions. For actual usage of these APIs, check out [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) or [vue-loader](https://github.com/vuejs/vue-loader/tree/next).
|
||||
|
|
|
@ -696,14 +696,14 @@ return { get vMyDir() { return vMyDir } }
|
|||
|
||||
exports[`SFC compile <script setup> > dev mode import usage check > dynamic arguments 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { FooBar, foo, bar, unused } from './x'
|
||||
import { FooBar, foo, bar, unused, baz } from './x'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar } }
|
||||
return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar }, get baz() { return baz } }
|
||||
}
|
||||
|
||||
})"
|
||||
|
@ -1003,10 +1003,12 @@ export default {
|
|||
setup(__props) {
|
||||
|
||||
const count = ref(0)
|
||||
const style = { color: 'red' }
|
||||
|
||||
return (_ctx, _push, _parent, _attrs) => {
|
||||
const _cssVars = { style: {
|
||||
\\"--xxxxxxxx-count\\": (count.value)
|
||||
\\"--xxxxxxxx-count\\": (count.value),
|
||||
\\"--xxxxxxxx-style\\\\\\\\.color\\": (style.color)
|
||||
}}
|
||||
_push(\`<!--[--><div\${
|
||||
_ssrRenderAttrs(_cssVars)
|
||||
|
|
|
@ -376,18 +376,19 @@ describe('SFC compile <script setup>', () => {
|
|||
test('dynamic arguments', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
import { FooBar, foo, bar, unused } from './x'
|
||||
import { FooBar, foo, bar, unused, baz } from './x'
|
||||
</script>
|
||||
<template>
|
||||
<FooBar #[foo.slotName] />
|
||||
<FooBar #unused />
|
||||
<div :[bar.attrName]="15"></div>
|
||||
<div unused="unused"></div>
|
||||
<div #[\`item:\${baz.key}\`]="{ value }"></div>
|
||||
</template>
|
||||
`)
|
||||
expect(content).toMatch(
|
||||
`return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
|
||||
`get bar() { return bar } }`
|
||||
`get bar() { return bar }, get baz() { return baz } }`
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
@ -777,6 +778,7 @@ describe('SFC compile <script setup>', () => {
|
|||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const count = ref(0)
|
||||
const style = { color: 'red' }
|
||||
</script>
|
||||
<template>
|
||||
<div>{{ count }}</div>
|
||||
|
@ -784,6 +786,7 @@ describe('SFC compile <script setup>', () => {
|
|||
</template>
|
||||
<style>
|
||||
div { color: v-bind(count) }
|
||||
span { color: v-bind(style.color) }
|
||||
</style>
|
||||
`,
|
||||
{
|
||||
|
@ -798,6 +801,7 @@ describe('SFC compile <script setup>', () => {
|
|||
expect(content).toMatch(`ssrInterpolate`)
|
||||
expect(content).not.toMatch(`useCssVars`)
|
||||
expect(content).toMatch(`"--${mockId}-count": (count.value)`)
|
||||
expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
|
|
|
@ -105,6 +105,24 @@ return { emit }
|
|||
})"
|
||||
`;
|
||||
|
||||
exports[`defineEmits > w/ type (interface w/ extends) 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Base { (e: 'foo'): void }
|
||||
interface Emits extends Base { (e: 'bar'): void }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"bar\\", \\"foo\\"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineEmits > w/ type (interface) 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Emits { (e: 'foo' | 'bar'): void }
|
||||
|
|
|
@ -7,15 +7,17 @@ export default {
|
|||
props: {
|
||||
\\"modelValue\\": { required: true },
|
||||
\\"count\\": {},
|
||||
\\"toString\\": { type: Function },
|
||||
},
|
||||
emits: [\\"update:modelValue\\", \\"update:count\\"],
|
||||
emits: [\\"update:modelValue\\", \\"update:count\\", \\"update:toString\\"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const modelValue = _useModel(__props, \\"modelValue\\")
|
||||
const c = _useModel(__props, \\"count\\")
|
||||
const toString = _useModel(__props, \\"toString\\")
|
||||
|
||||
return { modelValue, c }
|
||||
return { modelValue, c, toString }
|
||||
}
|
||||
|
||||
}"
|
||||
|
@ -25,7 +27,7 @@ exports[`defineModel() > w/ array props 1`] = `
|
|||
"import { useModel as _useModel, mergeModels as _mergeModels } from 'vue'
|
||||
|
||||
export default {
|
||||
props: _mergeModels(['foo', 'bar'], {
|
||||
props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
\\"count\\": {},
|
||||
}),
|
||||
emits: [\\"update:count\\"],
|
||||
|
@ -45,10 +47,10 @@ exports[`defineModel() > w/ defineProps and defineEmits 1`] = `
|
|||
"import { useModel as _useModel, mergeModels as _mergeModels } from 'vue'
|
||||
|
||||
export default {
|
||||
props: _mergeModels({ foo: String }, {
|
||||
props: /*#__PURE__*/_mergeModels({ foo: String }, {
|
||||
\\"modelValue\\": { default: 0 },
|
||||
}),
|
||||
emits: _mergeModels(['change'], [\\"update:modelValue\\"]),
|
||||
emits: /*#__PURE__*/_mergeModels(['change'], [\\"update:modelValue\\"]),
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
|
|
@ -46,6 +46,51 @@ export default /*#__PURE__*/_defineComponent({
|
|||
|
||||
const { foo } = __props
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineProps > should escape names w/ special symbols 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
\\"spa ce\\": { type: null, required: true },
|
||||
\\"exclamation!mark\\": { type: null, required: true },
|
||||
\\"double\\\\\\"quote\\": { type: null, required: true },
|
||||
\\"hash#tag\\": { type: null, required: true },
|
||||
\\"dollar$sign\\": { type: null, required: true },
|
||||
\\"percentage%sign\\": { type: null, required: true },
|
||||
\\"amper&sand\\": { type: null, required: true },
|
||||
\\"single'quote\\": { type: null, required: true },
|
||||
\\"round(brack)ets\\": { type: null, required: true },
|
||||
\\"aste*risk\\": { type: null, required: true },
|
||||
\\"pl+us\\": { type: null, required: true },
|
||||
\\"com,ma\\": { type: null, required: true },
|
||||
\\"do.t\\": { type: null, required: true },
|
||||
\\"sla/sh\\": { type: null, required: true },
|
||||
\\"co:lon\\": { type: null, required: true },
|
||||
\\"semi;colon\\": { type: null, required: true },
|
||||
\\"angle<brack>ets\\": { type: null, required: true },
|
||||
\\"equal=sign\\": { type: null, required: true },
|
||||
\\"question?mark\\": { type: null, required: true },
|
||||
\\"at@sign\\": { type: null, required: true },
|
||||
\\"square[brack]ets\\": { type: null, required: true },
|
||||
\\"back\\\\\\\\slash\\": { type: null, required: true },
|
||||
\\"ca^ret\\": { type: null, required: true },
|
||||
\\"back\`tick\\": { type: null, required: true },
|
||||
\\"curly{bra}ces\\": { type: null, required: true },
|
||||
\\"pi|pe\\": { type: null, required: true },
|
||||
\\"til~de\\": { type: null, required: true },
|
||||
\\"da-sh\\": { type: null, required: true }
|
||||
},
|
||||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
|
@ -232,6 +277,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
alias: { type: Array, required: true },
|
||||
method: { type: Function, required: true },
|
||||
symbol: { type: Symbol, required: true },
|
||||
error: { type: Error, required: true },
|
||||
extract: { type: Number, required: true },
|
||||
exclude: { type: [Number, Boolean], required: true },
|
||||
uppercase: { type: String, required: true },
|
||||
|
@ -286,7 +332,7 @@ exports[`defineProps > withDefaults (dynamic) 1`] = `
|
|||
import { defaults } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: _mergeDefaults({
|
||||
props: /*#__PURE__*/_mergeDefaults({
|
||||
foo: { type: String, required: false },
|
||||
bar: { type: Number, required: false },
|
||||
baz: { type: Boolean, required: true }
|
||||
|
@ -307,7 +353,7 @@ exports[`defineProps > withDefaults (dynamic) w/ production mode 1`] = `
|
|||
import { defaults } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: _mergeDefaults({
|
||||
props: /*#__PURE__*/_mergeDefaults({
|
||||
foo: { type: Function },
|
||||
bar: { type: Boolean },
|
||||
baz: { type: [Boolean, Function] },
|
||||
|
@ -329,7 +375,7 @@ exports[`defineProps > withDefaults (reference) 1`] = `
|
|||
import { defaults } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: _mergeDefaults({
|
||||
props: /*#__PURE__*/_mergeDefaults({
|
||||
foo: { type: String, required: false },
|
||||
bar: { type: Number, required: false },
|
||||
baz: { type: Boolean, required: true }
|
||||
|
@ -416,7 +462,7 @@ exports[`defineProps > withDefaults w/ dynamic object method 1`] = `
|
|||
"import { mergeDefaults as _mergeDefaults, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: _mergeDefaults({
|
||||
props: /*#__PURE__*/_mergeDefaults({
|
||||
foo: { type: Function, required: false }
|
||||
}, {
|
||||
['fo' + 'o']() { return 'foo' }
|
||||
|
|
|
@ -62,7 +62,7 @@ exports[`sfc reactive props destructure > default values w/ array runtime declar
|
|||
"import { mergeDefaults as _mergeDefaults } from 'vue'
|
||||
|
||||
export default {
|
||||
props: _mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
props: /*#__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
foo: 1,
|
||||
bar: () => ({}),
|
||||
func: () => {}, __skip_func: true
|
||||
|
@ -81,7 +81,7 @@ exports[`sfc reactive props destructure > default values w/ object runtime decla
|
|||
"import { mergeDefaults as _mergeDefaults } from 'vue'
|
||||
|
||||
export default {
|
||||
props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
|
||||
props: /*#__PURE__*/_mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
|
||||
foo: 1,
|
||||
bar: () => ({}),
|
||||
func: () => {}, __skip_func: true,
|
||||
|
@ -101,7 +101,7 @@ exports[`sfc reactive props destructure > default values w/ runtime declaration
|
|||
"import { mergeDefaults as _mergeDefaults } from 'vue'
|
||||
|
||||
export default {
|
||||
props: _mergeDefaults(['foo', 'foo:bar'], {
|
||||
props: /*#__PURE__*/_mergeDefaults(['foo', 'foo:bar'], {
|
||||
foo: 1,
|
||||
\\"foo:bar\\": 'foo-bar'
|
||||
}),
|
||||
|
|
|
@ -80,6 +80,18 @@ const emit = defineEmits(['a', 'b'])
|
|||
expect(content).toMatch(`emits: ["foo", "bar"]`)
|
||||
})
|
||||
|
||||
test('w/ type (interface w/ extends)', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
interface Base { (e: 'foo'): void }
|
||||
interface Emits extends Base { (e: 'bar'): void }
|
||||
const emit = defineEmits<Emits>()
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`emits: ["bar", "foo"]`)
|
||||
})
|
||||
|
||||
test('w/ type (exported interface)', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -8,6 +8,7 @@ describe('defineModel()', () => {
|
|||
<script setup>
|
||||
const modelValue = defineModel({ required: true })
|
||||
const c = defineModel('count')
|
||||
const toString = defineModel('toString', { type: Function })
|
||||
</script>
|
||||
`,
|
||||
{ defineModel: true }
|
||||
|
@ -16,18 +17,22 @@ describe('defineModel()', () => {
|
|||
expect(content).toMatch('props: {')
|
||||
expect(content).toMatch('"modelValue": { required: true },')
|
||||
expect(content).toMatch('"count": {},')
|
||||
expect(content).toMatch('emits: ["update:modelValue", "update:count"],')
|
||||
expect(content).toMatch('"toString": { type: Function },')
|
||||
expect(content).toMatch(
|
||||
'emits: ["update:modelValue", "update:count", "update:toString"],'
|
||||
)
|
||||
expect(content).toMatch(
|
||||
`const modelValue = _useModel(__props, "modelValue")`
|
||||
)
|
||||
expect(content).toMatch(`const c = _useModel(__props, "count")`)
|
||||
expect(content).toMatch(`return { modelValue, c }`)
|
||||
expect(content).toMatch(`return { modelValue, c, toString }`)
|
||||
expect(content).not.toMatch('defineModel')
|
||||
|
||||
expect(bindings).toStrictEqual({
|
||||
modelValue: BindingTypes.SETUP_REF,
|
||||
count: BindingTypes.PROPS,
|
||||
c: BindingTypes.SETUP_REF
|
||||
c: BindingTypes.SETUP_REF,
|
||||
toString: BindingTypes.SETUP_REF
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -43,7 +48,7 @@ describe('defineModel()', () => {
|
|||
{ defineModel: true }
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: _mergeModels({ foo: String }`)
|
||||
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels({ foo: String }`)
|
||||
expect(content).toMatch(`"modelValue": { default: 0 }`)
|
||||
expect(content).toMatch(`const count = _useModel(__props, "modelValue")`)
|
||||
expect(content).not.toMatch('defineModel')
|
||||
|
@ -65,7 +70,7 @@ describe('defineModel()', () => {
|
|||
{ defineModel: true }
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: _mergeModels(['foo', 'bar'], {
|
||||
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
"count": {},
|
||||
})`)
|
||||
expect(content).toMatch(`const count = _useModel(__props, "count")`)
|
||||
|
|
|
@ -97,6 +97,7 @@ const props = defineProps({ foo: String })
|
|||
alias: Alias
|
||||
method(): void
|
||||
symbol: symbol
|
||||
error: Error
|
||||
extract: Extract<1 | 2 | boolean, 2>
|
||||
exclude: Exclude<1 | 2 | boolean, 2>
|
||||
uppercase: Uppercase<'foo'>
|
||||
|
@ -143,6 +144,7 @@ const props = defineProps({ foo: String })
|
|||
expect(content).toMatch(`alias: { type: Array, required: true }`)
|
||||
expect(content).toMatch(`method: { type: Function, required: true }`)
|
||||
expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
|
||||
expect(content).toMatch(`error: { type: Error, required: true }`)
|
||||
expect(content).toMatch(
|
||||
`objectOrFn: { type: [Function, Object], required: true },`
|
||||
)
|
||||
|
@ -198,6 +200,7 @@ const props = defineProps({ foo: String })
|
|||
alias: BindingTypes.PROPS,
|
||||
method: BindingTypes.PROPS,
|
||||
symbol: BindingTypes.PROPS,
|
||||
error: BindingTypes.PROPS,
|
||||
objectOrFn: BindingTypes.PROPS,
|
||||
extract: BindingTypes.PROPS,
|
||||
exclude: BindingTypes.PROPS,
|
||||
|
@ -608,4 +611,103 @@ const props = defineProps({ foo: String })
|
|||
}).toThrow(`cannot accept both type and non-type arguments`)
|
||||
})
|
||||
})
|
||||
|
||||
test('should escape names w/ special symbols', () => {
|
||||
const { content, bindings } = compile(`
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
'spa ce': unknown
|
||||
'exclamation!mark': unknown
|
||||
'double"quote': unknown
|
||||
'hash#tag': unknown
|
||||
'dollar$sign': unknown
|
||||
'percentage%sign': unknown
|
||||
'amper&sand': unknown
|
||||
"single'quote": unknown
|
||||
'round(brack)ets': unknown
|
||||
'aste*risk': unknown
|
||||
'pl+us': unknown
|
||||
'com,ma': unknown
|
||||
'do.t': unknown
|
||||
'sla/sh': unknown
|
||||
'co:lon': unknown
|
||||
'semi;colon': unknown
|
||||
'angle<brack>ets': unknown
|
||||
'equal=sign': unknown
|
||||
'question?mark': unknown
|
||||
'at@sign': unknown
|
||||
'square[brack]ets': unknown
|
||||
'back\\\\slash': unknown
|
||||
'ca^ret': unknown
|
||||
'back\`tick': unknown
|
||||
'curly{bra}ces': unknown
|
||||
'pi|pe': unknown
|
||||
'til~de': unknown
|
||||
'da-sh': unknown
|
||||
}>()
|
||||
</script>`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`"spa ce": { type: null, required: true }`)
|
||||
expect(content).toMatch(
|
||||
`"exclamation!mark": { type: null, required: true }`
|
||||
)
|
||||
expect(content).toMatch(`"double\\"quote": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"hash#tag": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"dollar$sign": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"percentage%sign": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"amper&sand": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"single'quote": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"round(brack)ets": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"aste*risk": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"pl+us": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"com,ma": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"do.t": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"sla/sh": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"co:lon": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"semi;colon": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"angle<brack>ets": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"equal=sign": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"question?mark": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"at@sign": { type: null, required: true }`)
|
||||
expect(content).toMatch(
|
||||
`"square[brack]ets": { type: null, required: true }`
|
||||
)
|
||||
expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"ca^ret": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"back\`tick": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"curly{bra}ces": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"pi|pe": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"til~de": { type: null, required: true }`)
|
||||
expect(content).toMatch(`"da-sh": { type: null, required: true }`)
|
||||
expect(bindings).toStrictEqual({
|
||||
'spa ce': BindingTypes.PROPS,
|
||||
'exclamation!mark': BindingTypes.PROPS,
|
||||
'double"quote': BindingTypes.PROPS,
|
||||
'hash#tag': BindingTypes.PROPS,
|
||||
dollar$sign: BindingTypes.PROPS,
|
||||
'percentage%sign': BindingTypes.PROPS,
|
||||
'amper&sand': BindingTypes.PROPS,
|
||||
"single'quote": BindingTypes.PROPS,
|
||||
'round(brack)ets': BindingTypes.PROPS,
|
||||
'aste*risk': BindingTypes.PROPS,
|
||||
'pl+us': BindingTypes.PROPS,
|
||||
'com,ma': BindingTypes.PROPS,
|
||||
'do.t': BindingTypes.PROPS,
|
||||
'sla/sh': BindingTypes.PROPS,
|
||||
'co:lon': BindingTypes.PROPS,
|
||||
'semi;colon': BindingTypes.PROPS,
|
||||
'angle<brack>ets': BindingTypes.PROPS,
|
||||
'equal=sign': BindingTypes.PROPS,
|
||||
'question?mark': BindingTypes.PROPS,
|
||||
'at@sign': BindingTypes.PROPS,
|
||||
'square[brack]ets': BindingTypes.PROPS,
|
||||
'back\\slash': BindingTypes.PROPS,
|
||||
'ca^ret': BindingTypes.PROPS,
|
||||
'back`tick': BindingTypes.PROPS,
|
||||
'curly{bra}ces': BindingTypes.PROPS,
|
||||
'pi|pe': BindingTypes.PROPS,
|
||||
'til~de': BindingTypes.PROPS,
|
||||
'da-sh': BindingTypes.PROPS
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -78,7 +78,8 @@ describe('sfc reactive props destructure', () => {
|
|||
// literals can be used as-is, non-literals are always returned from a
|
||||
// function
|
||||
// functions need to be marked with a skip marker
|
||||
expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
expect(content)
|
||||
.toMatch(`props: /*#__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
foo: 1,
|
||||
bar: () => ({}),
|
||||
func: () => {}, __skip_func: true
|
||||
|
@ -98,7 +99,7 @@ describe('sfc reactive props destructure', () => {
|
|||
// safely infer whether runtime type is Function (e.g. if the runtime decl
|
||||
// is imported, or spreads another object)
|
||||
expect(content)
|
||||
.toMatch(`props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
|
||||
.toMatch(`props: /*#__PURE__*/_mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
|
||||
foo: 1,
|
||||
bar: () => ({}),
|
||||
func: () => {}, __skip_func: true,
|
||||
|
@ -122,7 +123,7 @@ describe('sfc reactive props destructure', () => {
|
|||
})
|
||||
|
||||
expect(content).toMatch(`
|
||||
props: _mergeDefaults(['foo', 'foo:bar'], {
|
||||
props: /*#__PURE__*/_mergeDefaults(['foo', 'foo:bar'], {
|
||||
foo: 1,
|
||||
"foo:bar": 'foo-bar'
|
||||
}),`)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { normalize } from 'node:path'
|
||||
import { Identifier } from '@babel/types'
|
||||
import { SFCScriptCompileOptions, parse } from '../../src'
|
||||
import { ScriptCompileContext } from '../../src/script/context'
|
||||
|
@ -454,6 +455,88 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('generics', () => {
|
||||
test('generic with type literal', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Props<T> = T
|
||||
defineProps<Props<{ foo: string }>>()
|
||||
`).props
|
||||
).toStrictEqual({
|
||||
foo: ['String']
|
||||
})
|
||||
})
|
||||
|
||||
test('generic used in intersection', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Foo = { foo: string; }
|
||||
type Bar = { bar: number; }
|
||||
type Props<T,U> = T & U & { baz: boolean }
|
||||
defineProps<Props<Foo, Bar>>()
|
||||
`).props
|
||||
).toStrictEqual({
|
||||
foo: ['String'],
|
||||
bar: ['Number'],
|
||||
baz: ['Boolean']
|
||||
})
|
||||
})
|
||||
|
||||
test('generic type /w generic type alias', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Aliased<T> = Readonly<Partial<T>>
|
||||
type Props<T> = Aliased<T>
|
||||
type Foo = { foo: string; }
|
||||
defineProps<Props<Foo>>()
|
||||
`).props
|
||||
).toStrictEqual({
|
||||
foo: ['String']
|
||||
})
|
||||
})
|
||||
|
||||
test('generic type /w aliased type literal', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Aliased<T> = { foo: T }
|
||||
defineProps<Aliased<string>>()
|
||||
`).props
|
||||
).toStrictEqual({
|
||||
foo: ['String']
|
||||
})
|
||||
})
|
||||
|
||||
test('generic type /w interface', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
interface Props<T> {
|
||||
foo: T
|
||||
}
|
||||
type Foo = string
|
||||
defineProps<Props<Foo>>()
|
||||
`).props
|
||||
).toStrictEqual({
|
||||
foo: ['String']
|
||||
})
|
||||
})
|
||||
|
||||
test('generic from external-file', () => {
|
||||
const files = {
|
||||
'/foo.ts': 'export type P<T> = { foo: T }'
|
||||
}
|
||||
const { props } = resolve(
|
||||
`
|
||||
import { P } from './foo'
|
||||
defineProps<P<string>>()
|
||||
`,
|
||||
files
|
||||
)
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['String']
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('external type imports', () => {
|
||||
test('relative ts', () => {
|
||||
const files = {
|
||||
|
@ -478,6 +561,36 @@ describe('resolveType', () => {
|
|||
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
|
||||
})
|
||||
|
||||
test.runIf(process.platform === 'win32')('relative ts on Windows', () => {
|
||||
const files = {
|
||||
'C:\\Test\\FolderA\\foo.ts': 'export type P = { foo: number }',
|
||||
'C:\\Test\\FolderA\\bar.d.ts':
|
||||
'type X = { bar: string }; export { X as Y };' +
|
||||
// verify that we can parse syntax that is only valid in d.ts
|
||||
'export const baz: boolean',
|
||||
'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }'
|
||||
}
|
||||
const { props, deps } = resolve(
|
||||
`
|
||||
import { P } from './foo'
|
||||
import { Y as PP } from './bar'
|
||||
import { Z as PPP } from '../FolderB/buz'
|
||||
defineProps<P & PP & PPP>()
|
||||
`,
|
||||
files,
|
||||
{},
|
||||
'C:\\Test\\FolderA\\Test.vue'
|
||||
)
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['Number'],
|
||||
bar: ['String'],
|
||||
buz: ['String']
|
||||
})
|
||||
expect(deps && [...deps].map(normalize)).toStrictEqual(
|
||||
Object.keys(files).map(normalize)
|
||||
)
|
||||
})
|
||||
|
||||
// #8244
|
||||
test('utility type in external file', () => {
|
||||
const files = {
|
||||
|
@ -898,19 +1011,20 @@ describe('resolveType', () => {
|
|||
function resolve(
|
||||
code: string,
|
||||
files: Record<string, string> = {},
|
||||
options?: Partial<SFCScriptCompileOptions>
|
||||
options?: Partial<SFCScriptCompileOptions>,
|
||||
sourceFileName: string = '/Test.vue'
|
||||
) {
|
||||
const { descriptor } = parse(`<script setup lang="ts">\n${code}\n</script>`, {
|
||||
filename: '/Test.vue'
|
||||
filename: sourceFileName
|
||||
})
|
||||
const ctx = new ScriptCompileContext(descriptor, {
|
||||
id: 'test',
|
||||
fs: {
|
||||
fileExists(file) {
|
||||
return !!files[file]
|
||||
return !!(files[file] ?? files[normalize(file)])
|
||||
},
|
||||
readFile(file) {
|
||||
return files[file]
|
||||
return files[file] ?? files[normalize(file)]
|
||||
}
|
||||
},
|
||||
...options
|
||||
|
|
|
@ -85,6 +85,16 @@ describe('SFC scoped CSS', () => {
|
|||
".baz .qux[data-v-test] .foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`:is(.foo :deep(.bar)) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
":is(.foo[data-v-test] .bar) { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`:where(.foo :deep(.bar)) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
":where(.foo[data-v-test] .bar) { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('::v-slotted', () => {
|
||||
|
|
|
@ -60,6 +60,33 @@ body
|
|||
expect(result.errors.length).toBe(0)
|
||||
})
|
||||
|
||||
test('preprocess pug with indents and blank lines', () => {
|
||||
const template = parse(
|
||||
`
|
||||
<template lang="pug">
|
||||
body
|
||||
h1 The next line contains four spaces.
|
||||
|
||||
div.container
|
||||
p The next line is empty.
|
||||
p This is the last line.
|
||||
</template>
|
||||
`,
|
||||
{ filename: 'example.vue', sourceMap: true }
|
||||
).descriptor.template as SFCTemplateBlock
|
||||
|
||||
const result = compile({
|
||||
filename: 'example.vue',
|
||||
source: template.content,
|
||||
preprocessLang: template.lang
|
||||
})
|
||||
|
||||
expect(result.errors.length).toBe(0)
|
||||
expect(result.source).toBe(
|
||||
'<body><h1>The next line contains four spaces.</h1><div class="container"><p>The next line is empty.</p></div><p>This is the last line.</p></body>'
|
||||
)
|
||||
})
|
||||
|
||||
test('warn missing preprocessor', () => {
|
||||
const template = parse(`<template lang="unknownLang">hi</template>\n`, {
|
||||
filename: 'example.vue',
|
||||
|
|
|
@ -272,5 +272,73 @@ describe('CSS vars injection', () => {
|
|||
`export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`
|
||||
)
|
||||
})
|
||||
|
||||
describe('skip codegen in SSR', () => {
|
||||
test('script setup, inline', () => {
|
||||
const { content } = compileSFCScript(
|
||||
`<script setup>
|
||||
let size = 1
|
||||
</script>\n` +
|
||||
`<style>
|
||||
div {
|
||||
font-size: v-bind(size);
|
||||
}
|
||||
</style>`,
|
||||
{
|
||||
inlineTemplate: true,
|
||||
templateOptions: {
|
||||
ssr: true
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(content).not.toMatch(`_useCssVars`)
|
||||
})
|
||||
|
||||
// #6926
|
||||
test('script, non-inline', () => {
|
||||
const { content } = compileSFCScript(
|
||||
`<script setup>
|
||||
let size = 1
|
||||
</script>\n` +
|
||||
`<style>
|
||||
div {
|
||||
font-size: v-bind(size);
|
||||
}
|
||||
</style>`,
|
||||
{
|
||||
inlineTemplate: false,
|
||||
templateOptions: {
|
||||
ssr: true
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(content).not.toMatch(`_useCssVars`)
|
||||
})
|
||||
|
||||
test('normal script', () => {
|
||||
const { content } = compileSFCScript(
|
||||
`<script>
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
size: ref('100px')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>\n` +
|
||||
`<style>
|
||||
div {
|
||||
font-size: v-bind(size);
|
||||
}
|
||||
</style>`,
|
||||
{
|
||||
templateOptions: {
|
||||
ssr: true
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(content).not.toMatch(`_useCssVars`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -34,6 +34,26 @@ describe('compiler:sfc', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('template block with lang + indent', () => {
|
||||
// Padding determines how many blank lines will there be before the style block
|
||||
const padding = Math.round(Math.random() * 10)
|
||||
const template = parse(
|
||||
`${'\n'.repeat(padding)}<template lang="pug">
|
||||
h1 foo
|
||||
div bar
|
||||
span baz
|
||||
</template>\n`
|
||||
).descriptor.template!
|
||||
|
||||
expect(template.map).not.toBeUndefined()
|
||||
|
||||
const consumer = new SourceMapConsumer(template.map!)
|
||||
consumer.eachMapping(mapping => {
|
||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
||||
expect(mapping.originalColumn - mapping.generatedColumn).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom block', () => {
|
||||
const padding = Math.round(Math.random() * 10)
|
||||
const custom = parse(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.3.5",
|
||||
"version": "3.3.9",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -32,27 +32,27 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/compiler-core": "3.3.5",
|
||||
"@vue/compiler-dom": "3.3.5",
|
||||
"@vue/compiler-ssr": "3.3.5",
|
||||
"@vue/reactivity-transform": "3.3.5",
|
||||
"@vue/shared": "3.3.5",
|
||||
"@babel/parser": "^7.23.4",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/compiler-ssr": "workspace:*",
|
||||
"@vue/reactivity-transform": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.5",
|
||||
"postcss": "^8.4.31",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.23.0",
|
||||
"@babel/types": "^7.23.4",
|
||||
"@vue/consolidate": "^0.17.3",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "^10.0.1",
|
||||
"lru-cache": "^10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "^9.0.3",
|
||||
"postcss-modules": "^4.3.1",
|
||||
"postcss-selector-parser": "^6.0.13",
|
||||
"pug": "^3.0.2",
|
||||
"sass": "^1.69.4"
|
||||
"sass": "^1.69.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -765,7 +765,7 @@ export function compileScript(
|
|||
if (
|
||||
sfc.cssVars.length &&
|
||||
// no need to do this when targeting SSR
|
||||
!(options.inlineTemplate && options.templateOptions?.ssr)
|
||||
!options.templateOptions?.ssr
|
||||
) {
|
||||
ctx.helperImports.add(CSS_VARS_HELPER)
|
||||
ctx.helperImports.add('unref')
|
||||
|
@ -1172,8 +1172,8 @@ function walkObjectPattern(
|
|||
const type = isDefineCall
|
||||
? BindingTypes.SETUP_CONST
|
||||
: isConst
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
registerBinding(bindings, p.key, type)
|
||||
} else {
|
||||
walkPattern(p.value, bindings, isConst, isDefineCall)
|
||||
|
@ -1208,8 +1208,8 @@ function walkPattern(
|
|||
const type = isDefineCall
|
||||
? BindingTypes.SETUP_CONST
|
||||
: isConst
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
registerBinding(bindings, node, type)
|
||||
} else if (node.type === 'RestElement') {
|
||||
// argument can only be identifier when destructuring
|
||||
|
@ -1224,8 +1224,8 @@ function walkPattern(
|
|||
const type = isDefineCall
|
||||
? BindingTypes.SETUP_CONST
|
||||
: isConst
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
registerBinding(bindings, node.left, type)
|
||||
} else {
|
||||
walkPattern(node.left, bindings, isConst)
|
||||
|
|
|
@ -124,8 +124,8 @@ export function compileTemplate(
|
|||
? preprocessCustomRequire
|
||||
? preprocessCustomRequire(preprocessLang)
|
||||
: __ESM_BROWSER__
|
||||
? undefined
|
||||
: consolidate[preprocessLang as keyof typeof consolidate]
|
||||
? undefined
|
||||
: consolidate[preprocessLang as keyof typeof consolidate]
|
||||
: false
|
||||
if (preprocessor) {
|
||||
try {
|
||||
|
@ -212,6 +212,7 @@ function doCompileTemplate({
|
|||
slotted,
|
||||
sourceMap: true,
|
||||
...compilerOptions,
|
||||
hmr: !isProd,
|
||||
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
|
||||
filename,
|
||||
onError: e => errors.push(e),
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
export const version = __VERSION__
|
||||
|
||||
// API
|
||||
export { parse, parseCache } from './parse'
|
||||
export { parse } from './parse'
|
||||
export { compileTemplate } from './compileTemplate'
|
||||
export { compileStyle, compileStyleAsync } from './compileStyle'
|
||||
export { compileScript } from './compileScript'
|
||||
export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault'
|
||||
export { resolveTypeElements, inferRuntimeType } from './script/resolveType'
|
||||
|
||||
import { SFCParseResult, parseCache as _parseCache } from './parse'
|
||||
// #9521 export parseCache as a simple map to avoid exposing LRU types
|
||||
export const parseCache = _parseCache as Map<string, SFCParseResult>
|
||||
|
||||
// TODO remove in 3.4
|
||||
export {
|
||||
shouldTransform as shouldTransformRef,
|
||||
|
|
|
@ -253,19 +253,31 @@ export function parse(
|
|||
}
|
||||
}
|
||||
|
||||
// dedent pug/jade templates
|
||||
let templateColumnOffset = 0
|
||||
if (
|
||||
descriptor.template &&
|
||||
(descriptor.template.lang === 'pug' || descriptor.template.lang === 'jade')
|
||||
) {
|
||||
;[descriptor.template.content, templateColumnOffset] = dedent(
|
||||
descriptor.template.content
|
||||
)
|
||||
}
|
||||
|
||||
if (sourceMap) {
|
||||
const genMap = (block: SFCBlock | null) => {
|
||||
const genMap = (block: SFCBlock | null, columnOffset = 0) => {
|
||||
if (block && !block.src) {
|
||||
block.map = generateSourceMap(
|
||||
filename,
|
||||
source,
|
||||
block.content,
|
||||
sourceRoot,
|
||||
!pad || block.type === 'template' ? block.loc.start.line - 1 : 0
|
||||
!pad || block.type === 'template' ? block.loc.start.line - 1 : 0,
|
||||
columnOffset
|
||||
)
|
||||
}
|
||||
}
|
||||
genMap(descriptor.template)
|
||||
genMap(descriptor.template, templateColumnOffset)
|
||||
genMap(descriptor.script)
|
||||
descriptor.styles.forEach(genMap)
|
||||
descriptor.customBlocks.forEach(genMap)
|
||||
|
@ -369,7 +381,8 @@ function generateSourceMap(
|
|||
source: string,
|
||||
generated: string,
|
||||
sourceRoot: string,
|
||||
lineOffset: number
|
||||
lineOffset: number,
|
||||
columnOffset: number
|
||||
): RawSourceMap {
|
||||
const map = new SourceMapGenerator({
|
||||
file: filename.replace(/\\/g, '/'),
|
||||
|
@ -386,7 +399,7 @@ function generateSourceMap(
|
|||
source: filename,
|
||||
original: {
|
||||
line: originalLine,
|
||||
column: i
|
||||
column: i + columnOffset
|
||||
},
|
||||
generated: {
|
||||
line: generatedLine,
|
||||
|
@ -466,3 +479,31 @@ export function hmrShouldReload(
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedent a string.
|
||||
*
|
||||
* This removes any whitespace that is common to all lines in the string from
|
||||
* each line in the string.
|
||||
*/
|
||||
function dedent(s: string): [string, number] {
|
||||
const lines = s.split('\n')
|
||||
const minIndent = lines.reduce(function (minIndent, line) {
|
||||
if (line.trim() === '') {
|
||||
return minIndent
|
||||
}
|
||||
const indent = line.match(/^\s*/)?.[0]?.length || 0
|
||||
return Math.min(indent, minIndent)
|
||||
}, Infinity)
|
||||
if (minIndent === 0) {
|
||||
return [s, minIndent]
|
||||
}
|
||||
return [
|
||||
lines
|
||||
.map(function (line) {
|
||||
return line.slice(minIndent)
|
||||
})
|
||||
.join('\n'),
|
||||
minIndent
|
||||
]
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ export class ScriptCompileContext {
|
|||
emitDecl: Node | undefined
|
||||
|
||||
// defineModel
|
||||
modelDecls: Record<string, ModelDecl> = {}
|
||||
modelDecls: Record<string, ModelDecl> = Object.create(null)
|
||||
|
||||
// defineOptions
|
||||
optionsRuntimeDecl: Node | undefined
|
||||
|
@ -164,7 +164,7 @@ export function resolveParserPlugins(
|
|||
}
|
||||
if (lang === 'ts' || lang === 'tsx') {
|
||||
plugins.push(['typescript', { dts }])
|
||||
if (!plugins.includes('decorators')) {
|
||||
if (!userPlugins || !userPlugins.includes('decorators')) {
|
||||
plugins.push('decorators-legacy')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,9 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined {
|
|||
.map(n => JSON.stringify(`update:${n}`))
|
||||
.join(', ')}]`
|
||||
emitsDecl = emitsDecl
|
||||
? `${ctx.helper('mergeModels')}(${emitsDecl}, ${modelEmitsDecl})`
|
||||
? `/*#__PURE__*/${ctx.helper(
|
||||
'mergeModels'
|
||||
)}(${emitsDecl}, ${modelEmitsDecl})`
|
||||
: modelEmitsDecl
|
||||
}
|
||||
return emitsDecl
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
isCallOf,
|
||||
unwrapTSNode,
|
||||
toRuntimeTypeString,
|
||||
getEscapedKey
|
||||
getEscapedPropName
|
||||
} from './utils'
|
||||
import { genModelProps } from './defineModel'
|
||||
import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings'
|
||||
|
@ -135,7 +135,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
|||
const defaults: string[] = []
|
||||
for (const key in ctx.propsDestructuredBindings) {
|
||||
const d = genDestructuredDefaultValue(ctx, key)
|
||||
const finalKey = getEscapedKey(key)
|
||||
const finalKey = getEscapedPropName(key)
|
||||
if (d)
|
||||
defaults.push(
|
||||
`${finalKey}: ${d.valueString}${
|
||||
|
@ -144,7 +144,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
|||
)
|
||||
}
|
||||
if (defaults.length) {
|
||||
propsDecls = `${ctx.helper(
|
||||
propsDecls = `/*#__PURE__*/${ctx.helper(
|
||||
`mergeDefaults`
|
||||
)}(${propsDecls}, {\n ${defaults.join(',\n ')}\n})`
|
||||
}
|
||||
|
@ -156,7 +156,9 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
|||
const modelsDecls = genModelProps(ctx)
|
||||
|
||||
if (propsDecls && modelsDecls) {
|
||||
return `${ctx.helper('mergeModels')}(${propsDecls}, ${modelsDecls})`
|
||||
return `/*#__PURE__*/${ctx.helper(
|
||||
'mergeModels'
|
||||
)}(${propsDecls}, ${modelsDecls})`
|
||||
} else {
|
||||
return modelsDecls || propsDecls
|
||||
}
|
||||
|
@ -184,9 +186,9 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
|
|||
${propStrings.join(',\n ')}\n }`
|
||||
|
||||
if (ctx.propsRuntimeDefaults && !hasStaticDefaults) {
|
||||
propsDecls = `${ctx.helper('mergeDefaults')}(${propsDecls}, ${ctx.getString(
|
||||
ctx.propsRuntimeDefaults
|
||||
)})`
|
||||
propsDecls = `/*#__PURE__*/${ctx.helper(
|
||||
'mergeDefaults'
|
||||
)}(${propsDecls}, ${ctx.getString(ctx.propsRuntimeDefaults)})`
|
||||
}
|
||||
|
||||
return propsDecls
|
||||
|
@ -251,7 +253,7 @@ function genRuntimePropFromType(
|
|||
}
|
||||
}
|
||||
|
||||
const finalKey = getEscapedKey(key)
|
||||
const finalKey = getEscapedPropName(key)
|
||||
if (!ctx.options.isProd) {
|
||||
return `${finalKey}: { ${concatStrings([
|
||||
`type: ${toRuntimeTypeString(type)}`,
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
NodeTypes,
|
||||
SimpleExpressionNode,
|
||||
createRoot,
|
||||
forAliasRE,
|
||||
parserOptions,
|
||||
transform,
|
||||
walkIdentifiers
|
||||
|
@ -50,12 +51,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
|||
if (!isBuiltInDirective(prop.name)) {
|
||||
code += `,v${capitalize(camelize(prop.name))}`
|
||||
}
|
||||
|
||||
// process dynamic directive arguments
|
||||
if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) {
|
||||
code += `,${processExp(
|
||||
(prop.arg as SimpleExpressionNode).content,
|
||||
prop.name
|
||||
code += `,${stripStrings(
|
||||
(prop.arg as SimpleExpressionNode).content
|
||||
)}`
|
||||
}
|
||||
|
||||
if (prop.exp) {
|
||||
code += `,${processExp(
|
||||
(prop.exp as SimpleExpressionNode).content,
|
||||
|
@ -85,8 +88,6 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
|||
return code
|
||||
}
|
||||
|
||||
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||
|
||||
function processExp(exp: string, dir?: string): string {
|
||||
if (/ as\s+\w|<.*>|:/.test(exp)) {
|
||||
if (dir === 'slot') {
|
||||
|
|
|
@ -55,7 +55,7 @@ export function processNormalScript(
|
|||
const s = new MagicString(content)
|
||||
rewriteDefaultAST(scriptAst.body, s, defaultVar)
|
||||
content = s.toString()
|
||||
if (cssVars.length) {
|
||||
if (cssVars.length && !ctx.options.templateOptions?.ssr) {
|
||||
content += genNormalScriptCssVarsCode(
|
||||
cssVars,
|
||||
bindings,
|
||||
|
|
|
@ -39,8 +39,9 @@ import { parse as babelParse } from '@babel/parser'
|
|||
import { parse } from '../parse'
|
||||
import { createCache } from '../cache'
|
||||
import type TS from 'typescript'
|
||||
import { extname, dirname } from 'path'
|
||||
import { extname, dirname, join } from 'path'
|
||||
import { minimatch as isMatch } from 'minimatch'
|
||||
import * as process from 'process'
|
||||
|
||||
/**
|
||||
* TypeResolveContext is compatible with ScriptCompileContext
|
||||
|
@ -117,7 +118,8 @@ interface ResolvedElements {
|
|||
export function resolveTypeElements(
|
||||
ctx: TypeResolveContext,
|
||||
node: Node & MaybeWithScope & { _resolvedElements?: ResolvedElements },
|
||||
scope?: TypeScope
|
||||
scope?: TypeScope,
|
||||
typeParameters?: Record<string, Node>
|
||||
): ResolvedElements {
|
||||
if (node._resolvedElements) {
|
||||
return node._resolvedElements
|
||||
|
@ -125,30 +127,37 @@ export function resolveTypeElements(
|
|||
return (node._resolvedElements = innerResolveTypeElements(
|
||||
ctx,
|
||||
node,
|
||||
node._ownerScope || scope || ctxToScope(ctx)
|
||||
node._ownerScope || scope || ctxToScope(ctx),
|
||||
typeParameters
|
||||
))
|
||||
}
|
||||
|
||||
function innerResolveTypeElements(
|
||||
ctx: TypeResolveContext,
|
||||
node: Node,
|
||||
scope: TypeScope
|
||||
scope: TypeScope,
|
||||
typeParameters?: Record<string, Node>
|
||||
): ResolvedElements {
|
||||
switch (node.type) {
|
||||
case 'TSTypeLiteral':
|
||||
return typeElementsToMap(ctx, node.members, scope)
|
||||
return typeElementsToMap(ctx, node.members, scope, typeParameters)
|
||||
case 'TSInterfaceDeclaration':
|
||||
return resolveInterfaceMembers(ctx, node, scope)
|
||||
return resolveInterfaceMembers(ctx, node, scope, typeParameters)
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TSParenthesizedType':
|
||||
return resolveTypeElements(ctx, node.typeAnnotation, scope)
|
||||
return resolveTypeElements(
|
||||
ctx,
|
||||
node.typeAnnotation,
|
||||
scope,
|
||||
typeParameters
|
||||
)
|
||||
case 'TSFunctionType': {
|
||||
return { props: {}, calls: [node] }
|
||||
}
|
||||
case 'TSUnionType':
|
||||
case 'TSIntersectionType':
|
||||
return mergeElements(
|
||||
node.types.map(t => resolveTypeElements(ctx, t, scope)),
|
||||
node.types.map(t => resolveTypeElements(ctx, t, scope, typeParameters)),
|
||||
node.type
|
||||
)
|
||||
case 'TSMappedType':
|
||||
|
@ -170,20 +179,57 @@ function innerResolveTypeElements(
|
|||
scope.imports[typeName]?.source === 'vue'
|
||||
) {
|
||||
return resolveExtractPropTypes(
|
||||
resolveTypeElements(ctx, node.typeParameters.params[0], scope),
|
||||
resolveTypeElements(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
typeParameters
|
||||
),
|
||||
scope
|
||||
)
|
||||
}
|
||||
const resolved = resolveTypeReference(ctx, node, scope)
|
||||
if (resolved) {
|
||||
return resolveTypeElements(ctx, resolved, resolved._ownerScope)
|
||||
const typeParams: Record<string, Node> = Object.create(null)
|
||||
if (
|
||||
(resolved.type === 'TSTypeAliasDeclaration' ||
|
||||
resolved.type === 'TSInterfaceDeclaration') &&
|
||||
resolved.typeParameters &&
|
||||
node.typeParameters
|
||||
) {
|
||||
resolved.typeParameters.params.forEach((p, i) => {
|
||||
let param = typeParameters && typeParameters[p.name]
|
||||
if (!param) param = node.typeParameters!.params[i]
|
||||
typeParams[p.name] = param
|
||||
})
|
||||
}
|
||||
return resolveTypeElements(
|
||||
ctx,
|
||||
resolved,
|
||||
resolved._ownerScope,
|
||||
typeParams
|
||||
)
|
||||
} else {
|
||||
if (typeof typeName === 'string') {
|
||||
if (typeParameters && typeParameters[typeName]) {
|
||||
return resolveTypeElements(
|
||||
ctx,
|
||||
typeParameters[typeName],
|
||||
scope,
|
||||
typeParameters
|
||||
)
|
||||
}
|
||||
if (
|
||||
// @ts-ignore
|
||||
SupportedBuiltinsSet.has(typeName)
|
||||
) {
|
||||
return resolveBuiltin(ctx, node, typeName as any, scope)
|
||||
return resolveBuiltin(
|
||||
ctx,
|
||||
node,
|
||||
typeName as any,
|
||||
scope,
|
||||
typeParameters
|
||||
)
|
||||
} else if (typeName === 'ReturnType' && node.typeParameters) {
|
||||
// limited support, only reference types
|
||||
const ret = resolveReturnType(
|
||||
|
@ -242,11 +288,17 @@ function innerResolveTypeElements(
|
|||
function typeElementsToMap(
|
||||
ctx: TypeResolveContext,
|
||||
elements: TSTypeElement[],
|
||||
scope = ctxToScope(ctx)
|
||||
scope = ctxToScope(ctx),
|
||||
typeParameters?: Record<string, Node>
|
||||
): ResolvedElements {
|
||||
const res: ResolvedElements = { props: {} }
|
||||
for (const e of elements) {
|
||||
if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
|
||||
// capture generic parameters on node's scope
|
||||
if (typeParameters) {
|
||||
scope = createChildScope(scope)
|
||||
Object.assign(scope.types, typeParameters)
|
||||
}
|
||||
;(e as MaybeWithScope)._ownerScope = scope
|
||||
const name = getId(e.key)
|
||||
if (name && !e.computed) {
|
||||
|
@ -322,9 +374,15 @@ function createProperty(
|
|||
function resolveInterfaceMembers(
|
||||
ctx: TypeResolveContext,
|
||||
node: TSInterfaceDeclaration & MaybeWithScope,
|
||||
scope: TypeScope
|
||||
scope: TypeScope,
|
||||
typeParameters?: Record<string, Node>
|
||||
): ResolvedElements {
|
||||
const base = typeElementsToMap(ctx, node.body.body, node._ownerScope)
|
||||
const base = typeElementsToMap(
|
||||
ctx,
|
||||
node.body.body,
|
||||
node._ownerScope,
|
||||
typeParameters
|
||||
)
|
||||
if (node.extends) {
|
||||
for (const ext of node.extends) {
|
||||
if (
|
||||
|
@ -334,12 +392,15 @@ function resolveInterfaceMembers(
|
|||
continue
|
||||
}
|
||||
try {
|
||||
const { props } = resolveTypeElements(ctx, ext, scope)
|
||||
const { props, calls } = resolveTypeElements(ctx, ext, scope)
|
||||
for (const key in props) {
|
||||
if (!hasOwn(base.props, key)) {
|
||||
base.props[key] = props[key]
|
||||
}
|
||||
}
|
||||
if (calls) {
|
||||
;(base.calls || (base.calls = [])).push(...calls)
|
||||
}
|
||||
} catch (e) {
|
||||
ctx.error(
|
||||
`Failed to resolve extends base type.\nIf this previously worked in 3.2, ` +
|
||||
|
@ -539,9 +600,15 @@ function resolveBuiltin(
|
|||
ctx: TypeResolveContext,
|
||||
node: TSTypeReference | TSExpressionWithTypeArguments,
|
||||
name: GetSetType<typeof SupportedBuiltinsSet>,
|
||||
scope: TypeScope
|
||||
scope: TypeScope,
|
||||
typeParameters?: Record<string, Node>
|
||||
): ResolvedElements {
|
||||
const t = resolveTypeElements(ctx, node.typeParameters!.params[0], scope)
|
||||
const t = resolveTypeElements(
|
||||
ctx,
|
||||
node.typeParameters!.params[0],
|
||||
scope,
|
||||
typeParameters
|
||||
)
|
||||
switch (name) {
|
||||
case 'Partial': {
|
||||
const res: ResolvedElements = { props: {}, calls: t.calls }
|
||||
|
@ -632,8 +699,8 @@ function innerResolveTypeReference(
|
|||
? scope.exportedDeclares
|
||||
: scope.declares
|
||||
: onlyExported
|
||||
? scope.exportedTypes
|
||||
: scope.types
|
||||
? scope.exportedTypes
|
||||
: scope.types
|
||||
if (lookupSource[name]) {
|
||||
return lookupSource[name]
|
||||
} else {
|
||||
|
@ -691,11 +758,11 @@ function getReferenceName(node: ReferenceTypes): string | string[] {
|
|||
? node.typeName
|
||||
: node.type === 'TSExpressionWithTypeArguments'
|
||||
? node.expression
|
||||
: node.type === 'TSImportType'
|
||||
? node.qualifier
|
||||
: node.type === 'TSTypeQuery'
|
||||
? node.exprName
|
||||
: node
|
||||
: node.type === 'TSImportType'
|
||||
? node.qualifier
|
||||
: node.type === 'TSTypeQuery'
|
||||
? node.exprName
|
||||
: node
|
||||
if (ref?.type === 'Identifier') {
|
||||
return ref.name
|
||||
} else if (ref?.type === 'TSQualifiedName') {
|
||||
|
@ -732,7 +799,25 @@ let loadTS: (() => typeof TS) | undefined
|
|||
* @private
|
||||
*/
|
||||
export function registerTS(_loadTS: () => typeof TS) {
|
||||
loadTS = _loadTS
|
||||
loadTS = () => {
|
||||
try {
|
||||
return _loadTS()
|
||||
} catch (err: any) {
|
||||
if (
|
||||
typeof err.message === 'string' &&
|
||||
err.message.includes('Cannot find module')
|
||||
) {
|
||||
throw new Error(
|
||||
'Failed to load TypeScript, which is required for resolving imported types. ' +
|
||||
'Please make sure "typescript" is installed as a project dependency.'
|
||||
)
|
||||
} else {
|
||||
throw new Error(
|
||||
'Failed to load TypeScript for resolving imported types.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FS = NonNullable<SFCScriptCompileOptions['fs']>
|
||||
|
@ -781,7 +866,12 @@ function importSourceToScope(
|
|||
scope: TypeScope,
|
||||
source: string
|
||||
): TypeScope {
|
||||
const fs = resolveFS(ctx)
|
||||
let fs: FS | undefined
|
||||
try {
|
||||
fs = resolveFS(ctx)
|
||||
} catch (err: any) {
|
||||
return ctx.error(err.message, node, scope)
|
||||
}
|
||||
if (!fs) {
|
||||
return ctx.error(
|
||||
`No fs option provided to \`compileScript\` in non-Node environment. ` +
|
||||
|
@ -793,9 +883,14 @@ function importSourceToScope(
|
|||
|
||||
let resolved: string | undefined = scope.resolvedImportSources[source]
|
||||
if (!resolved) {
|
||||
if (source.startsWith('.')) {
|
||||
if (source.startsWith('..')) {
|
||||
const osSpecificJoinFn = process.platform === 'win32' ? join : joinPaths
|
||||
|
||||
const filename = osSpecificJoinFn(dirname(scope.filename), source)
|
||||
resolved = resolveExt(filename, fs)
|
||||
} else if (source.startsWith('.')) {
|
||||
// relative import - fast path
|
||||
const filename = joinPaths(scope.filename, '..', source)
|
||||
const filename = joinPaths(dirname(scope.filename), source)
|
||||
resolved = resolveExt(filename, fs)
|
||||
} else {
|
||||
// module or aliased import - use full TS resolution, only supported in Node
|
||||
|
@ -1064,8 +1159,8 @@ function ctxToScope(ctx: TypeResolveContext): TypeScope {
|
|||
'ast' in ctx
|
||||
? ctx.ast
|
||||
: ctx.scriptAst
|
||||
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
|
||||
: ctx.scriptSetupAst!.body
|
||||
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
|
||||
: ctx.scriptSetupAst!.body
|
||||
|
||||
const scope = new TypeScope(
|
||||
ctx.filename,
|
||||
|
@ -1088,14 +1183,7 @@ function moduleDeclToScope(
|
|||
return node._resolvedChildScope
|
||||
}
|
||||
|
||||
const scope = new TypeScope(
|
||||
parentScope.filename,
|
||||
parentScope.source,
|
||||
parentScope.offset,
|
||||
Object.create(parentScope.imports),
|
||||
Object.create(parentScope.types),
|
||||
Object.create(parentScope.declares)
|
||||
)
|
||||
const scope = createChildScope(parentScope)
|
||||
|
||||
if (node.body.type === 'TSModuleDeclaration') {
|
||||
const decl = node.body as TSModuleDeclaration & WithScope
|
||||
|
@ -1109,6 +1197,17 @@ function moduleDeclToScope(
|
|||
return (node._resolvedChildScope = scope)
|
||||
}
|
||||
|
||||
function createChildScope(parentScope: TypeScope) {
|
||||
return new TypeScope(
|
||||
parentScope.filename,
|
||||
parentScope.source,
|
||||
parentScope.offset,
|
||||
Object.create(parentScope.imports),
|
||||
Object.create(parentScope.types),
|
||||
Object.create(parentScope.declares)
|
||||
)
|
||||
}
|
||||
|
||||
const importExportRE = /^Import|^Export/
|
||||
|
||||
function recordTypes(
|
||||
|
@ -1247,7 +1346,7 @@ function recordType(
|
|||
if (overwriteId || node.id) types[overwriteId || getId(node.id!)] = node
|
||||
break
|
||||
case 'TSTypeAliasDeclaration':
|
||||
types[node.id.name] = node.typeAnnotation
|
||||
types[node.id.name] = node.typeParameters ? node : node.typeAnnotation
|
||||
break
|
||||
case 'TSDeclareFunction':
|
||||
if (node.id) declares[node.id.name] = node
|
||||
|
@ -1408,6 +1507,7 @@ export function inferRuntimeType(
|
|||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
return [node.typeName.name]
|
||||
|
||||
// TS built-in utility types
|
||||
|
|
|
@ -76,8 +76,8 @@ export function getId(node: Expression) {
|
|||
return node.type === 'Identifier'
|
||||
? node.name
|
||||
: node.type === 'StringLiteral'
|
||||
? node.value
|
||||
: null
|
||||
? node.value
|
||||
: null
|
||||
}
|
||||
|
||||
const identity = (str: string) => str
|
||||
|
@ -113,8 +113,16 @@ export const joinPaths = (path.posix || path).join
|
|||
* key may contain symbols
|
||||
* e.g. onUpdate:modelValue -> "onUpdate:modelValue"
|
||||
*/
|
||||
export const escapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
|
||||
export const propNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~\-]/
|
||||
|
||||
export function getEscapedKey(key: string) {
|
||||
return escapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
||||
export function getEscapedPropName(key: string) {
|
||||
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
||||
}
|
||||
|
||||
export const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
|
||||
|
||||
export function getEscapedCssVarName(key: string, doubleEscape: boolean) {
|
||||
return key.replace(cssVarNameEscapeSymbolsRE, s =>
|
||||
doubleEscape ? `\\\\${s}` : `\\${s}`
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
BindingMetadata
|
||||
} from '@vue/compiler-dom'
|
||||
import { SFCDescriptor } from '../parse'
|
||||
import { escapeSymbolsRE } from '../script/utils'
|
||||
import { getEscapedCssVarName } from '../script/utils'
|
||||
import { PluginCreator } from 'postcss'
|
||||
import hash from 'hash-sum'
|
||||
|
||||
|
@ -22,17 +22,25 @@ export function genCssVarsFromList(
|
|||
): string {
|
||||
return `{\n ${vars
|
||||
.map(
|
||||
key => `"${isSSR ? `--` : ``}${genVarName(id, key, isProd)}": (${key})`
|
||||
key =>
|
||||
`"${isSSR ? `--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`
|
||||
)
|
||||
.join(',\n ')}\n}`
|
||||
}
|
||||
|
||||
function genVarName(id: string, raw: string, isProd: boolean): string {
|
||||
function genVarName(
|
||||
id: string,
|
||||
raw: string,
|
||||
isProd: boolean,
|
||||
isSSR = false
|
||||
): string {
|
||||
if (isProd) {
|
||||
return hash(id + raw)
|
||||
} else {
|
||||
// escape ASCII Punctuation & Symbols
|
||||
return `${id}-${raw.replace(escapeSymbolsRE, s => `\\${s}`)}`
|
||||
// #7823 need to double-escape in SSR because the attributes are rendered
|
||||
// into an HTML string
|
||||
return `${id}-${getEscapedCssVarName(raw, isSSR)}`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -173,6 +173,11 @@ function rewriteSelector(
|
|||
if (n.type !== 'pseudo' && n.type !== 'combinator') {
|
||||
node = n
|
||||
}
|
||||
|
||||
if (n.type === 'pseudo' && (n.value === ':is' || n.value === ':where')) {
|
||||
rewriteSelector(id, n.nodes[0], selectorRoot, slotted)
|
||||
shouldInject = false
|
||||
}
|
||||
})
|
||||
|
||||
if (node) {
|
||||
|
|
|
@ -181,11 +181,14 @@ describe('ssr: components', () => {
|
|||
})
|
||||
|
||||
test('v-for slot', () => {
|
||||
expect(
|
||||
compile(`<foo>
|
||||
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
|
||||
</foo>`).code
|
||||
).toMatchInlineSnapshot(`
|
||||
const { code } = compile(`<foo>
|
||||
<template v-for="(key, index) in names" v-slot:[key]="{ msg }">{{ msg + key + index + bar }}</template>
|
||||
</foo>`)
|
||||
expect(code).not.toMatch(`_ctx.msg`)
|
||||
expect(code).not.toMatch(`_ctx.key`)
|
||||
expect(code).not.toMatch(`_ctx.index`)
|
||||
expect(code).toMatch(`_ctx.bar`)
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"vue/server-renderer\\")
|
||||
|
||||
|
@ -193,15 +196,15 @@ describe('ssr: components', () => {
|
|||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [
|
||||
_renderList(_ctx.names, (key) => {
|
||||
_renderList(_ctx.names, (key, index) => {
|
||||
return {
|
||||
name: key,
|
||||
fn: _withCtx(({ msg }, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
|
||||
_push(\`\${_ssrInterpolate(msg + key + index + _ctx.bar)}\`)
|
||||
} else {
|
||||
return [
|
||||
_createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar), 1 /* TEXT */)
|
||||
_createTextVNode(_toDisplayString(msg + key + index + _ctx.bar), 1 /* TEXT */)
|
||||
]
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { compile } from '../src'
|
||||
|
||||
describe('transition', () => {
|
||||
test('basic', () => {
|
||||
expect(compile(`<transition><div>foo</div></transition>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}>foo</div>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('with appear', () => {
|
||||
expect(compile(`<transition appear><div>foo</div></transition>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<template><div\${_ssrRenderAttrs(_attrs)}>foo</div></template>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
|
@ -69,6 +69,57 @@ describe('ssr: v-model', () => {
|
|||
}></option></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileWithWrapper(`<select multiple v-model="model"><slot/></select>`)
|
||||
.code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderSlot: _ssrRenderSlot, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple>\`)
|
||||
_ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent)
|
||||
_push(\`</select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileWithWrapper(`
|
||||
<select multiple v-model="model">
|
||||
<optgroup label="foo">
|
||||
<option value="bar">bar</option>
|
||||
</optgroup>
|
||||
</select>`).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${
|
||||
_ssrRenderAttrs(_attrs)
|
||||
}><select multiple><optgroup label=\\"foo\\"><option value=\\"bar\\"\${
|
||||
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||
? _ssrLooseContain(_ctx.model, \\"bar\\")
|
||||
: _ssrLooseEqual(_ctx.model, \\"bar\\"))) ? \\" selected\\" : \\"\\"
|
||||
}>bar</option></optgroup></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileWithWrapper(`
|
||||
<select multiple v-model="model">
|
||||
<optgroup label="foo">
|
||||
<slot/>
|
||||
</optgroup>
|
||||
</select>`).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderSlot: _ssrRenderSlot, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup label=\\"foo\\">\`)
|
||||
_ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent)
|
||||
_push(\`</optgroup></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('<input type="radio">', () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.3.5",
|
||||
"version": "3.3.9",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
@ -28,7 +28,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.3.5",
|
||||
"@vue/compiler-dom": "3.3.5"
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ export function compile(
|
|||
// reusing core v-bind
|
||||
bind: transformBind,
|
||||
on: transformOn,
|
||||
// model and show has dedicated SSR handling
|
||||
// model and show have dedicated SSR handling
|
||||
model: ssrTransformModel,
|
||||
show: ssrTransformShow,
|
||||
// the following are ignored during SSR
|
||||
|
|
|
@ -56,6 +56,10 @@ import {
|
|||
} from './ssrTransformTransitionGroup'
|
||||
import { isSymbol, isObject, isArray } from '@vue/shared'
|
||||
import { buildSSRProps } from './ssrTransformElement'
|
||||
import {
|
||||
ssrProcessTransition,
|
||||
ssrTransformTransition
|
||||
} from './ssrTransformTransition'
|
||||
|
||||
// We need to construct the slot functions in the 1st pass to ensure proper
|
||||
// scope tracking, but the children of each slot cannot be processed until
|
||||
|
@ -99,9 +103,10 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||
if (isSymbol(component)) {
|
||||
if (component === SUSPENSE) {
|
||||
return ssrTransformSuspense(node, context)
|
||||
}
|
||||
if (component === TRANSITION_GROUP) {
|
||||
} else if (component === TRANSITION_GROUP) {
|
||||
return ssrTransformTransitionGroup(node, context)
|
||||
} else if (component === TRANSITION) {
|
||||
return ssrTransformTransition(node, context)
|
||||
}
|
||||
return // other built-in components: fallthrough
|
||||
}
|
||||
|
@ -120,8 +125,10 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||
// fallback in case the child is render-fn based). Store them in an array
|
||||
// for later use.
|
||||
if (clonedNode.children.length) {
|
||||
buildSlots(clonedNode, context, (props, children) => {
|
||||
vnodeBranches.push(createVNodeSlotBranch(props, children, context))
|
||||
buildSlots(clonedNode, context, (props, vFor, children) => {
|
||||
vnodeBranches.push(
|
||||
createVNodeSlotBranch(props, vFor, children, context)
|
||||
)
|
||||
return createFunctionExpression(undefined)
|
||||
})
|
||||
}
|
||||
|
@ -145,7 +152,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||
const wipEntries: WIPSlotEntry[] = []
|
||||
wipMap.set(node, wipEntries)
|
||||
|
||||
const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => {
|
||||
const buildSSRSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) => {
|
||||
const param0 = (props && stringifyExpression(props)) || `_`
|
||||
const fn = createFunctionExpression(
|
||||
[param0, `_push`, `_parent`, `_scopeId`],
|
||||
|
@ -216,9 +223,8 @@ export function ssrProcessComponent(
|
|||
if ((parent as WIPSlotEntry).type === WIP_SLOT) {
|
||||
context.pushStringPart(``)
|
||||
}
|
||||
// #5351: filter out comment children inside transition
|
||||
if (component === TRANSITION) {
|
||||
node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
|
||||
return ssrProcessTransition(node, context)
|
||||
}
|
||||
processChildren(node, context)
|
||||
}
|
||||
|
@ -273,6 +279,7 @@ const vnodeDirectiveTransforms = {
|
|||
|
||||
function createVNodeSlotBranch(
|
||||
props: ExpressionNode | undefined,
|
||||
vForExp: ExpressionNode | undefined,
|
||||
children: TemplateChildNode[],
|
||||
parentContext: TransformContext
|
||||
): ReturnStatement {
|
||||
|
@ -299,8 +306,8 @@ function createVNodeSlotBranch(
|
|||
tag: 'template',
|
||||
tagType: ElementTypes.TEMPLATE,
|
||||
isSelfClosing: false,
|
||||
// important: provide v-slot="props" on the wrapper for proper
|
||||
// scope analysis
|
||||
// important: provide v-slot="props" and v-for="exp" on the wrapper for
|
||||
// proper scope analysis
|
||||
props: [
|
||||
{
|
||||
type: NodeTypes.DIRECTIVE,
|
||||
|
@ -309,6 +316,14 @@ function createVNodeSlotBranch(
|
|||
arg: undefined,
|
||||
modifiers: [],
|
||||
loc: locStub
|
||||
},
|
||||
{
|
||||
type: NodeTypes.DIRECTIVE,
|
||||
name: 'for',
|
||||
exp: vForExp,
|
||||
arg: undefined,
|
||||
modifiers: [],
|
||||
loc: locStub
|
||||
}
|
||||
],
|
||||
children,
|
||||
|
|
|
@ -36,20 +36,24 @@ export function ssrTransformSuspense(
|
|||
wipSlots: []
|
||||
}
|
||||
wipMap.set(node, wipEntry)
|
||||
wipEntry.slotsExp = buildSlots(node, context, (_props, children, loc) => {
|
||||
const fn = createFunctionExpression(
|
||||
[],
|
||||
undefined, // no return, assign body later
|
||||
true, // newline
|
||||
false, // suspense slots are not treated as normal slots
|
||||
loc
|
||||
)
|
||||
wipEntry.wipSlots.push({
|
||||
fn,
|
||||
children
|
||||
})
|
||||
return fn
|
||||
}).slots
|
||||
wipEntry.slotsExp = buildSlots(
|
||||
node,
|
||||
context,
|
||||
(_props, _vForExp, children, loc) => {
|
||||
const fn = createFunctionExpression(
|
||||
[],
|
||||
undefined, // no return, assign body later
|
||||
true, // newline
|
||||
false, // suspense slots are not treated as normal slots
|
||||
loc
|
||||
)
|
||||
wipEntry.wipSlots.push({
|
||||
fn,
|
||||
children
|
||||
})
|
||||
return fn
|
||||
}
|
||||
).slots
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
ComponentNode,
|
||||
findProp,
|
||||
NodeTypes,
|
||||
TransformContext
|
||||
} from '@vue/compiler-dom'
|
||||
import { processChildren, SSRTransformContext } from '../ssrCodegenTransform'
|
||||
|
||||
const wipMap = new WeakMap<ComponentNode, Boolean>()
|
||||
|
||||
export function ssrTransformTransition(
|
||||
node: ComponentNode,
|
||||
context: TransformContext
|
||||
) {
|
||||
return () => {
|
||||
const appear = findProp(node, 'appear', false, true)
|
||||
wipMap.set(node, !!appear)
|
||||
}
|
||||
}
|
||||
|
||||
export function ssrProcessTransition(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext
|
||||
) {
|
||||
// #5351: filter out comment children inside transition
|
||||
node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
|
||||
|
||||
const appear = wipMap.get(node)
|
||||
if (appear) {
|
||||
context.pushStringPart(`<template>`)
|
||||
processChildren(node, context, false, true)
|
||||
context.pushStringPart(`</template>`)
|
||||
} else {
|
||||
processChildren(node, context, false, true)
|
||||
}
|
||||
}
|
|
@ -38,6 +38,38 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||
}
|
||||
}
|
||||
|
||||
function processOption(plainNode: PlainElementNode) {
|
||||
if (plainNode.tag === 'option') {
|
||||
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
|
||||
const value = findValueBinding(plainNode)
|
||||
plainNode.ssrCodegenNode!.elements.push(
|
||||
createConditionalExpression(
|
||||
createCallExpression(context.helper(SSR_INCLUDE_BOOLEAN_ATTR), [
|
||||
createConditionalExpression(
|
||||
createCallExpression(`Array.isArray`, [model]),
|
||||
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
|
||||
model,
|
||||
value
|
||||
]),
|
||||
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
|
||||
model,
|
||||
value
|
||||
])
|
||||
)
|
||||
]),
|
||||
createSimpleExpression(' selected', true),
|
||||
createSimpleExpression('', true),
|
||||
false /* no newline */
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if (plainNode.tag === 'optgroup') {
|
||||
plainNode.children.forEach(option =>
|
||||
processOption(option as PlainElementNode)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (node.tagType === ElementTypes.ELEMENT) {
|
||||
const res: DirectiveTransformResult = { props: [] }
|
||||
const defaultProps = [
|
||||
|
@ -130,32 +162,9 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||
checkDuplicatedValue()
|
||||
node.children = [createInterpolation(model, model.loc)]
|
||||
} else if (node.tag === 'select') {
|
||||
node.children.forEach(option => {
|
||||
if (option.type === NodeTypes.ELEMENT) {
|
||||
const plainNode = option as PlainElementNode
|
||||
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
|
||||
const value = findValueBinding(plainNode)
|
||||
plainNode.ssrCodegenNode!.elements.push(
|
||||
createConditionalExpression(
|
||||
createCallExpression(context.helper(SSR_INCLUDE_BOOLEAN_ATTR), [
|
||||
createConditionalExpression(
|
||||
createCallExpression(`Array.isArray`, [model]),
|
||||
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
|
||||
model,
|
||||
value
|
||||
]),
|
||||
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
|
||||
model,
|
||||
value
|
||||
])
|
||||
)
|
||||
]),
|
||||
createSimpleExpression(' selected', true),
|
||||
createSimpleExpression('', true),
|
||||
false /* no newline */
|
||||
)
|
||||
)
|
||||
}
|
||||
node.children.forEach(child => {
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
processOption(child as PlainElementNode)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# dts built-package test
|
||||
|
||||
This package is private and for testing only. It is used to verify edge cases for external libraries that build their types using Vue core types - e.g. Vuetify as in [#8376](https://github.com/vuejs/core/issues/8376).
|
||||
|
||||
When running the `build-dts` task, this package's types are built alongside other packages. Then, during `test-dts-only` it is imported and used in [`packages/dts-test/built.test-d.ts`](https://github.com/vuejs/core/blob/main/packages/dts-test/built.test-d.ts) to verify that the built types work correctly.
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "@vue/dts-built-test",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"types": "dist/dts-built-test.d.ts",
|
||||
"dependencies": {
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/reactivity": "workspace:*",
|
||||
"vue": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { defineComponent } from 'vue'
|
||||
|
||||
const _CustomPropsNotErased = defineComponent({
|
||||
props: {},
|
||||
setup() {}
|
||||
})
|
||||
|
||||
// #8376
|
||||
export const CustomPropsNotErased =
|
||||
_CustomPropsNotErased as typeof _CustomPropsNotErased & {
|
||||
foo: string
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { createApp, App, Plugin } from 'vue'
|
||||
import { createApp, App, Plugin, defineComponent } from 'vue'
|
||||
|
||||
const app = createApp({})
|
||||
|
||||
|
@ -93,3 +93,15 @@ const PluginTyped: Plugin<PluginOptions> = (app, options) => {}
|
|||
// @ts-expect-error: needs options
|
||||
app.use(PluginTyped)
|
||||
app.use(PluginTyped, { option2: 2, option3: true })
|
||||
|
||||
// vuetify usage
|
||||
const key: string = ''
|
||||
const aliases: Record<string, any> = {}
|
||||
app.component(
|
||||
key,
|
||||
defineComponent({
|
||||
...aliases[key],
|
||||
name: key,
|
||||
aliasName: aliases[key].name
|
||||
})
|
||||
)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { CustomPropsNotErased } from '@vue/dts-built-test'
|
||||
import { expectType, describe } from './utils'
|
||||
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProps {
|
||||
custom?: number
|
||||
}
|
||||
}
|
||||
|
||||
// #8376 - custom props should not be erased
|
||||
describe('Custom Props not erased', () => {
|
||||
expectType<number | undefined>(new CustomPropsNotErased().$props.custom)
|
||||
})
|
|
@ -11,7 +11,9 @@ import {
|
|||
h,
|
||||
SlotsType,
|
||||
Slots,
|
||||
VNode
|
||||
VNode,
|
||||
withKeys,
|
||||
withModifiers
|
||||
} from 'vue'
|
||||
import { describe, expectType, IsUnion } from './utils'
|
||||
|
||||
|
@ -1472,6 +1474,37 @@ describe('slots', () => {
|
|||
expectType<Slots | undefined>(new comp2().$slots)
|
||||
})
|
||||
|
||||
// #5885
|
||||
describe('should work when props type is incompatible with setup returned type ', () => {
|
||||
type SizeType = 'small' | 'big'
|
||||
const Comp = defineComponent({
|
||||
props: {
|
||||
size: {
|
||||
type: String as PropType<SizeType>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
expectType<SizeType>(props.size)
|
||||
return {
|
||||
size: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
type CompInstance = InstanceType<typeof Comp>
|
||||
|
||||
const CompA = {} as CompInstance
|
||||
expectType<ComponentPublicInstance>(CompA)
|
||||
expectType<number>(CompA.size)
|
||||
expectType<SizeType>(CompA.$props.size)
|
||||
})
|
||||
|
||||
describe('withKeys and withModifiers as pro', () => {
|
||||
const onKeydown = withKeys(e => {}, [''])
|
||||
const onClick = withModifiers(e => {}, [''])
|
||||
;<input onKeydown={onKeydown} onClick={onClick} />
|
||||
})
|
||||
|
||||
import {
|
||||
DefineComponent,
|
||||
ComponentOptionsMixin,
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { defineCustomElement } from 'vue'
|
||||
import { expectType, describe } from './utils'
|
||||
import {
|
||||
defineCustomElement,
|
||||
defineComponent,
|
||||
type VueElementConstructor
|
||||
} from 'vue'
|
||||
import { expectType, describe, test } from './utils'
|
||||
|
||||
describe('inject', () => {
|
||||
// with object inject
|
||||
|
@ -62,3 +66,20 @@ describe('inject', () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('defineCustomElement using defineComponent return type', () => {
|
||||
test('with emits', () => {
|
||||
const Comp1Vue = defineComponent({
|
||||
props: {
|
||||
a: String
|
||||
},
|
||||
emits: {
|
||||
click: () => true
|
||||
}
|
||||
})
|
||||
const Comp = defineCustomElement(Comp1Vue)
|
||||
expectType<VueElementConstructor>(Comp)
|
||||
|
||||
expectType<string | undefined>(new Comp().a)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -45,7 +45,7 @@ Bar.emits = {
|
|||
Bar.emits = { baz: () => void 0 }
|
||||
|
||||
// TSX
|
||||
expectType<JSX.Element>(<Bar foo={1} />)
|
||||
expectType<JSX.Element>(<Bar foo={1} onUpdate={() => {}} />)
|
||||
// @ts-expect-error
|
||||
;<Foo />
|
||||
// @ts-expect-error
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
h,
|
||||
defineComponent,
|
||||
DefineComponent,
|
||||
ref,
|
||||
Fragment,
|
||||
Teleport,
|
||||
|
@ -231,3 +232,18 @@ describe('resolveComponent should work', () => {
|
|||
message: '1'
|
||||
})
|
||||
})
|
||||
|
||||
// #5431
|
||||
describe('h should work with multiple types', () => {
|
||||
const serializers = {
|
||||
Paragraph: 'p',
|
||||
Component: {} as Component,
|
||||
DefineComponent: {} as DefineComponent
|
||||
}
|
||||
|
||||
const sampleComponent = serializers['' as keyof typeof serializers]
|
||||
|
||||
h(sampleComponent)
|
||||
h(sampleComponent, {})
|
||||
h(sampleComponent, {}, [])
|
||||
})
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"name": "dts-test",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"vue": "workspace:*"
|
||||
},
|
||||
"version": "3.3.5"
|
||||
"vue": "workspace:*",
|
||||
"@vue/dts-built-test": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,3 +62,31 @@ describe('should unwrap tuple correctly', () => {
|
|||
const reactiveTuple = reactive(tuple)
|
||||
expectType<Ref<number>>(reactiveTuple[0])
|
||||
})
|
||||
|
||||
describe('should unwrap Map correctly', () => {
|
||||
const map = reactive(new Map<string, Ref<number>>())
|
||||
expectType<Ref<number>>(map.get('a')!)
|
||||
|
||||
const map2 = reactive(new Map<string, { wrap: Ref<number> }>())
|
||||
expectType<number>(map2.get('a')!.wrap)
|
||||
|
||||
const wm = reactive(new WeakMap<object, Ref<number>>())
|
||||
expectType<Ref<number>>(wm.get({})!)
|
||||
|
||||
const wm2 = reactive(new WeakMap<object, { wrap: Ref<number> }>())
|
||||
expectType<number>(wm2.get({})!.wrap)
|
||||
})
|
||||
|
||||
describe('should unwrap Set correctly', () => {
|
||||
const set = reactive(new Set<Ref<number>>())
|
||||
expectType<Set<Ref<number>>>(set)
|
||||
|
||||
const set2 = reactive(new Set<{ wrap: Ref<number> }>())
|
||||
expectType<Set<{ wrap: number }>>(set2)
|
||||
|
||||
const ws = reactive(new WeakSet<Ref<number>>())
|
||||
expectType<WeakSet<Ref<number>>>(ws)
|
||||
|
||||
const ws2 = reactive(new WeakSet<{ wrap: Ref<number> }>())
|
||||
expectType<WeakSet<{ wrap: number }>>(ws2)
|
||||
})
|
||||
|
|
|
@ -15,9 +15,10 @@ import {
|
|||
MaybeRef,
|
||||
MaybeRefOrGetter,
|
||||
ComputedRef,
|
||||
computed
|
||||
computed,
|
||||
ShallowRef
|
||||
} from 'vue'
|
||||
import { expectType, describe } from './utils'
|
||||
import { expectType, describe, IsUnion } from './utils'
|
||||
|
||||
function plainType(arg: number | Ref<number>) {
|
||||
// ref coercing
|
||||
|
@ -174,6 +175,27 @@ if (refStatus.value === 'initial') {
|
|||
refStatus.value = 'invalidating'
|
||||
}
|
||||
|
||||
{
|
||||
const shallow = shallowRef(1)
|
||||
expectType<Ref<number>>(shallow)
|
||||
expectType<ShallowRef<number>>(shallow)
|
||||
}
|
||||
|
||||
{
|
||||
//#7852
|
||||
type Steps = { step: '1' } | { step: '2' }
|
||||
const shallowUnionGenParam = shallowRef<Steps>({ step: '1' })
|
||||
const shallowUnionAsCast = shallowRef({ step: '1' } as Steps)
|
||||
|
||||
expectType<IsUnion<typeof shallowUnionGenParam>>(false)
|
||||
expectType<IsUnion<typeof shallowUnionAsCast>>(false)
|
||||
}
|
||||
|
||||
describe('shallowRef with generic', <T>() => {
|
||||
const r = ref({}) as MaybeRef<T>
|
||||
expectType<ShallowRef<T> | Ref<T>>(shallowRef(r))
|
||||
})
|
||||
|
||||
// proxyRefs: should return `reactive` directly
|
||||
const r1 = reactive({
|
||||
k: 'v'
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
defineSlots,
|
||||
VNode,
|
||||
Ref,
|
||||
defineModel
|
||||
defineModel,
|
||||
toRefs
|
||||
} from 'vue'
|
||||
import { describe, expectType } from './utils'
|
||||
import { defineComponent } from 'vue'
|
||||
|
@ -20,6 +21,7 @@ describe('defineProps w/ type declaration', () => {
|
|||
foo: string
|
||||
bool?: boolean
|
||||
boolAndUndefined: boolean | undefined
|
||||
file?: File | File[]
|
||||
}>()
|
||||
// explicitly declared type should be refined
|
||||
expectType<string>(props.foo)
|
||||
|
@ -108,6 +110,7 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends
|
|||
defineProps<{
|
||||
n?: number
|
||||
bool?: boolean
|
||||
s?: string
|
||||
|
||||
generic1?: T[] | { x: T }
|
||||
generic2?: { x: T }
|
||||
|
@ -126,6 +129,10 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends
|
|||
)
|
||||
|
||||
res.n + 1
|
||||
// @ts-expect-error should be readonly
|
||||
res.n++
|
||||
// @ts-expect-error should be readonly
|
||||
res.s = ''
|
||||
|
||||
expectType<T[] | { x: T }>(res.generic1)
|
||||
expectType<{ x: T }>(res.generic2)
|
||||
|
@ -328,3 +335,11 @@ describe('useSlots', () => {
|
|||
const slots = useSlots()
|
||||
expectType<Slots>(slots)
|
||||
})
|
||||
|
||||
// #6420
|
||||
describe('toRefs w/ type declaration', () => {
|
||||
const props = defineProps<{
|
||||
file?: File | File[]
|
||||
}>()
|
||||
expectType<Ref<File | File[] | undefined>>(toRefs(props).file)
|
||||
})
|
||||
|
|
|
@ -17,6 +17,59 @@ expectType<JSX.Element>(
|
|||
<div style={[{ color: 'red' }, [{ fontSize: '1em' }]]} />
|
||||
)
|
||||
|
||||
// allow undefined, string, object, array and nested array classes
|
||||
expectType<JSX.Element>(<div class={undefined} />)
|
||||
expectType<JSX.Element>(<div class={'foo'} />)
|
||||
expectType<JSX.Element>(<div class={['foo', undefined, 'bar']} />)
|
||||
expectType<JSX.Element>(<div class={[]} />)
|
||||
expectType<JSX.Element>(<div class={['foo', ['bar'], [['baz']]]} />)
|
||||
expectType<JSX.Element>(<div class={{ foo: true, bar: false, baz: true }} />)
|
||||
expectType<JSX.Element>(<div class={{}} />)
|
||||
expectType<JSX.Element>(
|
||||
<div class={['foo', ['bar'], { baz: true }, [{ qux: true }]]} />
|
||||
)
|
||||
expectType<JSX.Element>(
|
||||
<div
|
||||
class={[
|
||||
{ foo: false },
|
||||
{ bar: 0 },
|
||||
{ baz: -0 },
|
||||
{ qux: '' },
|
||||
{ quux: null },
|
||||
{ corge: undefined },
|
||||
{ grault: NaN }
|
||||
]}
|
||||
/>
|
||||
)
|
||||
expectType<JSX.Element>(
|
||||
<div
|
||||
class={[
|
||||
{ foo: true },
|
||||
{ bar: 'not-empty' },
|
||||
{ baz: 1 },
|
||||
{ qux: {} },
|
||||
{ quux: [] }
|
||||
]}
|
||||
/>
|
||||
)
|
||||
|
||||
// #7955
|
||||
expectType<JSX.Element>(<div style={[undefined, '', null, false]} />)
|
||||
|
||||
expectType<JSX.Element>(<div style={undefined} />)
|
||||
|
||||
expectType<JSX.Element>(<div style={null} />)
|
||||
|
||||
expectType<JSX.Element>(<div style={''} />)
|
||||
|
||||
expectType<JSX.Element>(<div style={false} />)
|
||||
|
||||
// @ts-expect-error
|
||||
;<div style={[0]} />
|
||||
|
||||
// @ts-expect-error
|
||||
;<div style={0} />
|
||||
|
||||
// @ts-expect-error unknown prop
|
||||
;<div foo="bar" />
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ref, computed, watch, defineComponent } from 'vue'
|
||||
import { ref, computed, watch, defineComponent, shallowRef } from 'vue'
|
||||
import { expectType } from './utils'
|
||||
|
||||
const source = ref('foo')
|
||||
|
@ -92,3 +92,17 @@ defineComponent({
|
|||
)
|
||||
}
|
||||
})
|
||||
|
||||
{
|
||||
//#7852
|
||||
type Steps = { step: '1' } | { step: '2' }
|
||||
const shallowUnionGenParam = shallowRef<Steps>({ step: '1' })
|
||||
const shallowUnionAsCast = shallowRef({ step: '1' } as Steps)
|
||||
|
||||
watch(shallowUnionGenParam, value => {
|
||||
expectType<Steps>(value)
|
||||
})
|
||||
watch(shallowUnionAsCast, value => {
|
||||
expectType<Steps>(value)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity-transform",
|
||||
"version": "3.3.5",
|
||||
"version": "3.3.9",
|
||||
"description": "@vue/reactivity-transform",
|
||||
"main": "dist/reactivity-transform.cjs.js",
|
||||
"files": [
|
||||
|
@ -28,14 +28,14 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/compiler-core": "3.3.5",
|
||||
"@vue/shared": "3.3.5",
|
||||
"@babel/parser": "^7.23.4",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/types": "^7.23.0"
|
||||
"@babel/core": "^7.23.3",
|
||||
"@babel/types": "^7.23.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -448,8 +448,8 @@ export function transformAST(
|
|||
const keyStr = isString(key)
|
||||
? `'${key}'`
|
||||
: key
|
||||
? snip(key)
|
||||
: `'${nameId.name}'`
|
||||
? snip(key)
|
||||
: `'${nameId.name}'`
|
||||
const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``
|
||||
s.appendLeft(
|
||||
call.end! + offset,
|
||||
|
|
|
@ -243,6 +243,22 @@ describe('reactivity/effect', () => {
|
|||
expect(dummy).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should support manipulating an array while observing symbol keyed properties', () => {
|
||||
const key = Symbol()
|
||||
let dummy
|
||||
const array: any = reactive([1, 2, 3])
|
||||
effect(() => (dummy = array[key]))
|
||||
|
||||
expect(dummy).toBe(undefined)
|
||||
array.pop()
|
||||
array.shift()
|
||||
array.splice(0, 1)
|
||||
expect(dummy).toBe(undefined)
|
||||
array[key] = 'value'
|
||||
array.length = 0
|
||||
expect(dummy).toBe('value')
|
||||
})
|
||||
|
||||
it('should observe function valued properties', () => {
|
||||
const oldFunc = () => {}
|
||||
const newFunc = () => {}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue