mirror of https://github.com/vuejs/core.git
Merge branch 'minor' into edison/feat/svgAndMathML
This commit is contained in:
commit
d6e69c552b
|
@ -11,7 +11,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: Release
|
environment: Release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: minor
|
ref: minor
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: Release
|
environment: Release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
environment: Release
|
environment: Release
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
@ -36,12 +36,13 @@ jobs:
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Update npm
|
||||||
|
run: npm i -g npm@latest
|
||||||
|
|
||||||
- name: Build and publish
|
- name: Build and publish
|
||||||
id: publish
|
id: publish
|
||||||
run: |
|
run: |
|
||||||
pnpm release --publishOnly
|
pnpm release --publishOnly
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Create GitHub release
|
- name: Create GitHub release
|
||||||
id: release_tag
|
id: release_tag
|
||||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
github.event.workflow_run.event == 'pull_request' &&
|
github.event.workflow_run.event == 'pull_request' &&
|
||||||
github.event.workflow_run.conclusion == 'success'
|
github.event.workflow_run.conclusion == 'success'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
@ -37,7 +37,7 @@ jobs:
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Download Size Data
|
- name: Download Size Data
|
||||||
uses: dawidd6/action-download-artifact@v9
|
uses: dawidd6/action-download-artifact@v11
|
||||||
with:
|
with:
|
||||||
name: size-data
|
name: size-data
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
|
@ -56,7 +56,7 @@ jobs:
|
||||||
path: temp/size/base.txt
|
path: temp/size/base.txt
|
||||||
|
|
||||||
- name: Download Previous Size Data
|
- name: Download Previous Size Data
|
||||||
uses: dawidd6/action-download-artifact@v9
|
uses: dawidd6/action-download-artifact@v11
|
||||||
with:
|
with:
|
||||||
branch: ${{ steps.pr-base.outputs.content }}
|
branch: ${{ steps.pr-base.outputs.content }}
|
||||||
workflow: size-data.yml
|
workflow: size-data.yml
|
||||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
@ -32,7 +32,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
@ -54,7 +54,7 @@ jobs:
|
||||||
e2e-test:
|
e2e-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup cache for Chromium binary
|
- name: Setup cache for Chromium binary
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
@ -111,7 +111,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
|
@ -1,3 +1,44 @@
|
||||||
|
## [3.5.19](https://github.com/vuejs/core/compare/v3.5.18...v3.5.19) (2025-08-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-core:** adjacent v-else should cause a compiler error ([#13699](https://github.com/vuejs/core/issues/13699)) ([911e670](https://github.com/vuejs/core/commit/911e67045e2a63e0ecbd198ed4f567530f6d1c17)), closes [#13698](https://github.com/vuejs/core/issues/13698)
|
||||||
|
* **compiler-core:** prevent cached array children from retaining detached dom nodes ([#13691](https://github.com/vuejs/core/issues/13691)) ([7f60ef8](https://github.com/vuejs/core/commit/7f60ef83e735dbd29d323347acecf69f22b06d53)), closes [element-plus/element-plus#21408](https://github.com/element-plus/element-plus/issues/21408) [#13211](https://github.com/vuejs/core/issues/13211)
|
||||||
|
* **compiler-sfc:** improve type inference for generic type aliases types ([#12876](https://github.com/vuejs/core/issues/12876)) ([d9dd628](https://github.com/vuejs/core/commit/d9dd628800ae32e673bdfabfe79f1988037991d0)), closes [#12872](https://github.com/vuejs/core/issues/12872)
|
||||||
|
* **compiler-sfc:** throw mismatched script langs error before invoking babel ([#13194](https://github.com/vuejs/core/issues/13194)) ([0562548](https://github.com/vuejs/core/commit/0562548ab3a040073386021222225e0e9d43c632)), closes [#13193](https://github.com/vuejs/core/issues/13193)
|
||||||
|
* **compiler-ssr:** disable v-memo transform in ssr vdom fallback branch ([#13725](https://github.com/vuejs/core/issues/13725)) ([0a202d8](https://github.com/vuejs/core/commit/0a202d890ff2a564b1fab51e4ac621708640818e)), closes [#13724](https://github.com/vuejs/core/issues/13724)
|
||||||
|
* **devtools:** clear performance measures ([#13701](https://github.com/vuejs/core/issues/13701)) ([c875019](https://github.com/vuejs/core/commit/c875019d49b4c36a88d929ccadc31ad414747c7b)), closes [#13700](https://github.com/vuejs/core/issues/13700)
|
||||||
|
* **hmr:** prevent updating unmounting component during HMR rerender ([#13775](https://github.com/vuejs/core/issues/13775)) ([6e5143d](https://github.com/vuejs/core/commit/6e5143d9635dac3f20fb394a827109df30e232ae)), closes [#13771](https://github.com/vuejs/core/issues/13771) [#13772](https://github.com/vuejs/core/issues/13772)
|
||||||
|
* **hydration:** also set vShow name if `__FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__` flag is enabled ([#13777](https://github.com/vuejs/core/issues/13777)) ([439e1a5](https://github.com/vuejs/core/commit/439e1a543e62de4dbf7658d78d05c358c9677c86)), closes [#13744](https://github.com/vuejs/core/issues/13744)
|
||||||
|
* **reactivity:** warn on nested readonly ref update during unwrapping ([#12141](https://github.com/vuejs/core/issues/12141)) ([1498821](https://github.com/vuejs/core/commit/1498821ed9eeb22a0767e53ddc1f6a2840598a29))
|
||||||
|
* **runtime-core:** avoid setting direct ref of useTemplateRef in dev ([#13449](https://github.com/vuejs/core/issues/13449)) ([4a2953f](https://github.com/vuejs/core/commit/4a2953f57b90dfc24e34ff1a87cc1ebb0b97636d))
|
||||||
|
* **runtime-core:** improve consistency of `PublicInstanceProxyHandlers.has` ([#13507](https://github.com/vuejs/core/issues/13507)) ([d7283f3](https://github.com/vuejs/core/commit/d7283f3b7f0631c8b8a4a31a05983dac9f078c4f))
|
||||||
|
* **suspense:** don't immediately resolve suspense on last dep unmount ([#13456](https://github.com/vuejs/core/issues/13456)) ([a871315](https://github.com/vuejs/core/commit/a8713159ee24602c7c2b70c5fd52d2e5cd37dca5)), closes [#13453](https://github.com/vuejs/core/issues/13453)
|
||||||
|
* **transition:** handle KeepAlive + transition leaving edge case ([#13152](https://github.com/vuejs/core/issues/13152)) ([3190b17](https://github.com/vuejs/core/commit/3190b179b0545a3dc4549737793eec630cf9f0d1)), closes [#13153](https://github.com/vuejs/core/issues/13153)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.5.18](https://github.com/vuejs/core/compare/v3.5.17...v3.5.18) (2025-07-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-core:** avoid cached text vnodes retaining detached DOM nodes ([#13662](https://github.com/vuejs/core/issues/13662)) ([00695a5](https://github.com/vuejs/core/commit/00695a5b41b2d032deaeada83831ff83aa6bfd4e)), closes [#13661](https://github.com/vuejs/core/issues/13661)
|
||||||
|
* **compiler-core:** avoid self updates of `v-pre` ([#12556](https://github.com/vuejs/core/issues/12556)) ([21b685a](https://github.com/vuejs/core/commit/21b685ad9d9d0e6060fc7d07b719bf35f2d9ae1f))
|
||||||
|
* **compiler-core:** identifiers in function parameters should not be inferred as references ([#13548](https://github.com/vuejs/core/issues/13548)) ([9b02923](https://github.com/vuejs/core/commit/9b029239edf88558465b941e1e4c085f92b1ebff))
|
||||||
|
* **compiler-core:** recognize empty string as non-identifier ([#12553](https://github.com/vuejs/core/issues/12553)) ([ce93339](https://github.com/vuejs/core/commit/ce933390ad1c72bed258f7ad959a78f0e8acdf57))
|
||||||
|
* **compiler-core:** transform empty `v-bind` dynamic argument content correctly ([#12554](https://github.com/vuejs/core/issues/12554)) ([d3af67e](https://github.com/vuejs/core/commit/d3af67e878790892f9d34cfea15d13625aabe733))
|
||||||
|
* **compiler-sfc:** transform empty srcset w/ includeAbsolute: true ([#13639](https://github.com/vuejs/core/issues/13639)) ([d8e40ef](https://github.com/vuejs/core/commit/d8e40ef7e1c20ee86b294e7cf78e2de60d12830e)), closes [vitejs/vite-plugin-vue#631](https://github.com/vitejs/vite-plugin-vue/issues/631)
|
||||||
|
* **css-vars:** nullish v-bind in style should not lead to unexpected inheritance ([#12461](https://github.com/vuejs/core/issues/12461)) ([c85f1b5](https://github.com/vuejs/core/commit/c85f1b5a132eb8ec25f71b250e25e65a5c20964f)), closes [#12434](https://github.com/vuejs/core/issues/12434) [#12439](https://github.com/vuejs/core/issues/12439) [#7474](https://github.com/vuejs/core/issues/7474) [#7475](https://github.com/vuejs/core/issues/7475)
|
||||||
|
* **custom-element:** ensure exposed methods are accessible from custom elements by making them enumerable ([#13634](https://github.com/vuejs/core/issues/13634)) ([90573b0](https://github.com/vuejs/core/commit/90573b06bf6fb6c14c6bbff6c4e34e0ab108953a)), closes [#13632](https://github.com/vuejs/core/issues/13632)
|
||||||
|
* **hydration:** prevent lazy hydration for updated components ([#13511](https://github.com/vuejs/core/issues/13511)) ([a9269c6](https://github.com/vuejs/core/commit/a9269c642bf944560bc29adb5dae471c11cd9ee8)), closes [#13510](https://github.com/vuejs/core/issues/13510)
|
||||||
|
* **runtime-core:** ensure correct anchor el for unresolved async components ([#13560](https://github.com/vuejs/core/issues/13560)) ([7f29943](https://github.com/vuejs/core/commit/7f2994393dcdb82cacbf62e02b5ba5565f32588b)), closes [#13559](https://github.com/vuejs/core/issues/13559)
|
||||||
|
* **slots:** refine internal key checking to support slot names starting with an underscore ([#13612](https://github.com/vuejs/core/issues/13612)) ([c5f7db1](https://github.com/vuejs/core/commit/c5f7db11542bb2246363aef78c88a8e6cef0ee93)), closes [#13611](https://github.com/vuejs/core/issues/13611)
|
||||||
|
* **ssr:** ensure empty slots render as a comment node in Transition ([#13396](https://github.com/vuejs/core/issues/13396)) ([8cfc10a](https://github.com/vuejs/core/commit/8cfc10a80b9cbf5d801ab149e49b8506d192e7e1)), closes [#13394](https://github.com/vuejs/core/issues/13394)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.5.17](https://github.com/vuejs/core/compare/v3.5.16...v3.5.17) (2025-06-18)
|
## [3.5.17](https://github.com/vuejs/core/compare/v3.5.16...v3.5.17) (2025-06-18)
|
||||||
|
|
||||||
|
|
||||||
|
|
18
package.json
18
package.json
|
@ -69,18 +69,18 @@
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-replace": "5.0.4",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@swc/core": "^1.12.9",
|
"@swc/core": "^1.13.3",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^22.16.0",
|
"@types/node": "^22.17.2",
|
||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"@types/serve-handler": "^6.1.4",
|
"@types/serve-handler": "^6.1.4",
|
||||||
"@vitest/ui": "^3.0.2",
|
"@vitest/ui": "^3.0.2",
|
||||||
"@vitest/coverage-v8": "^3.1.4",
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
"@vitest/eslint-plugin": "^1.2.1",
|
"@vitest/eslint-plugin": "^1.3.4",
|
||||||
"@vue/consolidate": "1.0.0",
|
"@vue/consolidate": "1.0.0",
|
||||||
"conventional-changelog-cli": "^5.0.0",
|
"conventional-changelog-cli": "^5.0.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.25.5",
|
"esbuild": "^0.25.9",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.27.0",
|
||||||
"eslint-plugin-import-x": "^4.13.1",
|
"eslint-plugin-import-x": "^4.13.1",
|
||||||
|
@ -96,10 +96,10 @@
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"puppeteer": "~24.9.0",
|
"puppeteer": "~24.16.2",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.44.1",
|
"rollup": "^4.46.4",
|
||||||
"rollup-plugin-dts": "^6.2.1",
|
"rollup-plugin-dts": "^6.2.3",
|
||||||
"rollup-plugin-esbuild": "^6.2.1",
|
"rollup-plugin-esbuild": "^6.2.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
|
@ -111,6 +111,6 @@
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.32.1",
|
"typescript-eslint": "^8.32.1",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"vitest": "^3.1.4"
|
"vitest": "^3.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"vite": "catalog:"
|
"vite": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/repl": "^4.6.1",
|
"@vue/repl": "^4.6.3",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"vue": "workspace:*"
|
"vue": "workspace:*"
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
StoreState,
|
StoreState,
|
||||||
} from '@vue/repl'
|
} from '@vue/repl'
|
||||||
import Monaco from '@vue/repl/monaco-editor'
|
import Monaco from '@vue/repl/monaco-editor'
|
||||||
import { ref, watchEffect, onMounted, computed } from 'vue'
|
import { ref, watchEffect, onMounted, computed, watch } from 'vue'
|
||||||
|
|
||||||
const replRef = ref<InstanceType<typeof Repl>>()
|
const replRef = ref<InstanceType<typeof Repl>>()
|
||||||
|
|
||||||
|
@ -130,6 +130,34 @@ onMounted(() => {
|
||||||
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
|
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
|
||||||
window.process = { env: {} }
|
window.process = { env: {} }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isVaporSupported = ref(false)
|
||||||
|
watch(
|
||||||
|
() => store.vueVersion,
|
||||||
|
(version, oldVersion) => {
|
||||||
|
const [major, minor] = (version || store.compiler.version)
|
||||||
|
.split('.')
|
||||||
|
.map((v: string) => parseInt(v, 10))
|
||||||
|
isVaporSupported.value = major > 3 || (major === 3 && minor >= 6)
|
||||||
|
if (oldVersion) reloadPage()
|
||||||
|
},
|
||||||
|
{ immediate: true, flush: 'pre' },
|
||||||
|
)
|
||||||
|
|
||||||
|
const previewOptions = computed(() => ({
|
||||||
|
customCode: {
|
||||||
|
importCode: `import { initCustomFormatter${isVaporSupported.value ? ', vaporInteropPlugin' : ''} } from 'vue'`,
|
||||||
|
useCode: `
|
||||||
|
${isVaporSupported.value ? 'app.use(vaporInteropPlugin)' : ''}
|
||||||
|
if (window.devtoolsFormatters) {
|
||||||
|
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
||||||
|
window.devtoolsFormatters.splice(index, 1)
|
||||||
|
initCustomFormatter()
|
||||||
|
} else {
|
||||||
|
initCustomFormatter()
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -160,20 +188,7 @@ onMounted(() => {
|
||||||
:showOpenSourceMap="true"
|
:showOpenSourceMap="true"
|
||||||
:autoResize="true"
|
:autoResize="true"
|
||||||
:clearConsole="false"
|
:clearConsole="false"
|
||||||
:preview-options="{
|
:preview-options="previewOptions"
|
||||||
customCode: {
|
|
||||||
importCode: `import { initCustomFormatter, vaporInteropPlugin } from 'vue'`,
|
|
||||||
useCode: `
|
|
||||||
app.use(vaporInteropPlugin)
|
|
||||||
if (window.devtoolsFormatters) {
|
|
||||||
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
|
||||||
window.devtoolsFormatters.splice(index, 1)
|
|
||||||
initCustomFormatter()
|
|
||||||
} else {
|
|
||||||
initCustomFormatter()
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"vue": "latest"
|
"vue": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"vite": "^6.3.5"
|
"vite": "^7.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -21,7 +21,7 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("p", null, [
|
_createElementVNode("p", null, [
|
||||||
_createElementVNode("span"),
|
_createElementVNode("span"),
|
||||||
_createElementVNode("span")
|
_createElementVNode("span")
|
||||||
|
@ -30,7 +30,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("span"),
|
_createElementVNode("span"),
|
||||||
_createElementVNode("span")
|
_createElementVNode("span")
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -42,11 +42,11 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("div", null, [
|
_createElementVNode("div", null, [
|
||||||
_createCommentVNode("comment")
|
_createCommentVNode("comment")
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -58,11 +58,11 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", null, null, -1 /* CACHED */),
|
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||||
_createTextVNode("foo"),
|
_createTextVNode("foo", -1 /* CACHED */),
|
||||||
_createElementVNode("div", null, null, -1 /* CACHED */)
|
_createElementVNode("div", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -74,9 +74,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -147,9 +147,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -161,9 +161,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -215,9 +215,9 @@ return function render(_ctx, _cache) {
|
||||||
const _directive_foo = _resolveDirective("foo")
|
const _directive_foo = _resolveDirective("foo")
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
|
_withDirectives((_openBlock(), _createElementBlock("svg", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
||||||
]))), [
|
]))])), [
|
||||||
[_directive_foo]
|
[_directive_foo]
|
||||||
])
|
])
|
||||||
]))
|
]))
|
||||||
|
@ -401,9 +401,9 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
ok
|
ok
|
||||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
? (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
: _createCommentVNode("v-if", true)
|
: _createCommentVNode("v-if", true)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
@ -422,7 +422,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
_createCommentVNode("comment"),
|
_createCommentVNode("comment"),
|
||||||
_createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
_createElementVNode("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("div", { id: "b" }, [
|
_createElementVNode("div", { id: "b" }, [
|
||||||
_createElementVNode("div", { id: "c" }, [
|
_createElementVNode("div", { id: "c" }, [
|
||||||
_createElementVNode("div", { id: "d" }, [
|
_createElementVNode("div", { id: "d" }, [
|
||||||
|
@ -430,7 +430,7 @@ return function render(_ctx, _cache) {
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
]))
|
]))])
|
||||||
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
@ -448,9 +448,9 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
const cachedChildrenArrayMatcher = (
|
const cachedChildrenArrayMatcher = (
|
||||||
tags: string[],
|
tags: string[],
|
||||||
needArraySpread = false,
|
needArraySpread = true,
|
||||||
) => ({
|
) => ({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
needArraySpread,
|
needArraySpread,
|
||||||
|
@ -170,11 +170,6 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: NodeTypes.JS_PROPERTY,
|
|
||||||
key: { content: '__' },
|
|
||||||
value: { content: '[0]' },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -202,11 +197,6 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: NodeTypes.JS_PROPERTY,
|
|
||||||
key: { content: '__' },
|
|
||||||
value: { content: '[0]' },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -301,6 +301,25 @@ describe('compiler: v-if', () => {
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('error on adjacent v-else', () => {
|
||||||
|
const onError = vi.fn()
|
||||||
|
|
||||||
|
const {
|
||||||
|
node: { branches },
|
||||||
|
} = parseWithIfTransform(
|
||||||
|
`<div v-if="false"/><div v-else/><div v-else/>`,
|
||||||
|
{ onError },
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(onError.mock.calls[0]).toMatchObject([
|
||||||
|
{
|
||||||
|
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
|
||||||
|
loc: branches[branches.length - 1].loc,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test('error on user key', () => {
|
test('error on user key', () => {
|
||||||
const onError = vi.fn()
|
const onError = vi.fn()
|
||||||
// dynamic
|
// dynamic
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import type { ExpressionNode, TransformContext } from '../src'
|
import { babelParse, walkIdentifiers } from '@vue/compiler-sfc'
|
||||||
|
import {
|
||||||
|
type ExpressionNode,
|
||||||
|
type TransformContext,
|
||||||
|
isReferencedIdentifier,
|
||||||
|
} from '../src'
|
||||||
import { type Position, createSimpleExpression } from '../src/ast'
|
import { type Position, createSimpleExpression } from '../src/ast'
|
||||||
import {
|
import {
|
||||||
advancePositionWithClone,
|
advancePositionWithClone,
|
||||||
|
@ -115,3 +120,18 @@ test('toValidAssetId', () => {
|
||||||
'_component_test_2797935797_1',
|
'_component_test_2797935797_1',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('isReferencedIdentifier', () => {
|
||||||
|
test('identifiers in function parameters should not be inferred as references', () => {
|
||||||
|
expect.assertions(4)
|
||||||
|
const ast = babelParse(`(({ title }) => [])`)
|
||||||
|
walkIdentifiers(
|
||||||
|
ast.program.body[0],
|
||||||
|
(node, parent, parentStack, isReference) => {
|
||||||
|
expect(isReference).toBe(false)
|
||||||
|
expect(isReferencedIdentifier(node, parent, parentStack)).toBe(false)
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -123,7 +123,7 @@ export function isReferencedIdentifier(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isReferenced(id, parent)) {
|
if (isReferenced(id, parent, parentStack[parentStack.length - 2])) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,8 @@ export function isReferencedIdentifier(
|
||||||
case 'AssignmentExpression':
|
case 'AssignmentExpression':
|
||||||
case 'AssignmentPattern':
|
case 'AssignmentPattern':
|
||||||
return true
|
return true
|
||||||
case 'ObjectPattern':
|
case 'ObjectProperty':
|
||||||
|
return parent.key !== id && isInDestructureAssignment(parent, parentStack)
|
||||||
case 'ArrayPattern':
|
case 'ArrayPattern':
|
||||||
return isInDestructureAssignment(parent, parentStack)
|
return isInDestructureAssignment(parent, parentStack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import {
|
||||||
isCoreComponent,
|
isCoreComponent,
|
||||||
isSimpleIdentifier,
|
isSimpleIdentifier,
|
||||||
isStaticArgOf,
|
isStaticArgOf,
|
||||||
|
isVPre,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { decodeHTML } from 'entities/lib/decode.js'
|
import { decodeHTML } from 'entities/lib/decode.js'
|
||||||
import {
|
import {
|
||||||
|
@ -246,7 +247,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
ondirarg(start, end) {
|
ondirarg(start, end) {
|
||||||
if (start === end) return
|
if (start === end) return
|
||||||
const arg = getSlice(start, end)
|
const arg = getSlice(start, end)
|
||||||
if (inVPre) {
|
if (inVPre && !isVPre(currentProp!)) {
|
||||||
;(currentProp as AttributeNode).name += arg
|
;(currentProp as AttributeNode).name += arg
|
||||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||||
} else {
|
} else {
|
||||||
|
@ -262,7 +263,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
|
|
||||||
ondirmodifier(start, end) {
|
ondirmodifier(start, end) {
|
||||||
const mod = getSlice(start, end)
|
const mod = getSlice(start, end)
|
||||||
if (inVPre) {
|
if (inVPre && !isVPre(currentProp!)) {
|
||||||
;(currentProp as AttributeNode).name += '.' + mod
|
;(currentProp as AttributeNode).name += '.' + mod
|
||||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||||
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
||||||
|
|
|
@ -12,19 +12,22 @@ import {
|
||||||
type RootNode,
|
type RootNode,
|
||||||
type SimpleExpressionNode,
|
type SimpleExpressionNode,
|
||||||
type SlotFunctionExpression,
|
type SlotFunctionExpression,
|
||||||
type SlotsObjectProperty,
|
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
type TemplateNode,
|
type TemplateNode,
|
||||||
type TextCallNode,
|
type TextCallNode,
|
||||||
type VNodeCall,
|
type VNodeCall,
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createObjectProperty,
|
|
||||||
createSimpleExpression,
|
|
||||||
getVNodeBlockHelper,
|
getVNodeBlockHelper,
|
||||||
getVNodeHelper,
|
getVNodeHelper,
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import type { TransformContext } from '../transform'
|
import type { TransformContext } from '../transform'
|
||||||
import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
|
import {
|
||||||
|
PatchFlagNames,
|
||||||
|
PatchFlags,
|
||||||
|
isArray,
|
||||||
|
isString,
|
||||||
|
isSymbol,
|
||||||
|
} from '@vue/shared'
|
||||||
import { findDir, isSlotOutlet } from '../utils'
|
import { findDir, isSlotOutlet } from '../utils'
|
||||||
import {
|
import {
|
||||||
GUARD_REACTIVE_PROPS,
|
GUARD_REACTIVE_PROPS,
|
||||||
|
@ -109,6 +112,15 @@ function walk(
|
||||||
? ConstantTypes.NOT_CONSTANT
|
? ConstantTypes.NOT_CONSTANT
|
||||||
: getConstantType(child, context)
|
: getConstantType(child, context)
|
||||||
if (constantType >= ConstantTypes.CAN_CACHE) {
|
if (constantType >= ConstantTypes.CAN_CACHE) {
|
||||||
|
if (
|
||||||
|
child.codegenNode.type === NodeTypes.JS_CALL_EXPRESSION &&
|
||||||
|
child.codegenNode.arguments.length > 0
|
||||||
|
) {
|
||||||
|
child.codegenNode.arguments.push(
|
||||||
|
PatchFlags.CACHED +
|
||||||
|
(__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.CACHED]} */` : ``),
|
||||||
|
)
|
||||||
|
}
|
||||||
toCache.push(child)
|
toCache.push(child)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -142,7 +154,6 @@ function walk(
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedAsArray = false
|
let cachedAsArray = false
|
||||||
const slotCacheKeys = []
|
|
||||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||||
if (
|
if (
|
||||||
node.tagType === ElementTypes.ELEMENT &&
|
node.tagType === ElementTypes.ELEMENT &&
|
||||||
|
@ -166,7 +177,6 @@ function walk(
|
||||||
// default slot
|
// default slot
|
||||||
const slot = getSlotNode(node.codegenNode, 'default')
|
const slot = getSlotNode(node.codegenNode, 'default')
|
||||||
if (slot) {
|
if (slot) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -190,7 +200,6 @@ function walk(
|
||||||
slotName.arg &&
|
slotName.arg &&
|
||||||
getSlotNode(parent.codegenNode, slotName.arg)
|
getSlotNode(parent.codegenNode, slotName.arg)
|
||||||
if (slot) {
|
if (slot) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -201,39 +210,22 @@ function walk(
|
||||||
|
|
||||||
if (!cachedAsArray) {
|
if (!cachedAsArray) {
|
||||||
for (const child of toCache) {
|
for (const child of toCache) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
child.codegenNode = context.cache(child.codegenNode!)
|
child.codegenNode = context.cache(child.codegenNode!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the slot cached keys on the slot object, so that the cache
|
|
||||||
// can be removed when component unmounting to prevent memory leaks
|
|
||||||
if (
|
|
||||||
slotCacheKeys.length &&
|
|
||||||
node.type === NodeTypes.ELEMENT &&
|
|
||||||
node.tagType === ElementTypes.COMPONENT &&
|
|
||||||
node.codegenNode &&
|
|
||||||
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
|
||||||
node.codegenNode.children &&
|
|
||||||
!isArray(node.codegenNode.children) &&
|
|
||||||
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
|
|
||||||
) {
|
|
||||||
node.codegenNode.children.properties.push(
|
|
||||||
createObjectProperty(
|
|
||||||
`__`,
|
|
||||||
createSimpleExpression(JSON.stringify(slotCacheKeys), false),
|
|
||||||
) as SlotsObjectProperty,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCacheExpression(value: JSChildNode): CacheExpression {
|
function getCacheExpression(value: JSChildNode): CacheExpression {
|
||||||
const exp = context.cache(value)
|
const exp = context.cache(value)
|
||||||
// #6978, #7138, #7114
|
// #6978, #7138, #7114
|
||||||
// a cached children array inside v-for can caused HMR errors since
|
// a cached children array inside v-for can caused HMR errors since
|
||||||
// it might be mutated when mounting the first item
|
// it might be mutated when mounting the first item
|
||||||
if (inFor && context.hmr) {
|
// #13221
|
||||||
|
// fix memory leak in cached array:
|
||||||
|
// cached vnodes get replaced by cloned ones during mountChildren,
|
||||||
|
// which bind DOM elements. These DOM references persist after unmount,
|
||||||
|
// preventing garbage collection. Array spread avoids mutating cached
|
||||||
|
// array, preventing memory leaks.
|
||||||
exp.needArraySpread = true
|
exp.needArraySpread = true
|
||||||
}
|
|
||||||
return exp
|
return exp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
arg.children.unshift(`(`)
|
arg.children.unshift(`(`)
|
||||||
arg.children.push(`) || ""`)
|
arg.children.push(`) || ""`)
|
||||||
} else if (!arg.isStatic) {
|
} else if (!arg.isStatic) {
|
||||||
arg.content = `${arg.content} || ""`
|
arg.content = arg.content ? `${arg.content} || ""` : `""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// .sync is replaced by v-model:arg
|
// .sync is replaced by v-model:arg
|
||||||
|
|
|
@ -141,9 +141,9 @@ export function processIf(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sibling && sibling.type === NodeTypes.IF) {
|
if (sibling && sibling.type === NodeTypes.IF) {
|
||||||
// Check if v-else was followed by v-else-if
|
// Check if v-else was followed by v-else-if or there are two adjacent v-else
|
||||||
if (
|
if (
|
||||||
dir.name === 'else-if' &&
|
(dir.name === 'else-if' || dir.name === 'else') &&
|
||||||
sibling.branches[sibling.branches.length - 1].condition === undefined
|
sibling.branches[sibling.branches.length - 1].condition === undefined
|
||||||
) {
|
) {
|
||||||
context.onError(
|
context.onError(
|
||||||
|
|
|
@ -16,7 +16,7 @@ const seen = new WeakSet()
|
||||||
export const transformMemo: NodeTransform = (node, context) => {
|
export const transformMemo: NodeTransform = (node, context) => {
|
||||||
if (node.type === NodeTypes.ELEMENT) {
|
if (node.type === NodeTypes.ELEMENT) {
|
||||||
const dir = findDir(node, 'memo')
|
const dir = findDir(node, 'memo')
|
||||||
if (!dir || seen.has(node)) {
|
if (!dir || seen.has(node) || context.inSSR) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seen.add(node)
|
seen.add(node)
|
||||||
|
|
|
@ -63,7 +63,7 @@ export function isCoreComponent(tag: string): symbol | void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nonIdentifierRE = /^\d|[^\$\w\xA0-\uFFFF]/
|
const nonIdentifierRE = /^$|^\d|[^\$\w\xA0-\uFFFF]/
|
||||||
export const isSimpleIdentifier = (name: string): boolean =>
|
export const isSimpleIdentifier = (name: string): boolean =>
|
||||||
!nonIdentifierRE.test(name)
|
!nonIdentifierRE.test(name)
|
||||||
|
|
||||||
|
@ -344,6 +344,10 @@ export function isText(
|
||||||
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isVPre(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||||
|
return p.type === NodeTypes.DIRECTIVE && p.name === 'pre'
|
||||||
|
}
|
||||||
|
|
||||||
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||||
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ exports[`stringify static html > eligible content (elements > 20) + non-eligible
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
||||||
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
|
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
|
||||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
|
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@ exports[`stringify static html > escape 1`] = `
|
||||||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 1)
|
_createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -26,9 +26,9 @@ exports[`stringify static html > serializing constant bindings 1`] = `
|
||||||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@ exports[`stringify static html > serializing template string style 1`] = `
|
||||||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ exports[`stringify static html > should bail for <option> elements with null val
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("select", null, [
|
_createElementVNode("select", null, [
|
||||||
_createElementVNode("option", { value: null }),
|
_createElementVNode("option", { value: null }),
|
||||||
_createElementVNode("option", { value: "1" }),
|
_createElementVNode("option", { value: "1" }),
|
||||||
|
@ -55,7 +55,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("option", { value: "1" }),
|
_createElementVNode("option", { value: "1" }),
|
||||||
_createElementVNode("option", { value: "1" })
|
_createElementVNode("option", { value: "1" })
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ exports[`stringify static html > should bail for <option> elements with number v
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("select", null, [
|
_createElementVNode("select", null, [
|
||||||
_createElementVNode("option", { value: 1 }),
|
_createElementVNode("option", { value: 1 }),
|
||||||
_createElementVNode("option", { value: 1 }),
|
_createElementVNode("option", { value: 1 }),
|
||||||
|
@ -71,7 +71,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("option", { value: 1 }),
|
_createElementVNode("option", { value: 1 }),
|
||||||
_createElementVNode("option", { value: 1 })
|
_createElementVNode("option", { value: 1 })
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ exports[`stringify static html > should bail on bindings that are cached but not
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("div", null, [
|
_createElementVNode("div", null, [
|
||||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||||
|
@ -104,7 +104,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||||
_createElementVNode("img", { src: _imports_0_ })
|
_createElementVNode("img", { src: _imports_0_ })
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -112,9 +112,9 @@ exports[`stringify static html > should work for <option> elements with string v
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@ exports[`stringify static html > should work for multiple adjacent nodes 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
|
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -132,9 +132,9 @@ exports[`stringify static html > should work on eligible content (elements > 20)
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
|
_createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -142,9 +142,9 @@ exports[`stringify static html > should work on eligible content (elements with
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
|
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -152,9 +152,9 @@ exports[`stringify static html > should work with bindings that are non-static b
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
|
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ if (__TEST__) {
|
||||||
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`DOMErrorCodes need to be updated to ${
|
`DOMErrorCodes need to be updated to ${
|
||||||
ErrorCodes.__EXTEND_POINT__ + 1
|
ErrorCodes.__EXTEND_POINT__
|
||||||
} to match extension point from core ErrorCodes.`,
|
} to match extension point from core ErrorCodes.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -823,6 +823,228 @@ return (_ctx, _cache) => {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should alias __emit to $emit when defineEmits is used 1`] = `
|
||||||
|
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
emits: ['click'],
|
||||||
|
setup(__props, { emit: __emit }) {
|
||||||
|
const $emit = __emit
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", {
|
||||||
|
onClick: _cache[0] || (_cache[0] = $event => ($emit('click')))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should alias __props to $props when $props is used 1`] = `
|
||||||
|
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
const $props = __props
|
||||||
|
/* ... */
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _toDisplayString($props), 1 /* TEXT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should extract all built-in properties when they are used 1`] = `
|
||||||
|
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props, { emit: $emit, attrs: $attrs, slots: $slots }) {
|
||||||
|
const $props = __props
|
||||||
|
/* ... */
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _toDisplayString($props) + _toDisplayString($slots) + _toDisplayString($emit) + _toDisplayString($attrs), 1 /* TEXT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should extract attrs when $attrs is used 1`] = `
|
||||||
|
"import { normalizeProps as _normalizeProps, guardReactiveProps as _guardReactiveProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props, { attrs: $attrs }) {
|
||||||
|
/* ... */
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", _normalizeProps(_guardReactiveProps($attrs)), null, 16 /* FULL_PROPS */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should extract emit when $emit is used 1`] = `
|
||||||
|
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props, { emit: $emit }) {
|
||||||
|
/* ... */
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", {
|
||||||
|
onClick: _cache[0] || (_cache[0] = $event => ($emit('click')))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should extract slots when $slots is used 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props, { slots: $slots }) {
|
||||||
|
/* ... */
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Comp, {
|
||||||
|
foo: $slots.foo
|
||||||
|
}, null, 8 /* PROPS */, ["foo"]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should not extract built-in properties when neither is used 1`] = `
|
||||||
|
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
/* ... */
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_ctx.msg), 1 /* TEXT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should handle mixed defineEmits and user-defined $emit 1`] = `
|
||||||
|
"import { unref as _unref, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
emits: ['click'],
|
||||||
|
setup(__props, { emit: __emit }) {
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
let $emit
|
||||||
|
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", {
|
||||||
|
onClick: _cache[0] || (_cache[0] = $event => (_unref($emit)('click')))
|
||||||
|
}, "click"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should not extract $attrs when user defines it 1`] = `
|
||||||
|
"import { unref as _unref, normalizeProps as _normalizeProps, guardReactiveProps as _guardReactiveProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
let $attrs
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", _normalizeProps(_guardReactiveProps(_unref($attrs))), null, 16 /* FULL_PROPS */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should not extract $emit when user defines it 1`] = `
|
||||||
|
"import { unref as _unref, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
let $emit
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", {
|
||||||
|
onClick: _cache[0] || (_cache[0] = $event => (_unref($emit)('click')))
|
||||||
|
}, "click"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should not extract $slots when user defines it 1`] = `
|
||||||
|
"import { unref as _unref, resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
let $slots
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Comp, {
|
||||||
|
foo: _unref($slots).foo
|
||||||
|
}, null, 8 /* PROPS */, ["foo"]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should not generate $props alias when user defines it 1`] = `
|
||||||
|
"import { unref as _unref, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
let $props
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_unref($props).msg), 1 /* TEXT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should only extract non-user-defined properties 1`] = `
|
||||||
|
"import { unref as _unref, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props, { emit: $emit, slots: $slots }) {
|
||||||
|
const $props = __props
|
||||||
|
let $attrs
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_unref($attrs)) + _toDisplayString($slots) + _toDisplayString($emit) + _toDisplayString($props), 1 /* TEXT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > inlineTemplate mode > referencing scope components and directives 1`] = `
|
exports[`SFC compile <script setup> > inlineTemplate mode > referencing scope components and directives 1`] = `
|
||||||
"import { unref as _unref, createElementVNode as _createElementVNode, withDirectives as _withDirectives, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
"import { unref as _unref, createElementVNode as _createElementVNode, withDirectives as _withDirectives, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
|
@ -81,9 +81,9 @@ import _imports_1 from '/bar.png'
|
||||||
|
|
||||||
|
|
||||||
export function render(_ctx, _cache) {
|
export function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
|
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,16 @@ export function render(_ctx, _cache) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler sfc: transform srcset > transform empty srcset w/ includeAbsolute: true 1`] = `
|
||||||
|
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
const _hoisted_1 = { srcset: " " }
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("img", _hoisted_1))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler sfc: transform srcset > transform srcset 1`] = `
|
exports[`compiler sfc: transform srcset > transform srcset 1`] = `
|
||||||
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
import _imports_0 from './logo.png'
|
import _imports_0 from './logo.png'
|
||||||
|
@ -228,8 +238,8 @@ const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
|
||||||
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
||||||
|
|
||||||
export function render(_ctx, _cache) {
|
export function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
|
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -717,6 +717,151 @@ describe('SFC compile <script setup>', () => {
|
||||||
consumer.originalPositionFor(getPositionInCode(content, 'Error')),
|
consumer.originalPositionFor(getPositionInCode(content, 'Error')),
|
||||||
).toMatchObject(getPositionInCode(source, `Error`))
|
).toMatchObject(getPositionInCode(source, `Error`))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('destructure setup context for built-in properties', () => {
|
||||||
|
const theCompile = (template: string, setup = '/* ... */') =>
|
||||||
|
compile(
|
||||||
|
`<script setup>${setup}</script>\n<template>${template}</template>`,
|
||||||
|
{ inlineTemplate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
test('should extract attrs when $attrs is used', () => {
|
||||||
|
let { content } = theCompile('<div v-bind="$attrs"></div>')
|
||||||
|
expect(content).toMatch('setup(__props, { attrs: $attrs })')
|
||||||
|
expect(content).not.toMatch('slots: $slots')
|
||||||
|
expect(content).not.toMatch('emit: $emit')
|
||||||
|
expect(content).not.toMatch('const $props = __props')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should extract slots when $slots is used', () => {
|
||||||
|
let { content } = theCompile('<Comp :foo="$slots.foo"></Comp>')
|
||||||
|
expect(content).toMatch('setup(__props, { slots: $slots })')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should alias __props to $props when $props is used', () => {
|
||||||
|
let { content } = theCompile('<div>{{ $props }}</div>')
|
||||||
|
expect(content).toMatch('setup(__props)')
|
||||||
|
expect(content).toMatch('const $props = __props')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should extract emit when $emit is used', () => {
|
||||||
|
let { content } = theCompile(`<div @click="$emit('click')"></div>`)
|
||||||
|
expect(content).toMatch('setup(__props, { emit: $emit })')
|
||||||
|
expect(content).not.toMatch('const $emit = __emit')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should alias __emit to $emit when defineEmits is used', () => {
|
||||||
|
let { content } = compile(
|
||||||
|
`
|
||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['click'])
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div @click="$emit('click')"></div>
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
{ inlineTemplate: true },
|
||||||
|
)
|
||||||
|
expect(content).toMatch('setup(__props, { emit: __emit })')
|
||||||
|
expect(content).toMatch('const $emit = __emit')
|
||||||
|
expect(content).toMatch('const emit = __emit')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should extract all built-in properties when they are used', () => {
|
||||||
|
let { content } = theCompile(
|
||||||
|
'<div>{{ $props }}{{ $slots }}{{ $emit }}{{ $attrs }}</div>',
|
||||||
|
)
|
||||||
|
expect(content).toMatch(
|
||||||
|
'setup(__props, { emit: $emit, attrs: $attrs, slots: $slots })',
|
||||||
|
)
|
||||||
|
expect(content).toMatch('const $props = __props')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not extract built-in properties when neither is used', () => {
|
||||||
|
let { content } = theCompile('<div>{{ msg }}</div>')
|
||||||
|
expect(content).toMatch('setup(__props)')
|
||||||
|
expect(content).not.toMatch('attrs: $attrs')
|
||||||
|
expect(content).not.toMatch('slots: $slots')
|
||||||
|
expect(content).not.toMatch('emit: $emit')
|
||||||
|
expect(content).not.toMatch('props: $props')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user-defined properties override', () => {
|
||||||
|
test('should not extract $attrs when user defines it', () => {
|
||||||
|
let { content } = theCompile(
|
||||||
|
'<div v-bind="$attrs"></div>',
|
||||||
|
'let $attrs',
|
||||||
|
)
|
||||||
|
expect(content).toMatch('setup(__props)')
|
||||||
|
expect(content).not.toMatch('attrs: $attrs')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not extract $slots when user defines it', () => {
|
||||||
|
let { content } = theCompile(
|
||||||
|
'<Comp :foo="$slots.foo"></Comp>',
|
||||||
|
'let $slots',
|
||||||
|
)
|
||||||
|
expect(content).toMatch('setup(__props)')
|
||||||
|
expect(content).not.toMatch('slots: $slots')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not extract $emit when user defines it', () => {
|
||||||
|
let { content } = theCompile(
|
||||||
|
`<div @click="$emit('click')">click</div>`,
|
||||||
|
'let $emit',
|
||||||
|
)
|
||||||
|
expect(content).toMatch('setup(__props)')
|
||||||
|
expect(content).not.toMatch('emit: $emit')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not generate $props alias when user defines it', () => {
|
||||||
|
let { content } = theCompile(
|
||||||
|
'<div>{{ $props.msg }}</div>',
|
||||||
|
'let $props',
|
||||||
|
)
|
||||||
|
expect(content).toMatch('setup(__props)')
|
||||||
|
expect(content).not.toMatch('const $props = __props')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should only extract non-user-defined properties', () => {
|
||||||
|
let { content } = theCompile(
|
||||||
|
'<div>{{ $attrs }}{{ $slots }}{{ $emit }}{{ $props }}</div>',
|
||||||
|
'let $attrs',
|
||||||
|
)
|
||||||
|
expect(content).toMatch(
|
||||||
|
'setup(__props, { emit: $emit, slots: $slots })',
|
||||||
|
)
|
||||||
|
expect(content).not.toMatch('attrs: $attrs')
|
||||||
|
expect(content).toMatch('const $props = __props')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle mixed defineEmits and user-defined $emit', () => {
|
||||||
|
let { content } = theCompile(
|
||||||
|
`<div @click="$emit('click')">click</div>`,
|
||||||
|
`
|
||||||
|
const emit = defineEmits(['click'])
|
||||||
|
let $emit
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
expect(content).toMatch('setup(__props, { emit: __emit })')
|
||||||
|
expect(content).toMatch('const emit = __emit')
|
||||||
|
expect(content).not.toMatch('const $emit = __emit')
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with TypeScript', () => {
|
describe('with TypeScript', () => {
|
||||||
|
@ -913,6 +1058,13 @@ describe('SFC compile <script setup>', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
|
compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
|
||||||
).toThrow(`<script> and <script setup> must have the same language type`)
|
).toThrow(`<script> and <script setup> must have the same language type`)
|
||||||
|
|
||||||
|
// #13193 must check lang before parsing with babel
|
||||||
|
expect(() =>
|
||||||
|
compile(
|
||||||
|
`<script lang="ts">const a = 1</script><script setup lang="tsx">const Comp = () => <p>test</p></script>`,
|
||||||
|
),
|
||||||
|
).toThrow(`<script> and <script setup> must have the same language type`)
|
||||||
})
|
})
|
||||||
|
|
||||||
const moduleErrorMsg = `cannot contain ES module exports`
|
const moduleErrorMsg = `cannot contain ES module exports`
|
||||||
|
|
|
@ -538,7 +538,7 @@ describe('resolveType', () => {
|
||||||
|
|
||||||
expect(props).toStrictEqual({
|
expect(props).toStrictEqual({
|
||||||
foo: ['Symbol', 'String', 'Number'],
|
foo: ['Symbol', 'String', 'Number'],
|
||||||
bar: [UNKNOWN_TYPE],
|
bar: ['String', 'Number'],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -749,7 +749,7 @@ describe('resolveType', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fallback to Unknown', () => {
|
test('with intersection type', () => {
|
||||||
expect(
|
expect(
|
||||||
resolve(`
|
resolve(`
|
||||||
type Brand<T> = T & {};
|
type Brand<T> = T & {};
|
||||||
|
@ -758,7 +758,18 @@ describe('resolveType', () => {
|
||||||
}>()
|
}>()
|
||||||
`).props,
|
`).props,
|
||||||
).toStrictEqual({
|
).toStrictEqual({
|
||||||
foo: [UNKNOWN_TYPE],
|
foo: ['String', 'Object'],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with union type', () => {
|
||||||
|
expect(
|
||||||
|
resolve(`
|
||||||
|
type Wrapped<T> = T | symbol | number
|
||||||
|
defineProps<{foo?: Wrapped<boolean>}>()
|
||||||
|
`).props,
|
||||||
|
).toStrictEqual({
|
||||||
|
foo: ['Boolean', 'Symbol', 'Number'],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -72,6 +72,14 @@ describe('compiler sfc: transform srcset', () => {
|
||||||
).toMatchSnapshot()
|
).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('transform empty srcset w/ includeAbsolute: true', () => {
|
||||||
|
expect(
|
||||||
|
compileWithSrcset(`<img srcset=" " />`, {
|
||||||
|
includeAbsolute: true,
|
||||||
|
}).code,
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('transform srcset w/ stringify', () => {
|
test('transform srcset w/ stringify', () => {
|
||||||
const code = compileWithSrcset(
|
const code = compileWithSrcset(
|
||||||
`<div>${src}</div>`,
|
`<div>${src}</div>`,
|
||||||
|
|
|
@ -63,6 +63,6 @@
|
||||||
"postcss-modules": "^6.0.1",
|
"postcss-modules": "^6.0.1",
|
||||||
"postcss-selector-parser": "^7.1.0",
|
"postcss-selector-parser": "^7.1.0",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"sass": "^1.89.2"
|
"sass": "^1.90.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
|
||||||
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
||||||
import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
|
import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
|
||||||
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
||||||
import { isImportUsed } from './script/importUsageCheck'
|
import { isUsedInTemplate } from './script/importUsageCheck'
|
||||||
import { processAwait } from './script/topLevelAwait'
|
import { processAwait } from './script/topLevelAwait'
|
||||||
|
|
||||||
export interface SFCScriptCompileOptions {
|
export interface SFCScriptCompileOptions {
|
||||||
|
@ -173,7 +173,6 @@ export function compileScript(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = new ScriptCompileContext(sfc, options)
|
|
||||||
const { script, scriptSetup, source, filename } = sfc
|
const { script, scriptSetup, source, filename } = sfc
|
||||||
const hoistStatic = options.hoistStatic !== false && !script
|
const hoistStatic = options.hoistStatic !== false && !script
|
||||||
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
|
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
|
||||||
|
@ -181,6 +180,16 @@ export function compileScript(
|
||||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||||
const vapor = sfc.vapor || options.vapor
|
const vapor = sfc.vapor || options.vapor
|
||||||
const ssr = options.templateOptions?.ssr
|
const ssr = options.templateOptions?.ssr
|
||||||
|
const setupPreambleLines = [] as string[]
|
||||||
|
|
||||||
|
if (script && scriptSetup && scriptLang !== scriptSetupLang) {
|
||||||
|
throw new Error(
|
||||||
|
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
|
||||||
|
`language type.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = new ScriptCompileContext(sfc, options)
|
||||||
|
|
||||||
if (!scriptSetup) {
|
if (!scriptSetup) {
|
||||||
if (!script) {
|
if (!script) {
|
||||||
|
@ -190,13 +199,6 @@ export function compileScript(
|
||||||
return processNormalScript(ctx, scopeId)
|
return processNormalScript(ctx, scopeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (script && scriptLang !== scriptSetupLang) {
|
|
||||||
throw new Error(
|
|
||||||
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
|
|
||||||
`language type.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
|
if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
|
||||||
// do not process non js/ts script blocks
|
// do not process non js/ts script blocks
|
||||||
return scriptSetup
|
return scriptSetup
|
||||||
|
@ -246,7 +248,7 @@ export function compileScript(
|
||||||
) {
|
) {
|
||||||
// template usage check is only needed in non-inline mode, so we can skip
|
// template usage check is only needed in non-inline mode, so we can skip
|
||||||
// the work if inlineTemplate is true.
|
// the work if inlineTemplate is true.
|
||||||
let isUsedInTemplate = needTemplateUsageCheck
|
let isImportUsed = needTemplateUsageCheck
|
||||||
if (
|
if (
|
||||||
needTemplateUsageCheck &&
|
needTemplateUsageCheck &&
|
||||||
ctx.isTS &&
|
ctx.isTS &&
|
||||||
|
@ -254,7 +256,7 @@ export function compileScript(
|
||||||
!sfc.template.src &&
|
!sfc.template.src &&
|
||||||
!sfc.template.lang
|
!sfc.template.lang
|
||||||
) {
|
) {
|
||||||
isUsedInTemplate = isImportUsed(local, sfc)
|
isImportUsed = isUsedInTemplate(local, sfc)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.userImports[local] = {
|
ctx.userImports[local] = {
|
||||||
|
@ -263,7 +265,7 @@ export function compileScript(
|
||||||
local,
|
local,
|
||||||
source,
|
source,
|
||||||
isFromSetup,
|
isFromSetup,
|
||||||
isUsedInTemplate,
|
isUsedInTemplate: isImportUsed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,8 +286,42 @@ export function compileScript(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildDestructureElements() {
|
||||||
|
if (!sfc.template || !sfc.template.ast) return
|
||||||
|
|
||||||
|
const builtins = {
|
||||||
|
$props: {
|
||||||
|
bindingType: BindingTypes.SETUP_REACTIVE_CONST,
|
||||||
|
setup: () => setupPreambleLines.push(`const $props = __props`),
|
||||||
|
},
|
||||||
|
$emit: {
|
||||||
|
bindingType: BindingTypes.SETUP_CONST,
|
||||||
|
setup: () =>
|
||||||
|
ctx.emitDecl
|
||||||
|
? setupPreambleLines.push(`const $emit = __emit`)
|
||||||
|
: destructureElements.push('emit: $emit'),
|
||||||
|
},
|
||||||
|
$attrs: {
|
||||||
|
bindingType: BindingTypes.SETUP_REACTIVE_CONST,
|
||||||
|
setup: () => destructureElements.push('attrs: $attrs'),
|
||||||
|
},
|
||||||
|
$slots: {
|
||||||
|
bindingType: BindingTypes.SETUP_REACTIVE_CONST,
|
||||||
|
setup: () => destructureElements.push('slots: $slots'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name, config] of Object.entries(builtins)) {
|
||||||
|
if (isUsedInTemplate(name, sfc) && !ctx.bindingMetadata[name]) {
|
||||||
|
config.setup()
|
||||||
|
ctx.bindingMetadata[name] = config.bindingType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const scriptAst = ctx.scriptAst
|
const scriptAst = ctx.scriptAst
|
||||||
const scriptSetupAst = ctx.scriptSetupAst!
|
const scriptSetupAst = ctx.scriptSetupAst!
|
||||||
|
const inlineMode = options.inlineTemplate
|
||||||
|
|
||||||
// 1.1 walk import declarations of <script>
|
// 1.1 walk import declarations of <script>
|
||||||
if (scriptAst) {
|
if (scriptAst) {
|
||||||
|
@ -302,7 +338,7 @@ export function compileScript(
|
||||||
(specifier.type === 'ImportSpecifier' &&
|
(specifier.type === 'ImportSpecifier' &&
|
||||||
specifier.importKind === 'type'),
|
specifier.importKind === 'type'),
|
||||||
false,
|
false,
|
||||||
!options.inlineTemplate,
|
!inlineMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,7 +406,7 @@ export function compileScript(
|
||||||
(specifier.type === 'ImportSpecifier' &&
|
(specifier.type === 'ImportSpecifier' &&
|
||||||
specifier.importKind === 'type'),
|
specifier.importKind === 'type'),
|
||||||
true,
|
true,
|
||||||
!options.inlineTemplate,
|
!inlineMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -811,12 +847,16 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
const destructureElements =
|
const destructureElements =
|
||||||
ctx.hasDefineExposeCall || !options.inlineTemplate
|
ctx.hasDefineExposeCall || !inlineMode ? [`expose: __expose`] : []
|
||||||
? [`expose: __expose`]
|
|
||||||
: []
|
|
||||||
if (ctx.emitDecl) {
|
if (ctx.emitDecl) {
|
||||||
destructureElements.push(`emit: __emit`)
|
destructureElements.push(`emit: __emit`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destructure built-in properties (e.g. $emit, $attrs, $slots)
|
||||||
|
if (inlineMode) {
|
||||||
|
buildDestructureElements()
|
||||||
|
}
|
||||||
|
|
||||||
if (destructureElements.length) {
|
if (destructureElements.length) {
|
||||||
args += `, { ${destructureElements.join(', ')} }`
|
args += `, { ${destructureElements.join(', ')} }`
|
||||||
}
|
}
|
||||||
|
@ -824,10 +864,7 @@ export function compileScript(
|
||||||
let templateMap
|
let templateMap
|
||||||
// 9. generate return statement
|
// 9. generate return statement
|
||||||
let returned
|
let returned
|
||||||
if (
|
if (!inlineMode || (!sfc.template && ctx.hasDefaultExportRender)) {
|
||||||
!options.inlineTemplate ||
|
|
||||||
(!sfc.template && ctx.hasDefaultExportRender)
|
|
||||||
) {
|
|
||||||
// non-inline mode, or has manual render in normal <script>
|
// non-inline mode, or has manual render in normal <script>
|
||||||
// return bindings from script and script setup
|
// return bindings from script and script setup
|
||||||
const allBindings: Record<string, any> = {
|
const allBindings: Record<string, any> = {
|
||||||
|
@ -927,7 +964,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.inlineTemplate && !__TEST__) {
|
if (!inlineMode && !__TEST__) {
|
||||||
// in non-inline mode, the `__isScriptSetup: true` flag is used by
|
// in non-inline mode, the `__isScriptSetup: true` flag is used by
|
||||||
// componentPublicInstance proxy to allow properties that start with $ or _
|
// componentPublicInstance proxy to allow properties that start with $ or _
|
||||||
ctx.s.appendRight(
|
ctx.s.appendRight(
|
||||||
|
@ -976,8 +1013,12 @@ export function compileScript(
|
||||||
|
|
||||||
// <script setup> components are closed by default. If the user did not
|
// <script setup> components are closed by default. If the user did not
|
||||||
// explicitly call `defineExpose`, call expose() with no args.
|
// explicitly call `defineExpose`, call expose() with no args.
|
||||||
const exposeCall =
|
if (!ctx.hasDefineExposeCall && !inlineMode)
|
||||||
ctx.hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
|
setupPreambleLines.push(`__expose();`)
|
||||||
|
|
||||||
|
const setupPreamble = setupPreambleLines.length
|
||||||
|
? ` ${setupPreambleLines.join('\n ')}\n`
|
||||||
|
: ''
|
||||||
// wrap setup code with function.
|
// wrap setup code with function.
|
||||||
if (ctx.isTS) {
|
if (ctx.isTS) {
|
||||||
// for TS, make sure the exported type is still valid type with
|
// for TS, make sure the exported type is still valid type with
|
||||||
|
@ -994,7 +1035,7 @@ export function compileScript(
|
||||||
vapor && !ssr ? `defineVaporComponent` : `defineComponent`,
|
vapor && !ssr ? `defineVaporComponent` : `defineComponent`,
|
||||||
)}({${def}${runtimeOptions}\n ${
|
)}({${def}${runtimeOptions}\n ${
|
||||||
hasAwait ? `async ` : ``
|
hasAwait ? `async ` : ``
|
||||||
}setup(${args}) {\n${exposeCall}`,
|
}setup(${args}) {\n${setupPreamble}`,
|
||||||
)
|
)
|
||||||
ctx.s.appendRight(endOffset, `})`)
|
ctx.s.appendRight(endOffset, `})`)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1010,14 +1051,14 @@ export function compileScript(
|
||||||
`\n${genDefaultAs} /*@__PURE__*/Object.assign(${
|
`\n${genDefaultAs} /*@__PURE__*/Object.assign(${
|
||||||
defaultExport ? `${normalScriptDefaultVar}, ` : ''
|
defaultExport ? `${normalScriptDefaultVar}, ` : ''
|
||||||
}${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
|
}${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
|
||||||
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
|
`${hasAwait ? `async ` : ``}setup(${args}) {\n${setupPreamble}`,
|
||||||
)
|
)
|
||||||
ctx.s.appendRight(endOffset, `})`)
|
ctx.s.appendRight(endOffset, `})`)
|
||||||
} else {
|
} else {
|
||||||
ctx.s.prependLeft(
|
ctx.s.prependLeft(
|
||||||
startOffset,
|
startOffset,
|
||||||
`\n${genDefaultAs} {${runtimeOptions}\n ` +
|
`\n${genDefaultAs} {${runtimeOptions}\n ` +
|
||||||
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
|
`${hasAwait ? `async ` : ``}setup(${args}) {\n${setupPreamble}`,
|
||||||
)
|
)
|
||||||
ctx.s.appendRight(endOffset, `}`)
|
ctx.s.appendRight(endOffset, `}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { TemplateCompiler } from './compileTemplate'
|
||||||
import { parseCssVars } from './style/cssVars'
|
import { parseCssVars } from './style/cssVars'
|
||||||
import { createCache } from './cache'
|
import { createCache } from './cache'
|
||||||
import type { ImportBinding } from './compileScript'
|
import type { ImportBinding } from './compileScript'
|
||||||
import { isImportUsed } from './script/importUsageCheck'
|
import { isUsedInTemplate } from './script/importUsageCheck'
|
||||||
import type { LRUCache } from 'lru-cache'
|
import type { LRUCache } from 'lru-cache'
|
||||||
import { genCacheKey } from '@vue/shared'
|
import { genCacheKey } from '@vue/shared'
|
||||||
|
|
||||||
|
@ -449,7 +449,7 @@ export function hmrShouldReload(
|
||||||
for (const key in prevImports) {
|
for (const key in prevImports) {
|
||||||
// if an import was previous unused, but now is used, we need to force
|
// if an import was previous unused, but now is used, we need to force
|
||||||
// reload so that the script now includes that import.
|
// reload so that the script now includes that import.
|
||||||
if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next)) {
|
if (!prevImports[key].isUsedInTemplate && isUsedInTemplate(key, next)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,16 @@ import { createCache } from '../cache'
|
||||||
import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
|
import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an import is used in the SFC's template. This is used to determine
|
* Check if an identifier is used in the SFC's template.
|
||||||
* the properties that should be included in the object returned from setup()
|
* - 1.used to determine the properties that should be included in the object returned from setup()
|
||||||
* when not using inline mode.
|
* when not using inline mode.
|
||||||
|
* - 2.check whether the built-in properties such as $attrs, $slots, $emit are used in the template
|
||||||
*/
|
*/
|
||||||
export function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
|
export function isUsedInTemplate(
|
||||||
return resolveTemplateUsedIdentifiers(sfc).has(local)
|
identifier: string,
|
||||||
|
sfc: SFCDescriptor,
|
||||||
|
): boolean {
|
||||||
|
return resolveTemplateUsedIdentifiers(sfc).has(identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
const templateUsageCheckCache = createCache<Set<string>>()
|
const templateUsageCheckCache = createCache<Set<string>>()
|
||||||
|
|
|
@ -1500,6 +1500,7 @@ export function inferRuntimeType(
|
||||||
node: Node & MaybeWithScope,
|
node: Node & MaybeWithScope,
|
||||||
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
||||||
isKeyOf = false,
|
isKeyOf = false,
|
||||||
|
typeParameters?: Record<string, Node>,
|
||||||
): string[] {
|
): string[] {
|
||||||
try {
|
try {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
|
@ -1588,19 +1589,43 @@ export function inferRuntimeType(
|
||||||
case 'TSTypeReference': {
|
case 'TSTypeReference': {
|
||||||
const resolved = resolveTypeReference(ctx, node, scope)
|
const resolved = resolveTypeReference(ctx, node, scope)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
|
if (resolved.type === 'TSTypeAliasDeclaration') {
|
||||||
// #13240
|
// #13240
|
||||||
// Special case for function type aliases to ensure correct runtime behavior
|
// Special case for function type aliases to ensure correct runtime behavior
|
||||||
// other type aliases still fallback to unknown as before
|
// other type aliases still fallback to unknown as before
|
||||||
if (
|
if (resolved.typeAnnotation.type === 'TSFunctionType') {
|
||||||
resolved.type === 'TSTypeAliasDeclaration' &&
|
|
||||||
resolved.typeAnnotation.type === 'TSFunctionType'
|
|
||||||
) {
|
|
||||||
return ['Function']
|
return ['Function']
|
||||||
}
|
}
|
||||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
|
||||||
|
if (node.typeParameters) {
|
||||||
|
const typeParams: Record<string, Node> = Object.create(null)
|
||||||
|
if (resolved.typeParameters) {
|
||||||
|
resolved.typeParameters.params.forEach((p, i) => {
|
||||||
|
typeParams![p.name] = node.typeParameters!.params[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return inferRuntimeType(
|
||||||
|
ctx,
|
||||||
|
resolved.typeAnnotation,
|
||||||
|
resolved._ownerScope,
|
||||||
|
isKeyOf,
|
||||||
|
typeParams,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||||
|
}
|
||||||
if (node.typeName.type === 'Identifier') {
|
if (node.typeName.type === 'Identifier') {
|
||||||
|
if (typeParameters && typeParameters[node.typeName.name]) {
|
||||||
|
return inferRuntimeType(
|
||||||
|
ctx,
|
||||||
|
typeParameters[node.typeName.name],
|
||||||
|
scope,
|
||||||
|
isKeyOf,
|
||||||
|
typeParameters,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (isKeyOf) {
|
if (isKeyOf) {
|
||||||
switch (node.typeName.name) {
|
switch (node.typeName.name) {
|
||||||
case 'String':
|
case 'String':
|
||||||
|
@ -1733,11 +1758,15 @@ export function inferRuntimeType(
|
||||||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||||
|
|
||||||
case 'TSUnionType':
|
case 'TSUnionType':
|
||||||
return flattenTypes(ctx, node.types, scope, isKeyOf)
|
return flattenTypes(ctx, node.types, scope, isKeyOf, typeParameters)
|
||||||
case 'TSIntersectionType': {
|
case 'TSIntersectionType': {
|
||||||
return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
|
return flattenTypes(
|
||||||
t => t !== UNKNOWN_TYPE,
|
ctx,
|
||||||
)
|
node.types,
|
||||||
|
scope,
|
||||||
|
isKeyOf,
|
||||||
|
typeParameters,
|
||||||
|
).filter(t => t !== UNKNOWN_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'TSEnumDeclaration':
|
case 'TSEnumDeclaration':
|
||||||
|
@ -1808,14 +1837,17 @@ function flattenTypes(
|
||||||
types: TSType[],
|
types: TSType[],
|
||||||
scope: TypeScope,
|
scope: TypeScope,
|
||||||
isKeyOf: boolean = false,
|
isKeyOf: boolean = false,
|
||||||
|
typeParameters: Record<string, Node> | undefined = undefined,
|
||||||
): string[] {
|
): string[] {
|
||||||
if (types.length === 1) {
|
if (types.length === 1) {
|
||||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf)
|
return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters)
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
([] as string[]).concat(
|
([] as string[]).concat(
|
||||||
...types.map(t => inferRuntimeType(ctx, t, scope, isKeyOf)),
|
...types.map(t =>
|
||||||
|
inferRuntimeType(ctx, t, scope, isKeyOf, typeParameters),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { warn } from '../warn'
|
||||||
|
|
||||||
const animationNameRE = /^(-\w+-)?animation-name$/
|
const animationNameRE = /^(-\w+-)?animation-name$/
|
||||||
const animationRE = /^(-\w+-)?animation$/
|
const animationRE = /^(-\w+-)?animation$/
|
||||||
|
const keyframesRE = /^(?:-\w+-)?keyframes$/
|
||||||
|
|
||||||
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||||
const keyframes = Object.create(null)
|
const keyframes = Object.create(null)
|
||||||
|
@ -21,10 +22,7 @@ const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||||
processRule(id, rule)
|
processRule(id, rule)
|
||||||
},
|
},
|
||||||
AtRule(node) {
|
AtRule(node) {
|
||||||
if (
|
if (keyframesRE.test(node.name) && !node.params.endsWith(`-${shortId}`)) {
|
||||||
/-?keyframes$/.test(node.name) &&
|
|
||||||
!node.params.endsWith(`-${shortId}`)
|
|
||||||
) {
|
|
||||||
// register keyframes
|
// register keyframes
|
||||||
keyframes[node.params] = node.params = node.params + '-' + shortId
|
keyframes[node.params] = node.params = node.params + '-' + shortId
|
||||||
}
|
}
|
||||||
|
@ -72,7 +70,7 @@ function processRule(id: string, rule: Rule) {
|
||||||
processedRules.has(rule) ||
|
processedRules.has(rule) ||
|
||||||
(rule.parent &&
|
(rule.parent &&
|
||||||
rule.parent.type === 'atrule' &&
|
rule.parent.type === 'atrule' &&
|
||||||
/-?keyframes$/.test((rule.parent as AtRule).name))
|
keyframesRE.test((rule.parent as AtRule).name))
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ export const transformSrcset: NodeTransform = (
|
||||||
|
|
||||||
const shouldProcessUrl = (url: string) => {
|
const shouldProcessUrl = (url: string) => {
|
||||||
return (
|
return (
|
||||||
|
url &&
|
||||||
!isExternalUrl(url) &&
|
!isExternalUrl(url) &&
|
||||||
!isDataUrl(url) &&
|
!isDataUrl(url) &&
|
||||||
(options.includeAbsolute || isRelativeUrl(url))
|
(options.includeAbsolute || isRelativeUrl(url))
|
||||||
|
|
|
@ -317,6 +317,35 @@ describe('ssr: components', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #13724
|
||||||
|
test('slot content with v-memo', () => {
|
||||||
|
const { code } = compile(`<foo><bar v-memo="[]" /></foo>`)
|
||||||
|
expect(code).not.toMatch(`_cache`)
|
||||||
|
expect(compile(`<foo><bar v-memo="[]" /></foo>`).code)
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require("vue")
|
||||||
|
const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
const _component_foo = _resolveComponent("foo")
|
||||||
|
const _component_bar = _resolveComponent("bar")
|
||||||
|
|
||||||
|
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
||||||
|
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||||
|
if (_push) {
|
||||||
|
_push(_ssrRenderComponent(_component_bar, null, null, _parent, _scopeId))
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
_createVNode(_component_bar)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_: 1 /* STABLE */
|
||||||
|
}, _parent))
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
describe('built-in fallthroughs', () => {
|
describe('built-in fallthroughs', () => {
|
||||||
test('transition', () => {
|
test('transition', () => {
|
||||||
expect(compile(`<transition><div/></transition>`).code)
|
expect(compile(`<transition><div/></transition>`).code)
|
||||||
|
|
|
@ -29,7 +29,7 @@ if (__TEST__) {
|
||||||
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`SSRErrorCodes need to be updated to ${
|
`SSRErrorCodes need to be updated to ${
|
||||||
DOMErrorCodes.__EXTEND_POINT__ + 1
|
DOMErrorCodes.__EXTEND_POINT__
|
||||||
} to match extension point from core DOMErrorCodes.`,
|
} to match extension point from core DOMErrorCodes.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,6 +212,22 @@ export function render(_ctx) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compile > execution order > with insertionState 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, child as _child, setInsertionState as _setInsertionState, createSlot as _createSlot, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div><div></div></div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n3 = t0()
|
||||||
|
const n1 = _child(n3)
|
||||||
|
_setInsertionState(n1)
|
||||||
|
const n0 = _createSlot("default", null)
|
||||||
|
_setInsertionState(n3)
|
||||||
|
const n2 = _createComponentWithFallback(_component_Comp)
|
||||||
|
return n3
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compile > execution order > with v-once 1`] = `
|
exports[`compile > execution order > with v-once 1`] = `
|
||||||
"import { child as _child, next as _next, nthChild as _nthChild, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
|
"import { child as _child, next as _next, nthChild as _nthChild, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
const t0 = _template("<div><span> </span> <br> </div>", true)
|
const t0 = _template("<div><span> </span> <br> </div>", true)
|
||||||
|
|
|
@ -247,6 +247,7 @@ describe('compile', () => {
|
||||||
_setText(x0, _toDisplayString(_ctx.bar))`,
|
_setText(x0, _toDisplayString(_ctx.bar))`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('with v-once', () => {
|
test('with v-once', () => {
|
||||||
const code = compile(
|
const code = compile(
|
||||||
`<div>
|
`<div>
|
||||||
|
@ -261,5 +262,10 @@ describe('compile', () => {
|
||||||
_setText(n2, " " + _toDisplayString(_ctx.baz))`,
|
_setText(n2, " " + _toDisplayString(_ctx.baz))`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with insertionState', () => {
|
||||||
|
const code = compile(`<div><div><slot /></div><Comp/></div>`)
|
||||||
|
expect(code).matchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -57,10 +57,10 @@ const t0 = _template("<div><div>x</div><div><span> </span></div><div><span> </sp
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n3 = t0()
|
const n3 = t0()
|
||||||
const p0 = _next(_child(n3))
|
const p0 = _next(_child(n3))
|
||||||
const p1 = _next(p0)
|
|
||||||
const p2 = _next(p1)
|
|
||||||
const n0 = _child(p0)
|
const n0 = _child(p0)
|
||||||
|
const p1 = _next(p0)
|
||||||
const n1 = _child(p1)
|
const n1 = _child(p1)
|
||||||
|
const p2 = _next(p1)
|
||||||
const n2 = _child(p2)
|
const n2 = _child(p2)
|
||||||
const x0 = _child(n0)
|
const x0 = _child(n0)
|
||||||
const x1 = _child(n1)
|
const x1 = _child(n1)
|
||||||
|
|
|
@ -353,8 +353,8 @@ const t2 = _template("<form></form>")
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n1 = t1()
|
const n1 = t1()
|
||||||
const n3 = t2()
|
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
|
const n3 = t2()
|
||||||
const n2 = t2()
|
const n2 = t2()
|
||||||
_insert(n0, n1)
|
_insert(n0, n1)
|
||||||
_insert(n2, n3)
|
_insert(n2, n3)
|
||||||
|
|
|
@ -65,8 +65,10 @@ export function genBlockContent(
|
||||||
push(...genSelf(child, context))
|
push(...genSelf(child, context))
|
||||||
}
|
}
|
||||||
for (const child of dynamic.children) {
|
for (const child of dynamic.children) {
|
||||||
|
if (!child.hasDynamicChild) {
|
||||||
push(...genChildren(child, context, push, `n${child.id!}`))
|
push(...genChildren(child, context, push, `n${child.id!}`))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
push(...genOperations(operation, context))
|
push(...genOperations(operation, context))
|
||||||
push(...genEffects(effect, context, genEffectsExtraFrag))
|
push(...genEffects(effect, context, genEffectsExtraFrag))
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function genSelf(
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
const [frag, push] = buildCodeFragment()
|
const [frag, push] = buildCodeFragment()
|
||||||
const { id, template, operation } = dynamic
|
const { id, template, operation, hasDynamicChild } = dynamic
|
||||||
|
|
||||||
if (id !== undefined && template !== undefined) {
|
if (id !== undefined && template !== undefined) {
|
||||||
push(NEWLINE, `const n${id} = t${template}()`)
|
push(NEWLINE, `const n${id} = t${template}()`)
|
||||||
|
@ -38,6 +38,10 @@ export function genSelf(
|
||||||
push(...genOperationWithInsertionState(operation, context))
|
push(...genOperationWithInsertionState(operation, context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasDynamicChild) {
|
||||||
|
push(...genChildren(dynamic, context, push, `n${id}`))
|
||||||
|
}
|
||||||
|
|
||||||
return frag
|
return frag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +57,6 @@ export function genChildren(
|
||||||
|
|
||||||
let offset = 0
|
let offset = 0
|
||||||
let prev: [variable: string, elementIndex: number] | undefined
|
let prev: [variable: string, elementIndex: number] | undefined
|
||||||
const childrenToGen: [IRDynamicInfo, string][] = []
|
|
||||||
|
|
||||||
for (const [index, child] of children.entries()) {
|
for (const [index, child] of children.entries()) {
|
||||||
if (child.flags & DynamicFlag.NON_TEMPLATE) {
|
if (child.flags & DynamicFlag.NON_TEMPLATE) {
|
||||||
|
@ -99,7 +102,7 @@ export function genChildren(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id === child.anchor) {
|
if (id === child.anchor && !child.hasDynamicChild) {
|
||||||
push(...genSelf(child, context))
|
push(...genSelf(child, context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,13 +111,7 @@ export function genChildren(
|
||||||
}
|
}
|
||||||
|
|
||||||
prev = [variable, elementIndex]
|
prev = [variable, elementIndex]
|
||||||
childrenToGen.push([child, variable])
|
push(...genChildren(child, context, pushBlock, variable))
|
||||||
}
|
|
||||||
|
|
||||||
if (childrenToGen.length) {
|
|
||||||
for (const [child, from] of childrenToGen) {
|
|
||||||
push(...genChildren(child, context, pushBlock, from))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return frag
|
return frag
|
||||||
|
|
|
@ -498,9 +498,10 @@ describe('reactivity/readonly', () => {
|
||||||
const r = ref(false)
|
const r = ref(false)
|
||||||
const ror = readonly(r)
|
const ror = readonly(r)
|
||||||
const obj = reactive({ ror })
|
const obj = reactive({ ror })
|
||||||
expect(() => {
|
|
||||||
obj.ror = true
|
obj.ror = true
|
||||||
}).toThrow()
|
expect(
|
||||||
|
`Set operation on key "ror" failed: target is readonly.`,
|
||||||
|
).toHaveBeenWarned()
|
||||||
expect(obj.ror).toBe(false)
|
expect(obj.ror).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,13 @@ class MutableReactiveHandler extends BaseReactiveHandler {
|
||||||
}
|
}
|
||||||
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
||||||
if (isOldValueReadonly) {
|
if (isOldValueReadonly) {
|
||||||
return false
|
if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`Set operation on key "${String(key)}" failed: target is readonly.`,
|
||||||
|
target[key],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
} else {
|
} else {
|
||||||
oldValue.value = value
|
oldValue.value = value
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -167,13 +167,26 @@ describe('component: proxy', () => {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
foo: 0,
|
foo: 0,
|
||||||
|
$foo: 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
cmp: () => {
|
||||||
|
throw new Error('value of cmp should not be accessed')
|
||||||
|
},
|
||||||
|
$cmp: () => {
|
||||||
|
throw new Error('value of $cmp should not be read')
|
||||||
|
},
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
bar: 1,
|
bar: 1,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
__cssModules: {
|
||||||
|
$style: {},
|
||||||
|
cssStyles: {},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
instanceProxy = this
|
instanceProxy = this
|
||||||
},
|
},
|
||||||
|
@ -181,6 +194,7 @@ describe('component: proxy', () => {
|
||||||
|
|
||||||
const app = createApp(Comp, { msg: 'hello' })
|
const app = createApp(Comp, { msg: 'hello' })
|
||||||
app.config.globalProperties.global = 1
|
app.config.globalProperties.global = 1
|
||||||
|
app.config.globalProperties.$global = 1
|
||||||
|
|
||||||
app.mount(nodeOps.createElement('div'))
|
app.mount(nodeOps.createElement('div'))
|
||||||
|
|
||||||
|
@ -188,12 +202,20 @@ describe('component: proxy', () => {
|
||||||
expect('msg' in instanceProxy).toBe(true)
|
expect('msg' in instanceProxy).toBe(true)
|
||||||
// data
|
// data
|
||||||
expect('foo' in instanceProxy).toBe(true)
|
expect('foo' in instanceProxy).toBe(true)
|
||||||
// ctx
|
expect('$foo' in instanceProxy).toBe(false)
|
||||||
|
// setupState
|
||||||
expect('bar' in instanceProxy).toBe(true)
|
expect('bar' in instanceProxy).toBe(true)
|
||||||
|
// ctx
|
||||||
|
expect('cmp' in instanceProxy).toBe(true)
|
||||||
|
expect('$cmp' in instanceProxy).toBe(true)
|
||||||
// public properties
|
// public properties
|
||||||
expect('$el' in instanceProxy).toBe(true)
|
expect('$el' in instanceProxy).toBe(true)
|
||||||
|
// CSS modules
|
||||||
|
expect('$style' in instanceProxy).toBe(true)
|
||||||
|
expect('cssStyles' in instanceProxy).toBe(true)
|
||||||
// global properties
|
// global properties
|
||||||
expect('global' in instanceProxy).toBe(true)
|
expect('global' in instanceProxy).toBe(true)
|
||||||
|
expect('$global' in instanceProxy).toBe(true)
|
||||||
|
|
||||||
// non-existent
|
// non-existent
|
||||||
expect('$foobar' in instanceProxy).toBe(false)
|
expect('$foobar' in instanceProxy).toBe(false)
|
||||||
|
@ -202,11 +224,15 @@ describe('component: proxy', () => {
|
||||||
// #4962 triggering getter should not cause non-existent property to
|
// #4962 triggering getter should not cause non-existent property to
|
||||||
// pass the has check
|
// pass the has check
|
||||||
instanceProxy.baz
|
instanceProxy.baz
|
||||||
|
instanceProxy.$baz
|
||||||
expect('baz' in instanceProxy).toBe(false)
|
expect('baz' in instanceProxy).toBe(false)
|
||||||
|
expect('$baz' in instanceProxy).toBe(false)
|
||||||
|
|
||||||
// set non-existent (goes into proxyTarget sink)
|
// set non-existent (goes into proxyTarget sink)
|
||||||
instanceProxy.baz = 1
|
instanceProxy.baz = 1
|
||||||
expect('baz' in instanceProxy).toBe(true)
|
expect('baz' in instanceProxy).toBe(true)
|
||||||
|
instanceProxy.$baz = 1
|
||||||
|
expect('$baz' in instanceProxy).toBe(true)
|
||||||
|
|
||||||
// dev mode ownKeys check for console inspection
|
// dev mode ownKeys check for console inspection
|
||||||
// should only expose own keys
|
// should only expose own keys
|
||||||
|
@ -214,7 +240,10 @@ describe('component: proxy', () => {
|
||||||
'msg',
|
'msg',
|
||||||
'bar',
|
'bar',
|
||||||
'foo',
|
'foo',
|
||||||
|
'cmp',
|
||||||
|
'$cmp',
|
||||||
'baz',
|
'baz',
|
||||||
|
'$baz',
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
nodeOps,
|
nodeOps,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
|
serializeInner,
|
||||||
useSlots,
|
useSlots,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { createBlock, normalizeVNode } from '../src/vnode'
|
import { createBlock, normalizeVNode } from '../src/vnode'
|
||||||
|
@ -55,14 +56,10 @@ describe('component: slots', () => {
|
||||||
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
|
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
expect(slots).toHaveProperty('__')
|
|
||||||
expect(Object.getOwnPropertyDescriptor(slots, '__')!.enumerable).toBe(
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
return h('div')
|
return h('div')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const slots = { foo: () => {}, _: 1, __: [1] }
|
const slots = { foo: () => {}, _: 1 }
|
||||||
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
|
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -74,6 +71,10 @@ describe('component: slots', () => {
|
||||||
footer: ['f1', 'f2'],
|
footer: ['f1', 'f2'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
'[Vue warn]: Non-function value encountered for slot "_inner". Prefer function slots for better performance.',
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
'[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
|
'[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
|
||||||
).toHaveBeenWarned()
|
).toHaveBeenWarned()
|
||||||
|
@ -82,8 +83,8 @@ describe('component: slots', () => {
|
||||||
'[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
|
'[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
|
||||||
).toHaveBeenWarned()
|
).toHaveBeenWarned()
|
||||||
|
|
||||||
expect(slots).not.toHaveProperty('_inner')
|
|
||||||
expect(slots).not.toHaveProperty('foo')
|
expect(slots).not.toHaveProperty('foo')
|
||||||
|
expect(slots._inner()).toMatchObject([normalizeVNode('_inner')])
|
||||||
expect(slots.header()).toMatchObject([normalizeVNode('header')])
|
expect(slots.header()).toMatchObject([normalizeVNode('header')])
|
||||||
expect(slots.footer()).toMatchObject([
|
expect(slots.footer()).toMatchObject([
|
||||||
normalizeVNode('f1'),
|
normalizeVNode('f1'),
|
||||||
|
@ -442,4 +443,22 @@ describe('component: slots', () => {
|
||||||
'Slot "default" invoked outside of the render function',
|
'Slot "default" invoked outside of the render function',
|
||||||
).toHaveBeenWarned()
|
).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('slot name starts with underscore', () => {
|
||||||
|
const Comp = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
return () => slots._foo()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Comp, null, { _foo: () => 'foo' })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
createApp(App).mount(root)
|
||||||
|
expect(serializeInner(root)).toBe('foo')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,6 +17,8 @@ import {
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
|
renderList,
|
||||||
|
renderSlot,
|
||||||
resolveDynamicComponent,
|
resolveDynamicComponent,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
|
@ -2161,6 +2163,80 @@ describe('Suspense', () => {
|
||||||
await Promise.all(deps)
|
await Promise.all(deps)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #13453
|
||||||
|
test('add new async deps during patching', async () => {
|
||||||
|
const getComponent = (type: string) => {
|
||||||
|
if (type === 'A') {
|
||||||
|
return defineAsyncComponent({
|
||||||
|
setup() {
|
||||||
|
return () => h('div', 'A')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return defineAsyncComponent({
|
||||||
|
setup() {
|
||||||
|
return () => h('div', 'B')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const types = ref(['A'])
|
||||||
|
const add = async () => {
|
||||||
|
types.value.push('B')
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = async () => {
|
||||||
|
// mount Suspense B
|
||||||
|
// [Suspense A] -> [Suspense A(pending), Suspense B(pending)]
|
||||||
|
await add()
|
||||||
|
// patch Suspense B (still pending)
|
||||||
|
// [Suspense A(pending), Suspense B(pending)] -> [Suspense B(pending)]
|
||||||
|
types.value.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
render(this: any) {
|
||||||
|
return h(Fragment, null, [
|
||||||
|
renderList(types.value, type => {
|
||||||
|
return h(
|
||||||
|
Suspense,
|
||||||
|
{ key: type },
|
||||||
|
{
|
||||||
|
default: () => [
|
||||||
|
renderSlot(this.$slots, 'default', { type: type }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
return () =>
|
||||||
|
h(Comp, null, {
|
||||||
|
default: (params: any) => [h(getComponent(params.type))],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(App), root)
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
|
||||||
|
await Promise.all(deps)
|
||||||
|
expect(serializeInner(root)).toBe(`<div>A</div>`)
|
||||||
|
|
||||||
|
update()
|
||||||
|
await nextTick()
|
||||||
|
// wait for both A and B to resolve
|
||||||
|
await Promise.all(deps)
|
||||||
|
// wait for new B to resolve
|
||||||
|
await Promise.all(deps)
|
||||||
|
expect(serializeInner(root)).toBe(`<div>B</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
describe('warnings', () => {
|
describe('warnings', () => {
|
||||||
// base function to check if a combination of slots warns or not
|
// base function to check if a combination of slots warns or not
|
||||||
function baseCheckWarn(
|
function baseCheckWarn(
|
||||||
|
@ -2230,5 +2306,57 @@ describe('Suspense', () => {
|
||||||
fallback: [h('div'), h('div')],
|
fallback: [h('div'), h('div')],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #13559
|
||||||
|
test('renders multiple async components in Suspense with v-for and updates on items change', async () => {
|
||||||
|
const CompAsyncSetup = defineAsyncComponent({
|
||||||
|
props: ['item'],
|
||||||
|
render(ctx: any) {
|
||||||
|
return h('div', ctx.item.name)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const items = ref([
|
||||||
|
{ id: 1, name: '111' },
|
||||||
|
{ id: 2, name: '222' },
|
||||||
|
{ id: 3, name: '333' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
return () =>
|
||||||
|
h(Suspense, null, {
|
||||||
|
default: () =>
|
||||||
|
h(
|
||||||
|
Fragment,
|
||||||
|
null,
|
||||||
|
items.value.map(item =>
|
||||||
|
h(CompAsyncSetup, { item, key: item.id }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
await nextTick()
|
||||||
|
await Promise.all(deps)
|
||||||
|
|
||||||
|
expect(serializeInner(root)).toBe(
|
||||||
|
`<div>111</div><div>222</div><div>333</div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
items.value = [
|
||||||
|
{ id: 4, name: '444' },
|
||||||
|
{ id: 5, name: '555' },
|
||||||
|
{ id: 6, name: '666' },
|
||||||
|
]
|
||||||
|
await nextTick()
|
||||||
|
await Promise.all(deps)
|
||||||
|
expect(serializeInner(root)).toBe(
|
||||||
|
`<div>444</div><div>555</div><div>666</div>`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -106,6 +106,134 @@ describe('useTemplateRef', () => {
|
||||||
expect(tRef!.value).toBe(null)
|
expect(tRef!.value).toBe(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should work when used with direct ref value with ref_key', () => {
|
||||||
|
let tRef: ShallowRef
|
||||||
|
const key = 'refKey'
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
tRef = useTemplateRef(key)
|
||||||
|
return () => h('div', { ref: tRef, ref_key: key })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
|
||||||
|
expect('target is readonly').not.toHaveBeenWarned()
|
||||||
|
expect(tRef!.value).toBe(root.children[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work when used with direct ref value with ref_key and ref_for', () => {
|
||||||
|
let tRef: ShallowRef
|
||||||
|
const key = 'refKey'
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
tRef = useTemplateRef(key)
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
[1, 2, 3].map(x =>
|
||||||
|
h('span', { ref: tRef, ref_key: key, ref_for: true }, x.toString()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
|
||||||
|
expect('target is readonly').not.toHaveBeenWarned()
|
||||||
|
expect(tRef!.value).toHaveLength(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work when used with direct ref value with ref_key and dynamic value', async () => {
|
||||||
|
const refMode = ref('h1-ref')
|
||||||
|
|
||||||
|
let tRef: ShallowRef
|
||||||
|
const key = 'refKey'
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
tRef = useTemplateRef(key)
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
switch (refMode.value) {
|
||||||
|
case 'h1-ref':
|
||||||
|
return h('h1', { ref: tRef, ref_key: key })
|
||||||
|
case 'h2-ref':
|
||||||
|
return h('h2', { ref: tRef, ref_key: key })
|
||||||
|
case 'no-ref':
|
||||||
|
return h('span')
|
||||||
|
case 'nothing':
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
|
||||||
|
expect(tRef!.value.tag).toBe('h1')
|
||||||
|
|
||||||
|
refMode.value = 'h2-ref'
|
||||||
|
await nextTick()
|
||||||
|
expect(tRef!.value.tag).toBe('h2')
|
||||||
|
|
||||||
|
refMode.value = 'no-ref'
|
||||||
|
await nextTick()
|
||||||
|
expect(tRef!.value).toBeNull()
|
||||||
|
|
||||||
|
refMode.value = 'nothing'
|
||||||
|
await nextTick()
|
||||||
|
expect(tRef!.value).toBeNull()
|
||||||
|
|
||||||
|
expect('target is readonly').not.toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work when used with dynamic direct refs and ref_keys', async () => {
|
||||||
|
const refKey = ref('foo')
|
||||||
|
|
||||||
|
let tRefs: Record<string, ShallowRef>
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
tRefs = {
|
||||||
|
foo: useTemplateRef('foo'),
|
||||||
|
bar: useTemplateRef('bar'),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return h('div', { ref: tRefs[refKey.value], ref_key: refKey.value })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
|
||||||
|
expect(tRefs!['foo'].value).toBe(root.children[0])
|
||||||
|
expect(tRefs!['bar'].value).toBeNull()
|
||||||
|
|
||||||
|
refKey.value = 'bar'
|
||||||
|
await nextTick()
|
||||||
|
expect(tRefs!['foo'].value).toBeNull()
|
||||||
|
expect(tRefs!['bar'].value).toBe(root.children[0])
|
||||||
|
|
||||||
|
expect('target is readonly').not.toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not work when used with direct ref value without ref_key (in dev mode)', () => {
|
||||||
|
let tRef: ShallowRef
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
tRef = useTemplateRef('refKey')
|
||||||
|
return () => h('div', { ref: tRef })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
|
||||||
|
expect(tRef!.value).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
test('should work when used as direct ref value (compiled in prod mode)', () => {
|
test('should work when used as direct ref value (compiled in prod mode)', () => {
|
||||||
__DEV__ = false
|
__DEV__ = false
|
||||||
try {
|
try {
|
||||||
|
@ -125,4 +253,65 @@ describe('useTemplateRef', () => {
|
||||||
__DEV__ = true
|
__DEV__ = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should work when used as direct ref value with ref_key and ref_for (compiled in prod mode)', () => {
|
||||||
|
__DEV__ = false
|
||||||
|
try {
|
||||||
|
let tRef: ShallowRef
|
||||||
|
const key = 'refKey'
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
tRef = useTemplateRef(key)
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
[1, 2, 3].map(x =>
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{ ref: tRef, ref_key: key, ref_for: true },
|
||||||
|
x.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
|
||||||
|
expect('target is readonly').not.toHaveBeenWarned()
|
||||||
|
expect(tRef!.value).toHaveLength(3)
|
||||||
|
} finally {
|
||||||
|
__DEV__ = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work when used as direct ref value with ref_for but without ref_key (compiled in prod mode)', () => {
|
||||||
|
__DEV__ = false
|
||||||
|
try {
|
||||||
|
let tRef: ShallowRef
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
tRef = useTemplateRef('refKey')
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
[1, 2, 3].map(x =>
|
||||||
|
h('span', { ref: tRef, ref_for: true }, x.toString()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
|
||||||
|
expect('target is readonly').not.toHaveBeenWarned()
|
||||||
|
expect(tRef!.value).toHaveLength(3)
|
||||||
|
} finally {
|
||||||
|
__DEV__ = true
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -894,4 +894,47 @@ describe('hot module replacement', () => {
|
||||||
await timeout()
|
await timeout()
|
||||||
expect(serializeInner(root)).toBe('<div>bar</div>')
|
expect(serializeInner(root)).toBe('<div>bar</div>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('rerender for nested component', () => {
|
||||||
|
const id = 'child-nested-rerender'
|
||||||
|
const Foo: ComponentOptions = {
|
||||||
|
__hmrId: id,
|
||||||
|
render() {
|
||||||
|
return this.$slots.default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createRecord(id, Foo)
|
||||||
|
|
||||||
|
const parentId = 'parent-nested-rerender'
|
||||||
|
const Parent: ComponentOptions = {
|
||||||
|
__hmrId: parentId,
|
||||||
|
render() {
|
||||||
|
return h(Foo, null, {
|
||||||
|
default: () => this.$slots.default(),
|
||||||
|
_: 3 /* FORWARDED */,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const appId = 'app-nested-rerender'
|
||||||
|
const App: ComponentOptions = {
|
||||||
|
__hmrId: appId,
|
||||||
|
render: () =>
|
||||||
|
h(Parent, null, {
|
||||||
|
default: () => [
|
||||||
|
h(Foo, null, {
|
||||||
|
default: () => ['foo'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
createRecord(parentId, App)
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(App), root)
|
||||||
|
expect(serializeInner(root)).toBe('foo')
|
||||||
|
|
||||||
|
rerender(id, () => 'bar')
|
||||||
|
expect(serializeInner(root)).toBe('bar')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1160,6 +1160,69 @@ describe('SSR hydration', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #13510
|
||||||
|
test('update async component after parent mount before async component resolve', async () => {
|
||||||
|
const Comp = {
|
||||||
|
props: ['toggle'],
|
||||||
|
render(this: any) {
|
||||||
|
return h('h1', [
|
||||||
|
this.toggle ? 'Async component' : 'Updated async component',
|
||||||
|
])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let serverResolve: any
|
||||||
|
let AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
serverResolve = r
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const toggle = ref(true)
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
// change state, after mount and before async component resolve
|
||||||
|
nextTick(() => (toggle.value = false))
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return h(AsyncComp, { toggle: toggle.value })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// server render
|
||||||
|
const htmlPromise = renderToString(h(App))
|
||||||
|
serverResolve(Comp)
|
||||||
|
const html = await htmlPromise
|
||||||
|
expect(html).toMatchInlineSnapshot(`"<h1>Async component</h1>"`)
|
||||||
|
|
||||||
|
// hydration
|
||||||
|
let clientResolve: any
|
||||||
|
AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
clientResolve = r
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const container = document.createElement('div')
|
||||||
|
container.innerHTML = html
|
||||||
|
createSSRApp(App).mount(container)
|
||||||
|
|
||||||
|
// resolve
|
||||||
|
clientResolve(Comp)
|
||||||
|
await new Promise(r => setTimeout(r))
|
||||||
|
|
||||||
|
// prevent lazy hydration since the component has been patched
|
||||||
|
expect('Skipping lazy hydration for component').toHaveBeenWarned()
|
||||||
|
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
|
||||||
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<h1>Updated async component</h1>"`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('hydrate safely when property used by async setup changed before render', async () => {
|
test('hydrate safely when property used by async setup changed before render', async () => {
|
||||||
const toggle = ref(true)
|
const toggle = ref(true)
|
||||||
|
|
||||||
|
@ -1677,6 +1740,35 @@ describe('SSR hydration', () => {
|
||||||
expect(`mismatch`).not.toHaveBeenWarned()
|
expect(`mismatch`).not.toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #13394
|
||||||
|
test('transition appear work with empty content', async () => {
|
||||||
|
const show = ref(true)
|
||||||
|
const { vnode, container } = mountWithHydration(
|
||||||
|
`<template><!----></template>`,
|
||||||
|
function (this: any) {
|
||||||
|
return h(
|
||||||
|
Transition,
|
||||||
|
{ appear: true },
|
||||||
|
{
|
||||||
|
default: () =>
|
||||||
|
show.value
|
||||||
|
? renderSlot(this.$slots, 'default')
|
||||||
|
: createTextVNode('foo'),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// empty slot render as a comment node
|
||||||
|
expect(container.firstChild!.nodeType).toBe(Node.COMMENT_NODE)
|
||||||
|
expect(vnode.el).toBe(container.firstChild)
|
||||||
|
expect(`mismatch`).not.toHaveBeenWarned()
|
||||||
|
|
||||||
|
show.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe('foo')
|
||||||
|
})
|
||||||
|
|
||||||
test('transition appear with v-if', () => {
|
test('transition appear with v-if', () => {
|
||||||
const show = false
|
const show = false
|
||||||
const { vnode, container } = mountWithHydration(
|
const { vnode, container } = mountWithHydration(
|
||||||
|
|
|
@ -553,18 +553,6 @@ describe('vnode', () => {
|
||||||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('with suspense', () => {
|
|
||||||
const hoist = createVNode('div')
|
|
||||||
let vnode1
|
|
||||||
const vnode =
|
|
||||||
(openBlock(),
|
|
||||||
createBlock('div', null, [
|
|
||||||
hoist,
|
|
||||||
(vnode1 = createVNode(() => {}, null, 'text')),
|
|
||||||
]))
|
|
||||||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
|
||||||
})
|
|
||||||
|
|
||||||
// #1039
|
// #1039
|
||||||
// <component :is="foo">{{ bar }}</component>
|
// <component :is="foo">{{ bar }}</component>
|
||||||
// - content is compiled as slot
|
// - content is compiled as slot
|
||||||
|
|
|
@ -124,28 +124,30 @@ export function defineAsyncComponent<
|
||||||
|
|
||||||
__asyncHydrate(el, instance, hydrate) {
|
__asyncHydrate(el, instance, hydrate) {
|
||||||
let patched = false
|
let patched = false
|
||||||
const doHydrate = hydrateStrategy
|
;(instance.bu || (instance.bu = [])).push(() => (patched = true))
|
||||||
? () => {
|
|
||||||
const performHydrate = () => {
|
const performHydrate = () => {
|
||||||
// skip hydration if the component has been patched
|
// skip hydration if the component has been patched
|
||||||
if (__DEV__ && patched) {
|
if (patched) {
|
||||||
|
if (__DEV__) {
|
||||||
warn(
|
warn(
|
||||||
`Skipping lazy hydration for component '${getComponentName(resolvedComp!)}': ` +
|
`Skipping lazy hydration for component '${getComponentName(resolvedComp!) || resolvedComp!.__file}': ` +
|
||||||
`it was updated before lazy hydration performed.`,
|
`it was updated before lazy hydration performed.`,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hydrate()
|
hydrate()
|
||||||
}
|
}
|
||||||
|
const doHydrate = hydrateStrategy
|
||||||
|
? () => {
|
||||||
const teardown = hydrateStrategy(performHydrate, cb =>
|
const teardown = hydrateStrategy(performHydrate, cb =>
|
||||||
forEachElement(el, cb),
|
forEachElement(el, cb),
|
||||||
)
|
)
|
||||||
if (teardown) {
|
if (teardown) {
|
||||||
;(instance.bum || (instance.bum = [])).push(teardown)
|
;(instance.bum || (instance.bum = [])).push(teardown)
|
||||||
}
|
}
|
||||||
;(instance.u || (instance.u = [])).push(() => (patched = true))
|
|
||||||
}
|
}
|
||||||
: hydrate
|
: performHydrate
|
||||||
if (resolvedComp) {
|
if (resolvedComp) {
|
||||||
doHydrate()
|
doHydrate()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -383,17 +383,17 @@ export function withDefaults<
|
||||||
|
|
||||||
// TODO return type for Vapor components
|
// TODO return type for Vapor components
|
||||||
export function useSlots(): SetupContext['slots'] {
|
export function useSlots(): SetupContext['slots'] {
|
||||||
return getContext().slots
|
return getContext('useSlots').slots
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAttrs(): SetupContext['attrs'] {
|
export function useAttrs(): SetupContext['attrs'] {
|
||||||
return getContext().attrs
|
return getContext('useAttrs').attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContext(): SetupContext {
|
function getContext(calledFunctionName: string): SetupContext {
|
||||||
const i = getCurrentGenericInstance()!
|
const i = getCurrentGenericInstance()!
|
||||||
if (__DEV__ && !i) {
|
if (__DEV__ && !i) {
|
||||||
warn(`useContext() called without active instance.`)
|
warn(`${calledFunctionName}() called without active instance.`)
|
||||||
}
|
}
|
||||||
if (i.vapor) {
|
if (i.vapor) {
|
||||||
return i as any // vapor instance act as its own setup context
|
return i as any // vapor instance act as its own setup context
|
||||||
|
|
|
@ -756,6 +756,7 @@ export function applyOptions(instance: ComponentInternalInstance): void {
|
||||||
Object.defineProperty(exposed, key, {
|
Object.defineProperty(exposed, key, {
|
||||||
get: () => publicThis[key],
|
get: () => publicThis[key],
|
||||||
set: val => (publicThis[key] = val),
|
set: val => (publicThis[key] = val),
|
||||||
|
enumerable: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else if (!instance.exposed) {
|
} else if (!instance.exposed) {
|
||||||
|
|
|
@ -577,19 +577,20 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
|
|
||||||
has(
|
has(
|
||||||
{
|
{
|
||||||
_: { data, setupState, accessCache, ctx, appContext, propsOptions },
|
_: { data, setupState, accessCache, ctx, appContext, propsOptions, type },
|
||||||
}: ComponentRenderContext,
|
}: ComponentRenderContext,
|
||||||
key: string,
|
key: string,
|
||||||
) {
|
) {
|
||||||
let normalizedProps
|
let normalizedProps, cssModules
|
||||||
return (
|
return !!(
|
||||||
!!accessCache![key] ||
|
accessCache![key] ||
|
||||||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
(data !== EMPTY_OBJ && key[0] !== '$' && hasOwn(data, key)) ||
|
||||||
hasSetupBinding(setupState, key) ||
|
hasSetupBinding(setupState, key) ||
|
||||||
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
|
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
|
||||||
hasOwn(ctx, key) ||
|
hasOwn(ctx, key) ||
|
||||||
hasOwn(publicPropertiesMap, key) ||
|
hasOwn(publicPropertiesMap, key) ||
|
||||||
hasOwn(appContext.config.globalProperties, key)
|
hasOwn(appContext.config.globalProperties, key) ||
|
||||||
|
((cssModules = type.__cssModules) && cssModules[key])
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -79,14 +79,10 @@ export type RawSlots = {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_?: SlotFlags
|
_?: SlotFlags
|
||||||
/**
|
|
||||||
* cache indexes for slot content
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
__?: number[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
const isInternalKey = (key: string) =>
|
||||||
|
key === '_' || key === '_ctx' || key === '$stable'
|
||||||
|
|
||||||
const normalizeSlotValue = (value: unknown): VNode[] =>
|
const normalizeSlotValue = (value: unknown): VNode[] =>
|
||||||
isArray(value)
|
isArray(value)
|
||||||
|
@ -194,10 +190,6 @@ export const initSlots = (
|
||||||
): void => {
|
): void => {
|
||||||
const slots = (instance.slots = createInternalObject())
|
const slots = (instance.slots = createInternalObject())
|
||||||
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
||||||
const cacheIndexes = (children as RawSlots).__
|
|
||||||
// make cache indexes marker non-enumerable
|
|
||||||
if (cacheIndexes) def(slots, '__', cacheIndexes, true)
|
|
||||||
|
|
||||||
const type = (children as RawSlots)._
|
const type = (children as RawSlots)._
|
||||||
if (type) {
|
if (type) {
|
||||||
assignSlots(slots, children as Slots, optimized)
|
assignSlots(slots, children as Slots, optimized)
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { SchedulerJobFlags } from '../scheduler'
|
||||||
|
|
||||||
type Hook<T = () => void> = T | T[]
|
type Hook<T = () => void> = T | T[]
|
||||||
|
|
||||||
const leaveCbKey: unique symbol = Symbol('_leaveCb')
|
export const leaveCbKey: unique symbol = Symbol('_leaveCb')
|
||||||
const enterCbKey: unique symbol = Symbol('_enterCb')
|
const enterCbKey: unique symbol = Symbol('_enterCb')
|
||||||
|
|
||||||
export interface BaseTransitionProps<HostElement = RendererElement> {
|
export interface BaseTransitionProps<HostElement = RendererElement> {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* eslint-disable no-restricted-globals */
|
/* eslint-disable no-restricted-globals */
|
||||||
|
import { EffectFlags } from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
type ClassComponent,
|
type ClassComponent,
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
|
@ -99,9 +100,12 @@ function rerender(id: string, newRender?: Function): void {
|
||||||
instance.hmrRerender!()
|
instance.hmrRerender!()
|
||||||
} else {
|
} else {
|
||||||
const i = instance as ComponentInternalInstance
|
const i = instance as ComponentInternalInstance
|
||||||
|
// #13771 don't update if the job is already disposed
|
||||||
|
if (!(i.effect.flags! & EffectFlags.STOP)) {
|
||||||
i.renderCache = []
|
i.renderCache = []
|
||||||
i.effect.run()
|
i.effect.run()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
isHmrUpdating = false
|
isHmrUpdating = false
|
||||||
})
|
})
|
||||||
|
|
|
@ -562,3 +562,7 @@ export { initFeatureFlags } from './featureFlags'
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export { createInternalObject } from './internalObject'
|
export { createInternalObject } from './internalObject'
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export { createCanSetSetupRefChecker } from './rendererTemplateRef'
|
||||||
|
|
|
@ -40,12 +40,10 @@ export function endMeasure(
|
||||||
if (instance.appContext.config.performance && isSupported()) {
|
if (instance.appContext.config.performance && isSupported()) {
|
||||||
const startTag = `vue-${type}-${instance.uid}`
|
const startTag = `vue-${type}-${instance.uid}`
|
||||||
const endTag = startTag + `:end`
|
const endTag = startTag + `:end`
|
||||||
|
const measureName = `<${formatComponentName(instance, instance.type)}> ${type}`
|
||||||
perf.mark(endTag)
|
perf.mark(endTag)
|
||||||
perf.measure(
|
perf.measure(measureName, startTag, endTag)
|
||||||
`<${formatComponentName(instance, instance.type)}> ${type}`,
|
perf.clearMeasures(measureName)
|
||||||
startTag,
|
|
||||||
endTag,
|
|
||||||
)
|
|
||||||
perf.clearMarks(startTag)
|
perf.clearMarks(startTag)
|
||||||
perf.clearMarks(endTag)
|
perf.clearMarks(endTag)
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,8 +96,8 @@ import { initFeatureFlags } from './featureFlags'
|
||||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||||
import { isCompatEnabled } from './compat/compatConfig'
|
import { isCompatEnabled } from './compat/compatConfig'
|
||||||
import { DeprecationTypes } from './compat/compatConfig'
|
import { DeprecationTypes } from './compat/compatConfig'
|
||||||
import type { TransitionHooks } from './components/BaseTransition'
|
|
||||||
import type { VaporInteropInterface } from './apiCreateApp'
|
import type { VaporInteropInterface } from './apiCreateApp'
|
||||||
|
import { type TransitionHooks, leaveCbKey } from './components/BaseTransition'
|
||||||
import type { VueElement } from '@vue/runtime-dom'
|
import type { VueElement } from '@vue/runtime-dom'
|
||||||
|
|
||||||
export interface Renderer<HostElement = RendererElement> {
|
export interface Renderer<HostElement = RendererElement> {
|
||||||
|
@ -1278,6 +1278,7 @@ function baseCreateRenderer(
|
||||||
if (!initialVNode.el) {
|
if (!initialVNode.el) {
|
||||||
const placeholder = (instance.subTree = createVNode(Comment))
|
const placeholder = (instance.subTree = createVNode(Comment))
|
||||||
processCommentNode(null, placeholder, container!, anchor)
|
processCommentNode(null, placeholder, container!, anchor)
|
||||||
|
initialVNode.placeholder = placeholder.el
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setupRenderEffect(
|
setupRenderEffect(
|
||||||
|
@ -2081,8 +2082,12 @@ function baseCreateRenderer(
|
||||||
for (i = toBePatched - 1; i >= 0; i--) {
|
for (i = toBePatched - 1; i >= 0; i--) {
|
||||||
const nextIndex = s2 + i
|
const nextIndex = s2 + i
|
||||||
const nextChild = c2[nextIndex] as VNode
|
const nextChild = c2[nextIndex] as VNode
|
||||||
|
const anchorVNode = c2[nextIndex + 1] as VNode
|
||||||
const anchor =
|
const anchor =
|
||||||
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
|
nextIndex + 1 < l2
|
||||||
|
? // #13559, fallback to el placeholder for unresolved async component
|
||||||
|
anchorVNode.el || anchorVNode.placeholder
|
||||||
|
: parentAnchor
|
||||||
if (newIndexToOldIndexMap[i] === 0) {
|
if (newIndexToOldIndexMap[i] === 0) {
|
||||||
// mount new
|
// mount new
|
||||||
patch(
|
patch(
|
||||||
|
@ -2200,6 +2205,12 @@ function baseCreateRenderer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const performLeave = () => {
|
const performLeave = () => {
|
||||||
|
// #13153 move kept-alive node before v-show transition leave finishes
|
||||||
|
// it needs to call the leaving callback to ensure element's `display`
|
||||||
|
// is `none`
|
||||||
|
if (el!._isLeaving) {
|
||||||
|
el
|
||||||
|
}
|
||||||
leave(el!, () => {
|
leave(el!, () => {
|
||||||
remove()
|
remove()
|
||||||
afterLeave && afterLeave()
|
afterLeave && afterLeave()
|
||||||
|
@ -2421,17 +2432,7 @@ function baseCreateRenderer(
|
||||||
unregisterHMR(instance)
|
unregisterHMR(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { bum, scope, effect, subTree, um, m, a } = instance
|
||||||
bum,
|
|
||||||
scope,
|
|
||||||
effect,
|
|
||||||
subTree,
|
|
||||||
um,
|
|
||||||
m,
|
|
||||||
a,
|
|
||||||
parent,
|
|
||||||
slots: { __: slotCacheKeys },
|
|
||||||
} = instance
|
|
||||||
invalidateMount(m)
|
invalidateMount(m)
|
||||||
invalidateMount(a)
|
invalidateMount(a)
|
||||||
|
|
||||||
|
@ -2440,13 +2441,6 @@ function baseCreateRenderer(
|
||||||
invokeArrayFns(bum)
|
invokeArrayFns(bum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove slots content from parent renderCache
|
|
||||||
if (parent && isArray(slotCacheKeys)) {
|
|
||||||
slotCacheKeys.forEach(v => {
|
|
||||||
;(parent as ComponentInternalInstance).renderCache[v] = undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
__COMPAT__ &&
|
__COMPAT__ &&
|
||||||
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
||||||
|
@ -2484,24 +2478,6 @@ function baseCreateRenderer(
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
)
|
)
|
||||||
|
|
||||||
// A component with async dep inside a pending suspense is unmounted before
|
|
||||||
// its async dep resolves. This should remove the dep from the suspense, and
|
|
||||||
// cause the suspense to resolve immediately if that was the last dep.
|
|
||||||
if (
|
|
||||||
__FEATURE_SUSPENSE__ &&
|
|
||||||
parentSuspense &&
|
|
||||||
parentSuspense.pendingBranch &&
|
|
||||||
!parentSuspense.isUnmounted &&
|
|
||||||
instance.asyncDep &&
|
|
||||||
!instance.asyncResolved &&
|
|
||||||
instance.suspenseId === parentSuspense.pendingId
|
|
||||||
) {
|
|
||||||
parentSuspense.deps--
|
|
||||||
if (parentSuspense.deps === 0) {
|
|
||||||
parentSuspense.resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||||
devtoolsComponentRemoved(instance)
|
devtoolsComponentRemoved(instance)
|
||||||
}
|
}
|
||||||
|
@ -2708,7 +2684,11 @@ export function traverseStaticChildren(
|
||||||
traverseStaticChildren(c1, c2)
|
traverseStaticChildren(c1, c2)
|
||||||
}
|
}
|
||||||
// #6852 also inherit for text nodes
|
// #6852 also inherit for text nodes
|
||||||
if (c2.type === Text) {
|
if (
|
||||||
|
c2.type === Text &&
|
||||||
|
// avoid cached text nodes retaining detached dom nodes
|
||||||
|
c2.patchFlag !== PatchFlags.CACHED
|
||||||
|
) {
|
||||||
c2.el = c1.el
|
c2.el = c1.el
|
||||||
}
|
}
|
||||||
// #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
|
// #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
import type { SuspenseBoundary } from './components/Suspense'
|
import type { SuspenseBoundary } from './components/Suspense'
|
||||||
import type { VNode, VNodeNormalizedRef, VNodeNormalizedRefAtom } from './vnode'
|
import type {
|
||||||
|
VNode,
|
||||||
|
VNodeNormalizedRef,
|
||||||
|
VNodeNormalizedRefAtom,
|
||||||
|
VNodeRef,
|
||||||
|
} from './vnode'
|
||||||
import {
|
import {
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
|
NO,
|
||||||
ShapeFlags,
|
ShapeFlags,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
isArray,
|
isArray,
|
||||||
|
@ -14,7 +20,11 @@ import { warn } from './warning'
|
||||||
import { isRef, toRaw } from '@vue/reactivity'
|
import { isRef, toRaw } from '@vue/reactivity'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import { queuePostRenderEffect } from './renderer'
|
import { queuePostRenderEffect } from './renderer'
|
||||||
import { type ComponentOptions, getComponentPublicInstance } from './component'
|
import {
|
||||||
|
type ComponentOptions,
|
||||||
|
type Data,
|
||||||
|
getComponentPublicInstance,
|
||||||
|
} from './component'
|
||||||
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,24 +83,10 @@ export function setRef(
|
||||||
const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
|
const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
|
||||||
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
|
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
|
||||||
const setupState = owner.setupState
|
const setupState = owner.setupState
|
||||||
const rawSetupState = toRaw(setupState)
|
const canSetSetupRef = createCanSetSetupRefChecker(setupState)
|
||||||
const canSetSetupRef =
|
|
||||||
setupState === EMPTY_OBJ
|
|
||||||
? () => false
|
|
||||||
: (key: string) => {
|
|
||||||
if (__DEV__) {
|
|
||||||
if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
|
|
||||||
warn(
|
|
||||||
`Template ref "${key}" used on a non-ref value. ` +
|
|
||||||
`It will not work in the production build.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (knownTemplateRefs.has(rawSetupState[key] as any)) {
|
const canSetRef = (ref: VNodeRef) => {
|
||||||
return false
|
return !__DEV__ || !knownTemplateRefs.has(ref as any)
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasOwn(rawSetupState, key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dynamic ref changed. unset old ref
|
// dynamic ref changed. unset old ref
|
||||||
|
@ -101,8 +97,14 @@ export function setRef(
|
||||||
setupState[oldRef] = null
|
setupState[oldRef] = null
|
||||||
}
|
}
|
||||||
} else if (isRef(oldRef)) {
|
} else if (isRef(oldRef)) {
|
||||||
|
if (canSetRef(oldRef)) {
|
||||||
oldRef.value = null
|
oldRef.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this type assertion is valid since `oldRef` has already been asserted to be non-null
|
||||||
|
const oldRawRefAtom = oldRawRef as VNodeNormalizedRefAtom
|
||||||
|
if (oldRawRefAtom.k) refs[oldRawRefAtom.k] = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFunction(ref)) {
|
if (isFunction(ref)) {
|
||||||
|
@ -118,7 +120,9 @@ export function setRef(
|
||||||
? canSetSetupRef(ref)
|
? canSetSetupRef(ref)
|
||||||
? setupState[ref]
|
? setupState[ref]
|
||||||
: refs[ref]
|
: refs[ref]
|
||||||
: ref.value
|
: canSetRef(ref) || !rawRef.k
|
||||||
|
? ref.value
|
||||||
|
: refs[rawRef.k]
|
||||||
if (isUnmount) {
|
if (isUnmount) {
|
||||||
isArray(existing) && remove(existing, refValue)
|
isArray(existing) && remove(existing, refValue)
|
||||||
} else {
|
} else {
|
||||||
|
@ -129,8 +133,11 @@ export function setRef(
|
||||||
setupState[ref] = refs[ref]
|
setupState[ref] = refs[ref]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ref.value = [refValue]
|
const newVal = [refValue]
|
||||||
if (rawRef.k) refs[rawRef.k] = ref.value
|
if (canSetRef(ref)) {
|
||||||
|
ref.value = newVal
|
||||||
|
}
|
||||||
|
if (rawRef.k) refs[rawRef.k] = newVal
|
||||||
}
|
}
|
||||||
} else if (!existing.includes(refValue)) {
|
} else if (!existing.includes(refValue)) {
|
||||||
existing.push(refValue)
|
existing.push(refValue)
|
||||||
|
@ -142,7 +149,9 @@ export function setRef(
|
||||||
setupState[ref] = value
|
setupState[ref] = value
|
||||||
}
|
}
|
||||||
} else if (_isRef) {
|
} else if (_isRef) {
|
||||||
|
if (canSetRef(ref)) {
|
||||||
ref.value = value
|
ref.value = value
|
||||||
|
}
|
||||||
if (rawRef.k) refs[rawRef.k] = value
|
if (rawRef.k) refs[rawRef.k] = value
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
||||||
|
@ -161,3 +170,26 @@ export function setRef(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createCanSetSetupRefChecker(
|
||||||
|
setupState: Data,
|
||||||
|
): (key: string) => boolean {
|
||||||
|
const rawSetupState = toRaw(setupState)
|
||||||
|
return setupState === EMPTY_OBJ
|
||||||
|
? NO
|
||||||
|
: (key: string) => {
|
||||||
|
if (__DEV__) {
|
||||||
|
if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
|
||||||
|
warn(
|
||||||
|
`Template ref "${key}" used on a non-ref value. ` +
|
||||||
|
`It will not work in the production build.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (knownTemplateRefs.has(rawSetupState[key] as any)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasOwn(rawSetupState, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -200,6 +200,7 @@ export interface VNode<
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
|
placeholder: HostNode | null // async component el placeholder
|
||||||
anchor: HostNode | null // fragment anchor
|
anchor: HostNode | null // fragment anchor
|
||||||
target: HostElement | null // teleport target
|
target: HostElement | null // teleport target
|
||||||
targetStart: HostNode | null // teleport target start anchor
|
targetStart: HostNode | null // teleport target start anchor
|
||||||
|
@ -731,6 +732,8 @@ export function cloneVNode<T, U>(
|
||||||
suspense: vnode.suspense,
|
suspense: vnode.suspense,
|
||||||
ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
|
ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
|
||||||
ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
|
ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
|
||||||
|
placeholder: vnode.placeholder,
|
||||||
|
|
||||||
el: vnode.el,
|
el: vnode.el,
|
||||||
anchor: vnode.anchor,
|
anchor: vnode.anchor,
|
||||||
ctx: vnode.ctx,
|
ctx: vnode.ctx,
|
||||||
|
|
|
@ -1402,6 +1402,34 @@ describe('defineCustomElement', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('expose', () => {
|
describe('expose', () => {
|
||||||
|
test('expose w/ options api', async () => {
|
||||||
|
const E = defineCustomElement({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
foo() {
|
||||||
|
;(this as any).value++
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expose: ['foo'],
|
||||||
|
render(_ctx: any) {
|
||||||
|
return h('div', null, _ctx.value)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
customElements.define('my-el-expose-options-api', E)
|
||||||
|
|
||||||
|
container.innerHTML = `<my-el-expose-options-api></my-el-expose-options-api>`
|
||||||
|
const e = container.childNodes[0] as VueElement & {
|
||||||
|
foo: () => void
|
||||||
|
}
|
||||||
|
expect(e.shadowRoot!.innerHTML).toBe(`<div>0</div>`)
|
||||||
|
e.foo()
|
||||||
|
await nextTick()
|
||||||
|
expect(e.shadowRoot!.innerHTML).toBe(`<div>1</div>`)
|
||||||
|
})
|
||||||
test('expose attributes and callback', async () => {
|
test('expose attributes and callback', async () => {
|
||||||
type SetValue = (value: string) => void
|
type SetValue = (value: string) => void
|
||||||
let fn: MockedFunction<SetValue>
|
let fn: MockedFunction<SetValue>
|
||||||
|
|
|
@ -46,7 +46,7 @@ export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) {
|
||||||
vShow.name = 'show'
|
vShow.name = 'show'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -319,28 +319,6 @@ describe('api: createVaporApp', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('config.performance', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
window.performance.clearMeasures()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('with performance enabled', () => {
|
|
||||||
const { app, mount } = define({ setup: () => [] }).create()
|
|
||||||
|
|
||||||
app.config.performance = true
|
|
||||||
mount()
|
|
||||||
expect(window.performance.getEntries()).lengthOf(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('with performance disabled', () => {
|
|
||||||
const { app, mount } = define({ setup: () => [] }).create()
|
|
||||||
|
|
||||||
app.config.performance = false
|
|
||||||
mount()
|
|
||||||
expect(window.performance.getEntries()).lengthOf(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test.todo('config.globalProperty', () => {
|
test.todo('config.globalProperty', () => {
|
||||||
const { app } = define({
|
const { app } = define({
|
||||||
setup() {
|
setup() {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
insert,
|
insert,
|
||||||
prepend,
|
prepend,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
|
setInsertionState,
|
||||||
template,
|
template,
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { currentInstance, nextTick, ref } from '@vue/runtime-dom'
|
import { currentInstance, nextTick, ref } from '@vue/runtime-dom'
|
||||||
|
@ -502,5 +503,35 @@ describe('component: slots', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(host.innerHTML).toBe('<div><h1></h1><!--slot--></div>')
|
expect(host.innerHTML).toBe('<div><h1></h1><!--slot--></div>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('consecutive slots with insertion state', async () => {
|
||||||
|
const { component: Child } = define({
|
||||||
|
setup() {
|
||||||
|
const n2 = template('<div><div>baz</div></div>', true)() as any
|
||||||
|
setInsertionState(n2, 0)
|
||||||
|
createSlot('default', null)
|
||||||
|
setInsertionState(n2, 0)
|
||||||
|
createSlot('foo', null)
|
||||||
|
return n2
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { html } = define({
|
||||||
|
setup() {
|
||||||
|
return createComponent(Child, null, {
|
||||||
|
default: () => template('default')(),
|
||||||
|
foo: () => template('foo')(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(html()).toBe(
|
||||||
|
`<div>` +
|
||||||
|
`default<!--slot-->` +
|
||||||
|
`foo<!--slot-->` +
|
||||||
|
`<div>baz</div>` +
|
||||||
|
`</div>`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
nextTick,
|
nextTick,
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
|
shallowRef,
|
||||||
useTemplateRef,
|
useTemplateRef,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
|
@ -208,8 +209,8 @@ describe('api: template ref', () => {
|
||||||
const { render } = define({
|
const { render } = define({
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
foo: fooEl,
|
foo: shallowRef(fooEl),
|
||||||
bar: barEl,
|
bar: shallowRef(barEl),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
|
@ -251,6 +252,7 @@ describe('api: template ref', () => {
|
||||||
})
|
})
|
||||||
const { host } = render()
|
const { host } = render()
|
||||||
expect(state.refKey).toBe(host.children[0])
|
expect(state.refKey).toBe(host.children[0])
|
||||||
|
expect('Template ref "refKey" used on a non-ref value').toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('multiple root refs', () => {
|
test('multiple root refs', () => {
|
||||||
|
@ -713,6 +715,45 @@ describe('api: template ref', () => {
|
||||||
expect(html()).toBe('<div>changed</div><!--dynamic-component-->')
|
expect(html()).toBe('<div>changed</div><!--dynamic-component-->')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should not attempt to set when variable name is same as key', () => {
|
||||||
|
let tRef: ShallowRef
|
||||||
|
const key = 'refKey'
|
||||||
|
define({
|
||||||
|
setup() {
|
||||||
|
tRef = useTemplateRef('_')
|
||||||
|
return {
|
||||||
|
[key]: tRef,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const n0 = template('<div></div>')() as Element
|
||||||
|
createTemplateRefSetter()(n0, key)
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
expect('target is readonly').not.toHaveBeenWarned()
|
||||||
|
expect(tRef!.value).toBe(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work when used as direct ref value (compiled in prod mode)', () => {
|
||||||
|
__DEV__ = false
|
||||||
|
try {
|
||||||
|
let foo: ShallowRef
|
||||||
|
const { host } = define({
|
||||||
|
setup() {
|
||||||
|
foo = useTemplateRef('foo')
|
||||||
|
const n0 = template('<div></div>')() as Element
|
||||||
|
createTemplateRefSetter()(n0, foo)
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
expect('target is readonly').not.toHaveBeenWarned()
|
||||||
|
expect(foo!.value).toBe(host.children[0])
|
||||||
|
} finally {
|
||||||
|
__DEV__ = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// TODO: can not reproduce in Vapor
|
// TODO: can not reproduce in Vapor
|
||||||
// // #2078
|
// // #2078
|
||||||
// test('handling multiple merged refs', async () => {
|
// test('handling multiple merged refs', async () => {
|
||||||
|
|
|
@ -778,7 +778,7 @@ describe('createFor', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.todo('prepend', async () => {
|
test('prepend', async () => {
|
||||||
const arr = ref<number[]>([4, 5])
|
const arr = ref<number[]>([4, 5])
|
||||||
const { host, html } = render(arr)
|
const { host, html } = render(arr)
|
||||||
expect(host.children.length).toBe(2)
|
expect(host.children.length).toBe(2)
|
||||||
|
@ -940,7 +940,7 @@ describe('createFor', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.todo('remove from beginning and insert at end', async () => {
|
test('remove from beginning and insert at end', async () => {
|
||||||
const arr = ref<number[]>([1, 2, 3])
|
const arr = ref<number[]>([1, 2, 3])
|
||||||
const { host, html } = render(arr)
|
const { host, html } = render(arr)
|
||||||
expect(host.children.length).toBe(3)
|
expect(host.children.length).toBe(3)
|
||||||
|
@ -1028,7 +1028,7 @@ describe('createFor', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.todo('move to left & replace', async () => {
|
test('move to left & replace', async () => {
|
||||||
const arr = ref<number[]>([1, 2, 3, 4, 5])
|
const arr = ref<number[]>([1, 2, 3, 4, 5])
|
||||||
const { host, html } = render(arr)
|
const { host, html } = render(arr)
|
||||||
expect(host.children.length).toBe(5)
|
expect(host.children.length).toBe(5)
|
||||||
|
@ -1044,7 +1044,7 @@ describe('createFor', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.todo('move to left and leaves hold', async () => {
|
test('move to left and leaves hold', async () => {
|
||||||
const arr = ref<number[]>([1, 4, 5])
|
const arr = ref<number[]>([1, 4, 5])
|
||||||
const { host, html } = render(arr)
|
const { host, html } = render(arr)
|
||||||
expect(host.children.length).toBe(3)
|
expect(host.children.length).toBe(3)
|
||||||
|
@ -1058,9 +1058,7 @@ describe('createFor', () => {
|
||||||
expect(html()).toBe(`<span>4</span><span>6</span><!--for-->`)
|
expect(html()).toBe(`<span>4</span><span>6</span><!--for-->`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.todo(
|
test('moved and set to undefined element ending at the end', async () => {
|
||||||
'moved and set to undefined element ending at the end',
|
|
||||||
async () => {
|
|
||||||
const arr = ref<number[]>([2, 4, 5])
|
const arr = ref<number[]>([2, 4, 5])
|
||||||
const { host, html } = render(arr)
|
const { host, html } = render(arr)
|
||||||
expect(host.children.length).toBe(3)
|
expect(host.children.length).toBe(3)
|
||||||
|
@ -1074,8 +1072,7 @@ describe('createFor', () => {
|
||||||
expect(html()).toBe(
|
expect(html()).toBe(
|
||||||
`<span>4</span><span>5</span><span>3</span><!--for-->`,
|
`<span>4</span><span>5</span><span>3</span><!--for-->`,
|
||||||
)
|
)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test('reverse element', async () => {
|
test('reverse element', async () => {
|
||||||
const arr = ref<number[]>([1, 2, 3, 4, 5, 6, 7, 8])
|
const arr = ref<number[]>([1, 2, 3, 4, 5, 6, 7, 8])
|
||||||
|
@ -1323,7 +1320,7 @@ describe('createFor', () => {
|
||||||
}).render()
|
}).render()
|
||||||
}
|
}
|
||||||
|
|
||||||
test.todo('move a key in non-keyed nodes with a size up', async () => {
|
test('move a key in non-keyed nodes with a size up', async () => {
|
||||||
const arr = ref<any[]>([1, 'a', 'b', 'c'])
|
const arr = ref<any[]>([1, 'a', 'b', 'c'])
|
||||||
const { host, html } = define({
|
const { host, html } = define({
|
||||||
setup() {
|
setup() {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
import { makeInteropRender } from './_utils'
|
import { makeInteropRender } from './_utils'
|
||||||
import {
|
import {
|
||||||
applyTextModel,
|
applyTextModel,
|
||||||
|
applyVShow,
|
||||||
child,
|
child,
|
||||||
createComponent,
|
createComponent,
|
||||||
defineVaporComponent,
|
defineVaporComponent,
|
||||||
|
@ -113,6 +114,37 @@ describe('vdomInterop', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('v-show', () => {
|
||||||
|
test('apply v-show to vdom child', async () => {
|
||||||
|
const VDomChild = {
|
||||||
|
setup() {
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const VaporChild = defineVaporComponent({
|
||||||
|
setup() {
|
||||||
|
const n1 = createComponent(VDomChild as any)
|
||||||
|
applyVShow(n1, () => show.value)
|
||||||
|
return n1
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { html } = define({
|
||||||
|
setup() {
|
||||||
|
return () => h(VaporChild as any)
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(html()).toBe('<div style="display: none;"></div>')
|
||||||
|
|
||||||
|
show.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(html()).toBe('<div style=""></div>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('slots', () => {
|
describe('slots', () => {
|
||||||
test('basic', () => {
|
test('basic', () => {
|
||||||
const VDomChild = defineComponent({
|
const VDomChild = defineComponent({
|
||||||
|
|
|
@ -196,12 +196,15 @@ export const createFor = (
|
||||||
endOffset++
|
endOffset++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (endOffset !== 0) {
|
|
||||||
anchorFallback = normalizeAnchor(newBlocks[currentIndex + 1].nodes)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (endOffset !== 0) {
|
||||||
|
anchorFallback = normalizeAnchor(
|
||||||
|
newBlocks[newLength - endOffset].nodes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
while (startOffset < sharedBlockCount - endOffset) {
|
while (startOffset < sharedBlockCount - endOffset) {
|
||||||
const currentItem = getItem(source, startOffset)
|
const currentItem = getItem(source, startOffset)
|
||||||
const currentKey = getKey(...currentItem)
|
const currentKey = getKey(...currentItem)
|
||||||
|
@ -251,13 +254,9 @@ export const createFor = (
|
||||||
previousKeyIndexPairs.length = previousKeyIndexInsertIndex
|
previousKeyIndexPairs.length = previousKeyIndexInsertIndex
|
||||||
|
|
||||||
const previousKeyIndexMap = new Map(previousKeyIndexPairs)
|
const previousKeyIndexMap = new Map(previousKeyIndexPairs)
|
||||||
const blocksToMount: [
|
const operations: (() => void)[] = []
|
||||||
blockIndex: number,
|
|
||||||
blockItem: ReturnType<typeof getItem>,
|
|
||||||
blockKey: any,
|
|
||||||
anchorOffset: number,
|
|
||||||
][] = []
|
|
||||||
|
|
||||||
|
let mountCounter = 0
|
||||||
const relocateOrMountBlock = (
|
const relocateOrMountBlock = (
|
||||||
blockIndex: number,
|
blockIndex: number,
|
||||||
blockItem: ReturnType<typeof getItem>,
|
blockItem: ReturnType<typeof getItem>,
|
||||||
|
@ -269,21 +268,31 @@ export const createFor = (
|
||||||
const reusedBlock = (newBlocks[blockIndex] =
|
const reusedBlock = (newBlocks[blockIndex] =
|
||||||
oldBlocks[previousIndex])
|
oldBlocks[previousIndex])
|
||||||
update(reusedBlock, ...blockItem)
|
update(reusedBlock, ...blockItem)
|
||||||
|
previousKeyIndexMap.delete(blockKey)
|
||||||
|
if (previousIndex !== blockIndex) {
|
||||||
|
operations.push(() =>
|
||||||
insert(
|
insert(
|
||||||
reusedBlock,
|
reusedBlock,
|
||||||
parent!,
|
parent!,
|
||||||
anchorOffset === -1
|
anchorOffset === -1
|
||||||
? anchorFallback
|
? anchorFallback
|
||||||
: normalizeAnchor(newBlocks[anchorOffset].nodes),
|
: normalizeAnchor(newBlocks[anchorOffset].nodes),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
previousKeyIndexMap.delete(blockKey)
|
}
|
||||||
} else {
|
} else {
|
||||||
blocksToMount.push([
|
mountCounter++
|
||||||
|
operations.push(() =>
|
||||||
|
mount(
|
||||||
|
source,
|
||||||
blockIndex,
|
blockIndex,
|
||||||
|
anchorOffset === -1
|
||||||
|
? anchorFallback
|
||||||
|
: normalizeAnchor(newBlocks[anchorOffset].nodes),
|
||||||
blockItem,
|
blockItem,
|
||||||
blockKey,
|
blockKey,
|
||||||
anchorOffset,
|
),
|
||||||
])
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +312,7 @@ export const createFor = (
|
||||||
relocateOrMountBlock(i, blockItem, blockKey, -1)
|
relocateOrMountBlock(i, blockItem, blockKey, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const useFastRemove = blocksToMount.length === newLength
|
const useFastRemove = mountCounter === newLength
|
||||||
|
|
||||||
for (const leftoverIndex of previousKeyIndexMap.values()) {
|
for (const leftoverIndex of previousKeyIndexMap.values()) {
|
||||||
unmount(
|
unmount(
|
||||||
|
@ -322,21 +331,9 @@ export const createFor = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [
|
// perform mount and move operations
|
||||||
blockIndex,
|
for (const action of operations) {
|
||||||
blockItem,
|
action()
|
||||||
blockKey,
|
|
||||||
anchorOffset,
|
|
||||||
] of blocksToMount) {
|
|
||||||
mount(
|
|
||||||
source,
|
|
||||||
blockIndex,
|
|
||||||
anchorOffset === -1
|
|
||||||
? anchorFallback
|
|
||||||
: normalizeAnchor(newBlocks[anchorOffset].nodes),
|
|
||||||
blockItem,
|
|
||||||
blockKey,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
type SchedulerJob,
|
type SchedulerJob,
|
||||||
callWithErrorHandling,
|
callWithErrorHandling,
|
||||||
|
createCanSetSetupRefChecker,
|
||||||
queuePostFlushCb,
|
queuePostFlushCb,
|
||||||
warn,
|
warn,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
|
@ -55,6 +56,7 @@ export function setRef(
|
||||||
const refs =
|
const refs =
|
||||||
instance.refs === EMPTY_OBJ ? (instance.refs = {}) : instance.refs
|
instance.refs === EMPTY_OBJ ? (instance.refs = {}) : instance.refs
|
||||||
|
|
||||||
|
const canSetSetupRef = createCanSetSetupRefChecker(setupState)
|
||||||
// dynamic ref changed. unset old ref
|
// dynamic ref changed. unset old ref
|
||||||
if (oldRef != null && oldRef !== ref) {
|
if (oldRef != null && oldRef !== ref) {
|
||||||
if (isString(oldRef)) {
|
if (isString(oldRef)) {
|
||||||
|
@ -87,7 +89,7 @@ export function setRef(
|
||||||
const doSet: SchedulerJob = () => {
|
const doSet: SchedulerJob = () => {
|
||||||
if (refFor) {
|
if (refFor) {
|
||||||
existing = _isString
|
existing = _isString
|
||||||
? __DEV__ && hasOwn(setupState, ref)
|
? __DEV__ && canSetSetupRef(ref)
|
||||||
? setupState[ref]
|
? setupState[ref]
|
||||||
: refs[ref]
|
: refs[ref]
|
||||||
: ref.value
|
: ref.value
|
||||||
|
@ -96,7 +98,7 @@ export function setRef(
|
||||||
existing = [refValue]
|
existing = [refValue]
|
||||||
if (_isString) {
|
if (_isString) {
|
||||||
refs[ref] = existing
|
refs[ref] = existing
|
||||||
if (__DEV__ && hasOwn(setupState, ref)) {
|
if (__DEV__ && canSetSetupRef(ref)) {
|
||||||
setupState[ref] = refs[ref]
|
setupState[ref] = refs[ref]
|
||||||
// if setupState[ref] is a reactivity ref,
|
// if setupState[ref] is a reactivity ref,
|
||||||
// the existing will also become reactivity too
|
// the existing will also become reactivity too
|
||||||
|
@ -111,7 +113,7 @@ export function setRef(
|
||||||
}
|
}
|
||||||
} else if (_isString) {
|
} else if (_isString) {
|
||||||
refs[ref] = refValue
|
refs[ref] = refValue
|
||||||
if (__DEV__ && hasOwn(setupState, ref)) {
|
if (__DEV__ && canSetSetupRef(ref)) {
|
||||||
setupState[ref] = refValue
|
setupState[ref] = refValue
|
||||||
}
|
}
|
||||||
} else if (_isRef) {
|
} else if (_isRef) {
|
||||||
|
@ -129,7 +131,7 @@ export function setRef(
|
||||||
remove(existing, refValue)
|
remove(existing, refValue)
|
||||||
} else if (_isString) {
|
} else if (_isString) {
|
||||||
refs[ref] = null
|
refs[ref] = null
|
||||||
if (__DEV__ && hasOwn(setupState, ref)) {
|
if (__DEV__ && canSetSetupRef(ref)) {
|
||||||
setupState[ref] = null
|
setupState[ref] = null
|
||||||
}
|
}
|
||||||
} else if (_isRef) {
|
} else if (_isRef) {
|
||||||
|
|
|
@ -105,10 +105,10 @@ export function isValidBlock(block: Block): boolean {
|
||||||
|
|
||||||
export function insert(
|
export function insert(
|
||||||
block: Block,
|
block: Block,
|
||||||
parent: ParentNode,
|
parent: ParentNode & { $anchor?: Node | null },
|
||||||
anchor: Node | null | 0 = null, // 0 means prepend
|
anchor: Node | null | 0 = null, // 0 means prepend
|
||||||
): void {
|
): void {
|
||||||
anchor = anchor === 0 ? parent.firstChild : anchor
|
anchor = anchor === 0 ? parent.$anchor || parent.firstChild : anchor
|
||||||
if (block instanceof Node) {
|
if (block instanceof Node) {
|
||||||
if (!isHydrating) {
|
if (!isHydrating) {
|
||||||
parent.insertBefore(block, anchor)
|
parent.insertBefore(block, anchor)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { renderEffect } from '../renderEffect'
|
import { renderEffect } from '../renderEffect'
|
||||||
import { isVaporComponent } from '../component'
|
import { isVaporComponent } from '../component'
|
||||||
import { type Block, DynamicFragment } from '../block'
|
import { type Block, DynamicFragment, VaporFragment } from '../block'
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
|
|
||||||
export function applyVShow(target: Block, source: () => any): void {
|
export function applyVShow(target: Block, source: () => any): void {
|
||||||
|
@ -24,6 +24,12 @@ export function applyVShow(target: Block, source: () => any): void {
|
||||||
update.call(target, render, key)
|
update.call(target, render, key)
|
||||||
setDisplay(target, source())
|
setDisplay(target, source())
|
||||||
}
|
}
|
||||||
|
} else if (target instanceof VaporFragment && target.insert) {
|
||||||
|
const insert = target.insert
|
||||||
|
target.insert = (parent, anchor) => {
|
||||||
|
insert.call(target, parent, anchor)
|
||||||
|
setDisplay(target, source())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEffect(() => setDisplay(target, source()))
|
renderEffect(() => setDisplay(target, source()))
|
||||||
|
@ -33,12 +39,16 @@ function setDisplay(target: Block, value: unknown): void {
|
||||||
if (isVaporComponent(target)) {
|
if (isVaporComponent(target)) {
|
||||||
return setDisplay(target, value)
|
return setDisplay(target, value)
|
||||||
}
|
}
|
||||||
if (isArray(target) && target.length === 1) {
|
if (isArray(target)) {
|
||||||
return setDisplay(target[0], value)
|
if (target.length === 0) return
|
||||||
|
if (target.length === 1) return setDisplay(target[0], value)
|
||||||
}
|
}
|
||||||
if (target instanceof DynamicFragment) {
|
if (target instanceof DynamicFragment) {
|
||||||
return setDisplay(target.nodes, value)
|
return setDisplay(target.nodes, value)
|
||||||
}
|
}
|
||||||
|
if (target instanceof VaporFragment && target.insert) {
|
||||||
|
return setDisplay(target.nodes, value)
|
||||||
|
}
|
||||||
if (target instanceof Element) {
|
if (target instanceof Element) {
|
||||||
const el = target as VShowElement
|
const el = target as VShowElement
|
||||||
if (!(vShowOriginalDisplay in el)) {
|
if (!(vShowOriginalDisplay in el)) {
|
||||||
|
|
|
@ -296,7 +296,7 @@ export function optimizePropertyLookup(): void {
|
||||||
if (isOptimized) return
|
if (isOptimized) return
|
||||||
isOptimized = true
|
isOptimized = true
|
||||||
const proto = Element.prototype as any
|
const proto = Element.prototype as any
|
||||||
proto.$evtclick = undefined
|
proto.$anchor = proto.$evtclick = undefined
|
||||||
proto.$root = false
|
proto.$root = false
|
||||||
proto.$html =
|
proto.$html =
|
||||||
proto.$txt =
|
proto.$txt =
|
||||||
|
|
|
@ -6,7 +6,18 @@ export let insertionAnchor: Node | 0 | undefined
|
||||||
* (component, slot outlet, if, for) is created. The state is used for actual
|
* (component, slot outlet, if, for) is created. The state is used for actual
|
||||||
* insertion on client-side render, and used for node adoption during hydration.
|
* insertion on client-side render, and used for node adoption during hydration.
|
||||||
*/
|
*/
|
||||||
export function setInsertionState(parent: ParentNode, anchor?: Node | 0): void {
|
export function setInsertionState(
|
||||||
|
parent: ParentNode & { $anchor?: Node | null },
|
||||||
|
anchor?: Node | 0,
|
||||||
|
): void {
|
||||||
|
// When setInsertionState(n3, 0) is called consecutively, the first prepend operation
|
||||||
|
// uses parent.firstChild as the anchor. However, after insertion, parent.firstChild
|
||||||
|
// changes and cannot serve as the anchor for subsequent prepends. Therefore, we cache
|
||||||
|
// the original parent.firstChild on the first call for subsequent prepend operations.
|
||||||
|
if (anchor === 0 && !parent.$anchor) {
|
||||||
|
parent.$anchor = parent.firstChild
|
||||||
|
}
|
||||||
|
|
||||||
insertionParent = parent
|
insertionParent = parent
|
||||||
insertionAnchor = anchor
|
insertionAnchor = anchor
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,6 +216,8 @@ function createVDOMComponent(
|
||||||
parentInstance as any,
|
parentInstance as any,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frag.nodes = vnode.el as Block
|
||||||
}
|
}
|
||||||
|
|
||||||
frag.remove = unmount
|
frag.remove = unmount
|
||||||
|
|
|
@ -111,26 +111,106 @@ describe('ssr: slot', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('transition slot', async () => {
|
test('transition slot', async () => {
|
||||||
|
const ReusableTransition = {
|
||||||
|
template: `<transition><slot/></transition>`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReusableTransitionWithAppear = {
|
||||||
|
template: `<transition appear><slot/></transition>`,
|
||||||
|
}
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await renderToString(
|
await renderToString(
|
||||||
createApp({
|
createApp({
|
||||||
components: {
|
components: {
|
||||||
one: {
|
one: ReusableTransition,
|
||||||
template: `<transition><slot/></transition>`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
template: `<one><div v-if="false">foo</div></one>`,
|
template: `<one><div v-if="false">foo</div></one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<!---->`)
|
).toBe(`<!---->`)
|
||||||
|
|
||||||
|
expect(await renderToString(createApp(ReusableTransition))).toBe(`<!---->`)
|
||||||
|
|
||||||
|
expect(await renderToString(createApp(ReusableTransitionWithAppear))).toBe(
|
||||||
|
`<template><!----></template>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
one: ReusableTransition,
|
||||||
|
},
|
||||||
|
template: `<one><slot/></one>`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe(`<!---->`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await renderToString(
|
await renderToString(
|
||||||
createApp({
|
createApp({
|
||||||
components: {
|
components: {
|
||||||
one: {
|
one: ReusableTransitionWithAppear,
|
||||||
template: `<transition><slot/></transition>`,
|
|
||||||
},
|
},
|
||||||
|
template: `<one><slot/></one>`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe(`<template><!----></template>`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
createApp({
|
||||||
|
render() {
|
||||||
|
return h(ReusableTransition, null, {
|
||||||
|
default: () => null,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe(`<!---->`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
createApp({
|
||||||
|
render() {
|
||||||
|
return h(ReusableTransitionWithAppear, null, {
|
||||||
|
default: () => null,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe(`<template><!----></template>`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
createApp({
|
||||||
|
render() {
|
||||||
|
return h(ReusableTransitionWithAppear, null, {
|
||||||
|
default: () => [],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe(`<template><!----></template>`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
createApp({
|
||||||
|
render() {
|
||||||
|
return h(ReusableTransition, null, {
|
||||||
|
default: () => [],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe(`<!---->`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
one: ReusableTransition,
|
||||||
},
|
},
|
||||||
template: `<one><div v-if="true">foo</div></one>`,
|
template: `<one><div v-if="true">foo</div></one>`,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -74,6 +74,8 @@ export function ssrRenderSlotInner(
|
||||||
)
|
)
|
||||||
} else if (fallbackRenderFn) {
|
} else if (fallbackRenderFn) {
|
||||||
fallbackRenderFn()
|
fallbackRenderFn()
|
||||||
|
} else if (transition) {
|
||||||
|
push(`<!---->`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ssr slot.
|
// ssr slot.
|
||||||
|
@ -110,13 +112,19 @@ export function ssrRenderSlotInner(
|
||||||
end--
|
end--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (start < end) {
|
||||||
for (let i = start; i < end; i++) {
|
for (let i = start; i < end; i++) {
|
||||||
push(slotBuffer[i])
|
push(slotBuffer[i])
|
||||||
}
|
}
|
||||||
|
} else if (transition) {
|
||||||
|
push(`<!---->`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (fallbackRenderFn) {
|
} else if (fallbackRenderFn) {
|
||||||
fallbackRenderFn()
|
fallbackRenderFn()
|
||||||
|
} else if (transition) {
|
||||||
|
push(`<!---->`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1724,6 +1724,107 @@ describe('e2e: Transition', () => {
|
||||||
},
|
},
|
||||||
E2E_TIMEOUT,
|
E2E_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// #13153
|
||||||
|
test(
|
||||||
|
'move kept-alive node before v-show transition leave finishes',
|
||||||
|
async () => {
|
||||||
|
await page().evaluate(() => {
|
||||||
|
const { createApp, ref } = (window as any).Vue
|
||||||
|
const show = ref(true)
|
||||||
|
createApp({
|
||||||
|
template: `
|
||||||
|
<div id="container">
|
||||||
|
<KeepAlive :include="['Comp1', 'Comp2']">
|
||||||
|
<component :is="state === 1 ? 'Comp1' : 'Comp2'"/>
|
||||||
|
</KeepAlive>
|
||||||
|
</div>
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
`,
|
||||||
|
setup: () => {
|
||||||
|
const state = ref(1)
|
||||||
|
const click = () => (state.value = state.value === 1 ? 2 : 1)
|
||||||
|
return { state, click }
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Comp1: {
|
||||||
|
components: {
|
||||||
|
Item: {
|
||||||
|
name: 'Item',
|
||||||
|
setup() {
|
||||||
|
return { show }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Transition name="test">
|
||||||
|
<div v-show="show" >
|
||||||
|
<h2>{{ show ? "I should show" : "I shouldn't show " }}</h2>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'Comp1',
|
||||||
|
setup() {
|
||||||
|
const toggle = () => (show.value = !show.value)
|
||||||
|
return { show, toggle }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Item />
|
||||||
|
<h2>This is page1</h2>
|
||||||
|
<button id="changeShowBtn" @click="toggle">{{ show }}</button>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
Comp2: {
|
||||||
|
name: 'Comp2',
|
||||||
|
template: `<h2>This is page2</h2>`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await html('#container')).toBe(
|
||||||
|
`<div><h2>I should show</h2></div>` +
|
||||||
|
`<h2>This is page1</h2>` +
|
||||||
|
`<button id="changeShowBtn">true</button>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// trigger v-show transition leave
|
||||||
|
await click('#changeShowBtn')
|
||||||
|
await nextTick()
|
||||||
|
expect(await html('#container')).toBe(
|
||||||
|
`<div class="test-leave-from test-leave-active"><h2>I shouldn't show </h2></div>` +
|
||||||
|
`<h2>This is page1</h2>` +
|
||||||
|
`<button id="changeShowBtn">false</button>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// switch to page2, before leave finishes
|
||||||
|
// expect v-show element's display to be none
|
||||||
|
await click('#toggleBtn')
|
||||||
|
await nextTick()
|
||||||
|
expect(await html('#container')).toBe(
|
||||||
|
`<div class="test-leave-from test-leave-active" style="display: none;"><h2>I shouldn't show </h2></div>` +
|
||||||
|
`<h2>This is page2</h2>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// switch back to page1
|
||||||
|
// expect v-show element's display to be none
|
||||||
|
await click('#toggleBtn')
|
||||||
|
await nextTick()
|
||||||
|
expect(await html('#container')).toBe(
|
||||||
|
`<div class="test-enter-from test-enter-active" style="display: none;"><h2>I shouldn't show </h2></div>` +
|
||||||
|
`<h2>This is page1</h2>` +
|
||||||
|
`<button id="changeShowBtn">false</button>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await transitionFinish()
|
||||||
|
expect(await html('#container')).toBe(
|
||||||
|
`<div class="" style="display: none;"><h2>I shouldn't show </h2></div>` +
|
||||||
|
`<h2>This is page1</h2>` +
|
||||||
|
`<button id="changeShowBtn">false</button>`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('transition with Suspense', () => {
|
describe('transition with Suspense', () => {
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
const { page, html, click } = setupPuppeteer()
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await page().setContent(`<div id="app"></div>`)
|
||||||
|
await page().addScriptTag({
|
||||||
|
path: path.resolve(__dirname, '../../dist/vue.global.js'),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('not leaking', async () => {
|
||||||
|
// #13661
|
||||||
|
test(
|
||||||
|
'cached text vnodes should not retaining detached DOM nodes',
|
||||||
|
async () => {
|
||||||
|
const client = await page().createCDPSession()
|
||||||
|
await page().evaluate(async () => {
|
||||||
|
const { createApp, ref } = (window as any).Vue
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
Comp1: {
|
||||||
|
template: `
|
||||||
|
<h1><slot></slot></h1>
|
||||||
|
<div>{{ test.length }}</div>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
const test = ref([...Array(3000)].map((_, i) => ({ i })))
|
||||||
|
// @ts-expect-error
|
||||||
|
window.__REF__ = new WeakRef(test)
|
||||||
|
|
||||||
|
return { test }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Comp2: {
|
||||||
|
template: `<h2>comp2</h2>`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
<Comp1 v-if="toggle">
|
||||||
|
<div>
|
||||||
|
<Comp2/>
|
||||||
|
text node
|
||||||
|
</div>
|
||||||
|
</Comp1>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const click = () => (toggle.value = !toggle.value)
|
||||||
|
return { toggle, click }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="toggleBtn">button</button>` +
|
||||||
|
`<h1>` +
|
||||||
|
`<div>` +
|
||||||
|
`<h2>comp2</h2>` +
|
||||||
|
` text node ` +
|
||||||
|
`</div>` +
|
||||||
|
`</h1>` +
|
||||||
|
`<div>3000</div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await click('#toggleBtn')
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="toggleBtn">button</button><!--v-if-->`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const isCollected = async () =>
|
||||||
|
// @ts-expect-error
|
||||||
|
await page().evaluate(() => window.__REF__.deref() === undefined)
|
||||||
|
|
||||||
|
while ((await isCollected()) === false) {
|
||||||
|
await client.send('HeapProfiler.collectGarbage')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await isCollected()).toBe(true)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
// #13211
|
||||||
|
test(
|
||||||
|
'cached array vnodes should not retaining detached DOM nodes',
|
||||||
|
async () => {
|
||||||
|
const client = await page().createCDPSession()
|
||||||
|
await page().evaluate(async () => {
|
||||||
|
const { createApp, ref } = (window as any).Vue
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
Comp1: {
|
||||||
|
template: `
|
||||||
|
<h1><slot></slot></h1>
|
||||||
|
<div>{{ test.length }}</div>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
const test = ref([...Array(3000)].map((_, i) => ({ i })))
|
||||||
|
// @ts-expect-error
|
||||||
|
window.__REF__ = new WeakRef(test)
|
||||||
|
|
||||||
|
return { test }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
<Comp1 v-if="toggle">slot content</Comp1>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const click = () => (toggle.value = !toggle.value)
|
||||||
|
return { toggle, click }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="toggleBtn">button</button>` +
|
||||||
|
`<h1>` +
|
||||||
|
`slot content` +
|
||||||
|
`</h1>` +
|
||||||
|
`<div>3000</div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await click('#toggleBtn')
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="toggleBtn">button</button><!--v-if-->`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const isCollected = async () =>
|
||||||
|
// @ts-expect-error
|
||||||
|
await page().evaluate(() => window.__REF__.deref() === undefined)
|
||||||
|
|
||||||
|
while ((await isCollected()) === false) {
|
||||||
|
await client.send('HeapProfiler.collectGarbage')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await isCollected()).toBe(true)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/element-plus/element-plus/issues/21408
|
||||||
|
test(
|
||||||
|
'cached text nodes in Fragment should not retaining detached DOM nodes',
|
||||||
|
async () => {
|
||||||
|
const client = await page().createCDPSession()
|
||||||
|
await page().evaluate(async () => {
|
||||||
|
const { createApp, ref } = (window as any).Vue
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
Comp: {
|
||||||
|
template: `<div>{{ test.length }}</div>`,
|
||||||
|
setup() {
|
||||||
|
const test = ref([...Array(3000)].map((_, i) => ({ i })))
|
||||||
|
// @ts-expect-error
|
||||||
|
window.__REF__ = new WeakRef(test)
|
||||||
|
|
||||||
|
return { test }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<button id="addBtn" @click="add">add</button>
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
<div v-if="toggle">
|
||||||
|
<template v-for="item in items" :key="item">
|
||||||
|
text
|
||||||
|
<div>{{ item }}</div>
|
||||||
|
</template>
|
||||||
|
<Comp/>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const items = ref([1])
|
||||||
|
const click = () => (toggle.value = !toggle.value)
|
||||||
|
const add = () => items.value.push(2)
|
||||||
|
return { toggle, click, items, add }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="addBtn">add</button>` +
|
||||||
|
`<button id="toggleBtn">button</button>` +
|
||||||
|
`<div>` +
|
||||||
|
` text ` +
|
||||||
|
`<div>1</div>` +
|
||||||
|
`<div>3000</div></div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await click('#addBtn')
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="addBtn">add</button>` +
|
||||||
|
`<button id="toggleBtn">button</button>` +
|
||||||
|
`<div>` +
|
||||||
|
` text ` +
|
||||||
|
`<div>1</div>` +
|
||||||
|
` text ` +
|
||||||
|
`<div>2</div>` +
|
||||||
|
`<div>3000</div></div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await click('#toggleBtn')
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="addBtn">add</button>` +
|
||||||
|
`<button id="toggleBtn">button</button><!--v-if-->`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const isCollected = async () =>
|
||||||
|
// @ts-expect-error
|
||||||
|
await page().evaluate(() => window.__REF__.deref() === undefined)
|
||||||
|
|
||||||
|
while ((await isCollected()) === false) {
|
||||||
|
await client.send('HeapProfiler.collectGarbage')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await isCollected()).toBe(true)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
|
})
|
1766
pnpm-lock.yaml
1766
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -3,11 +3,11 @@ packages:
|
||||||
- 'packages-private/*'
|
- 'packages-private/*'
|
||||||
|
|
||||||
catalog:
|
catalog:
|
||||||
'@babel/parser': ^7.27.5
|
'@babel/parser': ^7.28.3
|
||||||
'@babel/types': ^7.27.6
|
'@babel/types': ^7.28.2
|
||||||
'estree-walker': ^2.0.2
|
'estree-walker': ^2.0.2
|
||||||
'vite': ^6.1.0
|
'vite': ^6.1.0
|
||||||
'@vitejs/plugin-vue': ^5.2.4
|
'@vitejs/plugin-vue': ^6.0.1
|
||||||
'magic-string': ^0.30.17
|
'magic-string': ^0.30.17
|
||||||
'source-map-js': ^1.2.1
|
'source-map-js': ^1.2.1
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue