Merge branch 'main' into fix/8466
This commit is contained in:
commit
ff1fa17024
|
@ -74,12 +74,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
// Node scripts
|
// Node scripts
|
||||||
{
|
{
|
||||||
files: [
|
files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'],
|
||||||
'scripts/**',
|
|
||||||
'*.{js,ts}',
|
|
||||||
'packages/**/index.js',
|
|
||||||
'packages/size-check/**'
|
|
||||||
],
|
|
||||||
rules: {
|
rules: {
|
||||||
'no-restricted-globals': 'off',
|
'no-restricted-globals': 'off',
|
||||||
'no-restricted-syntax': 'off'
|
'no-restricted-syntax': 'off'
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: Feature Request
|
||||||
|
url: https://github.com/vuejs/rfcs/discussions
|
||||||
|
about: Suggest new features for consideration
|
||||||
- name: Discord Chat
|
- name: Discord Chat
|
||||||
url: https://chat.vuejs.org
|
url: https://chat.vuejs.org
|
||||||
about: Ask questions and discuss with other Vue users in real time.
|
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
|
## 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.
|
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`
|
### `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`
|
### `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).
|
- `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
|
### 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:
|
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:
|
This is made possible via several configurations:
|
||||||
|
|
||||||
- For TypeScript, `compilerOptions.paths` in `tsconfig.json`
|
- 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).
|
- For plain Node.js, they are linked using [PNPM Workspaces](https://pnpm.io/workspaces).
|
||||||
|
|
||||||
### Package Dependencies
|
### 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!
|
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
|
runs-on: ubuntu-latest
|
||||||
environment: Release
|
environment: Release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
|
@ -14,8 +14,10 @@ jobs:
|
||||||
unit-test:
|
unit-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||||
|
env:
|
||||||
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
@ -26,9 +28,6 @@ jobs:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Skip Puppeteer download
|
|
||||||
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- run: pnpm install
|
- run: pnpm install
|
||||||
|
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
|
@ -37,8 +36,10 @@ jobs:
|
||||||
unit-test-windows:
|
unit-test-windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||||
|
env:
|
||||||
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
@ -49,9 +50,6 @@ jobs:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Skip Puppeteer download
|
|
||||||
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- run: pnpm install
|
- run: pnpm install
|
||||||
|
|
||||||
- name: Run compiler unit tests
|
- name: Run compiler unit tests
|
||||||
|
@ -64,12 +62,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup cache for Chromium binary
|
- name: Setup cache for Chromium binary
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/puppeteer/chrome
|
path: ~/.cache/puppeteer
|
||||||
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
|
@ -82,6 +80,7 @@ jobs:
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- run: pnpm install
|
- run: pnpm install
|
||||||
|
- run: node node_modules/puppeteer/install.js
|
||||||
|
|
||||||
- name: Run e2e tests
|
- name: Run e2e tests
|
||||||
run: pnpm run test-e2e
|
run: pnpm run test-e2e
|
||||||
|
@ -89,8 +88,10 @@ jobs:
|
||||||
lint-and-test-dts:
|
lint-and-test-dts:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||||
|
env:
|
||||||
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
@ -101,36 +102,13 @@ jobs:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Skip Puppeteer download
|
|
||||||
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- run: pnpm install
|
- run: pnpm install
|
||||||
|
|
||||||
- name: Run eslint
|
- name: Run eslint
|
||||||
run: pnpm run lint
|
run: pnpm run lint
|
||||||
|
|
||||||
# - name: Run prettier
|
- name: Run prettier
|
||||||
# run: pnpm run format-check
|
run: pnpm run format-check
|
||||||
|
|
||||||
- name: Run type declaration tests
|
- name: Run type declaration tests
|
||||||
run: pnpm run test-dts
|
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">
|
<p align="center">
|
||||||
<a target="_blank" href="https://sponsors.vuejs.org/backers.svg">
|
<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>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
108
package.json
108
package.json
|
@ -1,19 +1,21 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.3.4",
|
"version": "3.3.4",
|
||||||
"packageManager": "pnpm@8.4.0",
|
"packageManager": "pnpm@8.9.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/build.js",
|
||||||
"build-dts": "tsc -p tsconfig.build.json && rollup -c rollup.dts.config.js",
|
"build-dts": "tsc -p tsconfig.build.json && rollup -c rollup.dts.config.js",
|
||||||
"size": "run-s size-global size-baseline",
|
"clean": "rimraf packages/*/dist temp .eslintcache",
|
||||||
"size-global": "node scripts/build.js vue runtime-dom -f global -p",
|
"size": "run-s \"size-*\" && tsx scripts/usage-size.ts",
|
||||||
"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",
|
"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",
|
"check": "tsc --incremental --noEmit",
|
||||||
"lint": "eslint --cache --ext .ts packages/*/{src,__tests__}/**.ts",
|
"lint": "eslint --cache --ext .ts packages/*/{src,__tests__}/**.ts",
|
||||||
"format": "prettier --write --cache --parser typescript \"**/*.[tj]s?(x)\"",
|
"format": "prettier --write --cache \"**/*.[tj]s?(x)\"",
|
||||||
"format-check": "prettier --check --cache --parser typescript \"**/*.[tj]s?(x)\"",
|
"format-check": "prettier --check --cache \"**/*.[tj]s?(x)\"",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test-unit": "vitest -c vitest.unit.config.ts",
|
"test-unit": "vitest -c vitest.unit.config.ts",
|
||||||
"test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.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-serve": "vite packages/sfc-playground --host",
|
||||||
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
|
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
|
||||||
"serve": "serve",
|
"serve": "serve",
|
||||||
"open": "open http://localhost:5000/packages/template-explorer/local.html",
|
"open": "open http://localhost:3000/packages/template-explorer/local.html",
|
||||||
"build-sfc-playground": "run-s build-compiler-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
|
"build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
|
||||||
"build-compiler-cjs": "node scripts/build.js compiler reactivity-transform shared -af cjs",
|
"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-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-ssr-esm": "node scripts/build.js compiler-sfc server-renderer -f esm-browser",
|
||||||
"build-sfc-playground-self": "cd packages/sfc-playground && npm run build",
|
"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"
|
"postinstall": "simple-git-hooks"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
|
@ -52,53 +54,57 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.11.0"
|
"node": ">=18.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/parser": "^7.21.3",
|
"@babel/parser": "^7.23.0",
|
||||||
"@babel/types": "^7.21.3",
|
"@babel/types": "^7.23.0",
|
||||||
"@rollup/plugin-alias": "^4.0.3",
|
"@rollup/plugin-alias": "^5.0.1",
|
||||||
"@rollup/plugin-commonjs": "^24.0.1",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-json": "^6.0.0",
|
"@rollup/plugin-json": "^6.0.1",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-replace": "^5.0.2",
|
"@rollup/plugin-replace": "^5.0.4",
|
||||||
"@rollup/plugin-terser": "^0.4.0",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/hash-sum": "^1.0.0",
|
"@types/hash-sum": "^1.0.1",
|
||||||
"@types/node": "^16.4.7",
|
"@types/node": "^18.18.6",
|
||||||
"@typescript-eslint/parser": "^5.56.0",
|
"@typescript-eslint/parser": "^6.8.0",
|
||||||
"@vitest/coverage-istanbul": "^0.29.7",
|
"@vitest/coverage-istanbul": "^0.34.4",
|
||||||
"@vue/consolidate": "0.17.3",
|
"@vue/consolidate": "0.17.3",
|
||||||
"chalk": "^4.1.0",
|
"conventional-changelog-cli": "^4.1.0",
|
||||||
"conventional-changelog-cli": "^2.0.31",
|
"enquirer": "^2.4.1",
|
||||||
"enquirer": "^2.3.2",
|
"esbuild": "^0.19.5",
|
||||||
"esbuild": "^0.17.4",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"esbuild-plugin-polyfill-node": "^0.2.0",
|
"eslint": "^8.51.0",
|
||||||
"eslint": "^8.33.0",
|
"eslint-plugin-jest": "^27.4.2",
|
||||||
"eslint-plugin-jest": "^27.2.1",
|
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"execa": "^4.0.2",
|
"execa": "^8.0.1",
|
||||||
"jsdom": "^21.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"lint-staged": "^10.2.10",
|
"lint-staged": "^15.0.2",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.21",
|
||||||
"magic-string": "^0.30.0",
|
"magic-string": "^0.30.5",
|
||||||
"marked": "^4.0.10",
|
"markdown-table": "^3.0.3",
|
||||||
"minimist": "^1.2.0",
|
"marked": "^9.1.2",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.7.1",
|
"picocolors": "^1.0.0",
|
||||||
"pug": "^3.0.1",
|
"prettier": "^3.0.3",
|
||||||
"puppeteer": "~19.6.0",
|
"pretty-bytes": "^6.1.1",
|
||||||
"rollup": "^3.20.2",
|
"pug": "^3.0.2",
|
||||||
"rollup-plugin-dts": "^5.3.0",
|
"puppeteer": "~21.2.1",
|
||||||
"rollup-plugin-esbuild": "^5.0.0",
|
"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",
|
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.5.4",
|
||||||
"serve": "^12.0.0",
|
"serve": "^14.2.1",
|
||||||
"simple-git-hooks": "^2.8.1",
|
"simple-git-hooks": "^2.9.0",
|
||||||
"terser": "^5.15.1",
|
"terser": "^5.22.0",
|
||||||
"todomvc-app-css": "^2.3.0",
|
"todomvc-app-css": "^2.4.2",
|
||||||
"tslib": "^2.5.0",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.0.0",
|
"tsx": "^3.14.0",
|
||||||
|
"typescript": "^5.1.6",
|
||||||
"vite": "^4.3.0",
|
"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",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.21.3",
|
"@babel/parser": "^7.23.0",
|
||||||
"@vue/shared": "3.3.4",
|
"@vue/shared": "3.3.4",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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].type === 'ExpressionStatement' &&
|
||||||
root.body[0].expression
|
root.body[0].expression
|
||||||
|
|
||||||
;(walk as any)(root, {
|
walk(root, {
|
||||||
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
|
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
|
||||||
parent && parentStack.push(parent)
|
parent && parentStack.push(parent)
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -8,9 +8,9 @@ export function decodeHtmlBrowser(raw: string, asAttr = false): string {
|
||||||
}
|
}
|
||||||
if (asAttr) {
|
if (asAttr) {
|
||||||
decoder.innerHTML = `<div foo="${raw.replace(/"/g, '"')}">`
|
decoder.innerHTML = `<div foo="${raw.replace(/"/g, '"')}">`
|
||||||
return decoder.children[0].getAttribute('foo') as string
|
return decoder.children[0].getAttribute('foo')!
|
||||||
} else {
|
} else {
|
||||||
decoder.innerHTML = raw
|
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`] = `
|
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > script first 1`] = `
|
||||||
"import { x } from './x'
|
"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`] = `
|
exports[`SFC compile <script setup> > dev mode import usage check > TS annotations 1`] = `
|
||||||
"import { defineComponent as _defineComponent } from 'vue'
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
import { Foo, Bar, Baz, Qux, Fred } from './x'
|
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`] = `
|
exports[`SFC compile <script setup> > dev mode import usage check > js template string interpolations 1`] = `
|
||||||
"import { defineComponent as _defineComponent } from 'vue'
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
import { VAR, VAR2, VAR3 } from './x'
|
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`] = `
|
exports[`SFC compile <script setup> > dev mode import usage check > vue interpolations 1`] = `
|
||||||
"import { defineComponent as _defineComponent } from 'vue'
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
import { x, y, z, x$y } from './x'
|
import { x, y, z, x$y } from './x'
|
||||||
|
|
|
@ -68,64 +68,6 @@ describe('SFC compile <script setup>', () => {
|
||||||
assertCode(content)
|
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', () => {
|
describe('<script> and <script setup> co-usage', () => {
|
||||||
test('script first', () => {
|
test('script first', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
|
@ -156,6 +98,24 @@ describe('SFC compile <script setup>', () => {
|
||||||
assertCode(content)
|
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', () => {
|
test('script setup first, named default export', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@ -413,6 +373,25 @@ describe('SFC compile <script setup>', () => {
|
||||||
assertCode(content)
|
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
|
// https://github.com/vuejs/core/issues/4599
|
||||||
test('attribute expressions', () => {
|
test('attribute expressions', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
|
@ -513,6 +492,23 @@ describe('SFC compile <script setup>', () => {
|
||||||
</template>
|
</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', () => {
|
describe('inlineTemplate mode', () => {
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
exports[`defineEmits > basic usage 1`] = `
|
exports[`defineEmits > basic usage 1`] = `
|
||||||
"export default {
|
"export default {
|
||||||
emits: ['foo', 'bar'],
|
emits: ['foo', 'bar'],
|
||||||
setup(__props, { expose: __expose, emit: myEmit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const myEmit = __emit
|
||||||
|
|
||||||
return { myEmit }
|
return { myEmit }
|
||||||
}
|
}
|
||||||
|
@ -43,10 +43,10 @@ exports[`defineEmits > w/ runtime options 1`] = `
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: ['a', 'b'],
|
emits: ['a', 'b'],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -60,10 +60,10 @@ export interface Emits { (e: 'foo' | 'bar'): void }
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -77,10 +77,10 @@ export type Emits = { (e: 'foo' | 'bar'): void }
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -94,10 +94,10 @@ interface Emits { (e: 'foo'): void }
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: ['foo'],
|
emits: ['foo'],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit: Emits = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -111,10 +111,10 @@ interface Emits { (e: 'foo' | 'bar'): void }
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -127,10 +127,10 @@ exports[`defineEmits > w/ type (property syntax string literal) 1`] = `
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo:bar\\"],
|
emits: [\\"foo:bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -143,10 +143,10 @@ exports[`defineEmits > w/ type (property syntax) 1`] = `
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -160,10 +160,10 @@ export type Emits = (e: 'foo' | 'bar') => void
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -177,10 +177,10 @@ type Emits = (e: 'foo' | 'bar') => void
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -194,10 +194,10 @@ type Emits = { (e: 'foo' | 'bar'): void }
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -210,10 +210,10 @@ exports[`defineEmits > w/ type (type literal w/ call signatures) 1`] = `
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
|
emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -228,10 +228,10 @@ type BaseEmit = \\"change\\"
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"some\\", \\"emit\\", \\"change\\", \\"another\\"],
|
emits: [\\"some\\", \\"emit\\", \\"change\\", \\"another\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit;
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -244,10 +244,10 @@ exports[`defineEmits > w/ type (union) 1`] = `
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
|
emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -260,10 +260,10 @@ exports[`defineEmits > w/ type 1`] = `
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
@ -278,10 +278,10 @@ exports[`defineEmits > w/ type from normal script 1`] = `
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
emits: [\\"foo\\", \\"bar\\"],
|
emits: [\\"foo\\", \\"bar\\"],
|
||||||
setup(__props, { expose: __expose, emit }) {
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
return { emit }
|
return { emit }
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,7 @@ export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props, bar }
|
return { props, bar }
|
||||||
}
|
}
|
||||||
|
@ -28,9 +26,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props }
|
return { props }
|
||||||
}
|
}
|
||||||
|
@ -48,9 +44,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props: any, { expose: __expose }) {
|
setup(__props: any, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const { foo } = __props;
|
const { foo } = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
|
@ -167,9 +161,7 @@ export default {
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props, get propsModel() { return propsModel } }
|
return { props, get propsModel() { return propsModel } }
|
||||||
}
|
}
|
||||||
|
@ -203,9 +195,7 @@ export default {
|
||||||
props: {},
|
props: {},
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
const props = __props
|
||||||
const props = __props;
|
|
||||||
|
|
||||||
|
|
||||||
return { props, get x() { return x } }
|
return { props, get x() { return x } }
|
||||||
}
|
}
|
||||||
|
@ -304,9 +294,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props: any, { expose: __expose }) {
|
setup(__props: any, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props, get defaults() { return defaults } }
|
return { props, get defaults() { return defaults } }
|
||||||
}
|
}
|
||||||
|
@ -328,9 +316,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props: any, { expose: __expose }) {
|
setup(__props: any, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props, get defaults() { return defaults } }
|
return { props, get defaults() { return defaults } }
|
||||||
}
|
}
|
||||||
|
@ -351,9 +337,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props: any, { expose: __expose }) {
|
setup(__props: any, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props, get defaults() { return defaults } }
|
return { props, get defaults() { return defaults } }
|
||||||
}
|
}
|
||||||
|
@ -375,9 +359,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props: any, { expose: __expose }) {
|
setup(__props: any, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props }
|
return { props }
|
||||||
}
|
}
|
||||||
|
@ -401,9 +383,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props: any, { expose: __expose }) {
|
setup(__props: any, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props }
|
return { props }
|
||||||
}
|
}
|
||||||
|
@ -424,9 +404,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props: any, { expose: __expose }) {
|
setup(__props: any, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { props }
|
return { props }
|
||||||
}
|
}
|
||||||
|
@ -446,9 +424,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props: any, { expose: __expose }) {
|
setup(__props: any, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
const props = __props;
|
const props = __props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { 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`] = `
|
exports[`sfc reactive props destructure > multiple variable declarations 1`] = `
|
||||||
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
|
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
|
||||||
|
|
||||||
|
@ -237,9 +292,7 @@ export default {
|
||||||
props: ['foo', 'bar', 'baz'],
|
props: ['foo', 'bar', 'baz'],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
|
|
||||||
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"]);
|
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return () => {}
|
return () => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,9 @@ const myEmit = defineEmits(['foo', 'bar'])
|
||||||
expect(content).not.toMatch('defineEmits')
|
expect(content).not.toMatch('defineEmits')
|
||||||
// should generate correct setup signature
|
// should generate correct setup signature
|
||||||
expect(content).toMatch(
|
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
|
// should include context options in default export
|
||||||
expect(content).toMatch(`export default {
|
expect(content).toMatch(`export default {
|
||||||
emits: ['foo', 'bar'],`)
|
emits: ['foo', 'bar'],`)
|
||||||
|
@ -32,7 +33,8 @@ const emit = defineEmits(['a', 'b'])
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
|
expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
|
||||||
emits: ['a', 'b'],
|
emits: ['a', 'b'],
|
||||||
setup(__props, { expose: __expose, emit }) {`)
|
setup(__props, { expose: __expose, emit: __emit }) {`)
|
||||||
|
expect(content).toMatch('const emit = __emit')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('w/ type', () => {
|
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', () => {
|
describe('errors', () => {
|
||||||
test('should error on deep destructure', () => {
|
test('should error on deep destructure', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from '../../src/script/resolveType'
|
} from '../../src/script/resolveType'
|
||||||
|
|
||||||
import ts from 'typescript'
|
import ts from 'typescript'
|
||||||
registerTS(ts)
|
registerTS(() => ts)
|
||||||
|
|
||||||
describe('resolveType', () => {
|
describe('resolveType', () => {
|
||||||
test('type literal', () => {
|
test('type literal', () => {
|
||||||
|
|
|
@ -32,29 +32,28 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.20.15",
|
"@babel/parser": "^7.23.0",
|
||||||
"@vue/compiler-core": "3.3.4",
|
"@vue/compiler-core": "3.3.4",
|
||||||
"@vue/compiler-dom": "3.3.4",
|
"@vue/compiler-dom": "3.3.4",
|
||||||
"@vue/compiler-ssr": "3.3.4",
|
"@vue/compiler-ssr": "3.3.4",
|
||||||
"@vue/reactivity-transform": "3.3.4",
|
"@vue/reactivity-transform": "3.3.4",
|
||||||
"@vue/shared": "3.3.4",
|
"@vue/shared": "3.3.4",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.0",
|
"magic-string": "^0.30.5",
|
||||||
"postcss": "^8.1.10",
|
"postcss": "^8.4.31",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.21.3",
|
"@babel/types": "^7.23.0",
|
||||||
"@types/estree": "^0.0.48",
|
"@types/estree": "^0.0.52",
|
||||||
"@types/lru-cache": "^5.1.0",
|
|
||||||
"@vue/consolidate": "^0.17.3",
|
"@vue/consolidate": "^0.17.3",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^10.0.1",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"minimatch": "^9.0.0",
|
"minimatch": "^9.0.3",
|
||||||
"postcss-modules": "^4.0.0",
|
"postcss-modules": "^4.3.1",
|
||||||
"postcss-selector-parser": "^6.0.4",
|
"postcss-selector-parser": "^6.0.13",
|
||||||
"pug": "^3.0.1",
|
"pug": "^3.0.2",
|
||||||
"sass": "^1.26.9"
|
"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__) {
|
if (__GLOBAL__ || __ESM_BROWSER__) {
|
||||||
return new Map<string, T>()
|
return new Map<string, T>()
|
||||||
}
|
}
|
||||||
const cache = new LRU(size)
|
return new LRUCache({ max })
|
||||||
// @ts-expect-error
|
|
||||||
cache.delete = cache.del.bind(cache)
|
|
||||||
return cache as any as Map<string, T>
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,7 +274,7 @@ export function compileScript(
|
||||||
const scriptAst = ctx.scriptAst
|
const scriptAst = ctx.scriptAst
|
||||||
const scriptSetupAst = ctx.scriptSetupAst!
|
const scriptSetupAst = ctx.scriptSetupAst!
|
||||||
|
|
||||||
// 1.1 walk import delcarations of <script>
|
// 1.1 walk import declarations of <script>
|
||||||
if (scriptAst) {
|
if (scriptAst) {
|
||||||
for (const node of scriptAst.body) {
|
for (const node of scriptAst.body) {
|
||||||
if (node.type === 'ImportDeclaration') {
|
if (node.type === 'ImportDeclaration') {
|
||||||
|
@ -552,7 +552,11 @@ export function compileScript(
|
||||||
(processDefineSlots(ctx, init, decl.id) ||
|
(processDefineSlots(ctx, init, decl.id) ||
|
||||||
processDefineModel(ctx, init, decl.id))
|
processDefineModel(ctx, init, decl.id))
|
||||||
|
|
||||||
if (isDefineProps || isDefineEmits) {
|
if (
|
||||||
|
isDefineProps &&
|
||||||
|
!ctx.propsDestructureRestId &&
|
||||||
|
ctx.propsDestructureDecl
|
||||||
|
) {
|
||||||
if (left === 1) {
|
if (left === 1) {
|
||||||
ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
|
ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
|
||||||
} else {
|
} else {
|
||||||
|
@ -570,6 +574,12 @@ export function compileScript(
|
||||||
ctx.s.remove(start, end)
|
ctx.s.remove(start, end)
|
||||||
left--
|
left--
|
||||||
}
|
}
|
||||||
|
} else if (isDefineEmits) {
|
||||||
|
ctx.s.overwrite(
|
||||||
|
startOffset + init.start!,
|
||||||
|
startOffset + init.end!,
|
||||||
|
'__emit'
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
lastNonRemoved = i
|
lastNonRemoved = i
|
||||||
}
|
}
|
||||||
|
@ -607,8 +617,8 @@ export function compileScript(
|
||||||
node.type.endsWith('Statement')
|
node.type.endsWith('Statement')
|
||||||
) {
|
) {
|
||||||
const scope: Statement[][] = [scriptSetupAst.body]
|
const scope: Statement[][] = [scriptSetupAst.body]
|
||||||
;(walk as any)(node, {
|
walk(node, {
|
||||||
enter(child: Node, parent: Node) {
|
enter(child: Node, parent: Node | undefined) {
|
||||||
if (isFunctionType(child)) {
|
if (isFunctionType(child)) {
|
||||||
this.skip()
|
this.skip()
|
||||||
}
|
}
|
||||||
|
@ -633,7 +643,7 @@ export function compileScript(
|
||||||
ctx,
|
ctx,
|
||||||
child,
|
child,
|
||||||
needsSemi,
|
needsSemi,
|
||||||
parent.type === 'ExpressionStatement'
|
parent!.type === 'ExpressionStatement'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -781,22 +791,29 @@ export function compileScript(
|
||||||
// inject user assignment of props
|
// inject user assignment of props
|
||||||
// we use a default __props so that template expressions referencing props
|
// we use a default __props so that template expressions referencing props
|
||||||
// can use it directly
|
// can use it directly
|
||||||
if (ctx.propsIdentifier) {
|
if (ctx.propsDecl) {
|
||||||
ctx.s.prependLeft(
|
if (ctx.propsDestructureRestId) {
|
||||||
startOffset,
|
ctx.s.overwrite(
|
||||||
`\nconst ${ctx.propsIdentifier} = __props;\n`
|
startOffset + ctx.propsCall!.start!,
|
||||||
)
|
startOffset + ctx.propsCall!.end!,
|
||||||
}
|
`${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify(
|
||||||
if (ctx.propsDestructureRestId) {
|
Object.keys(ctx.propsDestructuredBindings)
|
||||||
ctx.s.prependLeft(
|
)})`
|
||||||
startOffset,
|
)
|
||||||
`\nconst ${ctx.propsDestructureRestId} = ${ctx.helper(
|
ctx.s.overwrite(
|
||||||
`createPropsRestProxy`
|
startOffset + ctx.propsDestructureDecl!.start!,
|
||||||
)}(__props, ${JSON.stringify(
|
startOffset + ctx.propsDestructureDecl!.end!,
|
||||||
Object.keys(ctx.propsDestructuredBindings)
|
ctx.propsDestructureRestId
|
||||||
)});\n`
|
)
|
||||||
)
|
} else if (!ctx.propsDestructureDecl) {
|
||||||
|
ctx.s.overwrite(
|
||||||
|
startOffset + ctx.propsCall!.start!,
|
||||||
|
startOffset + ctx.propsCall!.end!,
|
||||||
|
'__props'
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// inject temp variables for async context preservation
|
// inject temp variables for async context preservation
|
||||||
if (hasAwait) {
|
if (hasAwait) {
|
||||||
const any = ctx.isTS ? `: any` : ``
|
const any = ctx.isTS ? `: any` : ``
|
||||||
|
@ -807,10 +824,8 @@ export function compileScript(
|
||||||
ctx.hasDefineExposeCall || !options.inlineTemplate
|
ctx.hasDefineExposeCall || !options.inlineTemplate
|
||||||
? [`expose: __expose`]
|
? [`expose: __expose`]
|
||||||
: []
|
: []
|
||||||
if (ctx.emitIdentifier) {
|
if (ctx.emitDecl) {
|
||||||
destructureElements.push(
|
destructureElements.push(`emit: __emit`)
|
||||||
ctx.emitIdentifier === `emit` ? `emit` : `emit: ${ctx.emitIdentifier}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (destructureElements.length) {
|
if (destructureElements.length) {
|
||||||
args += `, { ${destructureElements.join(', ')} }`
|
args += `, { ${destructureElements.join(', ')} }`
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function rewriteDefaultAST(
|
||||||
// multi-line comments or template strings. fallback to a full parse.
|
// multi-line comments or template strings. fallback to a full parse.
|
||||||
ast.forEach(node => {
|
ast.forEach(node => {
|
||||||
if (node.type === 'ExportDefaultDeclaration') {
|
if (node.type === 'ExportDefaultDeclaration') {
|
||||||
if (node.declaration.type === 'ClassDeclaration') {
|
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
||||||
let start: number =
|
let start: number =
|
||||||
node.declaration.decorators && node.declaration.decorators.length > 0
|
node.declaration.decorators && node.declaration.decorators.length > 0
|
||||||
? node.declaration.decorators[
|
? 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 { SFCDescriptor } from '../parse'
|
||||||
import { generateCodeFrame } from '@vue/shared'
|
import { generateCodeFrame } from '@vue/shared'
|
||||||
import { parse as babelParse, ParserPlugin } from '@babel/parser'
|
import { parse as babelParse, ParserPlugin } from '@babel/parser'
|
||||||
|
@ -38,7 +38,8 @@ export class ScriptCompileContext {
|
||||||
hasDefineModelCall = false
|
hasDefineModelCall = false
|
||||||
|
|
||||||
// defineProps
|
// defineProps
|
||||||
propsIdentifier: string | undefined
|
propsCall: CallExpression | undefined
|
||||||
|
propsDecl: Node | undefined
|
||||||
propsRuntimeDecl: Node | undefined
|
propsRuntimeDecl: Node | undefined
|
||||||
propsTypeDecl: Node | undefined
|
propsTypeDecl: Node | undefined
|
||||||
propsDestructureDecl: ObjectPattern | undefined
|
propsDestructureDecl: ObjectPattern | undefined
|
||||||
|
@ -49,7 +50,7 @@ export class ScriptCompileContext {
|
||||||
// defineEmits
|
// defineEmits
|
||||||
emitsRuntimeDecl: Node | undefined
|
emitsRuntimeDecl: Node | undefined
|
||||||
emitsTypeDecl: Node | undefined
|
emitsTypeDecl: Node | undefined
|
||||||
emitIdentifier: string | undefined
|
emitDecl: Node | undefined
|
||||||
|
|
||||||
// defineModel
|
// defineModel
|
||||||
modelDecls: Record<string, ModelDecl> = {}
|
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 { isCallOf } from './utils'
|
||||||
import { ScriptCompileContext } from './context'
|
import { ScriptCompileContext } from './context'
|
||||||
import {
|
import {
|
||||||
|
@ -34,10 +41,7 @@ export function processDefineEmits(
|
||||||
ctx.emitsTypeDecl = node.typeParameters.params[0]
|
ctx.emitsTypeDecl = node.typeParameters.params[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declId) {
|
ctx.emitDecl = declId
|
||||||
ctx.emitIdentifier =
|
|
||||||
declId.type === 'Identifier' ? declId.name : ctx.getString(declId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -99,7 +103,7 @@ function extractRuntimeEmits(ctx: ScriptCompileContext): Set<string> {
|
||||||
|
|
||||||
function extractEventNames(
|
function extractEventNames(
|
||||||
ctx: ScriptCompileContext,
|
ctx: ScriptCompileContext,
|
||||||
eventName: Identifier | RestElement,
|
eventName: ArrayPattern | Identifier | ObjectPattern | RestElement,
|
||||||
emits: Set<string>
|
emits: Set<string>
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -24,13 +24,21 @@ export function processDefineModel(
|
||||||
node: Node,
|
node: Node,
|
||||||
declId?: LVal
|
declId?: LVal
|
||||||
): boolean {
|
): 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
warnOnce(
|
warnOnce(
|
||||||
`This project is using defineModel(), which is an experimental ` +
|
`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` +
|
`use at your own risk.\n` +
|
||||||
`To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/503.`
|
`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]
|
ctx.propsTypeDecl = node.typeParameters.params[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declId) {
|
// handle props destructure
|
||||||
// handle props destructure
|
if (declId && declId.type === 'ObjectPattern') {
|
||||||
if (declId.type === 'ObjectPattern') {
|
processPropsDestructure(ctx, declId)
|
||||||
processPropsDestructure(ctx, declId)
|
|
||||||
} else {
|
|
||||||
ctx.propsIdentifier = ctx.getString(declId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.propsCall = node
|
||||||
|
ctx.propsDecl = declId
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,31 +96,33 @@ function processWithDefaults(
|
||||||
if (!isCallOf(node, WITH_DEFAULTS)) {
|
if (!isCallOf(node, WITH_DEFAULTS)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (processDefineProps(ctx, node.arguments[0], declId)) {
|
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 {
|
|
||||||
ctx.error(
|
ctx.error(
|
||||||
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
|
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
|
||||||
node.arguments[0] || node
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,12 @@ export function processPropsDestructure(
|
||||||
declId: ObjectPattern
|
declId: ObjectPattern
|
||||||
) {
|
) {
|
||||||
if (!ctx.options.propsDestructure && !ctx.options.reactivityTransform) {
|
if (!ctx.options.propsDestructure && !ctx.options.reactivityTransform) {
|
||||||
ctx.propsIdentifier = ctx.getString(declId)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
warnOnce(
|
warnOnce(
|
||||||
`This project is using reactive props destructure, which is an experimental ` +
|
`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` +
|
`use at your own risk.\n` +
|
||||||
`To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/502.`
|
`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
|
// check root scope first
|
||||||
const ast = ctx.scriptSetupAst!
|
const ast = ctx.scriptSetupAst!
|
||||||
walkScope(ast, true)
|
walkScope(ast, true)
|
||||||
;(walk as any)(ast, {
|
walk(ast, {
|
||||||
enter(node: Node, parent?: Node) {
|
enter(node: Node, parent?: Node) {
|
||||||
parent && parentStack.push(parent)
|
parent && parentStack.push(parent)
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,12 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
||||||
if (!isBuiltInDirective(prop.name)) {
|
if (!isBuiltInDirective(prop.name)) {
|
||||||
code += `,v${capitalize(camelize(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) {
|
if (prop.exp) {
|
||||||
code += `,${processExp(
|
code += `,${processExp(
|
||||||
(prop.exp as SimpleExpressionNode).content,
|
(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) {
|
} else if (node.type === NodeTypes.INTERPOLATION) {
|
||||||
code += `,${processExp(
|
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
|
* @private
|
||||||
*/
|
*/
|
||||||
export function registerTS(_ts: any) {
|
export function registerTS(_loadTS: () => typeof TS) {
|
||||||
ts = _ts
|
loadTS = _loadTS
|
||||||
}
|
}
|
||||||
|
|
||||||
type FS = NonNullable<SFCScriptCompileOptions['fs']>
|
type FS = NonNullable<SFCScriptCompileOptions['fs']>
|
||||||
|
@ -740,7 +741,10 @@ function resolveFS(ctx: TypeResolveContext): FS | undefined {
|
||||||
if (ctx.fs) {
|
if (ctx.fs) {
|
||||||
return 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) {
|
if (!fs) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -796,22 +800,25 @@ function importSourceToScope(
|
||||||
} else {
|
} else {
|
||||||
// module or aliased import - use full TS resolution, only supported in Node
|
// module or aliased import - use full TS resolution, only supported in Node
|
||||||
if (!__NODE_JS__) {
|
if (!__NODE_JS__) {
|
||||||
ctx.error(
|
return ctx.error(
|
||||||
`Type import from non-relative sources is not supported in the browser build.`,
|
`Type import from non-relative sources is not supported in the browser build.`,
|
||||||
node,
|
node,
|
||||||
scope
|
scope
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!ts) {
|
if (!ts) {
|
||||||
ctx.error(
|
if (loadTS) ts = loadTS()
|
||||||
`Failed to resolve import source ${JSON.stringify(source)}. ` +
|
if (!ts) {
|
||||||
`typescript is required as a peer dep for vue in order ` +
|
return ctx.error(
|
||||||
`to support resolving types from module imports.`,
|
`Failed to resolve import source ${JSON.stringify(source)}. ` +
|
||||||
node,
|
`typescript is required as a peer dep for vue in order ` +
|
||||||
scope
|
`to support resolving types from module imports.`,
|
||||||
)
|
node,
|
||||||
|
scope
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
resolved = resolveWithTS(scope.filename, source, fs)
|
resolved = resolveWithTS(scope.filename, source, ts, fs)
|
||||||
}
|
}
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
resolved = scope.resolvedImportSources[source] = normalizePath(resolved)
|
resolved = scope.resolvedImportSources[source] = normalizePath(resolved)
|
||||||
|
@ -856,6 +863,7 @@ const tsConfigRefMap = new Map<string, string>()
|
||||||
function resolveWithTS(
|
function resolveWithTS(
|
||||||
containingFile: string,
|
containingFile: string,
|
||||||
source: string,
|
source: string,
|
||||||
|
ts: typeof TS,
|
||||||
fs: FS
|
fs: FS
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (!__NODE_JS__) return
|
if (!__NODE_JS__) return
|
||||||
|
@ -870,7 +878,7 @@ function resolveWithTS(
|
||||||
const normalizedConfigPath = normalizePath(configPath)
|
const normalizedConfigPath = normalizePath(configPath)
|
||||||
const cached = tsConfigCache.get(normalizedConfigPath)
|
const cached = tsConfigCache.get(normalizedConfigPath)
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
configs = loadTSConfig(configPath, fs).map(config => ({ config }))
|
configs = loadTSConfig(configPath, ts, fs).map(config => ({ config }))
|
||||||
tsConfigCache.set(normalizedConfigPath, configs)
|
tsConfigCache.set(normalizedConfigPath, configs)
|
||||||
} else {
|
} else {
|
||||||
configs = cached
|
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.
|
// The only case where `fs` is NOT `ts.sys` is during tests.
|
||||||
// parse config host requires an extra `readDirectory` method
|
// parse config host requires an extra `readDirectory` method
|
||||||
// during tests, which is stubbed.
|
// during tests, which is stubbed.
|
||||||
|
@ -957,7 +969,7 @@ function loadTSConfig(configPath: string, fs: FS): TS.ParsedCommandLine[] {
|
||||||
if (config.projectReferences) {
|
if (config.projectReferences) {
|
||||||
for (const ref of config.projectReferences) {
|
for (const ref of config.projectReferences) {
|
||||||
tsConfigRefMap.set(ref.path, configPath)
|
tsConfigRefMap.set(ref.path, configPath)
|
||||||
res.unshift(...loadTSConfig(ref.path, fs))
|
res.unshift(...loadTSConfig(ref.path, ts, fs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
|
@ -1232,7 +1244,7 @@ function recordType(
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'ClassDeclaration':
|
case 'ClassDeclaration':
|
||||||
types[overwriteId || getId(node.id)] = node
|
if (overwriteId || node.id) types[overwriteId || getId(node.id!)] = node
|
||||||
break
|
break
|
||||||
case 'TSTypeAliasDeclaration':
|
case 'TSTypeAliasDeclaration':
|
||||||
types[node.id.name] = node.typeAnnotation
|
types[node.id.name] = node.typeAnnotation
|
||||||
|
|
|
@ -53,8 +53,9 @@ export function parseCssVars(sfc: SFCDescriptor): string[] {
|
||||||
const vars: string[] = []
|
const vars: string[] = []
|
||||||
sfc.styles.forEach(style => {
|
sfc.styles.forEach(style => {
|
||||||
let match
|
let match
|
||||||
// ignore v-bind() in comments /* ... */
|
// ignore v-bind() in comments, eg /* ... */
|
||||||
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '')
|
// 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))) {
|
while ((match = vBindRE.exec(content))) {
|
||||||
const start = match.index + match[0].length
|
const start = match.index + match[0].length
|
||||||
const end = lexBinding(content, start)
|
const end = lexBinding(content, start)
|
||||||
|
|
|
@ -130,9 +130,10 @@ function rewriteSelector(
|
||||||
// DEPRECATED usage
|
// DEPRECATED usage
|
||||||
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
|
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
|
||||||
warn(
|
warn(
|
||||||
`::v-deep usage as a combinator has ` +
|
`${value} usage as a combinator has been deprecated. ` +
|
||||||
`been deprecated. Use :deep(<inner-selector>) instead.`
|
`Use :deep(<inner-selector>) instead of ${value} <inner-selector>.`
|
||||||
)
|
)
|
||||||
|
|
||||||
const prev = selector.at(selector.index(n) - 1)
|
const prev = selector.at(selector.index(n) - 1)
|
||||||
if (prev && isSpaceCombinator(prev)) {
|
if (prev && isSpaceCombinator(prev)) {
|
||||||
selector.removeChild(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 styl: StylePreprocessor = (source, map, options, load = require) => {
|
||||||
const nodeStylus = load('stylus')
|
const nodeStylus = load('stylus')
|
||||||
try {
|
try {
|
||||||
const ref = nodeStylus(source)
|
const ref = nodeStylus(source, options)
|
||||||
Object.keys(options).forEach(key => ref.set(key, options[key]))
|
|
||||||
if (map) ref.set('sourcemap', { inline: false, comment: false })
|
if (map) ref.set('sourcemap', { inline: false, comment: false })
|
||||||
|
|
||||||
const result = ref.render()
|
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">', () => {
|
test('<input type="radio">', () => {
|
||||||
expect(
|
expect(
|
||||||
compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code
|
compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code
|
||||||
|
|
|
@ -18,7 +18,8 @@ import {
|
||||||
import {
|
import {
|
||||||
SSR_LOOSE_EQUAL,
|
SSR_LOOSE_EQUAL,
|
||||||
SSR_LOOSE_CONTAIN,
|
SSR_LOOSE_CONTAIN,
|
||||||
SSR_RENDER_DYNAMIC_MODEL
|
SSR_RENDER_DYNAMIC_MODEL,
|
||||||
|
SSR_INCLUDE_BOOLEAN_ATTR
|
||||||
} from '../runtimeHelpers'
|
} from '../runtimeHelpers'
|
||||||
import { DirectiveTransformResult } from 'packages/compiler-core/src/transform'
|
import { DirectiveTransformResult } from 'packages/compiler-core/src/transform'
|
||||||
|
|
||||||
|
@ -129,8 +130,34 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
checkDuplicatedValue()
|
checkDuplicatedValue()
|
||||||
node.children = [createInterpolation(model, model.loc)]
|
node.children = [createInterpolation(model, model.loc)]
|
||||||
} else if (node.tag === 'select') {
|
} else if (node.tag === 'select') {
|
||||||
// NOOP
|
node.children.forEach(option => {
|
||||||
// select relies on client-side directive to set initial selected state.
|
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 {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
createDOMCompilerError(
|
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.
|
- 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(
|
defineComponent(
|
||||||
(_props: { msg: string }) => {
|
(_props: { msg: string }) => {
|
||||||
return () => {}
|
return () => {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
props: {
|
props: {
|
||||||
|
// @ts-expect-error prop type mismatch
|
||||||
msg: Number
|
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
|
a: string
|
||||||
}, TString extends string>() => {
|
}, TString extends string>() => {
|
||||||
const res = withDefaults(
|
const res = withDefaults(
|
||||||
|
@ -117,10 +118,10 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends nu
|
||||||
n: 123,
|
n: 123,
|
||||||
|
|
||||||
generic1: () => [123, 33] as T[],
|
generic1: () => [123, 33] as T[],
|
||||||
generic2: () => ({ x: 123 } as { x: T }),
|
generic2: () => ({ x: 123 }) as { x: T },
|
||||||
|
|
||||||
generic3: () => 'test' as TString,
|
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)
|
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', () => {
|
describe('defineProps w/ runtime declaration', () => {
|
||||||
// runtime declaration
|
// runtime declaration
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
@ -33,6 +33,17 @@ declare module 'file-saver' {
|
||||||
export function saveAs(blob: any, name: any): void
|
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 {
|
declare interface String {
|
||||||
/**
|
/**
|
||||||
* @deprecated Please use String.prototype.slice instead of String.prototype.substring in the repository.
|
* @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",
|
"homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.20.15",
|
"@babel/parser": "^7.23.0",
|
||||||
"@vue/compiler-core": "3.3.4",
|
"@vue/compiler-core": "3.3.4",
|
||||||
"@vue/shared": "3.3.4",
|
"@vue/shared": "3.3.4",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.0"
|
"magic-string": "^0.30.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.3",
|
"@babel/core": "^7.23.2",
|
||||||
"@babel/types": "^7.21.3"
|
"@babel/types": "^7.23.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -636,7 +636,7 @@ export function transformAST(
|
||||||
|
|
||||||
// check root scope first
|
// check root scope first
|
||||||
walkScope(ast, true)
|
walkScope(ast, true)
|
||||||
;(walk as any)(ast, {
|
walk(ast, {
|
||||||
enter(node: Node, parent?: Node) {
|
enter(node: Node, parent?: Node) {
|
||||||
parent && parentStack.push(parent)
|
parent && parentStack.push(parent)
|
||||||
|
|
||||||
|
|
|
@ -259,13 +259,13 @@ describe('reactivity/computed', () => {
|
||||||
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
||||||
events.push(e)
|
events.push(e)
|
||||||
})
|
})
|
||||||
const obj = reactive({ foo: 1 })
|
const obj = reactive<{ foo?: number }>({ foo: 1 })
|
||||||
const c = computed(() => obj.foo, { onTrigger })
|
const c = computed(() => obj.foo, { onTrigger })
|
||||||
|
|
||||||
// computed won't trigger compute until accessed
|
// computed won't trigger compute until accessed
|
||||||
c.value
|
c.value
|
||||||
|
|
||||||
obj.foo++
|
obj.foo!++
|
||||||
expect(c.value).toBe(2)
|
expect(c.value).toBe(2)
|
||||||
expect(onTrigger).toHaveBeenCalledTimes(1)
|
expect(onTrigger).toHaveBeenCalledTimes(1)
|
||||||
expect(events[0]).toEqual({
|
expect(events[0]).toEqual({
|
||||||
|
@ -277,7 +277,6 @@ describe('reactivity/computed', () => {
|
||||||
newValue: 2
|
newValue: 2
|
||||||
})
|
})
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
delete obj.foo
|
delete obj.foo
|
||||||
expect(c.value).toBeUndefined()
|
expect(c.value).toBeUndefined()
|
||||||
expect(onTrigger).toHaveBeenCalledTimes(2)
|
expect(onTrigger).toHaveBeenCalledTimes(2)
|
||||||
|
|
|
@ -585,6 +585,14 @@ describe('reactivity/effect', () => {
|
||||||
expect(runner.effect.fn).toBe(otherRunner.effect.fn)
|
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', () => {
|
it('should not run multiple times for a single mutation', () => {
|
||||||
let dummy
|
let dummy
|
||||||
const obj = reactive<Record<string, number>>({})
|
const obj = reactive<Record<string, number>>({})
|
||||||
|
|
|
@ -23,7 +23,7 @@ describe('reactivity/reactive', () => {
|
||||||
const reactiveObj = reactive(obj)
|
const reactiveObj = reactive(obj)
|
||||||
expect(isReactive(reactiveObj)).toBe(true)
|
expect(isReactive(reactiveObj)).toBe(true)
|
||||||
// read prop of reactiveObject will cause reactiveObj[prop] to be reactive
|
// read prop of reactiveObject will cause reactiveObj[prop] to be reactive
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const prototype = reactiveObj['__proto__']
|
const prototype = reactiveObj['__proto__']
|
||||||
const otherObj = { data: ['a'] }
|
const otherObj = { data: ['a'] }
|
||||||
expect(isReactive(otherObj)).toBe(false)
|
expect(isReactive(otherObj)).toBe(false)
|
||||||
|
@ -204,7 +204,7 @@ describe('reactivity/reactive', () => {
|
||||||
const dummy = computed(() => observed.a)
|
const dummy = computed(() => observed.a)
|
||||||
expect(dummy.value).toBe(0)
|
expect(dummy.value).toBe(0)
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
observed.a = bar
|
observed.a = bar
|
||||||
expect(dummy.value).toBe(1)
|
expect(dummy.value).toBe(1)
|
||||||
|
|
||||||
|
@ -233,6 +233,9 @@ describe('reactivity/reactive', () => {
|
||||||
// symbol
|
// symbol
|
||||||
const s = Symbol()
|
const s = Symbol()
|
||||||
assertValue(s)
|
assertValue(s)
|
||||||
|
// bigint
|
||||||
|
const bn = BigInt('9007199254740991')
|
||||||
|
assertValue(bn)
|
||||||
|
|
||||||
// built-ins should work and return same value
|
// built-ins should work and return same value
|
||||||
const p = Promise.resolve()
|
const p = Promise.resolve()
|
||||||
|
|
|
@ -28,19 +28,18 @@ describe('reactivity/ref', () => {
|
||||||
it('should be reactive', () => {
|
it('should be reactive', () => {
|
||||||
const a = ref(1)
|
const a = ref(1)
|
||||||
let dummy
|
let dummy
|
||||||
let calls = 0
|
const fn = vi.fn(() => {
|
||||||
effect(() => {
|
|
||||||
calls++
|
|
||||||
dummy = a.value
|
dummy = a.value
|
||||||
})
|
})
|
||||||
expect(calls).toBe(1)
|
effect(fn)
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
expect(dummy).toBe(1)
|
expect(dummy).toBe(1)
|
||||||
a.value = 2
|
a.value = 2
|
||||||
expect(calls).toBe(2)
|
expect(fn).toHaveBeenCalledTimes(2)
|
||||||
expect(dummy).toBe(2)
|
expect(dummy).toBe(2)
|
||||||
// same value should not trigger
|
// same value should not trigger
|
||||||
a.value = 2
|
a.value = 2
|
||||||
expect(calls).toBe(2)
|
expect(fn).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should make nested properties reactive', () => {
|
it('should make nested properties reactive', () => {
|
||||||
|
|
|
@ -26,7 +26,6 @@ import {
|
||||||
hasChanged,
|
hasChanged,
|
||||||
isArray,
|
isArray,
|
||||||
isIntegerKey,
|
isIntegerKey,
|
||||||
extend,
|
|
||||||
makeMap
|
makeMap
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { isRef } from './ref'
|
import { isRef } from './ref'
|
||||||
|
@ -45,11 +44,6 @@ const builtInSymbols = new Set(
|
||||||
.filter(isSymbol)
|
.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()
|
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
|
||||||
|
|
||||||
function createArrayInstrumentations() {
|
function createArrayInstrumentations() {
|
||||||
|
@ -91,8 +85,15 @@ function hasOwnProperty(this: object, key: string) {
|
||||||
return obj.hasOwnProperty(key)
|
return obj.hasOwnProperty(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGetter(isReadonly = false, shallow = false) {
|
class BaseReactiveHandler implements ProxyHandler<Target> {
|
||||||
return function get(target: Target, key: string | symbol, receiver: object) {
|
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) {
|
if (key === ReactiveFlags.IS_REACTIVE) {
|
||||||
return !isReadonly
|
return !isReadonly
|
||||||
} else if (key === ReactiveFlags.IS_READONLY) {
|
} else if (key === ReactiveFlags.IS_READONLY) {
|
||||||
|
@ -155,11 +156,12 @@ function createGetter(isReadonly = false, shallow = false) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const set = /*#__PURE__*/ createSetter()
|
class MutableReactiveHandler extends BaseReactiveHandler {
|
||||||
const shallowSet = /*#__PURE__*/ createSetter(true)
|
constructor(shallow = false) {
|
||||||
|
super(false, shallow)
|
||||||
|
}
|
||||||
|
|
||||||
function createSetter(shallow = false) {
|
set(
|
||||||
return function set(
|
|
||||||
target: object,
|
target: object,
|
||||||
key: string | symbol,
|
key: string | symbol,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
|
@ -169,7 +171,7 @@ function createSetter(shallow = false) {
|
||||||
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
|
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (!shallow) {
|
if (!this._shallow) {
|
||||||
if (!isShallow(value) && !isReadonly(value)) {
|
if (!isShallow(value) && !isReadonly(value)) {
|
||||||
oldValue = toRaw(oldValue)
|
oldValue = toRaw(oldValue)
|
||||||
value = toRaw(value)
|
value = toRaw(value)
|
||||||
|
@ -197,42 +199,40 @@ function createSetter(shallow = false) {
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function deleteProperty(target: object, key: string | symbol): boolean {
|
deleteProperty(target: object, key: string | symbol): boolean {
|
||||||
const hadKey = hasOwn(target, key)
|
const hadKey = hasOwn(target, key)
|
||||||
const oldValue = (target as any)[key]
|
const oldValue = (target as any)[key]
|
||||||
const result = Reflect.deleteProperty(target, key)
|
const result = Reflect.deleteProperty(target, key)
|
||||||
if (result && hadKey) {
|
if (result && hadKey) {
|
||||||
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
|
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function has(target: object, key: string | symbol): boolean {
|
has(target: object, key: string | symbol): boolean {
|
||||||
const result = Reflect.has(target, key)
|
const result = Reflect.has(target, key)
|
||||||
if (!isSymbol(key) || !builtInSymbols.has(key)) {
|
if (!isSymbol(key) || !builtInSymbols.has(key)) {
|
||||||
track(target, TrackOpTypes.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)[] {
|
class ReadonlyReactiveHandler extends BaseReactiveHandler {
|
||||||
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
|
constructor(shallow = false) {
|
||||||
return Reflect.ownKeys(target)
|
super(true, shallow)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutableHandlers: ProxyHandler<object> = {
|
set(target: object, key: string | symbol) {
|
||||||
get,
|
|
||||||
set,
|
|
||||||
deleteProperty,
|
|
||||||
has,
|
|
||||||
ownKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
export const readonlyHandlers: ProxyHandler<object> = {
|
|
||||||
get: readonlyGet,
|
|
||||||
set(target, key) {
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
warn(
|
warn(
|
||||||
`Set operation on key "${String(key)}" failed: target is readonly.`,
|
`Set operation on key "${String(key)}" failed: target is readonly.`,
|
||||||
|
@ -240,8 +240,9 @@ export const readonlyHandlers: ProxyHandler<object> = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
},
|
}
|
||||||
deleteProperty(target, key) {
|
|
||||||
|
deleteProperty(target: object, key: string | symbol) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
warn(
|
warn(
|
||||||
`Delete operation on key "${String(key)}" failed: target is readonly.`,
|
`Delete operation on key "${String(key)}" failed: target is readonly.`,
|
||||||
|
@ -252,22 +253,18 @@ export const readonlyHandlers: ProxyHandler<object> = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
|
export const mutableHandlers: ProxyHandler<object> =
|
||||||
{},
|
/*#__PURE__*/ new MutableReactiveHandler()
|
||||||
mutableHandlers,
|
|
||||||
{
|
export const readonlyHandlers: ProxyHandler<object> =
|
||||||
get: shallowGet,
|
/*#__PURE__*/ new ReadonlyReactiveHandler()
|
||||||
set: shallowSet
|
|
||||||
}
|
export const shallowReactiveHandlers = /*#__PURE__*/ new MutableReactiveHandler(
|
||||||
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
// Props handlers are special in the sense that it should not unwrap top-level
|
// 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
|
// refs (in order to allow refs to be explicitly passed down), but should
|
||||||
// retain the reactivity of the normal readonly object.
|
// retain the reactivity of the normal readonly object.
|
||||||
export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
|
export const shallowReadonlyHandlers =
|
||||||
{},
|
/*#__PURE__*/ new ReadonlyReactiveHandler(true)
|
||||||
readonlyHandlers,
|
|
||||||
{
|
|
||||||
get: shallowReadonlyGet
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ function get(
|
||||||
const rawTarget = toRaw(target)
|
const rawTarget = toRaw(target)
|
||||||
const rawKey = toRaw(key)
|
const rawKey = toRaw(key)
|
||||||
if (!isReadonly) {
|
if (!isReadonly) {
|
||||||
if (key !== rawKey) {
|
if (hasChanged(key, rawKey)) {
|
||||||
track(rawTarget, TrackOpTypes.GET, key)
|
track(rawTarget, TrackOpTypes.GET, key)
|
||||||
}
|
}
|
||||||
track(rawTarget, TrackOpTypes.GET, rawKey)
|
track(rawTarget, TrackOpTypes.GET, rawKey)
|
||||||
|
@ -50,7 +50,7 @@ function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
|
||||||
const rawTarget = toRaw(target)
|
const rawTarget = toRaw(target)
|
||||||
const rawKey = toRaw(key)
|
const rawKey = toRaw(key)
|
||||||
if (!isReadonly) {
|
if (!isReadonly) {
|
||||||
if (key !== rawKey) {
|
if (hasChanged(key, rawKey)) {
|
||||||
track(rawTarget, TrackOpTypes.HAS, key)
|
track(rawTarget, TrackOpTypes.HAS, key)
|
||||||
}
|
}
|
||||||
track(rawTarget, TrackOpTypes.HAS, rawKey)
|
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
|
// which maintains a Set of subscribers, but we simply store them as
|
||||||
// raw Sets to reduce memory overhead.
|
// raw Sets to reduce memory overhead.
|
||||||
type KeyToDepMap = Map<any, Dep>
|
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.
|
// The number of effects currently being tracked recursively.
|
||||||
let effectTrackDepth = 0
|
let effectTrackDepth = 0
|
||||||
|
@ -181,7 +181,7 @@ export function effect<T = any>(
|
||||||
fn: () => T,
|
fn: () => T,
|
||||||
options?: ReactiveEffectOptions
|
options?: ReactiveEffectOptions
|
||||||
): ReactiveEffectRunner {
|
): ReactiveEffectRunner {
|
||||||
if ((fn as ReactiveEffectRunner).effect) {
|
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
|
||||||
fn = (fn as ReactiveEffectRunner).effect.fn
|
fn = (fn as ReactiveEffectRunner).effect.fn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,10 @@ class RefImpl<T> {
|
||||||
public dep?: Dep = undefined
|
public dep?: Dep = undefined
|
||||||
public readonly __v_isRef = true
|
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._rawValue = __v_isShallow ? value : toRaw(value)
|
||||||
this._value = __v_isShallow ? value : toReactive(value)
|
this._value = __v_isShallow ? value : toReactive(value)
|
||||||
}
|
}
|
||||||
|
@ -342,7 +345,7 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
const val = this._object[this._key]
|
const val = this._object[this._key]
|
||||||
return val === undefined ? (this._defaultValue as T[K]) : val
|
return val === undefined ? this._defaultValue! : val
|
||||||
}
|
}
|
||||||
|
|
||||||
set value(newVal) {
|
set value(newVal) {
|
||||||
|
|
|
@ -382,7 +382,7 @@ describe('api: options', () => {
|
||||||
render() {
|
render() {
|
||||||
return this[injectedKey]
|
return this[injectedKey]
|
||||||
}
|
}
|
||||||
} as any)
|
}) as any
|
||||||
|
|
||||||
const ChildA = defineChild(['a'], 'a')
|
const ChildA = defineChild(['a'], 'a')
|
||||||
const ChildB = defineChild({ b: 'a' })
|
const ChildB = defineChild({ b: 'a' })
|
||||||
|
|
|
@ -1000,7 +1000,7 @@ describe('api: watch', () => {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// this call runs while Comp is currentInstance, but
|
// 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.comp!.$watch(
|
||||||
() => this.show,
|
() => this.show,
|
||||||
() => void 0
|
() => void 0
|
||||||
|
@ -1171,7 +1171,7 @@ describe('api: watch', () => {
|
||||||
expect(instance!.scope.effects.length).toBe(1)
|
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)
|
const trigger = ref(0)
|
||||||
let countWE = 0
|
let countWE = 0
|
||||||
let countW = 0
|
let countW = 0
|
||||||
|
|
|
@ -475,4 +475,57 @@ describe('renderer: teleport', () => {
|
||||||
expect(dir.mounted).toHaveBeenCalledTimes(1)
|
expect(dir.mounted).toHaveBeenCalledTimes(1)
|
||||||
expect(dir.unmounted).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 toggle = ref(true)
|
||||||
|
|
||||||
const Comp = defineComponent(
|
const Comp = defineComponent(
|
||||||
() => () => toggle.value ? h('div', { ref: fn }) : null
|
() => () => (toggle.value ? h('div', { ref: fn }) : null)
|
||||||
)
|
)
|
||||||
render(h(Comp), root)
|
render(h(Comp), root)
|
||||||
expect(fn.mock.calls[0][0]).toBe(root.children[0])
|
expect(fn.mock.calls[0][0]).toBe(root.children[0])
|
||||||
|
|
|
@ -546,4 +546,16 @@ describe('scheduler', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
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 =>
|
export const isAsyncWrapper = (i: ComponentInternalInstance | VNode): boolean =>
|
||||||
!!(i.type as ComponentOptions).__asyncLoader
|
!!(i.type as ComponentOptions).__asyncLoader
|
||||||
|
|
||||||
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function defineAsyncComponent<
|
export function defineAsyncComponent<
|
||||||
T extends Component = { new (): ComponentPublicInstance }
|
T extends Component = { new (): ComponentPublicInstance }
|
||||||
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
|
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
|
||||||
|
|
|
@ -221,7 +221,7 @@ export function createAppAPI<HostElement>(
|
||||||
set() {
|
set() {
|
||||||
warn(
|
warn(
|
||||||
`app.config.unwrapInjectedRef has been deprecated. ` +
|
`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
|
// implementation, close to no-op
|
||||||
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function defineComponent(
|
export function defineComponent(
|
||||||
options: unknown,
|
options: unknown,
|
||||||
extraOptions?: ComponentOptions
|
extraOptions?: ComponentOptions
|
||||||
|
|
|
@ -303,7 +303,13 @@ type PropsWithDefaults<
|
||||||
? T[K]
|
? T[K]
|
||||||
: NotUndefined<T[K]>
|
: NotUndefined<T[K]>
|
||||||
: never
|
: 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
|
* Vue `<script setup>` compiler macro for providing props default values when
|
||||||
|
|
|
@ -58,6 +58,7 @@ export interface LegacyPublicProperties {
|
||||||
export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
||||||
const set = (target: any, key: any, val: any) => {
|
const set = (target: any, key: any, val: any) => {
|
||||||
target[key] = val
|
target[key] = val
|
||||||
|
return target[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
const del = (target: any, key: any) => {
|
const del = (target: any, key: any) => {
|
||||||
|
|
|
@ -256,7 +256,7 @@ export interface ComponentInternalInstance {
|
||||||
*/
|
*/
|
||||||
ssrRender?: Function | null
|
ssrRender?: Function | null
|
||||||
/**
|
/**
|
||||||
* Object containing values this component provides for its descendents
|
* Object containing values this component provides for its descendants
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
provides: Data
|
provides: Data
|
||||||
|
|
|
@ -22,6 +22,9 @@ import { RendererElement } from '../renderer'
|
||||||
|
|
||||||
type Hook<T = () => void> = T | T[]
|
type Hook<T = () => void> = T | T[]
|
||||||
|
|
||||||
|
const leaveCbKey = Symbol('_leaveCb')
|
||||||
|
const enterCbKey = Symbol('_enterCb')
|
||||||
|
|
||||||
export interface BaseTransitionProps<HostElement = RendererElement> {
|
export interface BaseTransitionProps<HostElement = RendererElement> {
|
||||||
mode?: 'in-out' | 'out-in' | 'default'
|
mode?: 'in-out' | 'out-in' | 'default'
|
||||||
appear?: boolean
|
appear?: boolean
|
||||||
|
@ -89,8 +92,8 @@ export interface TransitionElement {
|
||||||
// in persisted mode (e.g. v-show), the same element is toggled, so the
|
// 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
|
// pending enter/leave callbacks may need to be cancelled if the state is toggled
|
||||||
// before it finishes.
|
// before it finishes.
|
||||||
_enterCb?: PendingCallback
|
[enterCbKey]?: PendingCallback
|
||||||
_leaveCb?: PendingCallback
|
[leaveCbKey]?: PendingCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTransitionState(): TransitionState {
|
export function useTransitionState(): TransitionState {
|
||||||
|
@ -259,9 +262,9 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
)
|
)
|
||||||
leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild
|
leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild
|
||||||
// early removal callback
|
// early removal callback
|
||||||
el._leaveCb = () => {
|
el[leaveCbKey] = () => {
|
||||||
earlyRemove()
|
earlyRemove()
|
||||||
el._leaveCb = undefined
|
el[leaveCbKey] = undefined
|
||||||
delete enterHooks.delayedLeave
|
delete enterHooks.delayedLeave
|
||||||
}
|
}
|
||||||
enterHooks.delayedLeave = delayedLeave
|
enterHooks.delayedLeave = delayedLeave
|
||||||
|
@ -366,18 +369,18 @@ export function resolveTransitionHooks(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// for same element (v-show)
|
// for same element (v-show)
|
||||||
if (el._leaveCb) {
|
if (el[leaveCbKey]) {
|
||||||
el._leaveCb(true /* cancelled */)
|
el[leaveCbKey](true /* cancelled */)
|
||||||
}
|
}
|
||||||
// for toggled element with same key (v-if)
|
// for toggled element with same key (v-if)
|
||||||
const leavingVNode = leavingVNodesCache[key]
|
const leavingVNode = leavingVNodesCache[key]
|
||||||
if (
|
if (
|
||||||
leavingVNode &&
|
leavingVNode &&
|
||||||
isSameVNodeType(vnode, leavingVNode) &&
|
isSameVNodeType(vnode, leavingVNode) &&
|
||||||
leavingVNode.el!._leaveCb
|
(leavingVNode.el as TransitionElement)[leaveCbKey]
|
||||||
) {
|
) {
|
||||||
// force early removal (not cancelled)
|
// force early removal (not cancelled)
|
||||||
leavingVNode.el!._leaveCb()
|
;(leavingVNode.el as TransitionElement)[leaveCbKey]!()
|
||||||
}
|
}
|
||||||
callHook(hook, [el])
|
callHook(hook, [el])
|
||||||
},
|
},
|
||||||
|
@ -396,7 +399,7 @@ export function resolveTransitionHooks(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let called = false
|
let called = false
|
||||||
const done = (el._enterCb = (cancelled?) => {
|
const done = (el[enterCbKey] = (cancelled?) => {
|
||||||
if (called) return
|
if (called) return
|
||||||
called = true
|
called = true
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
|
@ -407,7 +410,7 @@ export function resolveTransitionHooks(
|
||||||
if (hooks.delayedLeave) {
|
if (hooks.delayedLeave) {
|
||||||
hooks.delayedLeave()
|
hooks.delayedLeave()
|
||||||
}
|
}
|
||||||
el._enterCb = undefined
|
el[enterCbKey] = undefined
|
||||||
})
|
})
|
||||||
if (hook) {
|
if (hook) {
|
||||||
callAsyncHook(hook, [el, done])
|
callAsyncHook(hook, [el, done])
|
||||||
|
@ -418,15 +421,15 @@ export function resolveTransitionHooks(
|
||||||
|
|
||||||
leave(el, remove) {
|
leave(el, remove) {
|
||||||
const key = String(vnode.key)
|
const key = String(vnode.key)
|
||||||
if (el._enterCb) {
|
if (el[enterCbKey]) {
|
||||||
el._enterCb(true /* cancelled */)
|
el[enterCbKey](true /* cancelled */)
|
||||||
}
|
}
|
||||||
if (state.isUnmounting) {
|
if (state.isUnmounting) {
|
||||||
return remove()
|
return remove()
|
||||||
}
|
}
|
||||||
callHook(onBeforeLeave, [el])
|
callHook(onBeforeLeave, [el])
|
||||||
let called = false
|
let called = false
|
||||||
const done = (el._leaveCb = (cancelled?) => {
|
const done = (el[leaveCbKey] = (cancelled?) => {
|
||||||
if (called) return
|
if (called) return
|
||||||
called = true
|
called = true
|
||||||
remove()
|
remove()
|
||||||
|
@ -435,7 +438,7 @@ export function resolveTransitionHooks(
|
||||||
} else {
|
} else {
|
||||||
callHook(onAfterLeave, [el])
|
callHook(onAfterLeave, [el])
|
||||||
}
|
}
|
||||||
el._leaveCb = undefined
|
el[leaveCbKey] = undefined
|
||||||
if (leavingVNodesCache[key] === vnode) {
|
if (leavingVNodesCache[key] === vnode) {
|
||||||
delete leavingVNodesCache[key]
|
delete leavingVNodesCache[key]
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,6 +186,13 @@ export const TeleportImpl = {
|
||||||
internals,
|
internals,
|
||||||
TeleportMoveTypes.TOGGLE
|
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 {
|
} else {
|
||||||
// target changed
|
// target changed
|
||||||
|
@ -393,7 +400,7 @@ function hydrateTeleport(
|
||||||
// Force-casted public typing for h and TSX props inference
|
// Force-casted public typing for h and TSX props inference
|
||||||
export const Teleport = TeleportImpl as unknown as {
|
export const Teleport = TeleportImpl as unknown as {
|
||||||
__isTeleport: true
|
__isTeleport: true
|
||||||
new(): {
|
new (): {
|
||||||
$props: VNodeProps & TeleportProps
|
$props: VNodeProps & TeleportProps
|
||||||
$slots: {
|
$slots: {
|
||||||
default(): VNode[]
|
default(): VNode[]
|
||||||
|
|
|
@ -30,7 +30,7 @@ interface DevtoolsHook {
|
||||||
appRecords: AppRecord[]
|
appRecords: AppRecord[]
|
||||||
/**
|
/**
|
||||||
* Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9
|
* 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
|
cleanupBuffer?: (matchArg: unknown) => boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,8 +134,10 @@ export function createHydrationFunctions(
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
warn(
|
warn(
|
||||||
`Hydration text mismatch:` +
|
`Hydration text mismatch:` +
|
||||||
`\n- Client: ${JSON.stringify((node as Text).data)}` +
|
`\n- Server rendered: ${JSON.stringify(
|
||||||
`\n- Server: ${JSON.stringify(vnode.children)}`
|
(node as Text).data
|
||||||
|
)}` +
|
||||||
|
`\n- Client rendered: ${JSON.stringify(vnode.children)}`
|
||||||
)
|
)
|
||||||
;(node as Text).data = vnode.children as string
|
;(node as Text).data = vnode.children as string
|
||||||
}
|
}
|
||||||
|
@ -406,8 +408,8 @@ export function createHydrationFunctions(
|
||||||
`Hydration text content mismatch in <${
|
`Hydration text content mismatch in <${
|
||||||
vnode.type as string
|
vnode.type as string
|
||||||
}>:\n` +
|
}>:\n` +
|
||||||
`- Client: ${el.textContent}\n` +
|
`- Server rendered: ${el.textContent}\n` +
|
||||||
`- Server: ${vnode.children as string}`
|
`- Client rendered: ${vnode.children as string}`
|
||||||
)
|
)
|
||||||
el.textContent = vnode.children as string
|
el.textContent = vnode.children as string
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,13 @@ export {
|
||||||
defineSlots,
|
defineSlots,
|
||||||
defineModel,
|
defineModel,
|
||||||
withDefaults,
|
withDefaults,
|
||||||
useModel,
|
useModel
|
||||||
// internal
|
} from './apiSetupHelpers'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export {
|
||||||
mergeDefaults,
|
mergeDefaults,
|
||||||
mergeModels,
|
mergeModels,
|
||||||
createPropsRestProxy,
|
createPropsRestProxy,
|
||||||
|
@ -111,7 +116,9 @@ export { useSSRContext, ssrContextKey } from './helpers/useSsrContext'
|
||||||
|
|
||||||
export { createRenderer, createHydrationRenderer } from './renderer'
|
export { createRenderer, createHydrationRenderer } from './renderer'
|
||||||
export { queuePostFlushCb } from './scheduler'
|
export { queuePostFlushCb } from './scheduler'
|
||||||
export { warn, assertNumber } from './warning'
|
export { warn } from './warning'
|
||||||
|
/** @internal */
|
||||||
|
export { assertNumber } from './warning'
|
||||||
export {
|
export {
|
||||||
handleError,
|
handleError,
|
||||||
callWithErrorHandling,
|
callWithErrorHandling,
|
||||||
|
|
|
@ -584,7 +584,7 @@ function baseCreateRenderer(
|
||||||
slotScopeIds: string[] | null,
|
slotScopeIds: string[] | null,
|
||||||
optimized: boolean
|
optimized: boolean
|
||||||
) => {
|
) => {
|
||||||
isSVG = isSVG || (n2.type as string) === 'svg'
|
isSVG = isSVG || n2.type === 'svg'
|
||||||
if (n1 == null) {
|
if (n1 == null) {
|
||||||
mountElement(
|
mountElement(
|
||||||
n2,
|
n2,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import { isArray, NOOP } from '@vue/shared'
|
import { Awaited, isArray, NOOP } from '@vue/shared'
|
||||||
import { ComponentInternalInstance, getComponentName } from './component'
|
import { ComponentInternalInstance, getComponentName } from './component'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
@ -50,10 +50,10 @@ let currentFlushPromise: Promise<void> | null = null
|
||||||
const RECURSION_LIMIT = 100
|
const RECURSION_LIMIT = 100
|
||||||
type CountMap = Map<SchedulerJob, number>
|
type CountMap = Map<SchedulerJob, number>
|
||||||
|
|
||||||
export function nextTick<T = void>(
|
export function nextTick<T = void, R = void>(
|
||||||
this: T,
|
this: T,
|
||||||
fn?: (this: T) => void
|
fn?: (this: T) => R
|
||||||
): Promise<void> {
|
): Promise<Awaited<R>> {
|
||||||
const p = currentFlushPromise || resolvedPromise
|
const p = currentFlushPromise || resolvedPromise
|
||||||
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
||||||
}
|
}
|
||||||
|
|
|
@ -681,7 +681,7 @@ export function cloneVNode<T, U>(
|
||||||
if (__COMPAT__) {
|
if (__COMPAT__) {
|
||||||
defineLegacyVNodeProperties(cloned as VNode)
|
defineLegacyVNodeProperties(cloned as VNode)
|
||||||
}
|
}
|
||||||
return cloned as any
|
return cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -322,7 +322,7 @@ describe('defineCustomElement', () => {
|
||||||
emit('my-click', 1)
|
emit('my-click', 1)
|
||||||
},
|
},
|
||||||
onMousedown: () => {
|
onMousedown: () => {
|
||||||
emit('myEvent', 1) // validate hypenization
|
emit('myEvent', 1) // validate hyphenation
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ function triggerEvent(
|
||||||
event: string,
|
event: string,
|
||||||
process?: (e: any) => any
|
process?: (e: any) => any
|
||||||
) {
|
) {
|
||||||
const e = document.createEvent('HTMLEvents')
|
const e = new Event(event, {
|
||||||
e.initEvent(event, true, true)
|
bubbles: true,
|
||||||
|
cancelable: true
|
||||||
|
})
|
||||||
if (event === 'click') {
|
if (event === 'click') {
|
||||||
;(e as any).button = 0
|
;(e as any).button = 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { patchProp } from '../src/patchProp'
|
import { patchProp } from '../src/patchProp'
|
||||||
import { ElementWithTransition } from '../src/components/Transition'
|
import { ElementWithTransition, vtcKey } from '../src/components/Transition'
|
||||||
import { svgNS } from '../src/nodeOps'
|
import { svgNS } from '../src/nodeOps'
|
||||||
|
|
||||||
describe('runtime-dom: class patching', () => {
|
describe('runtime-dom: class patching', () => {
|
||||||
|
@ -13,12 +13,12 @@ describe('runtime-dom: class patching', () => {
|
||||||
|
|
||||||
test('transition class', () => {
|
test('transition class', () => {
|
||||||
const el = document.createElement('div') as ElementWithTransition
|
const el = document.createElement('div') as ElementWithTransition
|
||||||
el._vtc = new Set(['bar', 'baz'])
|
el[vtcKey] = new Set(['bar', 'baz'])
|
||||||
patchProp(el, 'class', null, 'foo')
|
patchProp(el, 'class', null, 'foo')
|
||||||
expect(el.className).toBe('foo bar baz')
|
expect(el.className).toBe('foo bar baz')
|
||||||
patchProp(el, 'class', null, null)
|
patchProp(el, 'class', null, null)
|
||||||
expect(el.className).toBe('bar baz')
|
expect(el.className).toBe('bar baz')
|
||||||
delete el._vtc
|
delete el[vtcKey]
|
||||||
patchProp(el, 'class', null, 'foo')
|
patchProp(el, 'class', null, 'foo')
|
||||||
expect(el.className).toBe('foo')
|
expect(el.className).toBe('foo')
|
||||||
})
|
})
|
||||||
|
|
|
@ -37,6 +37,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.3.4",
|
"@vue/shared": "3.3.4",
|
||||||
"@vue/runtime-core": "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
|
new (...args: any[]): ComponentPublicInstance
|
||||||
}): VueElementConstructor
|
}): VueElementConstructor
|
||||||
|
|
||||||
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function defineCustomElement(
|
export function defineCustomElement(
|
||||||
options: any,
|
options: any,
|
||||||
hydrate?: RootHydrateFunction
|
hydrate?: RootHydrateFunction
|
||||||
|
@ -155,6 +156,7 @@ export function defineCustomElement(
|
||||||
return VueCustomElement
|
return VueCustomElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export const defineSSRCustomElement = ((options: any) => {
|
export const defineSSRCustomElement = ((options: any) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return defineCustomElement(options, hydrate)
|
return defineCustomElement(options, hydrate)
|
||||||
|
@ -176,7 +178,7 @@ export class VueElement extends BaseClass {
|
||||||
private _resolved = false
|
private _resolved = false
|
||||||
private _numberProps: Record<string, true> | null = null
|
private _numberProps: Record<string, true> | null = null
|
||||||
private _styles?: HTMLStyleElement[]
|
private _styles?: HTMLStyleElement[]
|
||||||
|
private _ob?: MutationObserver | null = null
|
||||||
constructor(
|
constructor(
|
||||||
private _def: InnerComponentDef,
|
private _def: InnerComponentDef,
|
||||||
private _props: Record<string, any> = {},
|
private _props: Record<string, any> = {},
|
||||||
|
@ -213,6 +215,10 @@ export class VueElement extends BaseClass {
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
this._connected = false
|
this._connected = false
|
||||||
|
if (this._ob) {
|
||||||
|
this._ob.disconnect()
|
||||||
|
this._ob = null
|
||||||
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (!this._connected) {
|
if (!this._connected) {
|
||||||
render(null, this.shadowRoot!)
|
render(null, this.shadowRoot!)
|
||||||
|
@ -233,11 +239,13 @@ export class VueElement extends BaseClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
// watch future attr changes
|
// watch future attr changes
|
||||||
new MutationObserver(mutations => {
|
this._ob = new MutationObserver(mutations => {
|
||||||
for (const m of mutations) {
|
for (const m of mutations) {
|
||||||
this._setAttr(m.attributeName!)
|
this._setAttr(m.attributeName!)
|
||||||
}
|
}
|
||||||
}).observe(this, { attributes: true })
|
})
|
||||||
|
|
||||||
|
this._ob.observe(this, { attributes: true })
|
||||||
|
|
||||||
const resolve = (def: InnerComponentDef, isAsync = false) => {
|
const resolve = (def: InnerComponentDef, isAsync = false) => {
|
||||||
const { props, styles } = def
|
const { props, styles } = def
|
||||||
|
|
|
@ -32,12 +32,14 @@ export interface TransitionProps extends BaseTransitionProps<Element> {
|
||||||
leaveToClass?: string
|
leaveToClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const vtcKey = Symbol('_vtc')
|
||||||
|
|
||||||
export interface ElementWithTransition extends HTMLElement {
|
export interface ElementWithTransition extends HTMLElement {
|
||||||
// _vtc = Vue Transition Classes.
|
// _vtc = Vue Transition Classes.
|
||||||
// Store the temporarily-added transition classes on the element
|
// Store the temporarily-added transition classes on the element
|
||||||
// so that we can avoid overwriting them if the element's class is patched
|
// so that we can avoid overwriting them if the element's class is patched
|
||||||
// during the transition.
|
// during the transition.
|
||||||
_vtc?: Set<string>
|
[vtcKey]?: Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOM Transition is a higher-order-component based on the platform-agnostic
|
// 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) {
|
export function addTransitionClass(el: Element, cls: string) {
|
||||||
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
|
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
|
||||||
;(
|
;(
|
||||||
(el as ElementWithTransition)._vtc ||
|
(el as ElementWithTransition)[vtcKey] ||
|
||||||
((el as ElementWithTransition)._vtc = new Set())
|
((el as ElementWithTransition)[vtcKey] = new Set())
|
||||||
).add(cls)
|
).add(cls)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeTransitionClass(el: Element, cls: string) {
|
export function removeTransitionClass(el: Element, cls: string) {
|
||||||
cls.split(/\s+/).forEach(c => c && el.classList.remove(c))
|
cls.split(/\s+/).forEach(c => c && el.classList.remove(c))
|
||||||
const { _vtc } = el as ElementWithTransition
|
const _vtc = (el as ElementWithTransition)[vtcKey]
|
||||||
if (_vtc) {
|
if (_vtc) {
|
||||||
_vtc.delete(cls)
|
_vtc.delete(cls)
|
||||||
if (!_vtc!.size) {
|
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
|
// If comma is not replaced with a dot, the input will be rounded down
|
||||||
// (i.e. acting as a floor function) causing unexpected behaviors
|
// (i.e. acting as a floor function) causing unexpected behaviors
|
||||||
function toMs(s: string): number {
|
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
|
return Number(s.slice(0, -1).replace(',', '.')) * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ import {
|
||||||
getTransitionInfo,
|
getTransitionInfo,
|
||||||
resolveTransitionProps,
|
resolveTransitionProps,
|
||||||
TransitionPropsValidators,
|
TransitionPropsValidators,
|
||||||
forceReflow
|
forceReflow,
|
||||||
|
vtcKey
|
||||||
} from './Transition'
|
} from './Transition'
|
||||||
import {
|
import {
|
||||||
Fragment,
|
Fragment,
|
||||||
|
@ -29,7 +30,8 @@ import { extend } from '@vue/shared'
|
||||||
|
|
||||||
const positionMap = new WeakMap<VNode, DOMRect>()
|
const positionMap = new WeakMap<VNode, DOMRect>()
|
||||||
const newPositionMap = new WeakMap<VNode, DOMRect>()
|
const newPositionMap = new WeakMap<VNode, DOMRect>()
|
||||||
|
const moveCbKey = Symbol('_moveCb')
|
||||||
|
const enterCbKey = Symbol('_enterCb')
|
||||||
export type TransitionGroupProps = Omit<TransitionProps, 'mode'> & {
|
export type TransitionGroupProps = Omit<TransitionProps, 'mode'> & {
|
||||||
tag?: string
|
tag?: string
|
||||||
moveClass?: string
|
moveClass?: string
|
||||||
|
@ -80,13 +82,13 @@ const TransitionGroupImpl: ComponentOptions = {
|
||||||
const style = el.style
|
const style = el.style
|
||||||
addTransitionClass(el, moveClass)
|
addTransitionClass(el, moveClass)
|
||||||
style.transform = style.webkitTransform = style.transitionDuration = ''
|
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) {
|
if (e && e.target !== el) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!e || /transform$/.test(e.propertyName)) {
|
if (!e || /transform$/.test(e.propertyName)) {
|
||||||
el.removeEventListener('transitionend', cb)
|
el.removeEventListener('transitionend', cb)
|
||||||
;(el as any)._moveCb = null
|
;(el as any)[moveCbKey] = null
|
||||||
removeTransitionClass(el, moveClass)
|
removeTransitionClass(el, moveClass)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -162,11 +164,11 @@ export const TransitionGroup = TransitionGroupImpl as unknown as {
|
||||||
|
|
||||||
function callPendingCbs(c: VNode) {
|
function callPendingCbs(c: VNode) {
|
||||||
const el = c.el as any
|
const el = c.el as any
|
||||||
if (el._moveCb) {
|
if (el[moveCbKey]) {
|
||||||
el._moveCb()
|
el[moveCbKey]()
|
||||||
}
|
}
|
||||||
if (el._enterCb) {
|
if (el[enterCbKey]) {
|
||||||
el._enterCb()
|
el[enterCbKey]()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,8 +200,9 @@ function hasCSSTransform(
|
||||||
// all other transition classes applied to ensure only the move class
|
// all other transition classes applied to ensure only the move class
|
||||||
// is applied.
|
// is applied.
|
||||||
const clone = el.cloneNode() as HTMLElement
|
const clone = el.cloneNode() as HTMLElement
|
||||||
if (el._vtc) {
|
const _vtc = el[vtcKey]
|
||||||
el._vtc.forEach(cls => {
|
if (_vtc) {
|
||||||
|
_vtc.forEach(cls => {
|
||||||
cls.split(/\s+/).forEach(c => c && clone.classList.remove(c))
|
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
|
// 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.
|
// be tree-shaken in case v-model is never used.
|
||||||
|
@ -44,7 +46,7 @@ export const vModelText: ModelDirective<
|
||||||
HTMLInputElement | HTMLTextAreaElement
|
HTMLInputElement | HTMLTextAreaElement
|
||||||
> = {
|
> = {
|
||||||
created(el, { modifiers: { lazy, trim, number } }, vnode) {
|
created(el, { modifiers: { lazy, trim, number } }, vnode) {
|
||||||
el._assign = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
const castToNumber =
|
const castToNumber =
|
||||||
number || (vnode.props && vnode.props.type === 'number')
|
number || (vnode.props && vnode.props.type === 'number')
|
||||||
addEventListener(el, lazy ? 'change' : 'input', e => {
|
addEventListener(el, lazy ? 'change' : 'input', e => {
|
||||||
|
@ -56,7 +58,7 @@ export const vModelText: ModelDirective<
|
||||||
if (castToNumber) {
|
if (castToNumber) {
|
||||||
domValue = looseToNumber(domValue)
|
domValue = looseToNumber(domValue)
|
||||||
}
|
}
|
||||||
el._assign(domValue)
|
el[assignKey](domValue)
|
||||||
})
|
})
|
||||||
if (trim) {
|
if (trim) {
|
||||||
addEventListener(el, 'change', () => {
|
addEventListener(el, 'change', () => {
|
||||||
|
@ -78,7 +80,7 @@ export const vModelText: ModelDirective<
|
||||||
el.value = value == null ? '' : value
|
el.value = value == null ? '' : value
|
||||||
},
|
},
|
||||||
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
|
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
|
||||||
el._assign = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
// avoid clearing unresolved text. #2302
|
// avoid clearing unresolved text. #2302
|
||||||
if ((el as any).composing) return
|
if ((el as any).composing) return
|
||||||
if (document.activeElement === el && el.type !== 'range') {
|
if (document.activeElement === el && el.type !== 'range') {
|
||||||
|
@ -106,12 +108,12 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
||||||
// #4096 array checkboxes need to be deep traversed
|
// #4096 array checkboxes need to be deep traversed
|
||||||
deep: true,
|
deep: true,
|
||||||
created(el, _, vnode) {
|
created(el, _, vnode) {
|
||||||
el._assign = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
addEventListener(el, 'change', () => {
|
addEventListener(el, 'change', () => {
|
||||||
const modelValue = (el as any)._modelValue
|
const modelValue = (el as any)._modelValue
|
||||||
const elementValue = getValue(el)
|
const elementValue = getValue(el)
|
||||||
const checked = el.checked
|
const checked = el.checked
|
||||||
const assign = el._assign
|
const assign = el[assignKey]
|
||||||
if (isArray(modelValue)) {
|
if (isArray(modelValue)) {
|
||||||
const index = looseIndexOf(modelValue, elementValue)
|
const index = looseIndexOf(modelValue, elementValue)
|
||||||
const found = index !== -1
|
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
|
// set initial checked on mount to wait for true-value/false-value
|
||||||
mounted: setChecked,
|
mounted: setChecked,
|
||||||
beforeUpdate(el, binding, vnode) {
|
beforeUpdate(el, binding, vnode) {
|
||||||
el._assign = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
setChecked(el, binding, vnode)
|
setChecked(el, binding, vnode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,13 +165,13 @@ function setChecked(
|
||||||
export const vModelRadio: ModelDirective<HTMLInputElement> = {
|
export const vModelRadio: ModelDirective<HTMLInputElement> = {
|
||||||
created(el, { value }, vnode) {
|
created(el, { value }, vnode) {
|
||||||
el.checked = looseEqual(value, vnode.props!.value)
|
el.checked = looseEqual(value, vnode.props!.value)
|
||||||
el._assign = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
addEventListener(el, 'change', () => {
|
addEventListener(el, 'change', () => {
|
||||||
el._assign(getValue(el))
|
el[assignKey](getValue(el))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
beforeUpdate(el, { value, oldValue }, vnode) {
|
beforeUpdate(el, { value, oldValue }, vnode) {
|
||||||
el._assign = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
if (value !== oldValue) {
|
if (value !== oldValue) {
|
||||||
el.checked = looseEqual(value, vnode.props!.value)
|
el.checked = looseEqual(value, vnode.props!.value)
|
||||||
}
|
}
|
||||||
|
@ -187,7 +189,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
||||||
.map((o: HTMLOptionElement) =>
|
.map((o: HTMLOptionElement) =>
|
||||||
number ? looseToNumber(getValue(o)) : getValue(o)
|
number ? looseToNumber(getValue(o)) : getValue(o)
|
||||||
)
|
)
|
||||||
el._assign(
|
el[assignKey](
|
||||||
el.multiple
|
el.multiple
|
||||||
? isSetModel
|
? isSetModel
|
||||||
? new Set(selectedVal)
|
? new Set(selectedVal)
|
||||||
|
@ -195,7 +197,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
||||||
: selectedVal[0]
|
: selectedVal[0]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
el._assign = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
},
|
},
|
||||||
// set value in mounted & updated because <select> relies on its children
|
// set value in mounted & updated because <select> relies on its children
|
||||||
// <option>s.
|
// <option>s.
|
||||||
|
@ -203,7 +205,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
||||||
setSelected(el, value)
|
setSelected(el, value)
|
||||||
},
|
},
|
||||||
beforeUpdate(el, _binding, vnode) {
|
beforeUpdate(el, _binding, vnode) {
|
||||||
el._assign = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
},
|
},
|
||||||
updated(el, { value }) {
|
updated(el, { value }) {
|
||||||
setSelected(el, value)
|
setSelected(el, value)
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { ObjectDirective } from '@vue/runtime-core'
|
import { ObjectDirective } from '@vue/runtime-core'
|
||||||
|
|
||||||
|
export const vShowOldKey = Symbol('_vod')
|
||||||
|
|
||||||
interface VShowElement extends HTMLElement {
|
interface VShowElement extends HTMLElement {
|
||||||
// _vod = vue original display
|
// _vod = vue original display
|
||||||
_vod: string
|
[vShowOldKey]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const vShow: ObjectDirective<VShowElement> = {
|
export const vShow: ObjectDirective<VShowElement> = {
|
||||||
beforeMount(el, { value }, { transition }) {
|
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) {
|
if (transition && value) {
|
||||||
transition.beforeEnter(el)
|
transition.beforeEnter(el)
|
||||||
} else {
|
} else {
|
||||||
|
@ -41,7 +43,7 @@ export const vShow: ObjectDirective<VShowElement> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDisplay(el: VShowElement, value: unknown): void {
|
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
|
// SSR vnode transforms, only used when user includes client-oriented render
|
||||||
|
|
|
@ -248,8 +248,9 @@ export interface HTMLAttributes extends AriaAttributes, EventHandlers<Events> {
|
||||||
contextmenu?: string
|
contextmenu?: string
|
||||||
dir?: string
|
dir?: string
|
||||||
draggable?: Booleanish
|
draggable?: Booleanish
|
||||||
hidden?: Booleanish
|
hidden?: Booleanish | '' | 'hidden' | 'until-found'
|
||||||
id?: string
|
id?: string
|
||||||
|
inert?: Booleanish
|
||||||
lang?: string
|
lang?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
spellcheck?: Booleanish
|
spellcheck?: Booleanish
|
||||||
|
@ -457,6 +458,7 @@ export interface ImgHTMLAttributes extends HTMLAttributes {
|
||||||
srcset?: string
|
srcset?: string
|
||||||
usemap?: string
|
usemap?: string
|
||||||
width?: Numberish
|
width?: Numberish
|
||||||
|
loading?: 'lazy' | 'eager'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InsHTMLAttributes extends HTMLAttributes {
|
export interface InsHTMLAttributes extends HTMLAttributes {
|
||||||
|
@ -464,6 +466,31 @@ export interface InsHTMLAttributes extends HTMLAttributes {
|
||||||
datetime?: string
|
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 {
|
export interface InputHTMLAttributes extends HTMLAttributes {
|
||||||
accept?: string
|
accept?: string
|
||||||
alt?: string
|
alt?: string
|
||||||
|
@ -495,7 +522,7 @@ export interface InputHTMLAttributes extends HTMLAttributes {
|
||||||
size?: Numberish
|
size?: Numberish
|
||||||
src?: string
|
src?: string
|
||||||
step?: Numberish
|
step?: Numberish
|
||||||
type?: string
|
type?: InputTypeHTMLAttribute
|
||||||
value?: any // we support :value to be bound to anything w/ v-model
|
value?: any // we support :value to be bound to anything w/ v-model
|
||||||
width?: Numberish
|
width?: Numberish
|
||||||
}
|
}
|
||||||
|
@ -677,7 +704,7 @@ export interface TextareaHTMLAttributes extends HTMLAttributes {
|
||||||
minlength?: Numberish
|
minlength?: Numberish
|
||||||
name?: string
|
name?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
readonly?: boolean
|
readonly?: Booleanish
|
||||||
required?: Booleanish
|
required?: Booleanish
|
||||||
rows?: Numberish
|
rows?: Numberish
|
||||||
value?: string | string[] | number
|
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
|
* @see https://www.w3.org/TR/SVG/styling.html#ElementSpecificStyling
|
||||||
*/
|
*/
|
||||||
class?: any
|
class?: any
|
||||||
style?: string | CSSProperties
|
style?: StyleValue
|
||||||
|
|
||||||
color?: string
|
color?: string
|
||||||
height?: Numberish
|
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
|
// compiler should normalize class + :class bindings on the same element
|
||||||
// into a single binding ['staticClass', dynamic]
|
// 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
|
// directly setting className should be faster than setAttribute in theory
|
||||||
// if this is an element during a transition, take the temporary transition
|
// if this is an element during a transition, take the temporary transition
|
||||||
// classes into account.
|
// classes into account.
|
||||||
const transitionClasses = (el as ElementWithTransition)._vtc
|
const transitionClasses = (el as ElementWithTransition)[vtcKey]
|
||||||
if (transitionClasses) {
|
if (transitionClasses) {
|
||||||
value = (
|
value = (
|
||||||
value ? [value, ...transitionClasses] : [...transitionClasses]
|
value ? [value, ...transitionClasses] : [...transitionClasses]
|
||||||
|
|
|
@ -30,15 +30,17 @@ export function removeEventListener(
|
||||||
el.removeEventListener(event, handler, options)
|
el.removeEventListener(event, handler, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const veiKey = Symbol('_vei')
|
||||||
|
|
||||||
export function patchEvent(
|
export function patchEvent(
|
||||||
el: Element & { _vei?: Record<string, Invoker | undefined> },
|
el: Element & { [veiKey]?: Record<string, Invoker | undefined> },
|
||||||
rawName: string,
|
rawName: string,
|
||||||
prevValue: EventValue | null,
|
prevValue: EventValue | null,
|
||||||
nextValue: EventValue | null,
|
nextValue: EventValue | null,
|
||||||
instance: ComponentInternalInstance | null = null
|
instance: ComponentInternalInstance | null = null
|
||||||
) {
|
) {
|
||||||
// vei = vue event invokers
|
// vei = vue event invokers
|
||||||
const invokers = el._vei || (el._vei = {})
|
const invokers = el[veiKey] || (el[veiKey] = {})
|
||||||
const existingInvoker = invokers[rawName]
|
const existingInvoker = invokers[rawName]
|
||||||
if (nextValue && existingInvoker) {
|
if (nextValue && existingInvoker) {
|
||||||
// patch
|
// patch
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { isString, hyphenate, capitalize, isArray } from '@vue/shared'
|
import { isString, hyphenate, capitalize, isArray } from '@vue/shared'
|
||||||
import { camelize, warn } from '@vue/runtime-core'
|
import { camelize, warn } from '@vue/runtime-core'
|
||||||
|
import { vShowOldKey } from '../directives/vShow'
|
||||||
|
|
||||||
type Style = string | Record<string, string | string[]> | null
|
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`,
|
// indicates that the `display` of the element is controlled by `v-show`,
|
||||||
// so we always keep the current `display` value regardless of the `style`
|
// so we always keep the current `display` value regardless of the `style`
|
||||||
// value, thus handing over control to `v-show`.
|
// value, thus handing over control to `v-show`.
|
||||||
if ('_vod' in el) {
|
if (vShowOldKey in el) {
|
||||||
style.display = currentDisplay
|
style.display = currentDisplay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,30 @@ describe('ssr: directives', () => {
|
||||||
).toBe(`<input type="radio">`)
|
).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 () => {
|
test('checkbox', async () => {
|
||||||
expect(
|
expect(
|
||||||
await renderToString(
|
await renderToString(
|
||||||
|
|
|
@ -156,7 +156,7 @@ describe('ssr: scopedId runtime behavior', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// #3513
|
// #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 = {
|
const Child = {
|
||||||
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||||
push(`<div${ssrRenderAttrs(attrs)}></div>`)
|
push(`<div${ssrRenderAttrs(attrs)}></div>`)
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
<title>Vue SFC Playground</title>
|
<title>Vue SFC Playground</title>
|
||||||
<script>
|
<script>
|
||||||
// process shim for old versions of @vue/compiler-sfc dependency
|
// process shim for old versions of @vue/compiler-sfc dependency
|
||||||
window.process = { env: {} }
|
window.process = { env: {} }
|
||||||
const savedPreferDark = localStorage.getItem('vue-sfc-playground-prefer-dark')
|
const savedPreferDark = localStorage.getItem('vue-sfc-playground-prefer-dark')
|
||||||
if (
|
if (
|
||||||
savedPreferDark === 'true' ||
|
savedPreferDark === 'true' ||
|
||||||
(!savedPreferDark && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
(!savedPreferDark && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
) {
|
) {
|
||||||
document.documentElement.classList.add('dark')
|
document.documentElement.classList.add('dark')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/sfc-playground",
|
"name": "@vue/sfc-playground",
|
||||||
"version": "3.3.4",
|
"version": "3.3.4",
|
||||||
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
@ -8,13 +9,13 @@
|
||||||
"serve": "vite preview"
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.2.3",
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
"vite": "^4.3.0"
|
"vite": "^4.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/repl": "^1.4.1",
|
"@vue/repl": "^2.5.8",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jszip": "^3.6.0",
|
"jszip": "^3.10.1",
|
||||||
"vue": "workspace:*"
|
"vue": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Header from './Header.vue'
|
import Header from './Header.vue'
|
||||||
import { Repl, ReplStore, SFCOptions } from '@vue/repl'
|
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 = () => {
|
const setVH = () => {
|
||||||
document.documentElement.style.setProperty('--vh', window.innerHeight + `px`)
|
document.documentElement.style.setProperty('--vh', window.innerHeight + `px`)
|
||||||
|
@ -71,6 +72,15 @@ function toggleSSR() {
|
||||||
useSSRMode.value = !useSSRMode.value
|
useSSRMode.value = !useSSRMode.value
|
||||||
store.setFiles(store.getFiles())
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -78,10 +88,13 @@ function toggleSSR() {
|
||||||
:store="store"
|
:store="store"
|
||||||
:dev="useDevMode"
|
:dev="useDevMode"
|
||||||
:ssr="useSSRMode"
|
:ssr="useSSRMode"
|
||||||
|
@toggle-theme="toggleTheme"
|
||||||
@toggle-dev="toggleDevMode"
|
@toggle-dev="toggleDevMode"
|
||||||
@toggle-ssr="toggleSSR"
|
@toggle-ssr="toggleSSR"
|
||||||
/>
|
/>
|
||||||
<Repl
|
<Repl
|
||||||
|
:theme="theme"
|
||||||
|
:editor="Monaco"
|
||||||
@keydown.ctrl.s.prevent
|
@keydown.ctrl.s.prevent
|
||||||
@keydown.meta.s.prevent
|
@keydown.meta.s.prevent
|
||||||
:ssr="useSSRMode"
|
:ssr="useSSRMode"
|
||||||
|
@ -108,7 +121,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.vue-repl {
|
.vue-repl {
|
||||||
height: calc(var(--vh) - var(--nav-height));
|
height: calc(var(--vh) - var(--nav-height)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|
|
@ -1,44 +1,35 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { downloadProject } from './download/download'
|
import { downloadProject } from './download/download'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref } from 'vue'
|
||||||
import Sun from './icons/Sun.vue'
|
import Sun from './icons/Sun.vue'
|
||||||
import Moon from './icons/Moon.vue'
|
import Moon from './icons/Moon.vue'
|
||||||
import Share from './icons/Share.vue'
|
import Share from './icons/Share.vue'
|
||||||
import Download from './icons/Download.vue'
|
import Download from './icons/Download.vue'
|
||||||
import GitHub from './icons/GitHub.vue'
|
import GitHub from './icons/GitHub.vue'
|
||||||
import type { ReplStore } from '@vue/repl'
|
import type { ReplStore } from '@vue/repl'
|
||||||
|
import VersionSelect from './VersionSelect.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
store: ReplStore
|
store: ReplStore
|
||||||
dev: boolean
|
dev: boolean
|
||||||
ssr: boolean
|
ssr: boolean
|
||||||
}>()
|
}>()
|
||||||
|
const emit = defineEmits(['toggle-theme', 'toggle-ssr', 'toggle-dev'])
|
||||||
|
|
||||||
const { store } = props
|
const { store } = props
|
||||||
|
|
||||||
const currentCommit = __COMMIT__
|
const currentCommit = __COMMIT__
|
||||||
const activeVersion = ref(`@${currentCommit}`)
|
const vueVersion = ref(`@${currentCommit}`)
|
||||||
const publishedVersions = ref<string[]>()
|
|
||||||
const expanded = ref(false)
|
|
||||||
|
|
||||||
async function toggle() {
|
|
||||||
expanded.value = !expanded.value
|
|
||||||
if (!publishedVersions.value) {
|
|
||||||
publishedVersions.value = await fetchVersions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setVueVersion(v: string) {
|
async function setVueVersion(v: string) {
|
||||||
activeVersion.value = `loading...`
|
vueVersion.value = `loading...`
|
||||||
await store.setVueVersion(v)
|
await store.setVueVersion(v)
|
||||||
activeVersion.value = `v${v}`
|
vueVersion.value = `v${v}`
|
||||||
expanded.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetVueVersion() {
|
function resetVueVersion() {
|
||||||
store.resetVueVersion()
|
store.resetVueVersion()
|
||||||
activeVersion.value = `@${currentCommit}`
|
vueVersion.value = `@${currentCommit}`
|
||||||
expanded.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyLink(e: MouseEvent) {
|
async function copyLink(e: MouseEvent) {
|
||||||
|
@ -58,45 +49,7 @@ function toggleDark() {
|
||||||
'vue-sfc-playground-prefer-dark',
|
'vue-sfc-playground-prefer-dark',
|
||||||
String(cls.contains('dark'))
|
String(cls.contains('dark'))
|
||||||
)
|
)
|
||||||
}
|
emit('toggle-theme', 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
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -107,28 +60,28 @@ async function fetchVersions(): Promise<string[]> {
|
||||||
<span>Vue SFC Playground</span>
|
<span>Vue SFC Playground</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
<div class="version" @click.stop>
|
<VersionSelect
|
||||||
<span class="active-version" @click="toggle">
|
v-model="store.state.typescriptVersion"
|
||||||
Version
|
pkg="typescript"
|
||||||
<span class="number">{{ activeVersion }}</span>
|
label="TypeScript Version"
|
||||||
</span>
|
/>
|
||||||
<ul class="versions" :class="{ expanded }">
|
<VersionSelect
|
||||||
<li v-if="!publishedVersions"><a>loading versions...</a></li>
|
:model-value="vueVersion"
|
||||||
<li v-for="version of publishedVersions">
|
@update:model-value="setVueVersion"
|
||||||
<a @click="setVueVersion(version)">v{{ version }}</a>
|
pkg="vue"
|
||||||
</li>
|
label="Vue Version"
|
||||||
<li>
|
>
|
||||||
<a @click="resetVueVersion">This Commit ({{ currentCommit }})</a>
|
<li>
|
||||||
</li>
|
<a @click="resetVueVersion">This Commit ({{ currentCommit }})</a>
|
||||||
<li>
|
</li>
|
||||||
<a
|
<li>
|
||||||
href="https://app.netlify.com/sites/vue-sfc-playground/deploys"
|
<a
|
||||||
target="_blank"
|
href="https://app.netlify.com/sites/vue-sfc-playground/deploys"
|
||||||
>Commits History</a
|
target="_blank"
|
||||||
>
|
>Commits History</a
|
||||||
</li>
|
>
|
||||||
</ul>
|
</li>
|
||||||
</div>
|
</VersionSelect>
|
||||||
<button
|
<button
|
||||||
title="Toggle development production mode"
|
title="Toggle development production mode"
|
||||||
class="toggle-dev"
|
class="toggle-dev"
|
||||||
|
@ -159,14 +112,14 @@ async function fetchVersions(): Promise<string[]> {
|
||||||
>
|
>
|
||||||
<Download />
|
<Download />
|
||||||
</button>
|
</button>
|
||||||
<button title="View on GitHub" class="github">
|
<a
|
||||||
<a
|
href="https://github.com/vuejs/core/tree/main/packages/sfc-playground"
|
||||||
href="https://github.com/vuejs/core/tree/main/packages/sfc-playground"
|
target="_blank"
|
||||||
target="_blank"
|
title="View on GitHub"
|
||||||
>
|
class="github"
|
||||||
<GitHub />
|
>
|
||||||
</a>
|
<GitHub />
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
@ -233,33 +186,6 @@ h1 img {
|
||||||
display: flex;
|
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-dev span,
|
||||||
.toggle-ssr span {
|
.toggle-ssr span {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -300,12 +226,13 @@ h1 img {
|
||||||
}
|
}
|
||||||
|
|
||||||
.links button,
|
.links button,
|
||||||
.links button a {
|
.links .github {
|
||||||
|
padding: 1px 6px;
|
||||||
color: var(--btn);
|
color: var(--btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
.links button:hover,
|
.links button:hover,
|
||||||
.links button:hover a {
|
.links .github:hover {
|
||||||
color: var(--highlight);
|
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()
|
const files = store.getFiles()
|
||||||
for (const file in files) {
|
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' })
|
const blob = await zip.generateAsync({ type: 'blob' })
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"vue": "^3.3.0"
|
"vue": "^3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.2.3",
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
"vite": "^4.3.0"
|
"vite": "^4.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,15 @@ import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { defineConfig, Plugin } from 'vite'
|
import { defineConfig, Plugin } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
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({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue({
|
vue({
|
||||||
script: {
|
script: {
|
||||||
|
defineModel: true,
|
||||||
fs: {
|
fs: {
|
||||||
fileExists: fs.existsSync,
|
fileExists: fs.existsSync,
|
||||||
readFile: file => fs.readFileSync(file, 'utf-8')
|
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'
|
val !== null && typeof val === 'object'
|
||||||
|
|
||||||
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
|
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
|
export const objectToString = Object.prototype.toString
|
||||||
|
@ -110,16 +114,17 @@ export const hyphenate = cacheStringFunction((str: string) =>
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export const capitalize = cacheStringFunction(
|
export const capitalize = cacheStringFunction(<T extends string>(str: T) => {
|
||||||
(str: string) => str.charAt(0).toUpperCase() + str.slice(1)
|
return (str.charAt(0).toUpperCase() + str.slice(1)) as Capitalize<T>
|
||||||
)
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export const toHandlerKey = cacheStringFunction((str: string) =>
|
export const toHandlerKey = cacheStringFunction(<T extends string>(str: T) => {
|
||||||
str ? `on${capitalize(str)}` : ``
|
const s = str ? `on${capitalize(str)}` : ``
|
||||||
)
|
return s as T extends '' ? '' : `on${Capitalize<T>}`
|
||||||
|
})
|
||||||
|
|
||||||
// compare whether a value has changed, accounting for NaN.
|
// compare whether a value has changed, accounting for NaN.
|
||||||
export const hasChanged = (value: any, oldValue: any): boolean =>
|
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
|
* "123-foo" will be returned as-is
|
||||||
*/
|
*/
|
||||||
export const toNumber = (val: any): any => {
|
export const toNumber = (val: any): any => {
|
||||||
|
|
|
@ -6,3 +6,6 @@ const GLOBALS_ALLOWED =
|
||||||
'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console'
|
'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console'
|
||||||
|
|
||||||
export const isGloballyAllowed = /*#__PURE__*/ makeMap(GLOBALS_ALLOWED)
|
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