Merge branch 'main' into fix/8466
This commit is contained in:
commit
ff1fa17024
|
@ -74,12 +74,7 @@ module.exports = {
|
|||
},
|
||||
// Node scripts
|
||||
{
|
||||
files: [
|
||||
'scripts/**',
|
||||
'*.{js,ts}',
|
||||
'packages/**/index.js',
|
||||
'packages/size-check/**'
|
||||
],
|
||||
files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'],
|
||||
rules: {
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': 'off'
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature Request
|
||||
url: https://github.com/vuejs/rfcs/discussions
|
||||
about: Suggest new features for consideration
|
||||
- name: Discord Chat
|
||||
url: https://chat.vuejs.org
|
||||
about: Ask questions and discuss with other Vue users in real time.
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
name: "\U0001F680 New feature proposal"
|
||||
description: Suggest an idea for this project
|
||||
labels: [":sparkles: feature request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Before You Start...**
|
||||
|
||||
This form is only for submitting feature requests. If you have a usage question
|
||||
or are unsure if this is really a bug, make sure to:
|
||||
|
||||
- Read the [docs](https://vuejs.org/)
|
||||
- Ask on [Discord Chat](https://chat.vuejs.org/)
|
||||
- Ask on [GitHub Discussions](https://github.com/vuejs/core/discussions)
|
||||
- Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=vue.js)
|
||||
|
||||
Also try to search for your issue - another user may have already requested something similar!
|
||||
|
||||
- type: textarea
|
||||
id: problem-description
|
||||
attributes:
|
||||
label: What problem does this feature solve?
|
||||
description: |
|
||||
Explain your use case, context, and rationale behind this feature request. More importantly, what is the **end user experience** you are trying to build that led to the need for this feature?
|
||||
|
||||
An important design goal of Vue is keeping the API surface small and straightforward. In general, we only consider adding new features that solve a problem that cannot be easily dealt with using existing APIs (i.e. not just an alternative way of doing things that can already be done). The problem should also be common enough to justify the addition.
|
||||
placeholder: Problem description
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: proposed-API
|
||||
attributes:
|
||||
label: What does the proposed API look like?
|
||||
description: |
|
||||
Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format your code blocks.
|
||||
placeholder: Assumed API
|
||||
validations:
|
||||
required: true
|
|
@ -57,7 +57,7 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
|||
|
||||
## Development Setup
|
||||
|
||||
You will need [Node.js](https://nodejs.org) **version 16+**, and [PNPM](https://pnpm.io) **version 7+**.
|
||||
You will need [Node.js](https://nodejs.org) **version 18.12+**, and [PNPM](https://pnpm.io) **version 8+**.
|
||||
|
||||
We also recommend installing [ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier.
|
||||
|
||||
|
@ -185,7 +185,7 @@ Builds and watches `vue/dist/vue-runtime.esm-bundler.js` with all deps inlined u
|
|||
|
||||
### `nr dev-compiler`
|
||||
|
||||
The `dev-compiler` script builds, watches and serves the [Template Explorer](https://github.com/vuejs/core/tree/main/packages/template-explorer) at `http://localhost:5000`. This is useful when working on pure compiler issues.
|
||||
The `dev-compiler` script builds, watches and serves the [Template Explorer](https://github.com/vuejs/core/tree/main/packages/template-explorer) at `http://localhost:3000`. This is useful when working on pure compiler issues.
|
||||
|
||||
### `nr test`
|
||||
|
||||
|
@ -248,8 +248,6 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set
|
|||
|
||||
- `template-explorer`: A development tool for debugging compiler output, continuously deployed at https://template-explorer.vuejs.org/. To run it locally, run [`nr dev-compiler`](#nr-dev-compiler).
|
||||
|
||||
- `size-check`: Used for checking built bundle sizes on CI.
|
||||
|
||||
### Importing Packages
|
||||
|
||||
The packages can import each other directly using their package names. Note that when importing a package, the name listed in its `package.json` should be used. Most of the time the `@vue/` prefix is needed:
|
||||
|
@ -261,7 +259,7 @@ import { h } from '@vue/runtime-core'
|
|||
This is made possible via several configurations:
|
||||
|
||||
- For TypeScript, `compilerOptions.paths` in `tsconfig.json`
|
||||
- Vitest and Rollup share the sae set of aliases from `scripts/aliases.js`
|
||||
- Vitest and Rollup share the same set of aliases from `scripts/aliases.js`
|
||||
- For plain Node.js, they are linked using [PNPM Workspaces](https://pnpm.io/workspaces).
|
||||
|
||||
### Package Dependencies
|
||||
|
@ -330,4 +328,4 @@ Funds donated via Patreon go directly to support Evan You's full-time work on Vu
|
|||
|
||||
Thank you to all the people who have already contributed to Vue.js!
|
||||
|
||||
<a href="https://github.com/vuejs/vue/graphs/contributors"><img src="https://opencollective.com/vuejs/contributors.svg?width=890" /></a>
|
||||
<a href="https://github.com/vuejs/core/graphs/contributors"><img src="https://opencollective.com/vuejs/contributors.svg?width=890" /></a>
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: lockfile-only
|
||||
ignore:
|
||||
- dependency-name: "@types/node"
|
||||
versions:
|
||||
- 14.14.24
|
||||
- 14.14.37
|
||||
- dependency-name: "@babel/parser"
|
||||
versions:
|
||||
- 7.12.11
|
||||
- 7.12.13
|
||||
- 7.12.14
|
||||
- 7.12.15
|
||||
- 7.12.16
|
||||
- 7.12.17
|
||||
- 7.13.0
|
||||
- 7.13.10
|
||||
- 7.13.11
|
||||
- 7.13.13
|
||||
- 7.13.4
|
||||
- 7.13.9
|
||||
- dependency-name: eslint
|
||||
versions:
|
||||
- 7.23.0
|
||||
- dependency-name: postcss
|
||||
versions:
|
||||
- 8.2.4
|
||||
- 8.2.5
|
||||
- 8.2.7
|
||||
- 8.2.8
|
||||
- dependency-name: typescript
|
||||
versions:
|
||||
- 4.2.2
|
||||
- dependency-name: "@babel/types"
|
||||
versions:
|
||||
- 7.12.12
|
||||
- 7.12.13
|
||||
- 7.12.17
|
||||
- 7.13.0
|
||||
- dependency-name: pug-code-gen
|
||||
versions:
|
||||
- 2.0.3
|
||||
- dependency-name: estree-walker
|
||||
versions:
|
||||
- 2.0.2
|
||||
- dependency-name: "@typescript-eslint/parser"
|
||||
versions:
|
||||
- 4.14.2
|
||||
- 4.15.0
|
||||
- dependency-name: "@microsoft/api-extractor"
|
||||
versions:
|
||||
- 7.13.1
|
||||
- dependency-name: rollup
|
||||
versions:
|
||||
- 2.38.5
|
||||
- dependency-name: node-notifier
|
||||
versions:
|
||||
- 8.0.1
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: lockfile-only
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
||||
extends: ['config:base', 'schedule:weekly', 'group:allNonMajor'],
|
||||
labels: ['dependencies'],
|
||||
ignorePaths: ['**/__tests__/**'],
|
||||
rangeStrategy: 'bump',
|
||||
packageRules: [
|
||||
{
|
||||
depTypeList: ['peerDependencies'],
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
groupName: 'test',
|
||||
matchPackageNames: ['vitest', 'jsdom', 'puppeteer'],
|
||||
matchPackagePrefixes: ['@vitest']
|
||||
},
|
||||
{
|
||||
groupName: 'playground',
|
||||
matchFileNames: [
|
||||
'packages/sfc-playground/package.json',
|
||||
'packages/template-explorer/package.json'
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: 'compiler',
|
||||
matchPackageNames: ['magic-string'],
|
||||
matchPackagePrefixes: ['@babel', 'postcss']
|
||||
},
|
||||
{
|
||||
groupName: 'build',
|
||||
matchPackageNames: ['vite', 'terser'],
|
||||
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs']
|
||||
},
|
||||
{
|
||||
groupName: 'lint',
|
||||
matchPackageNames: ['simple-git-hooks', 'lint-staged'],
|
||||
matchPackagePrefixes: ['@typescript-eslint', 'eslint', 'prettier']
|
||||
}
|
||||
],
|
||||
ignoreDeps: [
|
||||
'vue',
|
||||
|
||||
// manually bumping
|
||||
'node',
|
||||
'typescript',
|
||||
|
||||
// ESM only
|
||||
'estree-walker'
|
||||
]
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
name: autofix.ci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- name: Run eslint
|
||||
run: pnpm run lint --fix
|
||||
|
||||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@d3e591514b99d0fca6779455ff8338516663f7cc
|
|
@ -0,0 +1,33 @@
|
|||
name: canary minor release
|
||||
on:
|
||||
# Runs every Monday at 1 AM UTC (9:00 AM in Singapore)
|
||||
schedule:
|
||||
- cron: 0 1 * * MON
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
canary:
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'vuejs/core'
|
||||
runs-on: ubuntu-latest
|
||||
environment: Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: minor
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- run: pnpm release --canary --tag minor
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
environment: Release
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
|
|
@ -14,8 +14,10 @@ jobs:
|
|||
unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
@ -26,9 +28,6 @@ jobs:
|
|||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Skip Puppeteer download
|
||||
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $GITHUB_ENV
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- name: Run unit tests
|
||||
|
@ -37,8 +36,10 @@ jobs:
|
|||
unit-test-windows:
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
@ -49,9 +50,6 @@ jobs:
|
|||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Skip Puppeteer download
|
||||
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $env:GITHUB_ENV
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- name: Run compiler unit tests
|
||||
|
@ -64,12 +62,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup cache for Chromium binary
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/puppeteer/chrome
|
||||
path: ~/.cache/puppeteer
|
||||
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
|
@ -82,6 +80,7 @@ jobs:
|
|||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
- run: node node_modules/puppeteer/install.js
|
||||
|
||||
- name: Run e2e tests
|
||||
run: pnpm run test-e2e
|
||||
|
@ -89,8 +88,10 @@ jobs:
|
|||
lint-and-test-dts:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
@ -101,36 +102,13 @@ jobs:
|
|||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Skip Puppeteer download
|
||||
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $GITHUB_ENV
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- name: Run eslint
|
||||
run: pnpm run lint
|
||||
|
||||
# - name: Run prettier
|
||||
# run: pnpm run format-check
|
||||
- name: Run prettier
|
||||
run: pnpm run format-check
|
||||
|
||||
- name: Run type declaration tests
|
||||
run: pnpm run test-dts
|
||||
|
||||
size:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
env:
|
||||
CI_JOB_NUMBER: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: PUPPETEER_SKIP_DOWNLOAD=1 pnpm install
|
||||
- run: pnpm run size
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
name: Lock Closed Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
action:
|
||||
if: github.repository == 'vuejs/core'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-inactive-days: '14'
|
||||
issue-lock-reason: ''
|
||||
process-only: 'issues'
|
|
@ -0,0 +1,52 @@
|
|||
name: size data
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to LTS
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- run: pnpm run size
|
||||
|
||||
- name: Upload Size Data
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: size-data
|
||||
path: temp/size
|
||||
|
||||
- name: Save PR number
|
||||
if: ${{github.event_name == 'pull_request'}}
|
||||
run: echo ${{ github.event.number }} > ./pr.txt
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{github.event_name == 'pull_request'}}
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr.txt
|
|
@ -0,0 +1,84 @@
|
|||
name: size report
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['size data']
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
|
||||
jobs:
|
||||
size-report:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to LTS
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Download PR number
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
name: pr-number
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
- name: Read PR Number
|
||||
id: pr-number
|
||||
uses: juliangruber/read-file-action@v1
|
||||
with:
|
||||
path: ./pr.txt
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: temp/size
|
||||
|
||||
- name: Download Previous Size Data
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
branch: main
|
||||
workflow: size-data.yml
|
||||
event: push
|
||||
name: size-data
|
||||
path: temp/size-prev
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: Compare size
|
||||
run: pnpm tsx scripts/size-report.ts > size-report.md
|
||||
|
||||
- name: Read Size Report
|
||||
id: size-report
|
||||
uses: juliangruber/read-file-action@v1
|
||||
with:
|
||||
path: ./size-report.md
|
||||
|
||||
- name: Create Comment
|
||||
uses: actions-cool/maintain-one-comment@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ steps.pr-number.outputs.content }}
|
||||
body: |
|
||||
${{ steps.size-report.outputs.content }}
|
||||
<!-- VUE_CORE_SIZE -->
|
||||
body-include: '<!-- VUE_CORE_SIZE -->'
|
|
@ -4,6 +4,6 @@ Vue.js is an MIT-licensed open source project with its ongoing development made
|
|||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://sponsors.vuejs.org/backers.svg">
|
||||
<img alt="sponsors" src="https://sponsors.vuejs.org/backers.svg">
|
||||
<img alt="sponsors" src="https://sponsors.vuejs.org/backers.svg?v1">
|
||||
</a>
|
||||
</p>
|
||||
|
|
108
package.json
108
package.json
|
@ -1,19 +1,21 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.3.4",
|
||||
"packageManager": "pnpm@8.4.0",
|
||||
"packageManager": "pnpm@8.9.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
"build": "node scripts/build.js",
|
||||
"build-dts": "tsc -p tsconfig.build.json && rollup -c rollup.dts.config.js",
|
||||
"size": "run-s size-global size-baseline",
|
||||
"size-global": "node scripts/build.js vue runtime-dom -f global -p",
|
||||
"size-baseline": "node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler && cd packages/size-check && vite build && node brotli",
|
||||
"clean": "rimraf packages/*/dist temp .eslintcache",
|
||||
"size": "run-s \"size-*\" && tsx scripts/usage-size.ts",
|
||||
"size-global": "node scripts/build.js vue runtime-dom -f global -p --size",
|
||||
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
|
||||
"size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler",
|
||||
"check": "tsc --incremental --noEmit",
|
||||
"lint": "eslint --cache --ext .ts packages/*/{src,__tests__}/**.ts",
|
||||
"format": "prettier --write --cache --parser typescript \"**/*.[tj]s?(x)\"",
|
||||
"format-check": "prettier --check --cache --parser typescript \"**/*.[tj]s?(x)\"",
|
||||
"format": "prettier --write --cache \"**/*.[tj]s?(x)\"",
|
||||
"format-check": "prettier --check --cache \"**/*.[tj]s?(x)\"",
|
||||
"test": "vitest",
|
||||
"test-unit": "vitest -c vitest.unit.config.ts",
|
||||
"test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts",
|
||||
|
@ -29,13 +31,13 @@
|
|||
"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",
|
||||
"serve": "serve",
|
||||
"open": "open http://localhost:5000/packages/template-explorer/local.html",
|
||||
"build-sfc-playground": "run-s build-compiler-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
|
||||
"build-compiler-cjs": "node scripts/build.js compiler reactivity-transform shared -af cjs",
|
||||
"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",
|
||||
"build-all-cjs": "node scripts/build.js vue runtime compiler reactivity reactivity-transform shared -af cjs",
|
||||
"build-runtime-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js vue -f esm-browser-runtime",
|
||||
"build-ssr-esm": "node scripts/build.js compiler-sfc server-renderer -f esm-browser",
|
||||
"build-sfc-playground-self": "cd packages/sfc-playground && npm run build",
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"postinstall": "simple-git-hooks"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
|
@ -52,53 +54,57 @@
|
|||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.21.3",
|
||||
"@babel/types": "^7.21.3",
|
||||
"@rollup/plugin-alias": "^4.0.3",
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-terser": "^0.4.0",
|
||||
"@types/hash-sum": "^1.0.0",
|
||||
"@types/node": "^16.4.7",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"@vitest/coverage-istanbul": "^0.29.7",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@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",
|
||||
"@vue/consolidate": "0.17.3",
|
||||
"chalk": "^4.1.0",
|
||||
"conventional-changelog-cli": "^2.0.31",
|
||||
"enquirer": "^2.3.2",
|
||||
"esbuild": "^0.17.4",
|
||||
"esbuild-plugin-polyfill-node": "^0.2.0",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"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",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^4.0.2",
|
||||
"jsdom": "^21.1.0",
|
||||
"lint-staged": "^10.2.10",
|
||||
"lodash": "^4.17.15",
|
||||
"magic-string": "^0.30.0",
|
||||
"marked": "^4.0.10",
|
||||
"minimist": "^1.2.0",
|
||||
"execa": "^8.0.1",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.5",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^9.1.2",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.7.1",
|
||||
"pug": "^3.0.1",
|
||||
"puppeteer": "~19.6.0",
|
||||
"rollup": "^3.20.2",
|
||||
"rollup-plugin-dts": "^5.3.0",
|
||||
"rollup-plugin-esbuild": "^5.0.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~21.2.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^3.29.4",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-esbuild": "^6.1.0",
|
||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||
"semver": "^7.3.2",
|
||||
"serve": "^12.0.0",
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
"terser": "^5.15.1",
|
||||
"todomvc-app-css": "^2.3.0",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.0",
|
||||
"semver": "^7.5.4",
|
||||
"serve": "^14.2.1",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"terser": "^5.22.0",
|
||||
"todomvc-app-css": "^2.4.2",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^3.14.0",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.3.0",
|
||||
"vitest": "^0.30.1"
|
||||
"vitest": "^0.34.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,12 +32,12 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.21.3",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/shared": "3.3.4",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.21.3"
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export function walkIdentifiers(
|
|||
root.body[0].type === 'ExpressionStatement' &&
|
||||
root.body[0].expression
|
||||
|
||||
;(walk as any)(root, {
|
||||
walk(root, {
|
||||
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
|
||||
parent && parentStack.push(parent)
|
||||
if (
|
||||
|
|
|
@ -8,9 +8,9 @@ export function decodeHtmlBrowser(raw: string, asAttr = false): string {
|
|||
}
|
||||
if (asAttr) {
|
||||
decoder.innerHTML = `<div foo="${raw.replace(/"/g, '"')}">`
|
||||
return decoder.children[0].getAttribute('foo') as string
|
||||
return decoder.children[0].getAttribute('foo')!
|
||||
} else {
|
||||
decoder.innerHTML = raw
|
||||
return decoder.textContent as string
|
||||
return decoder.textContent!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,24 @@ return { fn }
|
|||
})"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > keep original semi style 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
emits: ['change'],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
console.log('test')
|
||||
const props = __props;
|
||||
const emit = __emit;
|
||||
(function () {})()
|
||||
|
||||
return { props, emit }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > script first 1`] = `
|
||||
"import { x } from './x'
|
||||
|
||||
|
@ -612,75 +630,6 @@ return { foo, bar, baz, y, z }
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > defineProps/defineEmits in multi-variable declaration (full removal) 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
emits: ['a'],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
|
||||
return { props, emit }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > defineProps/defineEmits in multi-variable declaration 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
emits: ['a'],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
const a = 1;
|
||||
|
||||
return { props, a, emit }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > defineProps/defineEmits in multi-variable declaration fix #6757 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
emits: ['a'],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
const a = 1;
|
||||
|
||||
return { a, props, emit }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > defineProps/defineEmits in multi-variable declaration fix #7422 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
emits: ['foo'],
|
||||
setup(__props, { expose: __expose, emit: emits }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
const a = 0,
|
||||
b = 0;
|
||||
|
||||
return { props, emits, a, b }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > dev mode import usage check > TS annotations 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { Foo, Bar, Baz, Qux, Fred } from './x'
|
||||
|
@ -745,6 +694,21 @@ 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'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar } }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > dev mode import usage check > js template string interpolations 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { VAR, VAR2, VAR3 } from './x'
|
||||
|
@ -775,6 +739,21 @@ return { get FooBaz() { return FooBaz }, get Last() { return Last } }
|
|||
})"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > dev mode import usage check > template ref 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { foo, bar, Baz } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > dev mode import usage check > vue interpolations 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { x, y, z, x$y } from './x'
|
||||
|
|
|
@ -68,64 +68,6 @@ describe('SFC compile <script setup>', () => {
|
|||
assertCode(content)
|
||||
})
|
||||
|
||||
test('defineProps/defineEmits in multi-variable declaration', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const props = defineProps(['item']),
|
||||
a = 1,
|
||||
emit = defineEmits(['a']);
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`const a = 1;`) // test correct removal
|
||||
expect(content).toMatch(`props: ['item'],`)
|
||||
expect(content).toMatch(`emits: ['a'],`)
|
||||
})
|
||||
|
||||
// #6757
|
||||
test('defineProps/defineEmits in multi-variable declaration fix #6757 ', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const a = 1,
|
||||
props = defineProps(['item']),
|
||||
emit = defineEmits(['a']);
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`const a = 1;`) // test correct removal
|
||||
expect(content).toMatch(`props: ['item'],`)
|
||||
expect(content).toMatch(`emits: ['a'],`)
|
||||
})
|
||||
|
||||
// #7422
|
||||
test('defineProps/defineEmits in multi-variable declaration fix #7422', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const props = defineProps(['item']),
|
||||
emits = defineEmits(['foo']),
|
||||
a = 0,
|
||||
b = 0;
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: ['item'],`)
|
||||
expect(content).toMatch(`emits: ['foo'],`)
|
||||
expect(content).toMatch(`const a = 0,`)
|
||||
expect(content).toMatch(`b = 0;`)
|
||||
})
|
||||
|
||||
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const props = defineProps(['item']),
|
||||
emit = defineEmits(['a']);
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: ['item'],`)
|
||||
expect(content).toMatch(`emits: ['a'],`)
|
||||
})
|
||||
|
||||
describe('<script> and <script setup> co-usage', () => {
|
||||
test('script first', () => {
|
||||
const { content } = compile(`
|
||||
|
@ -156,6 +98,24 @@ describe('SFC compile <script setup>', () => {
|
|||
assertCode(content)
|
||||
})
|
||||
|
||||
// #7805
|
||||
test('keep original semi style', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
console.log('test')
|
||||
const props = defineProps(['item']);
|
||||
const emit = defineEmits(['change']);
|
||||
(function () {})()
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
|
||||
expect(content).toMatch(`console.log('test')`)
|
||||
expect(content).toMatch(`const props = __props;`)
|
||||
expect(content).toMatch(`const emit = __emit;`)
|
||||
expect(content).toMatch(`(function () {})()`)
|
||||
})
|
||||
|
||||
test('script setup first, named default export', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
|
@ -413,6 +373,25 @@ describe('SFC compile <script setup>', () => {
|
|||
assertCode(content)
|
||||
})
|
||||
|
||||
test('dynamic arguments', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
import { FooBar, foo, bar, unused } from './x'
|
||||
</script>
|
||||
<template>
|
||||
<FooBar #[foo.slotName] />
|
||||
<FooBar #unused />
|
||||
<div :[bar.attrName]="15"></div>
|
||||
<div unused="unused"></div>
|
||||
</template>
|
||||
`)
|
||||
expect(content).toMatch(
|
||||
`return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
|
||||
`get bar() { return bar } }`
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
// https://github.com/vuejs/core/issues/4599
|
||||
test('attribute expressions', () => {
|
||||
const { content } = compile(`
|
||||
|
@ -513,6 +492,23 @@ describe('SFC compile <script setup>', () => {
|
|||
</template>
|
||||
`)
|
||||
})
|
||||
|
||||
test('template ref', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
import { foo, bar, Baz } from './foo'
|
||||
</script>
|
||||
<template>
|
||||
<div ref="foo"></div>
|
||||
<div ref=""></div>
|
||||
<Baz ref="bar" />
|
||||
</template>
|
||||
`)
|
||||
expect(content).toMatch(
|
||||
'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }'
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
})
|
||||
|
||||
describe('inlineTemplate mode', () => {
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
exports[`defineEmits > basic usage 1`] = `
|
||||
"export default {
|
||||
emits: ['foo', 'bar'],
|
||||
setup(__props, { expose: __expose, emit: myEmit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const myEmit = __emit
|
||||
|
||||
return { myEmit }
|
||||
}
|
||||
|
@ -43,10 +43,10 @@ exports[`defineEmits > w/ runtime options 1`] = `
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: ['a', 'b'],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -60,10 +60,10 @@ export interface Emits { (e: 'foo' | 'bar'): void }
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -77,10 +77,10 @@ export type Emits = { (e: 'foo' | 'bar'): void }
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -94,10 +94,10 @@ interface Emits { (e: 'foo'): void }
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: ['foo'],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit: Emits = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -111,10 +111,10 @@ interface Emits { (e: 'foo' | 'bar'): void }
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -127,10 +127,10 @@ exports[`defineEmits > w/ type (property syntax string literal) 1`] = `
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo:bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -143,10 +143,10 @@ exports[`defineEmits > w/ type (property syntax) 1`] = `
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -160,10 +160,10 @@ export type Emits = (e: 'foo' | 'bar') => void
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -177,10 +177,10 @@ type Emits = (e: 'foo' | 'bar') => void
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -194,10 +194,10 @@ type Emits = { (e: 'foo' | 'bar'): void }
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -210,10 +210,10 @@ exports[`defineEmits > w/ type (type literal w/ call signatures) 1`] = `
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -228,10 +228,10 @@ type BaseEmit = \\"change\\"
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"some\\", \\"emit\\", \\"change\\", \\"another\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit;
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -244,10 +244,10 @@ exports[`defineEmits > w/ type (union) 1`] = `
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -260,10 +260,10 @@ exports[`defineEmits > w/ type 1`] = `
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
@ -278,10 +278,10 @@ exports[`defineEmits > w/ type from normal script 1`] = `
|
|||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
emits: [\\"foo\\", \\"bar\\"],
|
||||
setup(__props, { expose: __expose, emit }) {
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
||||
|
||||
const emit = __emit
|
||||
|
||||
return { emit }
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@ export default {
|
|||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props, bar }
|
||||
}
|
||||
|
@ -28,9 +26,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
@ -48,9 +44,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const { foo } = __props;
|
||||
|
||||
|
||||
const { foo } = __props
|
||||
|
||||
return { }
|
||||
}
|
||||
|
@ -167,9 +161,7 @@ export default {
|
|||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props, get propsModel() { return propsModel } }
|
||||
}
|
||||
|
@ -203,9 +195,7 @@ export default {
|
|||
props: {},
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props, get x() { return x } }
|
||||
}
|
||||
|
@ -304,9 +294,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props, get defaults() { return defaults } }
|
||||
}
|
||||
|
@ -328,9 +316,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props, get defaults() { return defaults } }
|
||||
}
|
||||
|
@ -351,9 +337,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props, get defaults() { return defaults } }
|
||||
}
|
||||
|
@ -375,9 +359,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props;
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
@ -401,9 +383,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
@ -424,9 +404,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
@ -446,9 +424,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
|
|
@ -176,6 +176,61 @@ return () => {}
|
|||
})"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > defineProps/defineEmits in multi-variable declaration (full removal) 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
emits: ['a'],
|
||||
setup(__props, { emit: __emit }) {
|
||||
|
||||
const props = __props,
|
||||
emit = __emit;
|
||||
|
||||
return () => {}
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
setup(__props) {
|
||||
|
||||
const a = 1;
|
||||
|
||||
return () => {}
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > multi-variable declaration fix #6757 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
setup(__props) {
|
||||
|
||||
const a = 1;
|
||||
|
||||
return () => {}
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > multi-variable declaration fix #7422 1`] = `
|
||||
"export default {
|
||||
props: ['item'],
|
||||
setup(__props) {
|
||||
|
||||
const a = 0,
|
||||
b = 0;
|
||||
|
||||
return () => {}
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > multiple variable declarations 1`] = `
|
||||
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
|
||||
|
||||
|
@ -237,9 +292,7 @@ export default {
|
|||
props: ['foo', 'bar', 'baz'],
|
||||
setup(__props) {
|
||||
|
||||
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"]);
|
||||
|
||||
|
||||
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"])
|
||||
|
||||
return () => {}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ const myEmit = defineEmits(['foo', 'bar'])
|
|||
expect(content).not.toMatch('defineEmits')
|
||||
// should generate correct setup signature
|
||||
expect(content).toMatch(
|
||||
`setup(__props, { expose: __expose, emit: myEmit }) {`
|
||||
`setup(__props, { expose: __expose, emit: __emit }) {`
|
||||
)
|
||||
expect(content).toMatch('const myEmit = __emit')
|
||||
// should include context options in default export
|
||||
expect(content).toMatch(`export default {
|
||||
emits: ['foo', 'bar'],`)
|
||||
|
@ -32,7 +33,8 @@ const emit = defineEmits(['a', 'b'])
|
|||
assertCode(content)
|
||||
expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
|
||||
emits: ['a', 'b'],
|
||||
setup(__props, { expose: __expose, emit }) {`)
|
||||
setup(__props, { expose: __expose, emit: __emit }) {`)
|
||||
expect(content).toMatch('const emit = __emit')
|
||||
})
|
||||
|
||||
test('w/ type', () => {
|
||||
|
|
|
@ -282,6 +282,58 @@ describe('sfc reactive props destructure', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('multi-variable declaration', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const { item } = defineProps(['item']),
|
||||
a = 1;
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`const a = 1;`)
|
||||
expect(content).toMatch(`props: ['item'],`)
|
||||
})
|
||||
|
||||
// #6757
|
||||
test('multi-variable declaration fix #6757 ', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const a = 1,
|
||||
{ item } = defineProps(['item']);
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`const a = 1;`)
|
||||
expect(content).toMatch(`props: ['item'],`)
|
||||
})
|
||||
|
||||
// #7422
|
||||
test('multi-variable declaration fix #7422', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const { item } = defineProps(['item']),
|
||||
a = 0,
|
||||
b = 0;
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`const a = 0,`)
|
||||
expect(content).toMatch(`b = 0;`)
|
||||
expect(content).toMatch(`props: ['item'],`)
|
||||
})
|
||||
|
||||
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const props = defineProps(['item']),
|
||||
emit = defineEmits(['a']);
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: ['item'],`)
|
||||
expect(content).toMatch(`emits: ['a'],`)
|
||||
})
|
||||
|
||||
describe('errors', () => {
|
||||
test('should error on deep destructure', () => {
|
||||
expect(() =>
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from '../../src/script/resolveType'
|
||||
|
||||
import ts from 'typescript'
|
||||
registerTS(ts)
|
||||
registerTS(() => ts)
|
||||
|
||||
describe('resolveType', () => {
|
||||
test('type literal', () => {
|
||||
|
|
|
@ -32,29 +32,28 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.15",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/compiler-core": "3.3.4",
|
||||
"@vue/compiler-dom": "3.3.4",
|
||||
"@vue/compiler-ssr": "3.3.4",
|
||||
"@vue/reactivity-transform": "3.3.4",
|
||||
"@vue/shared": "3.3.4",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.0",
|
||||
"postcss": "^8.1.10",
|
||||
"magic-string": "^0.30.5",
|
||||
"postcss": "^8.4.31",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.21.3",
|
||||
"@types/estree": "^0.0.48",
|
||||
"@types/lru-cache": "^5.1.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@types/estree": "^0.0.52",
|
||||
"@vue/consolidate": "^0.17.3",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "^5.1.1",
|
||||
"lru-cache": "^10.0.1",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "^9.0.0",
|
||||
"postcss-modules": "^4.0.0",
|
||||
"postcss-selector-parser": "^6.0.4",
|
||||
"pug": "^3.0.1",
|
||||
"sass": "^1.26.9"
|
||||
"minimatch": "^9.0.3",
|
||||
"postcss-modules": "^4.3.1",
|
||||
"postcss-selector-parser": "^6.0.13",
|
||||
"pug": "^3.0.2",
|
||||
"sass": "^1.69.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import LRU from 'lru-cache'
|
||||
import { LRUCache } from 'lru-cache'
|
||||
|
||||
export function createCache<T>(size = 500): Map<string, T> & { max?: number } {
|
||||
export function createCache<T extends {}>(
|
||||
max = 500
|
||||
): Map<string, T> | LRUCache<string, T> {
|
||||
if (__GLOBAL__ || __ESM_BROWSER__) {
|
||||
return new Map<string, T>()
|
||||
}
|
||||
const cache = new LRU(size)
|
||||
// @ts-expect-error
|
||||
cache.delete = cache.del.bind(cache)
|
||||
return cache as any as Map<string, T>
|
||||
return new LRUCache({ max })
|
||||
}
|
||||
|
|
|
@ -274,7 +274,7 @@ export function compileScript(
|
|||
const scriptAst = ctx.scriptAst
|
||||
const scriptSetupAst = ctx.scriptSetupAst!
|
||||
|
||||
// 1.1 walk import delcarations of <script>
|
||||
// 1.1 walk import declarations of <script>
|
||||
if (scriptAst) {
|
||||
for (const node of scriptAst.body) {
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
|
@ -552,7 +552,11 @@ export function compileScript(
|
|||
(processDefineSlots(ctx, init, decl.id) ||
|
||||
processDefineModel(ctx, init, decl.id))
|
||||
|
||||
if (isDefineProps || isDefineEmits) {
|
||||
if (
|
||||
isDefineProps &&
|
||||
!ctx.propsDestructureRestId &&
|
||||
ctx.propsDestructureDecl
|
||||
) {
|
||||
if (left === 1) {
|
||||
ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
|
||||
} else {
|
||||
|
@ -570,6 +574,12 @@ export function compileScript(
|
|||
ctx.s.remove(start, end)
|
||||
left--
|
||||
}
|
||||
} else if (isDefineEmits) {
|
||||
ctx.s.overwrite(
|
||||
startOffset + init.start!,
|
||||
startOffset + init.end!,
|
||||
'__emit'
|
||||
)
|
||||
} else {
|
||||
lastNonRemoved = i
|
||||
}
|
||||
|
@ -607,8 +617,8 @@ export function compileScript(
|
|||
node.type.endsWith('Statement')
|
||||
) {
|
||||
const scope: Statement[][] = [scriptSetupAst.body]
|
||||
;(walk as any)(node, {
|
||||
enter(child: Node, parent: Node) {
|
||||
walk(node, {
|
||||
enter(child: Node, parent: Node | undefined) {
|
||||
if (isFunctionType(child)) {
|
||||
this.skip()
|
||||
}
|
||||
|
@ -633,7 +643,7 @@ export function compileScript(
|
|||
ctx,
|
||||
child,
|
||||
needsSemi,
|
||||
parent.type === 'ExpressionStatement'
|
||||
parent!.type === 'ExpressionStatement'
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -781,22 +791,29 @@ export function compileScript(
|
|||
// inject user assignment of props
|
||||
// we use a default __props so that template expressions referencing props
|
||||
// can use it directly
|
||||
if (ctx.propsIdentifier) {
|
||||
ctx.s.prependLeft(
|
||||
startOffset,
|
||||
`\nconst ${ctx.propsIdentifier} = __props;\n`
|
||||
)
|
||||
}
|
||||
if (ctx.propsDestructureRestId) {
|
||||
ctx.s.prependLeft(
|
||||
startOffset,
|
||||
`\nconst ${ctx.propsDestructureRestId} = ${ctx.helper(
|
||||
`createPropsRestProxy`
|
||||
)}(__props, ${JSON.stringify(
|
||||
Object.keys(ctx.propsDestructuredBindings)
|
||||
)});\n`
|
||||
)
|
||||
if (ctx.propsDecl) {
|
||||
if (ctx.propsDestructureRestId) {
|
||||
ctx.s.overwrite(
|
||||
startOffset + ctx.propsCall!.start!,
|
||||
startOffset + ctx.propsCall!.end!,
|
||||
`${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify(
|
||||
Object.keys(ctx.propsDestructuredBindings)
|
||||
)})`
|
||||
)
|
||||
ctx.s.overwrite(
|
||||
startOffset + ctx.propsDestructureDecl!.start!,
|
||||
startOffset + ctx.propsDestructureDecl!.end!,
|
||||
ctx.propsDestructureRestId
|
||||
)
|
||||
} else if (!ctx.propsDestructureDecl) {
|
||||
ctx.s.overwrite(
|
||||
startOffset + ctx.propsCall!.start!,
|
||||
startOffset + ctx.propsCall!.end!,
|
||||
'__props'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// inject temp variables for async context preservation
|
||||
if (hasAwait) {
|
||||
const any = ctx.isTS ? `: any` : ``
|
||||
|
@ -807,10 +824,8 @@ export function compileScript(
|
|||
ctx.hasDefineExposeCall || !options.inlineTemplate
|
||||
? [`expose: __expose`]
|
||||
: []
|
||||
if (ctx.emitIdentifier) {
|
||||
destructureElements.push(
|
||||
ctx.emitIdentifier === `emit` ? `emit` : `emit: ${ctx.emitIdentifier}`
|
||||
)
|
||||
if (ctx.emitDecl) {
|
||||
destructureElements.push(`emit: __emit`)
|
||||
}
|
||||
if (destructureElements.length) {
|
||||
args += `, { ${destructureElements.join(', ')} }`
|
||||
|
|
|
@ -37,7 +37,7 @@ export function rewriteDefaultAST(
|
|||
// multi-line comments or template strings. fallback to a full parse.
|
||||
ast.forEach(node => {
|
||||
if (node.type === 'ExportDefaultDeclaration') {
|
||||
if (node.declaration.type === 'ClassDeclaration') {
|
||||
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
||||
let start: number =
|
||||
node.declaration.decorators && node.declaration.decorators.length > 0
|
||||
? node.declaration.decorators[
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Node, ObjectPattern, Program } from '@babel/types'
|
||||
import { CallExpression, Node, ObjectPattern, Program } from '@babel/types'
|
||||
import { SFCDescriptor } from '../parse'
|
||||
import { generateCodeFrame } from '@vue/shared'
|
||||
import { parse as babelParse, ParserPlugin } from '@babel/parser'
|
||||
|
@ -38,7 +38,8 @@ export class ScriptCompileContext {
|
|||
hasDefineModelCall = false
|
||||
|
||||
// defineProps
|
||||
propsIdentifier: string | undefined
|
||||
propsCall: CallExpression | undefined
|
||||
propsDecl: Node | undefined
|
||||
propsRuntimeDecl: Node | undefined
|
||||
propsTypeDecl: Node | undefined
|
||||
propsDestructureDecl: ObjectPattern | undefined
|
||||
|
@ -49,7 +50,7 @@ export class ScriptCompileContext {
|
|||
// defineEmits
|
||||
emitsRuntimeDecl: Node | undefined
|
||||
emitsTypeDecl: Node | undefined
|
||||
emitIdentifier: string | undefined
|
||||
emitDecl: Node | undefined
|
||||
|
||||
// defineModel
|
||||
modelDecls: Record<string, ModelDecl> = {}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { Identifier, LVal, Node, RestElement } from '@babel/types'
|
||||
import {
|
||||
ArrayPattern,
|
||||
Identifier,
|
||||
LVal,
|
||||
Node,
|
||||
ObjectPattern,
|
||||
RestElement
|
||||
} from '@babel/types'
|
||||
import { isCallOf } from './utils'
|
||||
import { ScriptCompileContext } from './context'
|
||||
import {
|
||||
|
@ -34,10 +41,7 @@ export function processDefineEmits(
|
|||
ctx.emitsTypeDecl = node.typeParameters.params[0]
|
||||
}
|
||||
|
||||
if (declId) {
|
||||
ctx.emitIdentifier =
|
||||
declId.type === 'Identifier' ? declId.name : ctx.getString(declId)
|
||||
}
|
||||
ctx.emitDecl = declId
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -99,7 +103,7 @@ function extractRuntimeEmits(ctx: ScriptCompileContext): Set<string> {
|
|||
|
||||
function extractEventNames(
|
||||
ctx: ScriptCompileContext,
|
||||
eventName: Identifier | RestElement,
|
||||
eventName: ArrayPattern | Identifier | ObjectPattern | RestElement,
|
||||
emits: Set<string>
|
||||
) {
|
||||
if (
|
||||
|
|
|
@ -24,13 +24,21 @@ export function processDefineModel(
|
|||
node: Node,
|
||||
declId?: LVal
|
||||
): boolean {
|
||||
if (!ctx.options.defineModel || !isCallOf(node, DEFINE_MODEL)) {
|
||||
if (!isCallOf(node, DEFINE_MODEL)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!ctx.options.defineModel) {
|
||||
warnOnce(
|
||||
`defineModel() is an experimental feature and disabled by default.\n` +
|
||||
`To enable it, follow the RFC at https://github.com/vuejs/rfcs/discussions/503.`
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
warnOnce(
|
||||
`This project is using defineModel(), which is an experimental ` +
|
||||
` feature. It may receive breaking changes or be removed in the future, so ` +
|
||||
`feature. It may receive breaking changes or be removed in the future, so ` +
|
||||
`use at your own risk.\n` +
|
||||
`To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/503.`
|
||||
)
|
||||
|
|
|
@ -77,15 +77,14 @@ export function processDefineProps(
|
|||
ctx.propsTypeDecl = node.typeParameters.params[0]
|
||||
}
|
||||
|
||||
if (declId) {
|
||||
// handle props destructure
|
||||
if (declId.type === 'ObjectPattern') {
|
||||
processPropsDestructure(ctx, declId)
|
||||
} else {
|
||||
ctx.propsIdentifier = ctx.getString(declId)
|
||||
}
|
||||
// handle props destructure
|
||||
if (declId && declId.type === 'ObjectPattern') {
|
||||
processPropsDestructure(ctx, declId)
|
||||
}
|
||||
|
||||
ctx.propsCall = node
|
||||
ctx.propsDecl = declId
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -97,31 +96,33 @@ function processWithDefaults(
|
|||
if (!isCallOf(node, WITH_DEFAULTS)) {
|
||||
return false
|
||||
}
|
||||
if (processDefineProps(ctx, node.arguments[0], declId)) {
|
||||
if (ctx.propsRuntimeDecl) {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS} can only be used with type-based ` +
|
||||
`${DEFINE_PROPS} declaration.`,
|
||||
node
|
||||
)
|
||||
}
|
||||
if (ctx.propsDestructureDecl) {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
|
||||
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
|
||||
node.callee
|
||||
)
|
||||
}
|
||||
ctx.propsRuntimeDefaults = node.arguments[1]
|
||||
if (!ctx.propsRuntimeDefaults) {
|
||||
ctx.error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node)
|
||||
}
|
||||
} else {
|
||||
if (!processDefineProps(ctx, node.arguments[0], declId)) {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
|
||||
node.arguments[0] || node
|
||||
)
|
||||
}
|
||||
|
||||
if (ctx.propsRuntimeDecl) {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS} can only be used with type-based ` +
|
||||
`${DEFINE_PROPS} declaration.`,
|
||||
node
|
||||
)
|
||||
}
|
||||
if (ctx.propsDestructureDecl) {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
|
||||
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
|
||||
node.callee
|
||||
)
|
||||
}
|
||||
ctx.propsRuntimeDefaults = node.arguments[1]
|
||||
if (!ctx.propsRuntimeDefaults) {
|
||||
ctx.error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node)
|
||||
}
|
||||
ctx.propsCall = node
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -28,13 +28,12 @@ export function processPropsDestructure(
|
|||
declId: ObjectPattern
|
||||
) {
|
||||
if (!ctx.options.propsDestructure && !ctx.options.reactivityTransform) {
|
||||
ctx.propsIdentifier = ctx.getString(declId)
|
||||
return
|
||||
}
|
||||
|
||||
warnOnce(
|
||||
`This project is using reactive props destructure, which is an experimental ` +
|
||||
` feature. It may receive breaking changes or be removed in the future, so ` +
|
||||
`feature. It may receive breaking changes or be removed in the future, so ` +
|
||||
`use at your own risk.\n` +
|
||||
`To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/502.`
|
||||
)
|
||||
|
@ -238,7 +237,7 @@ export function transformDestructuredProps(
|
|||
// check root scope first
|
||||
const ast = ctx.scriptSetupAst!
|
||||
walkScope(ast, true)
|
||||
;(walk as any)(ast, {
|
||||
walk(ast, {
|
||||
enter(node: Node, parent?: Node) {
|
||||
parent && parentStack.push(parent)
|
||||
|
||||
|
|
|
@ -50,6 +50,12 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
|||
if (!isBuiltInDirective(prop.name)) {
|
||||
code += `,v${capitalize(camelize(prop.name))}`
|
||||
}
|
||||
if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) {
|
||||
code += `,${processExp(
|
||||
(prop.arg as SimpleExpressionNode).content,
|
||||
prop.name
|
||||
)}`
|
||||
}
|
||||
if (prop.exp) {
|
||||
code += `,${processExp(
|
||||
(prop.exp as SimpleExpressionNode).content,
|
||||
|
@ -57,6 +63,13 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
|||
)}`
|
||||
}
|
||||
}
|
||||
if (
|
||||
prop.type === NodeTypes.ATTRIBUTE &&
|
||||
prop.name === 'ref' &&
|
||||
prop.value?.content
|
||||
) {
|
||||
code += `,${prop.value.content}`
|
||||
}
|
||||
}
|
||||
} else if (node.type === NodeTypes.INTERPOLATION) {
|
||||
code += `,${processExp(
|
||||
|
|
|
@ -725,13 +725,14 @@ function resolveGlobalScope(ctx: TypeResolveContext): TypeScope[] | undefined {
|
|||
}
|
||||
}
|
||||
|
||||
let ts: typeof TS
|
||||
let ts: typeof TS | undefined
|
||||
let loadTS: (() => typeof TS) | undefined
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function registerTS(_ts: any) {
|
||||
ts = _ts
|
||||
export function registerTS(_loadTS: () => typeof TS) {
|
||||
loadTS = _loadTS
|
||||
}
|
||||
|
||||
type FS = NonNullable<SFCScriptCompileOptions['fs']>
|
||||
|
@ -740,7 +741,10 @@ function resolveFS(ctx: TypeResolveContext): FS | undefined {
|
|||
if (ctx.fs) {
|
||||
return ctx.fs
|
||||
}
|
||||
const fs = ctx.options.fs || ts.sys
|
||||
if (!ts && loadTS) {
|
||||
ts = loadTS()
|
||||
}
|
||||
const fs = ctx.options.fs || ts?.sys
|
||||
if (!fs) {
|
||||
return
|
||||
}
|
||||
|
@ -796,22 +800,25 @@ function importSourceToScope(
|
|||
} else {
|
||||
// module or aliased import - use full TS resolution, only supported in Node
|
||||
if (!__NODE_JS__) {
|
||||
ctx.error(
|
||||
return ctx.error(
|
||||
`Type import from non-relative sources is not supported in the browser build.`,
|
||||
node,
|
||||
scope
|
||||
)
|
||||
}
|
||||
if (!ts) {
|
||||
ctx.error(
|
||||
`Failed to resolve import source ${JSON.stringify(source)}. ` +
|
||||
`typescript is required as a peer dep for vue in order ` +
|
||||
`to support resolving types from module imports.`,
|
||||
node,
|
||||
scope
|
||||
)
|
||||
if (loadTS) ts = loadTS()
|
||||
if (!ts) {
|
||||
return ctx.error(
|
||||
`Failed to resolve import source ${JSON.stringify(source)}. ` +
|
||||
`typescript is required as a peer dep for vue in order ` +
|
||||
`to support resolving types from module imports.`,
|
||||
node,
|
||||
scope
|
||||
)
|
||||
}
|
||||
}
|
||||
resolved = resolveWithTS(scope.filename, source, fs)
|
||||
resolved = resolveWithTS(scope.filename, source, ts, fs)
|
||||
}
|
||||
if (resolved) {
|
||||
resolved = scope.resolvedImportSources[source] = normalizePath(resolved)
|
||||
|
@ -856,6 +863,7 @@ const tsConfigRefMap = new Map<string, string>()
|
|||
function resolveWithTS(
|
||||
containingFile: string,
|
||||
source: string,
|
||||
ts: typeof TS,
|
||||
fs: FS
|
||||
): string | undefined {
|
||||
if (!__NODE_JS__) return
|
||||
|
@ -870,7 +878,7 @@ function resolveWithTS(
|
|||
const normalizedConfigPath = normalizePath(configPath)
|
||||
const cached = tsConfigCache.get(normalizedConfigPath)
|
||||
if (!cached) {
|
||||
configs = loadTSConfig(configPath, fs).map(config => ({ config }))
|
||||
configs = loadTSConfig(configPath, ts, fs).map(config => ({ config }))
|
||||
tsConfigCache.set(normalizedConfigPath, configs)
|
||||
} else {
|
||||
configs = cached
|
||||
|
@ -935,7 +943,11 @@ function resolveWithTS(
|
|||
}
|
||||
}
|
||||
|
||||
function loadTSConfig(configPath: string, fs: FS): TS.ParsedCommandLine[] {
|
||||
function loadTSConfig(
|
||||
configPath: string,
|
||||
ts: typeof TS,
|
||||
fs: FS
|
||||
): TS.ParsedCommandLine[] {
|
||||
// The only case where `fs` is NOT `ts.sys` is during tests.
|
||||
// parse config host requires an extra `readDirectory` method
|
||||
// during tests, which is stubbed.
|
||||
|
@ -957,7 +969,7 @@ function loadTSConfig(configPath: string, fs: FS): TS.ParsedCommandLine[] {
|
|||
if (config.projectReferences) {
|
||||
for (const ref of config.projectReferences) {
|
||||
tsConfigRefMap.set(ref.path, configPath)
|
||||
res.unshift(...loadTSConfig(ref.path, fs))
|
||||
res.unshift(...loadTSConfig(ref.path, ts, fs))
|
||||
}
|
||||
}
|
||||
return res
|
||||
|
@ -1232,7 +1244,7 @@ function recordType(
|
|||
break
|
||||
}
|
||||
case 'ClassDeclaration':
|
||||
types[overwriteId || getId(node.id)] = node
|
||||
if (overwriteId || node.id) types[overwriteId || getId(node.id!)] = node
|
||||
break
|
||||
case 'TSTypeAliasDeclaration':
|
||||
types[node.id.name] = node.typeAnnotation
|
||||
|
|
|
@ -53,8 +53,9 @@ export function parseCssVars(sfc: SFCDescriptor): string[] {
|
|||
const vars: string[] = []
|
||||
sfc.styles.forEach(style => {
|
||||
let match
|
||||
// ignore v-bind() in comments /* ... */
|
||||
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '')
|
||||
// ignore v-bind() in comments, eg /* ... */
|
||||
// and // (Less, Sass and Stylus all support the use of // to comment)
|
||||
const content = style.content.replace(/\/\*([\s\S]*?)\*\/|\/\/.*/g, '')
|
||||
while ((match = vBindRE.exec(content))) {
|
||||
const start = match.index + match[0].length
|
||||
const end = lexBinding(content, start)
|
||||
|
|
|
@ -130,9 +130,10 @@ function rewriteSelector(
|
|||
// DEPRECATED usage
|
||||
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
|
||||
warn(
|
||||
`::v-deep usage as a combinator has ` +
|
||||
`been deprecated. Use :deep(<inner-selector>) instead.`
|
||||
`${value} usage as a combinator has been deprecated. ` +
|
||||
`Use :deep(<inner-selector>) instead of ${value} <inner-selector>.`
|
||||
)
|
||||
|
||||
const prev = selector.at(selector.index(n) - 1)
|
||||
if (prev && isSpaceCombinator(prev)) {
|
||||
selector.removeChild(prev)
|
||||
|
|
|
@ -98,8 +98,7 @@ const less: StylePreprocessor = (source, map, options, load = require) => {
|
|||
const styl: StylePreprocessor = (source, map, options, load = require) => {
|
||||
const nodeStylus = load('stylus')
|
||||
try {
|
||||
const ref = nodeStylus(source)
|
||||
Object.keys(options).forEach(key => ref.set(key, options[key]))
|
||||
const ref = nodeStylus(source, options)
|
||||
if (map) ref.set('sourcemap', { inline: false, comment: false })
|
||||
|
||||
const result = ref.render()
|
||||
|
|
|
@ -33,6 +33,44 @@ describe('ssr: v-model', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
test('<select v-model>', () => {
|
||||
expect(
|
||||
compileWithWrapper(
|
||||
`<select v-model="model"><option value="1"></option></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><option value=\\"1\\"\${
|
||||
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||
? _ssrLooseContain(_ctx.model, \\"1\\")
|
||||
: _ssrLooseEqual(_ctx.model, \\"1\\"))) ? \\" selected\\" : \\"\\"
|
||||
}></option></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileWithWrapper(
|
||||
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></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><option value=\\"1\\" selected></option><option value=\\"2\\"\${
|
||||
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||
? _ssrLooseContain(_ctx.model, \\"2\\")
|
||||
: _ssrLooseEqual(_ctx.model, \\"2\\"))) ? \\" selected\\" : \\"\\"
|
||||
}></option></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('<input type="radio">', () => {
|
||||
expect(
|
||||
compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code
|
||||
|
|
|
@ -18,7 +18,8 @@ import {
|
|||
import {
|
||||
SSR_LOOSE_EQUAL,
|
||||
SSR_LOOSE_CONTAIN,
|
||||
SSR_RENDER_DYNAMIC_MODEL
|
||||
SSR_RENDER_DYNAMIC_MODEL,
|
||||
SSR_INCLUDE_BOOLEAN_ATTR
|
||||
} from '../runtimeHelpers'
|
||||
import { DirectiveTransformResult } from 'packages/compiler-core/src/transform'
|
||||
|
||||
|
@ -129,8 +130,34 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||
checkDuplicatedValue()
|
||||
node.children = [createInterpolation(model, model.loc)]
|
||||
} else if (node.tag === 'select') {
|
||||
// NOOP
|
||||
// select relies on client-side directive to set initial selected state.
|
||||
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 */
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
|
|
|
@ -4,4 +4,4 @@ Tests Typescript types to ensure the types remain as expected.
|
|||
|
||||
- This directory is included in the root `tsconfig.json`, where package imports are aliased to `src` directories, so in IDEs and the `pnpm check` script the types are validated against source code.
|
||||
|
||||
- When running `tsc` with `packages/dts-test/tsconfig.test.json`, packages are resolved using using normal `node` resolution, so the types are validated against actual **built** types. This requires the types to be built first via `pnpm build-types`.
|
||||
- When running `tsc` with `packages/dts-test/tsconfig.test.json`, packages are resolved using normal `node` resolution, so the types are validated against actual **built** types. This requires the types to be built first via `pnpm build-types`.
|
||||
|
|
|
@ -1363,13 +1363,13 @@ describe('function syntax w/ runtime props', () => {
|
|||
}
|
||||
)
|
||||
|
||||
// @ts-expect-error prop type mismatch
|
||||
defineComponent(
|
||||
(_props: { msg: string }) => {
|
||||
return () => {}
|
||||
},
|
||||
{
|
||||
props: {
|
||||
// @ts-expect-error prop type mismatch
|
||||
msg: Number
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,8 @@ describe('defineProps w/ union type declaration + withDefaults', () => {
|
|||
)
|
||||
})
|
||||
|
||||
describe('defineProps w/ generic type declaration + withDefaults', <T extends number, TA extends {
|
||||
describe('defineProps w/ generic type declaration + withDefaults', <T extends
|
||||
number, TA extends {
|
||||
a: string
|
||||
}, TString extends string>() => {
|
||||
const res = withDefaults(
|
||||
|
@ -117,10 +118,10 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends nu
|
|||
n: 123,
|
||||
|
||||
generic1: () => [123, 33] as T[],
|
||||
generic2: () => ({ x: 123 } as { x: T }),
|
||||
generic2: () => ({ x: 123 }) as { x: T },
|
||||
|
||||
generic3: () => 'test' as TString,
|
||||
generic4: () => ({ a: 'test' } as TA)
|
||||
generic4: () => ({ a: 'test' }) as TA
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -134,6 +135,26 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends nu
|
|||
expectType<boolean>(res.bool)
|
||||
})
|
||||
|
||||
describe('withDefaults w/ boolean type', () => {
|
||||
const res1 = withDefaults(
|
||||
defineProps<{
|
||||
bool?: boolean
|
||||
}>(),
|
||||
{ bool: false }
|
||||
)
|
||||
expectType<boolean>(res1.bool)
|
||||
|
||||
const res2 = withDefaults(
|
||||
defineProps<{
|
||||
bool?: boolean
|
||||
}>(),
|
||||
{
|
||||
bool: undefined
|
||||
}
|
||||
)
|
||||
expectType<boolean | undefined>(res2.bool)
|
||||
})
|
||||
|
||||
describe('defineProps w/ runtime declaration', () => {
|
||||
// runtime declaration
|
||||
const props = defineProps({
|
||||
|
|
|
@ -33,6 +33,17 @@ declare module 'file-saver' {
|
|||
export function saveAs(blob: any, name: any): void
|
||||
}
|
||||
|
||||
declare module 'estree-walker' {
|
||||
export function walk<T>(
|
||||
root: T,
|
||||
options: {
|
||||
enter?: (node: T, parent: T | undefined) => any
|
||||
leave?: (node: T, parent: T | undefined) => any
|
||||
exit?: (node: T) => any
|
||||
} & ThisType<{ skip: () => void }>
|
||||
)
|
||||
}
|
||||
|
||||
declare interface String {
|
||||
/**
|
||||
* @deprecated Please use String.prototype.slice instead of String.prototype.substring in the repository.
|
||||
|
|
|
@ -28,14 +28,14 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.15",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/compiler-core": "3.3.4",
|
||||
"@vue/shared": "3.3.4",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.0"
|
||||
"magic-string": "^0.30.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/types": "^7.21.3"
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -636,7 +636,7 @@ export function transformAST(
|
|||
|
||||
// check root scope first
|
||||
walkScope(ast, true)
|
||||
;(walk as any)(ast, {
|
||||
walk(ast, {
|
||||
enter(node: Node, parent?: Node) {
|
||||
parent && parentStack.push(parent)
|
||||
|
||||
|
|
|
@ -259,13 +259,13 @@ describe('reactivity/computed', () => {
|
|||
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
||||
events.push(e)
|
||||
})
|
||||
const obj = reactive({ foo: 1 })
|
||||
const obj = reactive<{ foo?: number }>({ foo: 1 })
|
||||
const c = computed(() => obj.foo, { onTrigger })
|
||||
|
||||
// computed won't trigger compute until accessed
|
||||
c.value
|
||||
|
||||
obj.foo++
|
||||
obj.foo!++
|
||||
expect(c.value).toBe(2)
|
||||
expect(onTrigger).toHaveBeenCalledTimes(1)
|
||||
expect(events[0]).toEqual({
|
||||
|
@ -277,7 +277,6 @@ describe('reactivity/computed', () => {
|
|||
newValue: 2
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
delete obj.foo
|
||||
expect(c.value).toBeUndefined()
|
||||
expect(onTrigger).toHaveBeenCalledTimes(2)
|
||||
|
|
|
@ -585,6 +585,14 @@ describe('reactivity/effect', () => {
|
|||
expect(runner.effect.fn).toBe(otherRunner.effect.fn)
|
||||
})
|
||||
|
||||
it('should wrap if the passed function is a fake effect', () => {
|
||||
const fakeRunner = () => {}
|
||||
fakeRunner.effect = {}
|
||||
const runner = effect(fakeRunner)
|
||||
expect(fakeRunner).not.toBe(runner)
|
||||
expect(runner.effect.fn).toBe(fakeRunner)
|
||||
})
|
||||
|
||||
it('should not run multiple times for a single mutation', () => {
|
||||
let dummy
|
||||
const obj = reactive<Record<string, number>>({})
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('reactivity/reactive', () => {
|
|||
const reactiveObj = reactive(obj)
|
||||
expect(isReactive(reactiveObj)).toBe(true)
|
||||
// read prop of reactiveObject will cause reactiveObj[prop] to be reactive
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const prototype = reactiveObj['__proto__']
|
||||
const otherObj = { data: ['a'] }
|
||||
expect(isReactive(otherObj)).toBe(false)
|
||||
|
@ -204,7 +204,7 @@ describe('reactivity/reactive', () => {
|
|||
const dummy = computed(() => observed.a)
|
||||
expect(dummy.value).toBe(0)
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
observed.a = bar
|
||||
expect(dummy.value).toBe(1)
|
||||
|
||||
|
@ -233,6 +233,9 @@ describe('reactivity/reactive', () => {
|
|||
// symbol
|
||||
const s = Symbol()
|
||||
assertValue(s)
|
||||
// bigint
|
||||
const bn = BigInt('9007199254740991')
|
||||
assertValue(bn)
|
||||
|
||||
// built-ins should work and return same value
|
||||
const p = Promise.resolve()
|
||||
|
|
|
@ -28,19 +28,18 @@ describe('reactivity/ref', () => {
|
|||
it('should be reactive', () => {
|
||||
const a = ref(1)
|
||||
let dummy
|
||||
let calls = 0
|
||||
effect(() => {
|
||||
calls++
|
||||
const fn = vi.fn(() => {
|
||||
dummy = a.value
|
||||
})
|
||||
expect(calls).toBe(1)
|
||||
effect(fn)
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
expect(dummy).toBe(1)
|
||||
a.value = 2
|
||||
expect(calls).toBe(2)
|
||||
expect(fn).toHaveBeenCalledTimes(2)
|
||||
expect(dummy).toBe(2)
|
||||
// same value should not trigger
|
||||
a.value = 2
|
||||
expect(calls).toBe(2)
|
||||
expect(fn).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should make nested properties reactive', () => {
|
||||
|
|
|
@ -26,7 +26,6 @@ import {
|
|||
hasChanged,
|
||||
isArray,
|
||||
isIntegerKey,
|
||||
extend,
|
||||
makeMap
|
||||
} from '@vue/shared'
|
||||
import { isRef } from './ref'
|
||||
|
@ -45,11 +44,6 @@ const builtInSymbols = new Set(
|
|||
.filter(isSymbol)
|
||||
)
|
||||
|
||||
const get = /*#__PURE__*/ createGetter()
|
||||
const shallowGet = /*#__PURE__*/ createGetter(false, true)
|
||||
const readonlyGet = /*#__PURE__*/ createGetter(true)
|
||||
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
|
||||
|
||||
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
|
||||
|
||||
function createArrayInstrumentations() {
|
||||
|
@ -91,8 +85,15 @@ function hasOwnProperty(this: object, key: string) {
|
|||
return obj.hasOwnProperty(key)
|
||||
}
|
||||
|
||||
function createGetter(isReadonly = false, shallow = false) {
|
||||
return function get(target: Target, key: string | symbol, receiver: object) {
|
||||
class BaseReactiveHandler implements ProxyHandler<Target> {
|
||||
constructor(
|
||||
protected readonly _isReadonly = false,
|
||||
protected readonly _shallow = false
|
||||
) {}
|
||||
|
||||
get(target: Target, key: string | symbol, receiver: object) {
|
||||
const isReadonly = this._isReadonly,
|
||||
shallow = this._shallow
|
||||
if (key === ReactiveFlags.IS_REACTIVE) {
|
||||
return !isReadonly
|
||||
} else if (key === ReactiveFlags.IS_READONLY) {
|
||||
|
@ -155,11 +156,12 @@ function createGetter(isReadonly = false, shallow = false) {
|
|||
}
|
||||
}
|
||||
|
||||
const set = /*#__PURE__*/ createSetter()
|
||||
const shallowSet = /*#__PURE__*/ createSetter(true)
|
||||
class MutableReactiveHandler extends BaseReactiveHandler {
|
||||
constructor(shallow = false) {
|
||||
super(false, shallow)
|
||||
}
|
||||
|
||||
function createSetter(shallow = false) {
|
||||
return function set(
|
||||
set(
|
||||
target: object,
|
||||
key: string | symbol,
|
||||
value: unknown,
|
||||
|
@ -169,7 +171,7 @@ function createSetter(shallow = false) {
|
|||
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
|
||||
return false
|
||||
}
|
||||
if (!shallow) {
|
||||
if (!this._shallow) {
|
||||
if (!isShallow(value) && !isReadonly(value)) {
|
||||
oldValue = toRaw(oldValue)
|
||||
value = toRaw(value)
|
||||
|
@ -197,42 +199,40 @@ function createSetter(shallow = false) {
|
|||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
function deleteProperty(target: object, key: string | symbol): boolean {
|
||||
const hadKey = hasOwn(target, key)
|
||||
const oldValue = (target as any)[key]
|
||||
const result = Reflect.deleteProperty(target, key)
|
||||
if (result && hadKey) {
|
||||
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
|
||||
deleteProperty(target: object, key: string | symbol): boolean {
|
||||
const hadKey = hasOwn(target, key)
|
||||
const oldValue = (target as any)[key]
|
||||
const result = Reflect.deleteProperty(target, key)
|
||||
if (result && hadKey) {
|
||||
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function has(target: object, key: string | symbol): boolean {
|
||||
const result = Reflect.has(target, key)
|
||||
if (!isSymbol(key) || !builtInSymbols.has(key)) {
|
||||
track(target, TrackOpTypes.HAS, key)
|
||||
has(target: object, key: string | symbol): boolean {
|
||||
const result = Reflect.has(target, key)
|
||||
if (!isSymbol(key) || !builtInSymbols.has(key)) {
|
||||
track(target, TrackOpTypes.HAS, key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
ownKeys(target: object): (string | symbol)[] {
|
||||
track(
|
||||
target,
|
||||
TrackOpTypes.ITERATE,
|
||||
isArray(target) ? 'length' : ITERATE_KEY
|
||||
)
|
||||
return Reflect.ownKeys(target)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function ownKeys(target: object): (string | symbol)[] {
|
||||
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
|
||||
return Reflect.ownKeys(target)
|
||||
}
|
||||
class ReadonlyReactiveHandler extends BaseReactiveHandler {
|
||||
constructor(shallow = false) {
|
||||
super(true, shallow)
|
||||
}
|
||||
|
||||
export const mutableHandlers: ProxyHandler<object> = {
|
||||
get,
|
||||
set,
|
||||
deleteProperty,
|
||||
has,
|
||||
ownKeys
|
||||
}
|
||||
|
||||
export const readonlyHandlers: ProxyHandler<object> = {
|
||||
get: readonlyGet,
|
||||
set(target, key) {
|
||||
set(target: object, key: string | symbol) {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`Set operation on key "${String(key)}" failed: target is readonly.`,
|
||||
|
@ -240,8 +240,9 @@ export const readonlyHandlers: ProxyHandler<object> = {
|
|||
)
|
||||
}
|
||||
return true
|
||||
},
|
||||
deleteProperty(target, key) {
|
||||
}
|
||||
|
||||
deleteProperty(target: object, key: string | symbol) {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`Delete operation on key "${String(key)}" failed: target is readonly.`,
|
||||
|
@ -252,22 +253,18 @@ export const readonlyHandlers: ProxyHandler<object> = {
|
|||
}
|
||||
}
|
||||
|
||||
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
|
||||
{},
|
||||
mutableHandlers,
|
||||
{
|
||||
get: shallowGet,
|
||||
set: shallowSet
|
||||
}
|
||||
export const mutableHandlers: ProxyHandler<object> =
|
||||
/*#__PURE__*/ new MutableReactiveHandler()
|
||||
|
||||
export const readonlyHandlers: ProxyHandler<object> =
|
||||
/*#__PURE__*/ new ReadonlyReactiveHandler()
|
||||
|
||||
export const shallowReactiveHandlers = /*#__PURE__*/ new MutableReactiveHandler(
|
||||
true
|
||||
)
|
||||
|
||||
// Props handlers are special in the sense that it should not unwrap top-level
|
||||
// refs (in order to allow refs to be explicitly passed down), but should
|
||||
// retain the reactivity of the normal readonly object.
|
||||
export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
|
||||
{},
|
||||
readonlyHandlers,
|
||||
{
|
||||
get: shallowReadonlyGet
|
||||
}
|
||||
)
|
||||
export const shallowReadonlyHandlers =
|
||||
/*#__PURE__*/ new ReadonlyReactiveHandler(true)
|
||||
|
|
|
@ -27,7 +27,7 @@ function get(
|
|||
const rawTarget = toRaw(target)
|
||||
const rawKey = toRaw(key)
|
||||
if (!isReadonly) {
|
||||
if (key !== rawKey) {
|
||||
if (hasChanged(key, rawKey)) {
|
||||
track(rawTarget, TrackOpTypes.GET, key)
|
||||
}
|
||||
track(rawTarget, TrackOpTypes.GET, rawKey)
|
||||
|
@ -50,7 +50,7 @@ function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
|
|||
const rawTarget = toRaw(target)
|
||||
const rawKey = toRaw(key)
|
||||
if (!isReadonly) {
|
||||
if (key !== rawKey) {
|
||||
if (hasChanged(key, rawKey)) {
|
||||
track(rawTarget, TrackOpTypes.HAS, key)
|
||||
}
|
||||
track(rawTarget, TrackOpTypes.HAS, rawKey)
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ComputedRefImpl } from './computed'
|
|||
// which maintains a Set of subscribers, but we simply store them as
|
||||
// raw Sets to reduce memory overhead.
|
||||
type KeyToDepMap = Map<any, Dep>
|
||||
const targetMap = new WeakMap<any, KeyToDepMap>()
|
||||
const targetMap = new WeakMap<object, KeyToDepMap>()
|
||||
|
||||
// The number of effects currently being tracked recursively.
|
||||
let effectTrackDepth = 0
|
||||
|
@ -181,7 +181,7 @@ export function effect<T = any>(
|
|||
fn: () => T,
|
||||
options?: ReactiveEffectOptions
|
||||
): ReactiveEffectRunner {
|
||||
if ((fn as ReactiveEffectRunner).effect) {
|
||||
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
|
||||
fn = (fn as ReactiveEffectRunner).effect.fn
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,10 @@ class RefImpl<T> {
|
|||
public dep?: Dep = undefined
|
||||
public readonly __v_isRef = true
|
||||
|
||||
constructor(value: T, public readonly __v_isShallow: boolean) {
|
||||
constructor(
|
||||
value: T,
|
||||
public readonly __v_isShallow: boolean
|
||||
) {
|
||||
this._rawValue = __v_isShallow ? value : toRaw(value)
|
||||
this._value = __v_isShallow ? value : toReactive(value)
|
||||
}
|
||||
|
@ -342,7 +345,7 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
|
|||
|
||||
get value() {
|
||||
const val = this._object[this._key]
|
||||
return val === undefined ? (this._defaultValue as T[K]) : val
|
||||
return val === undefined ? this._defaultValue! : val
|
||||
}
|
||||
|
||||
set value(newVal) {
|
||||
|
|
|
@ -382,7 +382,7 @@ describe('api: options', () => {
|
|||
render() {
|
||||
return this[injectedKey]
|
||||
}
|
||||
} as any)
|
||||
}) as any
|
||||
|
||||
const ChildA = defineChild(['a'], 'a')
|
||||
const ChildB = defineChild({ b: 'a' })
|
||||
|
|
|
@ -1000,7 +1000,7 @@ describe('api: watch', () => {
|
|||
},
|
||||
mounted() {
|
||||
// this call runs while Comp is currentInstance, but
|
||||
// the effect for this `$watch` should nontheless be registered with Child
|
||||
// the effect for this `$watch` should nonetheless be registered with Child
|
||||
this.comp!.$watch(
|
||||
() => this.show,
|
||||
() => void 0
|
||||
|
@ -1171,7 +1171,7 @@ describe('api: watch', () => {
|
|||
expect(instance!.scope.effects.length).toBe(1)
|
||||
})
|
||||
|
||||
test('watchEffect should keep running if created in a detatched scope', async () => {
|
||||
test('watchEffect should keep running if created in a detached scope', async () => {
|
||||
const trigger = ref(0)
|
||||
let countWE = 0
|
||||
let countW = 0
|
||||
|
|
|
@ -475,4 +475,57 @@ describe('renderer: teleport', () => {
|
|||
expect(dir.mounted).toHaveBeenCalledTimes(1)
|
||||
expect(dir.unmounted).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
// #7835
|
||||
test(`ensure that target changes when disabled are updated correctly when enabled`, async () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const target1 = nodeOps.createElement('div')
|
||||
const target2 = nodeOps.createElement('div')
|
||||
const target3 = nodeOps.createElement('div')
|
||||
const target = ref(target1)
|
||||
const disabled = ref(true)
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Fragment, [
|
||||
h(
|
||||
Teleport,
|
||||
{ to: target.value, disabled: disabled.value },
|
||||
h('div', 'teleported')
|
||||
)
|
||||
])
|
||||
}
|
||||
}
|
||||
render(h(App), root)
|
||||
disabled.value = false
|
||||
await nextTick()
|
||||
expect(serializeInner(target1)).toMatchInlineSnapshot(
|
||||
`"<div>teleported</div>"`
|
||||
)
|
||||
expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
|
||||
expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
|
||||
|
||||
disabled.value = true
|
||||
await nextTick()
|
||||
target.value = target2
|
||||
await nextTick()
|
||||
expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
|
||||
expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
|
||||
expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
|
||||
|
||||
target.value = target3
|
||||
await nextTick()
|
||||
expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
|
||||
expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
|
||||
expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
|
||||
|
||||
disabled.value = false
|
||||
await nextTick()
|
||||
expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
|
||||
expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
|
||||
expect(serializeInner(target3)).toMatchInlineSnapshot(
|
||||
`"<div>teleported</div>"`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -116,7 +116,7 @@ describe('api: template refs', () => {
|
|||
const toggle = ref(true)
|
||||
|
||||
const Comp = defineComponent(
|
||||
() => () => toggle.value ? h('div', { ref: fn }) : null
|
||||
() => () => (toggle.value ? h('div', { ref: fn }) : null)
|
||||
)
|
||||
render(h(Comp), root)
|
||||
expect(fn.mock.calls[0][0]).toBe(root.children[0])
|
||||
|
|
|
@ -546,4 +546,16 @@ describe('scheduler', () => {
|
|||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('nextTick should return promise', async () => {
|
||||
const fn = vi.fn(() => {
|
||||
return 1
|
||||
})
|
||||
|
||||
const p = nextTick(fn)
|
||||
|
||||
expect(p).toBeInstanceOf(Promise)
|
||||
expect(await p).toBe(1)
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -40,6 +40,7 @@ export interface AsyncComponentOptions<T = any> {
|
|||
export const isAsyncWrapper = (i: ComponentInternalInstance | VNode): boolean =>
|
||||
!!(i.type as ComponentOptions).__asyncLoader
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function defineAsyncComponent<
|
||||
T extends Component = { new (): ComponentPublicInstance }
|
||||
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
|
||||
|
|
|
@ -221,7 +221,7 @@ export function createAppAPI<HostElement>(
|
|||
set() {
|
||||
warn(
|
||||
`app.config.unwrapInjectedRef has been deprecated. ` +
|
||||
`3.3 now alawys unwraps injected refs in Options API.`
|
||||
`3.3 now always unwraps injected refs in Options API.`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -274,6 +274,7 @@ export function defineComponent<
|
|||
>
|
||||
|
||||
// implementation, close to no-op
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function defineComponent(
|
||||
options: unknown,
|
||||
extraOptions?: ComponentOptions
|
||||
|
|
|
@ -303,7 +303,13 @@ type PropsWithDefaults<
|
|||
? T[K]
|
||||
: NotUndefined<T[K]>
|
||||
: never
|
||||
} & { readonly [K in BKeys]-?: boolean }
|
||||
} & {
|
||||
readonly [K in BKeys]-?: K extends keyof Defaults
|
||||
? Defaults[K] extends undefined
|
||||
? boolean | undefined
|
||||
: boolean
|
||||
: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue `<script setup>` compiler macro for providing props default values when
|
||||
|
|
|
@ -58,6 +58,7 @@ export interface LegacyPublicProperties {
|
|||
export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
||||
const set = (target: any, key: any, val: any) => {
|
||||
target[key] = val
|
||||
return target[key]
|
||||
}
|
||||
|
||||
const del = (target: any, key: any) => {
|
||||
|
|
|
@ -256,7 +256,7 @@ export interface ComponentInternalInstance {
|
|||
*/
|
||||
ssrRender?: Function | null
|
||||
/**
|
||||
* Object containing values this component provides for its descendents
|
||||
* Object containing values this component provides for its descendants
|
||||
* @internal
|
||||
*/
|
||||
provides: Data
|
||||
|
|
|
@ -22,6 +22,9 @@ import { RendererElement } from '../renderer'
|
|||
|
||||
type Hook<T = () => void> = T | T[]
|
||||
|
||||
const leaveCbKey = Symbol('_leaveCb')
|
||||
const enterCbKey = Symbol('_enterCb')
|
||||
|
||||
export interface BaseTransitionProps<HostElement = RendererElement> {
|
||||
mode?: 'in-out' | 'out-in' | 'default'
|
||||
appear?: boolean
|
||||
|
@ -89,8 +92,8 @@ export interface TransitionElement {
|
|||
// in persisted mode (e.g. v-show), the same element is toggled, so the
|
||||
// pending enter/leave callbacks may need to be cancelled if the state is toggled
|
||||
// before it finishes.
|
||||
_enterCb?: PendingCallback
|
||||
_leaveCb?: PendingCallback
|
||||
[enterCbKey]?: PendingCallback
|
||||
[leaveCbKey]?: PendingCallback
|
||||
}
|
||||
|
||||
export function useTransitionState(): TransitionState {
|
||||
|
@ -259,9 +262,9 @@ const BaseTransitionImpl: ComponentOptions = {
|
|||
)
|
||||
leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild
|
||||
// early removal callback
|
||||
el._leaveCb = () => {
|
||||
el[leaveCbKey] = () => {
|
||||
earlyRemove()
|
||||
el._leaveCb = undefined
|
||||
el[leaveCbKey] = undefined
|
||||
delete enterHooks.delayedLeave
|
||||
}
|
||||
enterHooks.delayedLeave = delayedLeave
|
||||
|
@ -366,18 +369,18 @@ export function resolveTransitionHooks(
|
|||
}
|
||||
}
|
||||
// for same element (v-show)
|
||||
if (el._leaveCb) {
|
||||
el._leaveCb(true /* cancelled */)
|
||||
if (el[leaveCbKey]) {
|
||||
el[leaveCbKey](true /* cancelled */)
|
||||
}
|
||||
// for toggled element with same key (v-if)
|
||||
const leavingVNode = leavingVNodesCache[key]
|
||||
if (
|
||||
leavingVNode &&
|
||||
isSameVNodeType(vnode, leavingVNode) &&
|
||||
leavingVNode.el!._leaveCb
|
||||
(leavingVNode.el as TransitionElement)[leaveCbKey]
|
||||
) {
|
||||
// force early removal (not cancelled)
|
||||
leavingVNode.el!._leaveCb()
|
||||
;(leavingVNode.el as TransitionElement)[leaveCbKey]!()
|
||||
}
|
||||
callHook(hook, [el])
|
||||
},
|
||||
|
@ -396,7 +399,7 @@ export function resolveTransitionHooks(
|
|||
}
|
||||
}
|
||||
let called = false
|
||||
const done = (el._enterCb = (cancelled?) => {
|
||||
const done = (el[enterCbKey] = (cancelled?) => {
|
||||
if (called) return
|
||||
called = true
|
||||
if (cancelled) {
|
||||
|
@ -407,7 +410,7 @@ export function resolveTransitionHooks(
|
|||
if (hooks.delayedLeave) {
|
||||
hooks.delayedLeave()
|
||||
}
|
||||
el._enterCb = undefined
|
||||
el[enterCbKey] = undefined
|
||||
})
|
||||
if (hook) {
|
||||
callAsyncHook(hook, [el, done])
|
||||
|
@ -418,15 +421,15 @@ export function resolveTransitionHooks(
|
|||
|
||||
leave(el, remove) {
|
||||
const key = String(vnode.key)
|
||||
if (el._enterCb) {
|
||||
el._enterCb(true /* cancelled */)
|
||||
if (el[enterCbKey]) {
|
||||
el[enterCbKey](true /* cancelled */)
|
||||
}
|
||||
if (state.isUnmounting) {
|
||||
return remove()
|
||||
}
|
||||
callHook(onBeforeLeave, [el])
|
||||
let called = false
|
||||
const done = (el._leaveCb = (cancelled?) => {
|
||||
const done = (el[leaveCbKey] = (cancelled?) => {
|
||||
if (called) return
|
||||
called = true
|
||||
remove()
|
||||
|
@ -435,7 +438,7 @@ export function resolveTransitionHooks(
|
|||
} else {
|
||||
callHook(onAfterLeave, [el])
|
||||
}
|
||||
el._leaveCb = undefined
|
||||
el[leaveCbKey] = undefined
|
||||
if (leavingVNodesCache[key] === vnode) {
|
||||
delete leavingVNodesCache[key]
|
||||
}
|
||||
|
|
|
@ -186,6 +186,13 @@ export const TeleportImpl = {
|
|||
internals,
|
||||
TeleportMoveTypes.TOGGLE
|
||||
)
|
||||
} else {
|
||||
// #7835
|
||||
// When `teleport` is disabled, `to` may change, making it always old,
|
||||
// to ensure the correct `to` when enabled
|
||||
if (n2.props && n1.props && n2.props.to !== n1.props.to) {
|
||||
n2.props.to = n1.props.to
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// target changed
|
||||
|
@ -393,7 +400,7 @@ function hydrateTeleport(
|
|||
// Force-casted public typing for h and TSX props inference
|
||||
export const Teleport = TeleportImpl as unknown as {
|
||||
__isTeleport: true
|
||||
new(): {
|
||||
new (): {
|
||||
$props: VNodeProps & TeleportProps
|
||||
$slots: {
|
||||
default(): VNode[]
|
||||
|
|
|
@ -30,7 +30,7 @@ interface DevtoolsHook {
|
|||
appRecords: AppRecord[]
|
||||
/**
|
||||
* Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9
|
||||
* Returns wether the arg was buffered or not
|
||||
* Returns whether the arg was buffered or not
|
||||
*/
|
||||
cleanupBuffer?: (matchArg: unknown) => boolean
|
||||
}
|
||||
|
|
|
@ -134,8 +134,10 @@ export function createHydrationFunctions(
|
|||
__DEV__ &&
|
||||
warn(
|
||||
`Hydration text mismatch:` +
|
||||
`\n- Client: ${JSON.stringify((node as Text).data)}` +
|
||||
`\n- Server: ${JSON.stringify(vnode.children)}`
|
||||
`\n- Server rendered: ${JSON.stringify(
|
||||
(node as Text).data
|
||||
)}` +
|
||||
`\n- Client rendered: ${JSON.stringify(vnode.children)}`
|
||||
)
|
||||
;(node as Text).data = vnode.children as string
|
||||
}
|
||||
|
@ -406,8 +408,8 @@ export function createHydrationFunctions(
|
|||
`Hydration text content mismatch in <${
|
||||
vnode.type as string
|
||||
}>:\n` +
|
||||
`- Client: ${el.textContent}\n` +
|
||||
`- Server: ${vnode.children as string}`
|
||||
`- Server rendered: ${el.textContent}\n` +
|
||||
`- Client rendered: ${vnode.children as string}`
|
||||
)
|
||||
el.textContent = vnode.children as string
|
||||
}
|
||||
|
|
|
@ -73,8 +73,13 @@ export {
|
|||
defineSlots,
|
||||
defineModel,
|
||||
withDefaults,
|
||||
useModel,
|
||||
// internal
|
||||
useModel
|
||||
} from './apiSetupHelpers'
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export {
|
||||
mergeDefaults,
|
||||
mergeModels,
|
||||
createPropsRestProxy,
|
||||
|
@ -111,7 +116,9 @@ export { useSSRContext, ssrContextKey } from './helpers/useSsrContext'
|
|||
|
||||
export { createRenderer, createHydrationRenderer } from './renderer'
|
||||
export { queuePostFlushCb } from './scheduler'
|
||||
export { warn, assertNumber } from './warning'
|
||||
export { warn } from './warning'
|
||||
/** @internal */
|
||||
export { assertNumber } from './warning'
|
||||
export {
|
||||
handleError,
|
||||
callWithErrorHandling,
|
||||
|
|
|
@ -584,7 +584,7 @@ function baseCreateRenderer(
|
|||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
isSVG = isSVG || (n2.type as string) === 'svg'
|
||||
isSVG = isSVG || n2.type === 'svg'
|
||||
if (n1 == null) {
|
||||
mountElement(
|
||||
n2,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||
import { isArray, NOOP } from '@vue/shared'
|
||||
import { Awaited, isArray, NOOP } from '@vue/shared'
|
||||
import { ComponentInternalInstance, getComponentName } from './component'
|
||||
import { warn } from './warning'
|
||||
|
||||
|
@ -50,10 +50,10 @@ let currentFlushPromise: Promise<void> | null = null
|
|||
const RECURSION_LIMIT = 100
|
||||
type CountMap = Map<SchedulerJob, number>
|
||||
|
||||
export function nextTick<T = void>(
|
||||
export function nextTick<T = void, R = void>(
|
||||
this: T,
|
||||
fn?: (this: T) => void
|
||||
): Promise<void> {
|
||||
fn?: (this: T) => R
|
||||
): Promise<Awaited<R>> {
|
||||
const p = currentFlushPromise || resolvedPromise
|
||||
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
||||
}
|
||||
|
|
|
@ -681,7 +681,7 @@ export function cloneVNode<T, U>(
|
|||
if (__COMPAT__) {
|
||||
defineLegacyVNodeProperties(cloned as VNode)
|
||||
}
|
||||
return cloned as any
|
||||
return cloned
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -322,7 +322,7 @@ describe('defineCustomElement', () => {
|
|||
emit('my-click', 1)
|
||||
},
|
||||
onMousedown: () => {
|
||||
emit('myEvent', 1) // validate hypenization
|
||||
emit('myEvent', 1) // validate hyphenation
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ function triggerEvent(
|
|||
event: string,
|
||||
process?: (e: any) => any
|
||||
) {
|
||||
const e = document.createEvent('HTMLEvents')
|
||||
e.initEvent(event, true, true)
|
||||
const e = new Event(event, {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
})
|
||||
if (event === 'click') {
|
||||
;(e as any).button = 0
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { patchProp } from '../src/patchProp'
|
||||
import { ElementWithTransition } from '../src/components/Transition'
|
||||
import { ElementWithTransition, vtcKey } from '../src/components/Transition'
|
||||
import { svgNS } from '../src/nodeOps'
|
||||
|
||||
describe('runtime-dom: class patching', () => {
|
||||
|
@ -13,12 +13,12 @@ describe('runtime-dom: class patching', () => {
|
|||
|
||||
test('transition class', () => {
|
||||
const el = document.createElement('div') as ElementWithTransition
|
||||
el._vtc = new Set(['bar', 'baz'])
|
||||
el[vtcKey] = new Set(['bar', 'baz'])
|
||||
patchProp(el, 'class', null, 'foo')
|
||||
expect(el.className).toBe('foo bar baz')
|
||||
patchProp(el, 'class', null, null)
|
||||
expect(el.className).toBe('bar baz')
|
||||
delete el._vtc
|
||||
delete el[vtcKey]
|
||||
patchProp(el, 'class', null, 'foo')
|
||||
expect(el.className).toBe('foo')
|
||||
})
|
||||
|
|
|
@ -37,6 +37,6 @@
|
|||
"dependencies": {
|
||||
"@vue/shared": "3.3.4",
|
||||
"@vue/runtime-core": "3.3.4",
|
||||
"csstype": "^3.1.1"
|
||||
"csstype": "^3.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,6 +140,7 @@ export function defineCustomElement(options: {
|
|||
new (...args: any[]): ComponentPublicInstance
|
||||
}): VueElementConstructor
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function defineCustomElement(
|
||||
options: any,
|
||||
hydrate?: RootHydrateFunction
|
||||
|
@ -155,6 +156,7 @@ export function defineCustomElement(
|
|||
return VueCustomElement
|
||||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export const defineSSRCustomElement = ((options: any) => {
|
||||
// @ts-ignore
|
||||
return defineCustomElement(options, hydrate)
|
||||
|
@ -176,7 +178,7 @@ export class VueElement extends BaseClass {
|
|||
private _resolved = false
|
||||
private _numberProps: Record<string, true> | null = null
|
||||
private _styles?: HTMLStyleElement[]
|
||||
|
||||
private _ob?: MutationObserver | null = null
|
||||
constructor(
|
||||
private _def: InnerComponentDef,
|
||||
private _props: Record<string, any> = {},
|
||||
|
@ -213,6 +215,10 @@ export class VueElement extends BaseClass {
|
|||
|
||||
disconnectedCallback() {
|
||||
this._connected = false
|
||||
if (this._ob) {
|
||||
this._ob.disconnect()
|
||||
this._ob = null
|
||||
}
|
||||
nextTick(() => {
|
||||
if (!this._connected) {
|
||||
render(null, this.shadowRoot!)
|
||||
|
@ -233,11 +239,13 @@ export class VueElement extends BaseClass {
|
|||
}
|
||||
|
||||
// watch future attr changes
|
||||
new MutationObserver(mutations => {
|
||||
this._ob = new MutationObserver(mutations => {
|
||||
for (const m of mutations) {
|
||||
this._setAttr(m.attributeName!)
|
||||
}
|
||||
}).observe(this, { attributes: true })
|
||||
})
|
||||
|
||||
this._ob.observe(this, { attributes: true })
|
||||
|
||||
const resolve = (def: InnerComponentDef, isAsync = false) => {
|
||||
const { props, styles } = def
|
||||
|
|
|
@ -32,12 +32,14 @@ export interface TransitionProps extends BaseTransitionProps<Element> {
|
|||
leaveToClass?: string
|
||||
}
|
||||
|
||||
export const vtcKey = Symbol('_vtc')
|
||||
|
||||
export interface ElementWithTransition extends HTMLElement {
|
||||
// _vtc = Vue Transition Classes.
|
||||
// Store the temporarily-added transition classes on the element
|
||||
// so that we can avoid overwriting them if the element's class is patched
|
||||
// during the transition.
|
||||
_vtc?: Set<string>
|
||||
[vtcKey]?: Set<string>
|
||||
}
|
||||
|
||||
// DOM Transition is a higher-order-component based on the platform-agnostic
|
||||
|
@ -295,18 +297,18 @@ function NumberOf(val: unknown): number {
|
|||
export function addTransitionClass(el: Element, cls: string) {
|
||||
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
|
||||
;(
|
||||
(el as ElementWithTransition)._vtc ||
|
||||
((el as ElementWithTransition)._vtc = new Set())
|
||||
(el as ElementWithTransition)[vtcKey] ||
|
||||
((el as ElementWithTransition)[vtcKey] = new Set())
|
||||
).add(cls)
|
||||
}
|
||||
|
||||
export function removeTransitionClass(el: Element, cls: string) {
|
||||
cls.split(/\s+/).forEach(c => c && el.classList.remove(c))
|
||||
const { _vtc } = el as ElementWithTransition
|
||||
const _vtc = (el as ElementWithTransition)[vtcKey]
|
||||
if (_vtc) {
|
||||
_vtc.delete(cls)
|
||||
if (!_vtc!.size) {
|
||||
;(el as ElementWithTransition)._vtc = undefined
|
||||
;(el as ElementWithTransition)[vtcKey] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -445,6 +447,8 @@ function getTimeout(delays: string[], durations: string[]): number {
|
|||
// If comma is not replaced with a dot, the input will be rounded down
|
||||
// (i.e. acting as a floor function) causing unexpected behaviors
|
||||
function toMs(s: string): number {
|
||||
// #8409 default value for CSS durations can be 'auto'
|
||||
if (s === 'auto') return 0
|
||||
return Number(s.slice(0, -1).replace(',', '.')) * 1000
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@ import {
|
|||
getTransitionInfo,
|
||||
resolveTransitionProps,
|
||||
TransitionPropsValidators,
|
||||
forceReflow
|
||||
forceReflow,
|
||||
vtcKey
|
||||
} from './Transition'
|
||||
import {
|
||||
Fragment,
|
||||
|
@ -29,7 +30,8 @@ import { extend } from '@vue/shared'
|
|||
|
||||
const positionMap = new WeakMap<VNode, DOMRect>()
|
||||
const newPositionMap = new WeakMap<VNode, DOMRect>()
|
||||
|
||||
const moveCbKey = Symbol('_moveCb')
|
||||
const enterCbKey = Symbol('_enterCb')
|
||||
export type TransitionGroupProps = Omit<TransitionProps, 'mode'> & {
|
||||
tag?: string
|
||||
moveClass?: string
|
||||
|
@ -80,13 +82,13 @@ const TransitionGroupImpl: ComponentOptions = {
|
|||
const style = el.style
|
||||
addTransitionClass(el, moveClass)
|
||||
style.transform = style.webkitTransform = style.transitionDuration = ''
|
||||
const cb = ((el as any)._moveCb = (e: TransitionEvent) => {
|
||||
const cb = ((el as any)[moveCbKey] = (e: TransitionEvent) => {
|
||||
if (e && e.target !== el) {
|
||||
return
|
||||
}
|
||||
if (!e || /transform$/.test(e.propertyName)) {
|
||||
el.removeEventListener('transitionend', cb)
|
||||
;(el as any)._moveCb = null
|
||||
;(el as any)[moveCbKey] = null
|
||||
removeTransitionClass(el, moveClass)
|
||||
}
|
||||
})
|
||||
|
@ -162,11 +164,11 @@ export const TransitionGroup = TransitionGroupImpl as unknown as {
|
|||
|
||||
function callPendingCbs(c: VNode) {
|
||||
const el = c.el as any
|
||||
if (el._moveCb) {
|
||||
el._moveCb()
|
||||
if (el[moveCbKey]) {
|
||||
el[moveCbKey]()
|
||||
}
|
||||
if (el._enterCb) {
|
||||
el._enterCb()
|
||||
if (el[enterCbKey]) {
|
||||
el[enterCbKey]()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,8 +200,9 @@ function hasCSSTransform(
|
|||
// all other transition classes applied to ensure only the move class
|
||||
// is applied.
|
||||
const clone = el.cloneNode() as HTMLElement
|
||||
if (el._vtc) {
|
||||
el._vtc.forEach(cls => {
|
||||
const _vtc = el[vtcKey]
|
||||
if (_vtc) {
|
||||
_vtc.forEach(cls => {
|
||||
cls.split(/\s+/).forEach(c => c && clone.classList.remove(c))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -36,7 +36,9 @@ function onCompositionEnd(e: Event) {
|
|||
}
|
||||
}
|
||||
|
||||
type ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }>
|
||||
const assignKey = Symbol('_assign')
|
||||
|
||||
type ModelDirective<T> = ObjectDirective<T & { [assignKey]: AssignerFn }>
|
||||
|
||||
// We are exporting the v-model runtime directly as vnode hooks so that it can
|
||||
// be tree-shaken in case v-model is never used.
|
||||
|
@ -44,7 +46,7 @@ export const vModelText: ModelDirective<
|
|||
HTMLInputElement | HTMLTextAreaElement
|
||||
> = {
|
||||
created(el, { modifiers: { lazy, trim, number } }, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
const castToNumber =
|
||||
number || (vnode.props && vnode.props.type === 'number')
|
||||
addEventListener(el, lazy ? 'change' : 'input', e => {
|
||||
|
@ -56,7 +58,7 @@ export const vModelText: ModelDirective<
|
|||
if (castToNumber) {
|
||||
domValue = looseToNumber(domValue)
|
||||
}
|
||||
el._assign(domValue)
|
||||
el[assignKey](domValue)
|
||||
})
|
||||
if (trim) {
|
||||
addEventListener(el, 'change', () => {
|
||||
|
@ -78,7 +80,7 @@ export const vModelText: ModelDirective<
|
|||
el.value = value == null ? '' : value
|
||||
},
|
||||
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
// avoid clearing unresolved text. #2302
|
||||
if ((el as any).composing) return
|
||||
if (document.activeElement === el && el.type !== 'range') {
|
||||
|
@ -106,12 +108,12 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
|||
// #4096 array checkboxes need to be deep traversed
|
||||
deep: true,
|
||||
created(el, _, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
addEventListener(el, 'change', () => {
|
||||
const modelValue = (el as any)._modelValue
|
||||
const elementValue = getValue(el)
|
||||
const checked = el.checked
|
||||
const assign = el._assign
|
||||
const assign = el[assignKey]
|
||||
if (isArray(modelValue)) {
|
||||
const index = looseIndexOf(modelValue, elementValue)
|
||||
const found = index !== -1
|
||||
|
@ -138,7 +140,7 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
|||
// set initial checked on mount to wait for true-value/false-value
|
||||
mounted: setChecked,
|
||||
beforeUpdate(el, binding, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
setChecked(el, binding, vnode)
|
||||
}
|
||||
}
|
||||
|
@ -163,13 +165,13 @@ function setChecked(
|
|||
export const vModelRadio: ModelDirective<HTMLInputElement> = {
|
||||
created(el, { value }, vnode) {
|
||||
el.checked = looseEqual(value, vnode.props!.value)
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
addEventListener(el, 'change', () => {
|
||||
el._assign(getValue(el))
|
||||
el[assignKey](getValue(el))
|
||||
})
|
||||
},
|
||||
beforeUpdate(el, { value, oldValue }, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
if (value !== oldValue) {
|
||||
el.checked = looseEqual(value, vnode.props!.value)
|
||||
}
|
||||
|
@ -187,7 +189,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
|||
.map((o: HTMLOptionElement) =>
|
||||
number ? looseToNumber(getValue(o)) : getValue(o)
|
||||
)
|
||||
el._assign(
|
||||
el[assignKey](
|
||||
el.multiple
|
||||
? isSetModel
|
||||
? new Set(selectedVal)
|
||||
|
@ -195,7 +197,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
|||
: selectedVal[0]
|
||||
)
|
||||
})
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
},
|
||||
// set value in mounted & updated because <select> relies on its children
|
||||
// <option>s.
|
||||
|
@ -203,7 +205,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
|||
setSelected(el, value)
|
||||
},
|
||||
beforeUpdate(el, _binding, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
},
|
||||
updated(el, { value }) {
|
||||
setSelected(el, value)
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { ObjectDirective } from '@vue/runtime-core'
|
||||
|
||||
export const vShowOldKey = Symbol('_vod')
|
||||
|
||||
interface VShowElement extends HTMLElement {
|
||||
// _vod = vue original display
|
||||
_vod: string
|
||||
[vShowOldKey]: string
|
||||
}
|
||||
|
||||
export const vShow: ObjectDirective<VShowElement> = {
|
||||
beforeMount(el, { value }, { transition }) {
|
||||
el._vod = el.style.display === 'none' ? '' : el.style.display
|
||||
el[vShowOldKey] = el.style.display === 'none' ? '' : el.style.display
|
||||
if (transition && value) {
|
||||
transition.beforeEnter(el)
|
||||
} else {
|
||||
|
@ -41,7 +43,7 @@ export const vShow: ObjectDirective<VShowElement> = {
|
|||
}
|
||||
|
||||
function setDisplay(el: VShowElement, value: unknown): void {
|
||||
el.style.display = value ? el._vod : 'none'
|
||||
el.style.display = value ? el[vShowOldKey] : 'none'
|
||||
}
|
||||
|
||||
// SSR vnode transforms, only used when user includes client-oriented render
|
||||
|
|
|
@ -248,8 +248,9 @@ export interface HTMLAttributes extends AriaAttributes, EventHandlers<Events> {
|
|||
contextmenu?: string
|
||||
dir?: string
|
||||
draggable?: Booleanish
|
||||
hidden?: Booleanish
|
||||
hidden?: Booleanish | '' | 'hidden' | 'until-found'
|
||||
id?: string
|
||||
inert?: Booleanish
|
||||
lang?: string
|
||||
placeholder?: string
|
||||
spellcheck?: Booleanish
|
||||
|
@ -457,6 +458,7 @@ export interface ImgHTMLAttributes extends HTMLAttributes {
|
|||
srcset?: string
|
||||
usemap?: string
|
||||
width?: Numberish
|
||||
loading?: 'lazy' | 'eager'
|
||||
}
|
||||
|
||||
export interface InsHTMLAttributes extends HTMLAttributes {
|
||||
|
@ -464,6 +466,31 @@ export interface InsHTMLAttributes extends HTMLAttributes {
|
|||
datetime?: string
|
||||
}
|
||||
|
||||
export type InputTypeHTMLAttribute =
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'hidden'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'radio'
|
||||
| 'range'
|
||||
| 'reset'
|
||||
| 'search'
|
||||
| 'submit'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week'
|
||||
| (string & {})
|
||||
|
||||
export interface InputHTMLAttributes extends HTMLAttributes {
|
||||
accept?: string
|
||||
alt?: string
|
||||
|
@ -495,7 +522,7 @@ export interface InputHTMLAttributes extends HTMLAttributes {
|
|||
size?: Numberish
|
||||
src?: string
|
||||
step?: Numberish
|
||||
type?: string
|
||||
type?: InputTypeHTMLAttribute
|
||||
value?: any // we support :value to be bound to anything w/ v-model
|
||||
width?: Numberish
|
||||
}
|
||||
|
@ -677,7 +704,7 @@ export interface TextareaHTMLAttributes extends HTMLAttributes {
|
|||
minlength?: Numberish
|
||||
name?: string
|
||||
placeholder?: string
|
||||
readonly?: boolean
|
||||
readonly?: Booleanish
|
||||
required?: Booleanish
|
||||
rows?: Numberish
|
||||
value?: string | string[] | number
|
||||
|
@ -749,7 +776,7 @@ export interface SVGAttributes extends AriaAttributes, EventHandlers<Events> {
|
|||
* @see https://www.w3.org/TR/SVG/styling.html#ElementSpecificStyling
|
||||
*/
|
||||
class?: any
|
||||
style?: string | CSSProperties
|
||||
style?: StyleValue
|
||||
|
||||
color?: string
|
||||
height?: Numberish
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ElementWithTransition } from '../components/Transition'
|
||||
import { ElementWithTransition, vtcKey } from '../components/Transition'
|
||||
|
||||
// compiler should normalize class + :class bindings on the same element
|
||||
// into a single binding ['staticClass', dynamic]
|
||||
|
@ -6,7 +6,7 @@ export function patchClass(el: Element, value: string | null, isSVG: boolean) {
|
|||
// directly setting className should be faster than setAttribute in theory
|
||||
// if this is an element during a transition, take the temporary transition
|
||||
// classes into account.
|
||||
const transitionClasses = (el as ElementWithTransition)._vtc
|
||||
const transitionClasses = (el as ElementWithTransition)[vtcKey]
|
||||
if (transitionClasses) {
|
||||
value = (
|
||||
value ? [value, ...transitionClasses] : [...transitionClasses]
|
||||
|
|
|
@ -30,15 +30,17 @@ export function removeEventListener(
|
|||
el.removeEventListener(event, handler, options)
|
||||
}
|
||||
|
||||
const veiKey = Symbol('_vei')
|
||||
|
||||
export function patchEvent(
|
||||
el: Element & { _vei?: Record<string, Invoker | undefined> },
|
||||
el: Element & { [veiKey]?: Record<string, Invoker | undefined> },
|
||||
rawName: string,
|
||||
prevValue: EventValue | null,
|
||||
nextValue: EventValue | null,
|
||||
instance: ComponentInternalInstance | null = null
|
||||
) {
|
||||
// vei = vue event invokers
|
||||
const invokers = el._vei || (el._vei = {})
|
||||
const invokers = el[veiKey] || (el[veiKey] = {})
|
||||
const existingInvoker = invokers[rawName]
|
||||
if (nextValue && existingInvoker) {
|
||||
// patch
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { isString, hyphenate, capitalize, isArray } from '@vue/shared'
|
||||
import { camelize, warn } from '@vue/runtime-core'
|
||||
import { vShowOldKey } from '../directives/vShow'
|
||||
|
||||
type Style = string | Record<string, string | string[]> | null
|
||||
|
||||
|
@ -29,7 +30,7 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
|
|||
// indicates that the `display` of the element is controlled by `v-show`,
|
||||
// so we always keep the current `display` value regardless of the `style`
|
||||
// value, thus handing over control to `v-show`.
|
||||
if ('_vod' in el) {
|
||||
if (vShowOldKey in el) {
|
||||
style.display = currentDisplay
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,30 @@ describe('ssr: directives', () => {
|
|||
).toBe(`<input type="radio">`)
|
||||
})
|
||||
|
||||
test('select', async () => {
|
||||
expect(
|
||||
await renderToString(
|
||||
createApp({
|
||||
data: () => ({ model: 1 }),
|
||||
template: `<select v-model="model"><option value="0"></option><option value="1"></option></select>`
|
||||
})
|
||||
)
|
||||
).toBe(
|
||||
`<select><option value="0"></option><option value="1" selected></option></select>`
|
||||
)
|
||||
|
||||
expect(
|
||||
await renderToString(
|
||||
createApp({
|
||||
data: () => ({ model: [0, 1] }),
|
||||
template: `<select multiple v-model="model"><option value="0"></option><option value="1"></option></select>`
|
||||
})
|
||||
)
|
||||
).toBe(
|
||||
`<select multiple><option value="0" selected></option><option value="1" selected></option></select>`
|
||||
)
|
||||
})
|
||||
|
||||
test('checkbox', async () => {
|
||||
expect(
|
||||
await renderToString(
|
||||
|
|
|
@ -156,7 +156,7 @@ describe('ssr: scopedId runtime behavior', () => {
|
|||
})
|
||||
|
||||
// #3513
|
||||
test('scopeId inheritance across ssr-compiled andn on-ssr compiled parent chain', async () => {
|
||||
test('scopeId inheritance across ssr-compiled and on-ssr compiled parent chain', async () => {
|
||||
const Child = {
|
||||
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||
push(`<div${ssrRenderAttrs(attrs)}></div>`)
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
<title>Vue SFC Playground</title>
|
||||
<script>
|
||||
// process shim for old versions of @vue/compiler-sfc dependency
|
||||
window.process = { env: {} }
|
||||
const savedPreferDark = localStorage.getItem('vue-sfc-playground-prefer-dark')
|
||||
if (
|
||||
savedPreferDark === 'true' ||
|
||||
(!savedPreferDark && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
window.process = { env: {} }
|
||||
const savedPreferDark = localStorage.getItem('vue-sfc-playground-prefer-dark')
|
||||
if (
|
||||
savedPreferDark === 'true' ||
|
||||
(!savedPreferDark && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
document.documentElement.classList.add('dark')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</head>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "@vue/sfc-playground",
|
||||
"version": "3.3.4",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
@ -8,13 +9,13 @@
|
|||
"serve": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"vite": "^4.3.0"
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"vite": "^4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^1.4.1",
|
||||
"@vue/repl": "^2.5.8",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.6.0",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import Header from './Header.vue'
|
||||
import { Repl, ReplStore, SFCOptions } from '@vue/repl'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import Monaco from '@vue/repl/monaco-editor'
|
||||
import { ref, watchEffect, onMounted } from 'vue'
|
||||
|
||||
const setVH = () => {
|
||||
document.documentElement.style.setProperty('--vh', window.innerHeight + `px`)
|
||||
|
@ -71,6 +72,15 @@ function toggleSSR() {
|
|||
useSSRMode.value = !useSSRMode.value
|
||||
store.setFiles(store.getFiles())
|
||||
}
|
||||
|
||||
const theme = ref<'dark' | 'light'>('dark')
|
||||
function toggleTheme(isDark: boolean) {
|
||||
theme.value = isDark ? 'dark' : 'light'
|
||||
}
|
||||
onMounted(() => {
|
||||
const cls = document.documentElement.classList
|
||||
toggleTheme(cls.contains('dark'))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -78,10 +88,13 @@ function toggleSSR() {
|
|||
:store="store"
|
||||
:dev="useDevMode"
|
||||
:ssr="useSSRMode"
|
||||
@toggle-theme="toggleTheme"
|
||||
@toggle-dev="toggleDevMode"
|
||||
@toggle-ssr="toggleSSR"
|
||||
/>
|
||||
<Repl
|
||||
:theme="theme"
|
||||
:editor="Monaco"
|
||||
@keydown.ctrl.s.prevent
|
||||
@keydown.meta.s.prevent
|
||||
:ssr="useSSRMode"
|
||||
|
@ -108,7 +121,7 @@ body {
|
|||
}
|
||||
|
||||
.vue-repl {
|
||||
height: calc(var(--vh) - var(--nav-height));
|
||||
height: calc(var(--vh) - var(--nav-height)) !important;
|
||||
}
|
||||
|
||||
button {
|
||||
|
|
|
@ -1,44 +1,35 @@
|
|||
<script setup lang="ts">
|
||||
import { downloadProject } from './download/download'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import Sun from './icons/Sun.vue'
|
||||
import Moon from './icons/Moon.vue'
|
||||
import Share from './icons/Share.vue'
|
||||
import Download from './icons/Download.vue'
|
||||
import GitHub from './icons/GitHub.vue'
|
||||
import type { ReplStore } from '@vue/repl'
|
||||
import VersionSelect from './VersionSelect.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
store: ReplStore
|
||||
dev: boolean
|
||||
ssr: boolean
|
||||
}>()
|
||||
const emit = defineEmits(['toggle-theme', 'toggle-ssr', 'toggle-dev'])
|
||||
|
||||
const { store } = props
|
||||
|
||||
const currentCommit = __COMMIT__
|
||||
const activeVersion = ref(`@${currentCommit}`)
|
||||
const publishedVersions = ref<string[]>()
|
||||
const expanded = ref(false)
|
||||
|
||||
async function toggle() {
|
||||
expanded.value = !expanded.value
|
||||
if (!publishedVersions.value) {
|
||||
publishedVersions.value = await fetchVersions()
|
||||
}
|
||||
}
|
||||
const vueVersion = ref(`@${currentCommit}`)
|
||||
|
||||
async function setVueVersion(v: string) {
|
||||
activeVersion.value = `loading...`
|
||||
vueVersion.value = `loading...`
|
||||
await store.setVueVersion(v)
|
||||
activeVersion.value = `v${v}`
|
||||
expanded.value = false
|
||||
vueVersion.value = `v${v}`
|
||||
}
|
||||
|
||||
function resetVueVersion() {
|
||||
store.resetVueVersion()
|
||||
activeVersion.value = `@${currentCommit}`
|
||||
expanded.value = false
|
||||
vueVersion.value = `@${currentCommit}`
|
||||
}
|
||||
|
||||
async function copyLink(e: MouseEvent) {
|
||||
|
@ -58,45 +49,7 @@ function toggleDark() {
|
|||
'vue-sfc-playground-prefer-dark',
|
||||
String(cls.contains('dark'))
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
window.addEventListener('click', () => {
|
||||
expanded.value = false
|
||||
})
|
||||
window.addEventListener('blur', () => {
|
||||
if (document.activeElement?.tagName === 'IFRAME') {
|
||||
expanded.value = false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
async function fetchVersions(): Promise<string[]> {
|
||||
const res = await fetch(
|
||||
`https://api.github.com/repos/vuejs/core/releases?per_page=100`
|
||||
)
|
||||
const releases: any[] = await res.json()
|
||||
const versions = releases.map(r =>
|
||||
/^v/.test(r.tag_name) ? r.tag_name.slice(1) : r.tag_name
|
||||
)
|
||||
// if the latest version is a pre-release, list all current pre-releases
|
||||
// otherwise filter out pre-releases
|
||||
let isInPreRelease = versions[0].includes('-')
|
||||
const filteredVersions: string[] = []
|
||||
for (const v of versions) {
|
||||
if (v.includes('-')) {
|
||||
if (isInPreRelease) {
|
||||
filteredVersions.push(v)
|
||||
}
|
||||
} else {
|
||||
filteredVersions.push(v)
|
||||
isInPreRelease = false
|
||||
}
|
||||
if (filteredVersions.length >= 30 || v === '3.0.10') {
|
||||
break
|
||||
}
|
||||
}
|
||||
return filteredVersions
|
||||
emit('toggle-theme', cls.contains('dark'))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -107,28 +60,28 @@ async function fetchVersions(): Promise<string[]> {
|
|||
<span>Vue SFC Playground</span>
|
||||
</h1>
|
||||
<div class="links">
|
||||
<div class="version" @click.stop>
|
||||
<span class="active-version" @click="toggle">
|
||||
Version
|
||||
<span class="number">{{ activeVersion }}</span>
|
||||
</span>
|
||||
<ul class="versions" :class="{ expanded }">
|
||||
<li v-if="!publishedVersions"><a>loading versions...</a></li>
|
||||
<li v-for="version of publishedVersions">
|
||||
<a @click="setVueVersion(version)">v{{ version }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a @click="resetVueVersion">This Commit ({{ currentCommit }})</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://app.netlify.com/sites/vue-sfc-playground/deploys"
|
||||
target="_blank"
|
||||
>Commits History</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<VersionSelect
|
||||
v-model="store.state.typescriptVersion"
|
||||
pkg="typescript"
|
||||
label="TypeScript Version"
|
||||
/>
|
||||
<VersionSelect
|
||||
:model-value="vueVersion"
|
||||
@update:model-value="setVueVersion"
|
||||
pkg="vue"
|
||||
label="Vue Version"
|
||||
>
|
||||
<li>
|
||||
<a @click="resetVueVersion">This Commit ({{ currentCommit }})</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://app.netlify.com/sites/vue-sfc-playground/deploys"
|
||||
target="_blank"
|
||||
>Commits History</a
|
||||
>
|
||||
</li>
|
||||
</VersionSelect>
|
||||
<button
|
||||
title="Toggle development production mode"
|
||||
class="toggle-dev"
|
||||
|
@ -159,14 +112,14 @@ async function fetchVersions(): Promise<string[]> {
|
|||
>
|
||||
<Download />
|
||||
</button>
|
||||
<button title="View on GitHub" class="github">
|
||||
<a
|
||||
href="https://github.com/vuejs/core/tree/main/packages/sfc-playground"
|
||||
target="_blank"
|
||||
>
|
||||
<GitHub />
|
||||
</a>
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/vuejs/core/tree/main/packages/sfc-playground"
|
||||
target="_blank"
|
||||
title="View on GitHub"
|
||||
class="github"
|
||||
>
|
||||
<GitHub />
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
@ -233,33 +186,6 @@ h1 img {
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.version {
|
||||
margin-right: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.active-version {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.active-version .number {
|
||||
color: var(--green);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.active-version::after {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 6px solid #aaa;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.toggle-dev span,
|
||||
.toggle-ssr span {
|
||||
font-size: 12px;
|
||||
|
@ -300,12 +226,13 @@ h1 img {
|
|||
}
|
||||
|
||||
.links button,
|
||||
.links button a {
|
||||
.links .github {
|
||||
padding: 1px 6px;
|
||||
color: var(--btn);
|
||||
}
|
||||
|
||||
.links button:hover,
|
||||
.links button:hover a {
|
||||
.links .github:hover {
|
||||
color: var(--highlight);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const expanded = ref(false)
|
||||
const versions = ref<string[]>()
|
||||
|
||||
const version = defineModel()
|
||||
const props = defineProps<{
|
||||
pkg: string
|
||||
label: string
|
||||
}>()
|
||||
|
||||
async function toggle() {
|
||||
expanded.value = !expanded.value
|
||||
if (!versions.value) {
|
||||
versions.value = await fetchVersions()
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchVersions(): Promise<string[]> {
|
||||
const res = await fetch(
|
||||
`https://data.jsdelivr.com/v1/package/npm/${props.pkg}`
|
||||
)
|
||||
const { versions } = (await res.json()) as { versions: string[] }
|
||||
|
||||
if (props.pkg === 'vue') {
|
||||
// if the latest version is a pre-release, list all current pre-releases
|
||||
// otherwise filter out pre-releases
|
||||
let isInPreRelease = versions[0].includes('-')
|
||||
const filteredVersions: string[] = []
|
||||
for (const v of versions) {
|
||||
if (v.includes('-')) {
|
||||
if (isInPreRelease) {
|
||||
filteredVersions.push(v)
|
||||
}
|
||||
} else {
|
||||
filteredVersions.push(v)
|
||||
isInPreRelease = false
|
||||
}
|
||||
if (filteredVersions.length >= 30 || v === '3.0.10') {
|
||||
break
|
||||
}
|
||||
}
|
||||
return filteredVersions
|
||||
} else if (props.pkg === 'typescript') {
|
||||
return versions.filter(v => !v.includes('dev') && !v.includes('insiders'))
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
function setVersion(v: string) {
|
||||
version.value = v
|
||||
expanded.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', () => {
|
||||
expanded.value = false
|
||||
})
|
||||
window.addEventListener('blur', () => {
|
||||
if (document.activeElement?.tagName === 'IFRAME') {
|
||||
expanded.value = false
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="version" @click.stop>
|
||||
<span class="active-version" @click="toggle">
|
||||
{{ label }}
|
||||
<span class="number">{{ version }}</span>
|
||||
</span>
|
||||
|
||||
<ul class="versions" :class="{ expanded }">
|
||||
<li v-if="!versions"><a>loading versions...</a></li>
|
||||
<li v-for="version of versions">
|
||||
<a @click="setVersion(version)">v{{ version }}</a>
|
||||
</li>
|
||||
<div @click="expanded = false">
|
||||
<slot />
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.version {
|
||||
margin-right: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.active-version {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.active-version .number {
|
||||
color: var(--green);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.active-version::after {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 6px solid #aaa;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
|
@ -27,7 +27,11 @@ export async function downloadProject(store: ReplStore) {
|
|||
|
||||
const files = store.getFiles()
|
||||
for (const file in files) {
|
||||
src.file(file, files[file])
|
||||
if (file !== 'import-map.json') {
|
||||
src.file(file, files[file])
|
||||
} else {
|
||||
zip.file(file, files[file])
|
||||
}
|
||||
}
|
||||
|
||||
const blob = await zip.generateAsync({ type: 'blob' })
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"vue": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"vite": "^4.3.0"
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"vite": "^4.5.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@ import fs from 'fs'
|
|||
import path from 'path'
|
||||
import { defineConfig, Plugin } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import execa from 'execa'
|
||||
import { execaSync } from 'execa'
|
||||
|
||||
const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7)
|
||||
const commit = execaSync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7)
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
fs: {
|
||||
fileExists: fs.existsSync,
|
||||
readFile: file => fs.readFileSync(file, 'utf-8')
|
||||
|
|
|
@ -50,7 +50,11 @@ export const isObject = (val: unknown): val is Record<any, any> =>
|
|||
val !== null && typeof val === 'object'
|
||||
|
||||
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
|
||||
return isObject(val) && isFunction(val.then) && isFunction(val.catch)
|
||||
return (
|
||||
(isObject(val) || isFunction(val)) &&
|
||||
isFunction((val as any).then) &&
|
||||
isFunction((val as any).catch)
|
||||
)
|
||||
}
|
||||
|
||||
export const objectToString = Object.prototype.toString
|
||||
|
@ -110,16 +114,17 @@ export const hyphenate = cacheStringFunction((str: string) =>
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
export const capitalize = cacheStringFunction(
|
||||
(str: string) => str.charAt(0).toUpperCase() + str.slice(1)
|
||||
)
|
||||
export const capitalize = cacheStringFunction(<T extends string>(str: T) => {
|
||||
return (str.charAt(0).toUpperCase() + str.slice(1)) as Capitalize<T>
|
||||
})
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export const toHandlerKey = cacheStringFunction((str: string) =>
|
||||
str ? `on${capitalize(str)}` : ``
|
||||
)
|
||||
export const toHandlerKey = cacheStringFunction(<T extends string>(str: T) => {
|
||||
const s = str ? `on${capitalize(str)}` : ``
|
||||
return s as T extends '' ? '' : `on${Capitalize<T>}`
|
||||
})
|
||||
|
||||
// compare whether a value has changed, accounting for NaN.
|
||||
export const hasChanged = (value: any, oldValue: any): boolean =>
|
||||
|
@ -149,7 +154,7 @@ export const looseToNumber = (val: any): any => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Only conerces number-like strings
|
||||
* Only concerns number-like strings
|
||||
* "123-foo" will be returned as-is
|
||||
*/
|
||||
export const toNumber = (val: any): any => {
|
||||
|
|
|
@ -6,3 +6,6 @@ const GLOBALS_ALLOWED =
|
|||
'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console'
|
||||
|
||||
export const isGloballyAllowed = /*#__PURE__*/ makeMap(GLOBALS_ALLOWED)
|
||||
|
||||
/** @deprecated use `isGloballyAllowed` instead */
|
||||
export const isGloballyWhitelisted = isGloballyAllowed
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue