mirror of https://github.com/vuejs/core.git
chore: Merge branch 'minor' into vapor
This commit is contained in:
commit
674151c9b9
|
@ -31,4 +31,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||
|
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
run: pnpm install
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
@ -56,7 +56,7 @@ jobs:
|
|||
path: temp/size/base.txt
|
||||
|
||||
- name: Download Previous Size Data
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
with:
|
||||
branch: ${{ steps.pr-base.outputs.content }}
|
||||
workflow: size-data.yml
|
||||
|
|
|
@ -741,17 +741,6 @@ Note that this is a type-only breaking change in a minor release, which adheres
|
|||
|
||||
|
||||
|
||||
## [3.3.13](https://github.com/vuejs/core/compare/v3.3.12...v3.3.13) (2023-12-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** fix v-on with modifiers on inline expression of undefined ([#9866](https://github.com/vuejs/core/issues/9866)) ([bae79dd](https://github.com/vuejs/core/commit/bae79ddf8564a2da4a5365cfeb8d811990f42335)), closes [#9865](https://github.com/vuejs/core/issues/9865)
|
||||
* **runtime-dom:** cache event handlers by key/modifiers ([#9851](https://github.com/vuejs/core/issues/9851)) ([04d2c05](https://github.com/vuejs/core/commit/04d2c05054c26b02fbc1d84839b0ed5cd36455b6)), closes [#9849](https://github.com/vuejs/core/issues/9849)
|
||||
* **types:** extract properties from extended collections ([#9854](https://github.com/vuejs/core/issues/9854)) ([24b1c1d](https://github.com/vuejs/core/commit/24b1c1dd57fd55d998aa231a147500e010b10219)), closes [#9852](https://github.com/vuejs/core/issues/9852)
|
||||
|
||||
|
||||
|
||||
# [3.4.0-beta.3](https://github.com/vuejs/core/compare/v3.3.12...v3.4.0-beta.3) (2023-12-16)
|
||||
|
||||
|
||||
|
@ -764,19 +753,6 @@ Note that this is a type-only breaking change in a minor release, which adheres
|
|||
|
||||
|
||||
|
||||
## [3.3.12](https://github.com/vuejs/core/compare/v3.3.11...v3.3.12) (2023-12-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **hydration:** handle appear transition before patch props ([#9837](https://github.com/vuejs/core/issues/9837)) ([e70f4c4](https://github.com/vuejs/core/commit/e70f4c47c553b6e16d8fad70743271ca23802fe7)), closes [#9832](https://github.com/vuejs/core/issues/9832)
|
||||
* **sfc/cssVars:** fix loss of CSS v-bind variables when setting inline style with string value ([#9824](https://github.com/vuejs/core/issues/9824)) ([0a387df](https://github.com/vuejs/core/commit/0a387dfb1d04afb6eae4296b6da76dfdaca77af4)), closes [#9821](https://github.com/vuejs/core/issues/9821)
|
||||
* **ssr:** fix suspense hydration of fallback content ([#7188](https://github.com/vuejs/core/issues/7188)) ([60415b5](https://github.com/vuejs/core/commit/60415b5d67df55f1fd6b176615299c08640fa142))
|
||||
* **types:** add `xmlns:xlink` to `SVGAttributes` ([#9300](https://github.com/vuejs/core/issues/9300)) ([0d61b42](https://github.com/vuejs/core/commit/0d61b429ecf63591d31e09702058fa4c7132e1a7)), closes [#9299](https://github.com/vuejs/core/issues/9299)
|
||||
* **types:** fix `shallowRef` type error ([#9839](https://github.com/vuejs/core/issues/9839)) ([9a57158](https://github.com/vuejs/core/commit/9a571582b53220270e498d8712ea59312c0bef3a))
|
||||
* **types:** support for generic keyof slots ([#8374](https://github.com/vuejs/core/issues/8374)) ([213eba4](https://github.com/vuejs/core/commit/213eba479ce080efc1053fe636f6be4a4c889b44))
|
||||
|
||||
|
||||
|
||||
# [3.4.0-beta.2](https://github.com/vuejs/core/compare/v3.4.0-beta.1...v3.4.0-beta.2) (2023-12-14)
|
||||
|
||||
|
||||
|
@ -836,22 +812,6 @@ default.
|
|||
|
||||
|
||||
|
||||
## [3.3.11](https://github.com/vuejs/core/compare/v3.3.10...v3.3.11) (2023-12-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **custom-element:** correctly handle number type props in prod ([#8989](https://github.com/vuejs/core/issues/8989)) ([d74d364](https://github.com/vuejs/core/commit/d74d364d62db8e48881af6b5a75ce4fb5f36cc35))
|
||||
* **reactivity:** fix mutation on user proxy of reactive Array ([6ecbd5c](https://github.com/vuejs/core/commit/6ecbd5ce2a7f59314a8326a1d193874b87f4d8c8)), closes [#9742](https://github.com/vuejs/core/issues/9742) [#9751](https://github.com/vuejs/core/issues/9751) [#9750](https://github.com/vuejs/core/issues/9750)
|
||||
* **runtime-dom:** fix width and height prop check condition ([5b00286](https://github.com/vuejs/core/commit/5b002869c533220706f9788b496b8ca8d8e98609)), closes [#9762](https://github.com/vuejs/core/issues/9762)
|
||||
* **shared:** handle Map with symbol keys in toDisplayString ([#9731](https://github.com/vuejs/core/issues/9731)) ([364821d](https://github.com/vuejs/core/commit/364821d6bdb1775e2f55a69bcfb9f40f7acf1506)), closes [#9727](https://github.com/vuejs/core/issues/9727)
|
||||
* **shared:** handle more Symbol cases in toDisplayString ([983d45d](https://github.com/vuejs/core/commit/983d45d4f8eb766b5a16b7ea93b86d3c51618fa6))
|
||||
* **Suspense:** properly get anchor when mount fallback vnode ([#9770](https://github.com/vuejs/core/issues/9770)) ([b700328](https://github.com/vuejs/core/commit/b700328342e17dc16b19316c2e134a26107139d2)), closes [#9769](https://github.com/vuejs/core/issues/9769)
|
||||
* **types:** ref() return type should not be any when initial value is any ([#9768](https://github.com/vuejs/core/issues/9768)) ([cdac121](https://github.com/vuejs/core/commit/cdac12161ec27b45ded48854c3d749664b6d4a6d))
|
||||
* **watch:** should not fire pre watcher on child component unmount ([#7181](https://github.com/vuejs/core/issues/7181)) ([6784f0b](https://github.com/vuejs/core/commit/6784f0b1f8501746ea70d87d18ed63a62cf6b76d)), closes [#7030](https://github.com/vuejs/core/issues/7030)
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.4](https://github.com/vuejs/core/compare/v3.3.10...v3.4.0-alpha.4) (2023-12-04)
|
||||
|
||||
|
||||
|
@ -873,37 +833,6 @@ default.
|
|||
|
||||
|
||||
|
||||
## [3.3.10](https://github.com/vuejs/core/compare/v3.3.9...v3.3.10) (2023-12-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **app:** prevent template from being cached between apps with different options ([#9724](https://github.com/vuejs/core/issues/9724)) ([ec71585](https://github.com/vuejs/core/commit/ec715854ca12520b2afc9e9b3981cbae05ae5206)), closes [#9618](https://github.com/vuejs/core/issues/9618)
|
||||
* **compiler-sfc:** avoid passing forEach index to genMap ([f12db7f](https://github.com/vuejs/core/commit/f12db7fb564a534cef2e5805cc9f54afe5d72fbf))
|
||||
* **compiler-sfc:** deindent pug/jade templates ([6345197](https://github.com/vuejs/core/commit/634519720a21fb5a6871454e1cadad7053a568b8)), closes [#3231](https://github.com/vuejs/core/issues/3231) [#3842](https://github.com/vuejs/core/issues/3842) [#7723](https://github.com/vuejs/core/issues/7723)
|
||||
* **compiler-sfc:** fix :where and :is selector in scoped mode with multiple selectors ([#9735](https://github.com/vuejs/core/issues/9735)) ([c3e2c55](https://github.com/vuejs/core/commit/c3e2c556b532656b50b8ab5cd2d9eabc26622d63)), closes [#9707](https://github.com/vuejs/core/issues/9707)
|
||||
* **compiler-sfc:** generate more treeshaking friendly code ([#9507](https://github.com/vuejs/core/issues/9507)) ([8d74ca0](https://github.com/vuejs/core/commit/8d74ca0e6fa2738ca6854b7e879ff59419f948c7)), closes [#9500](https://github.com/vuejs/core/issues/9500)
|
||||
* **compiler-sfc:** support inferring generic types ([#8511](https://github.com/vuejs/core/issues/8511)) ([eb5e307](https://github.com/vuejs/core/commit/eb5e307c0be62002e62c4c800d0dfacb39b0d4ca)), closes [#8482](https://github.com/vuejs/core/issues/8482)
|
||||
* **compiler-sfc:** support resolving components from props ([#8785](https://github.com/vuejs/core/issues/8785)) ([7cbcee3](https://github.com/vuejs/core/commit/7cbcee3d831241a8bd3588ae92d3f27e3641e25f))
|
||||
* **compiler-sfc:** throw error when failing to load TS during type resolution ([#8883](https://github.com/vuejs/core/issues/8883)) ([4936d2e](https://github.com/vuejs/core/commit/4936d2e11a8d0ca3704bfe408548cb26bb3fd5e9))
|
||||
* **cssVars:** cssVar names should be double-escaped when generating code for ssr ([#8824](https://github.com/vuejs/core/issues/8824)) ([5199a12](https://github.com/vuejs/core/commit/5199a12f8855cd06f24bf355708b5a2134f63176)), closes [#7823](https://github.com/vuejs/core/issues/7823)
|
||||
* **deps:** update compiler to ^7.23.4 ([#9681](https://github.com/vuejs/core/issues/9681)) ([31f6ebc](https://github.com/vuejs/core/commit/31f6ebc4df84490ed29fb75e7bf4259200eb51f0))
|
||||
* **runtime-core:** Suspense get anchor properly in Transition ([#9309](https://github.com/vuejs/core/issues/9309)) ([65f3fe2](https://github.com/vuejs/core/commit/65f3fe273127a8b68e1222fbb306d28d85f01757)), closes [#8105](https://github.com/vuejs/core/issues/8105)
|
||||
* **runtime-dom:** set width/height with units as attribute ([#8781](https://github.com/vuejs/core/issues/8781)) ([bfc1838](https://github.com/vuejs/core/commit/bfc1838f31199de3f189198a3c234fa7bae91386))
|
||||
* **ssr:** avoid computed being accidentally cached before server render ([#9688](https://github.com/vuejs/core/issues/9688)) ([30d5d93](https://github.com/vuejs/core/commit/30d5d93a92b2154406ec04f8aca6b217fa01177c)), closes [#5300](https://github.com/vuejs/core/issues/5300)
|
||||
* **types:** expose emits as props in functional components ([#9234](https://github.com/vuejs/core/issues/9234)) ([887e54c](https://github.com/vuejs/core/commit/887e54c347ea9eac4c721b5e2288f054873d1d30))
|
||||
* **types:** fix reactive collection types ([#8960](https://github.com/vuejs/core/issues/8960)) ([ad27473](https://github.com/vuejs/core/commit/ad274737015c36906d76f3189203093fa3a2e4e7)), closes [#8904](https://github.com/vuejs/core/issues/8904)
|
||||
* **types:** improve return type withKeys and withModifiers ([#9734](https://github.com/vuejs/core/issues/9734)) ([43c3cfd](https://github.com/vuejs/core/commit/43c3cfdec5ae5d70fa2a21e857abc2d73f1a0d07))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* optimize on* prop check ([38aaa8c](https://github.com/vuejs/core/commit/38aaa8c88648c54fe2616ad9c0961288092fcb44))
|
||||
* **runtime-dom:** cache modifier wrapper functions ([da4a4fb](https://github.com/vuejs/core/commit/da4a4fb5e8eee3c6d31f24ebd79a9d0feca56cb2)), closes [#8882](https://github.com/vuejs/core/issues/8882)
|
||||
* **v-on:** constant handlers with modifiers should not be treated as dynamic ([4d94ebf](https://github.com/vuejs/core/commit/4d94ebfe75174b340d2b794e699cad1add3600a9))
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
|
||||
|
||||
|
||||
|
@ -960,55 +889,6 @@ default.
|
|||
|
||||
|
||||
|
||||
## [3.3.9](https://github.com/vuejs/core/compare/v3.3.8...v3.3.9) (2023-11-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** avoid rewriting scope variables in inline for loops ([#7245](https://github.com/vuejs/core/issues/7245)) ([a2d810e](https://github.com/vuejs/core/commit/a2d810eb40cef631f61991ca68b426ee9546aba0)), closes [#7238](https://github.com/vuejs/core/issues/7238)
|
||||
* **compiler-core:** fix `resolveParserPlugins` decorators check ([#9566](https://github.com/vuejs/core/issues/9566)) ([9d0eba9](https://github.com/vuejs/core/commit/9d0eba916f3bf6fb5c03222400edae1a2db7444f)), closes [#9560](https://github.com/vuejs/core/issues/9560)
|
||||
* **compiler-sfc:** consistently escape type-only prop names ([#8654](https://github.com/vuejs/core/issues/8654)) ([3e08d24](https://github.com/vuejs/core/commit/3e08d246dfd8523c54fb8e7a4a6fd5506ffb1bcc)), closes [#8635](https://github.com/vuejs/core/issues/8635) [#8910](https://github.com/vuejs/core/issues/8910) [vitejs/vite-plugin-vue#184](https://github.com/vitejs/vite-plugin-vue/issues/184)
|
||||
* **compiler-sfc:** malformed filename on windows using path.posix.join() ([#9478](https://github.com/vuejs/core/issues/9478)) ([f18a174](https://github.com/vuejs/core/commit/f18a174979626b3429db93c5d5b7ae5448917c70)), closes [#8671](https://github.com/vuejs/core/issues/8671) [#9583](https://github.com/vuejs/core/issues/9583) [#9446](https://github.com/vuejs/core/issues/9446) [#9473](https://github.com/vuejs/core/issues/9473)
|
||||
* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([3227e50](https://github.com/vuejs/core/commit/3227e50b32105f8893f7dff2f29278c5b3a9f621))
|
||||
* **compiler-sfc:** support resolve extends interface for defineEmits ([#8470](https://github.com/vuejs/core/issues/8470)) ([9e1b74b](https://github.com/vuejs/core/commit/9e1b74bcd5fa4151f5d1bc02c69fbbfa4762f577)), closes [#8465](https://github.com/vuejs/core/issues/8465)
|
||||
* **hmr/transition:** fix kept-alive component inside transition disappearing after hmr ([#7126](https://github.com/vuejs/core/issues/7126)) ([d11e978](https://github.com/vuejs/core/commit/d11e978fc98dcc83526c167e603b8308f317f786)), closes [#7121](https://github.com/vuejs/core/issues/7121)
|
||||
* **hydration:** force hydration for v-bind with .prop modifier ([364f319](https://github.com/vuejs/core/commit/364f319d214226770d97c98d8fcada80c9e8dde3)), closes [#7490](https://github.com/vuejs/core/issues/7490)
|
||||
* **hydration:** properly hydrate indeterminate prop ([34b5a5d](https://github.com/vuejs/core/commit/34b5a5da4ae9c9faccac237acd7acc8e7e017571)), closes [#7476](https://github.com/vuejs/core/issues/7476)
|
||||
* **reactivity:** clear method on readonly collections should return undefined ([#7316](https://github.com/vuejs/core/issues/7316)) ([657476d](https://github.com/vuejs/core/commit/657476dcdb964be4fbb1277c215c073f3275728e))
|
||||
* **reactivity:** onCleanup also needs to be cleaned ([#8655](https://github.com/vuejs/core/issues/8655)) ([73fd810](https://github.com/vuejs/core/commit/73fd810eebdd383a2b4629f67736c4db1f428abd)), closes [#5151](https://github.com/vuejs/core/issues/5151) [#7695](https://github.com/vuejs/core/issues/7695)
|
||||
* **ssr:** hydration `__vnode` missing for devtools ([#9328](https://github.com/vuejs/core/issues/9328)) ([5156ac5](https://github.com/vuejs/core/commit/5156ac5b38cfa80d3db26f2c9bf40cb22a7521cb))
|
||||
* **types:** allow falsy value types in `StyleValue` ([#7954](https://github.com/vuejs/core/issues/7954)) ([17aa92b](https://github.com/vuejs/core/commit/17aa92b79b31d8bb8b5873ddc599420cb9806db8)), closes [#7955](https://github.com/vuejs/core/issues/7955)
|
||||
* **types:** defineCustomElement using defineComponent return type with emits ([#7937](https://github.com/vuejs/core/issues/7937)) ([5d932a8](https://github.com/vuejs/core/commit/5d932a8e6d14343c9d7fc7c2ecb58ac618b2f938)), closes [#7782](https://github.com/vuejs/core/issues/7782)
|
||||
* **types:** fix `unref` and `toValue` when input union type contains ComputedRef ([#8748](https://github.com/vuejs/core/issues/8748)) ([176d476](https://github.com/vuejs/core/commit/176d47671271b1abc21b1508e9a493c7efca6451)), closes [#8747](https://github.com/vuejs/core/issues/8747) [#8857](https://github.com/vuejs/core/issues/8857)
|
||||
* **types:** fix instance type when props type is incompatible with setup returned type ([#7338](https://github.com/vuejs/core/issues/7338)) ([0e1e8f9](https://github.com/vuejs/core/commit/0e1e8f919e5a74cdaadf9c80ee135088b25e7fa3)), closes [#5885](https://github.com/vuejs/core/issues/5885)
|
||||
* **types:** fix shallowRef return type with union value type ([#7853](https://github.com/vuejs/core/issues/7853)) ([7c44800](https://github.com/vuejs/core/commit/7c448000b0def910c2cfabfdf7ff20a3d6bc844f)), closes [#7852](https://github.com/vuejs/core/issues/7852)
|
||||
* **types:** more precise types for class bindings ([#8012](https://github.com/vuejs/core/issues/8012)) ([46e3374](https://github.com/vuejs/core/commit/46e33744c890bd49482c5e5c5cdea44e00ec84d5))
|
||||
* **types:** remove optional properties from defineProps return type ([#6421](https://github.com/vuejs/core/issues/6421)) ([94c049d](https://github.com/vuejs/core/commit/94c049d930d922069e38ea8700d7ff0970f71e61)), closes [#6420](https://github.com/vuejs/core/issues/6420)
|
||||
* **types:** return type of withDefaults should be readonly ([#8601](https://github.com/vuejs/core/issues/8601)) ([f15debc](https://github.com/vuejs/core/commit/f15debc01acb22d23f5acee97e6f02db88cef11a))
|
||||
* **types:** revert class type restrictions ([5d077c8](https://github.com/vuejs/core/commit/5d077c8754cc14f85d2d6d386df70cf8c0d93842)), closes [#8012](https://github.com/vuejs/core/issues/8012)
|
||||
* **types:** update jsx type definitions ([#8607](https://github.com/vuejs/core/issues/8607)) ([58e2a94](https://github.com/vuejs/core/commit/58e2a94871ae06a909c5f8bad07fb401193e6a38))
|
||||
* **types:** widen ClassValue type ([2424013](https://github.com/vuejs/core/commit/242401305944422d0c361b16101a4d18908927af))
|
||||
* **v-model:** avoid overwriting number input with same value ([#7004](https://github.com/vuejs/core/issues/7004)) ([40f4b77](https://github.com/vuejs/core/commit/40f4b77bb570868cb6e47791078767797e465989)), closes [#7003](https://github.com/vuejs/core/issues/7003)
|
||||
* **v-model:** unnecessary value binding error should apply to dynamic instead of static binding ([2859b65](https://github.com/vuejs/core/commit/2859b653c9a22460e60233cac10fe139e359b046)), closes [#3596](https://github.com/vuejs/core/issues/3596)
|
||||
|
||||
|
||||
|
||||
## [3.3.8](https://github.com/vuejs/core/compare/v3.3.7...v3.3.8) (2023-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compile-sfc:** support `Error` type in `defineProps` ([#5955](https://github.com/vuejs/core/issues/5955)) ([a989345](https://github.com/vuejs/core/commit/a9893458ec519aae442e1b99e64e6d74685cd22c))
|
||||
* **compiler-core:** known global should be shadowed by local variables in expression rewrite ([#9492](https://github.com/vuejs/core/issues/9492)) ([a75d1c5](https://github.com/vuejs/core/commit/a75d1c5c6242e91a73cc5ba01e6da620dea0b3d9)), closes [#9482](https://github.com/vuejs/core/issues/9482)
|
||||
* **compiler-sfc:** fix dynamic directive arguments usage check for slots ([#9495](https://github.com/vuejs/core/issues/9495)) ([b39fa1f](https://github.com/vuejs/core/commit/b39fa1f8157647859331ce439c42ae016a49b415)), closes [#9493](https://github.com/vuejs/core/issues/9493)
|
||||
* **deps:** update dependency @vue/repl to ^2.6.2 ([#9536](https://github.com/vuejs/core/issues/9536)) ([5cef325](https://github.com/vuejs/core/commit/5cef325f41e3b38657c72fa1a38dedeee1c7a60a))
|
||||
* **deps:** update dependency @vue/repl to ^2.6.3 ([#9540](https://github.com/vuejs/core/issues/9540)) ([176d590](https://github.com/vuejs/core/commit/176d59058c9aecffe9da4d4311e98496684f06d4))
|
||||
* **hydration:** fix tagName access error on comment/text node hydration mismatch ([dd8a0cf](https://github.com/vuejs/core/commit/dd8a0cf5dcde13d2cbd899262a0e07f16e14e489)), closes [#9531](https://github.com/vuejs/core/issues/9531)
|
||||
* **types:** avoid exposing lru-cache types in generated dts ([462aeb3](https://github.com/vuejs/core/commit/462aeb3b600765e219ded2ee9a0ed1e74df61de0)), closes [#9521](https://github.com/vuejs/core/issues/9521)
|
||||
* **warn:** avoid warning on empty children with Suspense ([#3962](https://github.com/vuejs/core/issues/3962)) ([405f345](https://github.com/vuejs/core/commit/405f34587a63a5f1e3d147b9848219ea98acc22d))
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
||||
|
||||
|
||||
|
|
40
package.json
40
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.13",
|
||||
"packageManager": "pnpm@9.12.3",
|
||||
"packageManager": "pnpm@9.15.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
@ -64,40 +64,40 @@
|
|||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.2",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.9.1",
|
||||
"@swc/core": "^1.10.8",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^22.8.7",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitest/coverage-v8": "^2.1.1",
|
||||
"@vitest/ui": "^2.1.1",
|
||||
"@vitest/coverage-v8": "^3.0.2",
|
||||
"@vitest/ui": "^3.0.2",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^5.0.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.24.0",
|
||||
"esbuild": "^0.24.2",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-import-x": "^4.4.0",
|
||||
"@vitest/eslint-plugin": "^1.0.1",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-import-x": "^4.6.1",
|
||||
"@vitest/eslint-plugin": "^1.1.25",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^25.0.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"jsdom": "^26.0.0",
|
||||
"lint-staged": "^15.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.12",
|
||||
"magic-string": "^0.30.17",
|
||||
"markdown-table": "^3.0.4",
|
||||
"marked": "13.0.3",
|
||||
"npm-run-all2": "^7.0.1",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"picocolors": "^1.1.1",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "^3.4.2",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~23.3.0",
|
||||
"puppeteer": "~24.1.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.25.0",
|
||||
"rollup": "^4.31.0",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
|
@ -108,9 +108,9 @@
|
|||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.12.2",
|
||||
"typescript-eslint": "^8.20.0",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^2.1.1"
|
||||
"vitest": "^3.0.2"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"vite": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.4.2",
|
||||
"@vue/repl": "^4.4.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"vue": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.5",
|
||||
"vite": "^5.4.10"
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vue/compiler-vapor": "workspace:^",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { camelize } from '@vue/shared'
|
|||
import { CAMELIZE } from '../runtimeHelpers'
|
||||
import { processExpression } from './transformExpression'
|
||||
|
||||
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
|
||||
// v-bind without arg is handled directly in ./transformElement.ts due to its affecting
|
||||
// codegen for the entire props object. This transform here is only for v-bind
|
||||
// *with* args.
|
||||
export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||
|
|
|
@ -17,7 +17,7 @@ import { hasScopeRef, isFnExpression, isMemberExpression } from '../utils'
|
|||
import { TO_HANDLER_KEY } from '../runtimeHelpers'
|
||||
|
||||
export interface VOnDirectiveNode extends DirectiveNode {
|
||||
// v-on without arg is handled directly in ./transformElements.ts due to it affecting
|
||||
// v-on without arg is handled directly in ./transformElement.ts due to its affecting
|
||||
// codegen for the entire props object. This transform here is only for v-on
|
||||
// *with* args.
|
||||
arg: ExpressionNode
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "catalog:",
|
||||
"magic-string": "catalog:",
|
||||
"postcss": "^8.4.48",
|
||||
"postcss": "^8.5.1",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -59,10 +59,10 @@
|
|||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "~9.0.5",
|
||||
"postcss-modules": "^6.0.0",
|
||||
"minimatch": "~10.0.1",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"postcss-selector-parser": "^7.0.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.80.6"
|
||||
"sass": "^1.83.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,8 +176,6 @@ export function compileScript(
|
|||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||
const vapor = sfc.vapor || options.vapor
|
||||
|
||||
let refBindings: string[] | undefined
|
||||
|
||||
if (!scriptSetup) {
|
||||
if (!script) {
|
||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||
|
@ -746,12 +744,6 @@ export function compileScript(
|
|||
for (const key in setupBindings) {
|
||||
ctx.bindingMetadata[key] = setupBindings[key]
|
||||
}
|
||||
// known ref bindings
|
||||
if (refBindings) {
|
||||
for (const key of refBindings) {
|
||||
ctx.bindingMetadata[key] = BindingTypes.SETUP_REF
|
||||
}
|
||||
}
|
||||
|
||||
// 7. inject `useCssVars` calls
|
||||
if (
|
||||
|
|
|
@ -25,8 +25,9 @@ import {
|
|||
toRaw,
|
||||
triggerRef,
|
||||
} from '../src'
|
||||
import { EffectFlags, pauseTracking, resetTracking } from '../src/effect'
|
||||
import type { ComputedRef, ComputedRefImpl } from '../src/computed'
|
||||
import { pauseTracking, resetTracking } from '../src/effect'
|
||||
import { SubscriberFlags } from '../src/system'
|
||||
|
||||
describe('reactivity/computed', () => {
|
||||
it('should return updated value', () => {
|
||||
|
@ -409,9 +410,9 @@ describe('reactivity/computed', () => {
|
|||
a.value++
|
||||
e.value
|
||||
|
||||
expect(e.deps!.dep).toBe(b.dep)
|
||||
expect(e.deps!.nextDep!.dep).toBe(d.dep)
|
||||
expect(e.deps!.nextDep!.nextDep!.dep).toBe(c.dep)
|
||||
expect(e.deps!.dep).toBe(b)
|
||||
expect(e.deps!.nextDep!.dep).toBe(d)
|
||||
expect(e.deps!.nextDep!.nextDep!.dep).toBe(c)
|
||||
expect(cSpy).toHaveBeenCalledTimes(2)
|
||||
|
||||
a.value++
|
||||
|
@ -466,8 +467,12 @@ describe('reactivity/computed', () => {
|
|||
const c2 = computed(() => c1.value) as unknown as ComputedRefImpl
|
||||
|
||||
c2.value
|
||||
expect(c1.flags & EffectFlags.DIRTY).toBeFalsy()
|
||||
expect(c2.flags & EffectFlags.DIRTY).toBeFalsy()
|
||||
expect(
|
||||
c1.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed),
|
||||
).toBe(0)
|
||||
expect(
|
||||
c2.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed),
|
||||
).toBe(0)
|
||||
})
|
||||
|
||||
it('should chained computeds dirtyLevel update with first computed effect', () => {
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
import {
|
||||
computed,
|
||||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
ref,
|
||||
render,
|
||||
serializeInner,
|
||||
} from '@vue/runtime-test'
|
||||
import { ITERATE_KEY, getDepFromReactive } from '../src/dep'
|
||||
import { onEffectCleanup, pauseTracking, resetTracking } from '../src/effect'
|
||||
import {
|
||||
type DebuggerEvent,
|
||||
type ReactiveEffectRunner,
|
||||
|
@ -11,23 +22,7 @@ import {
|
|||
stop,
|
||||
toRaw,
|
||||
} from '../src/index'
|
||||
import { type Dep, ITERATE_KEY, getDepFromReactive } from '../src/dep'
|
||||
import {
|
||||
computed,
|
||||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
ref,
|
||||
render,
|
||||
serializeInner,
|
||||
} from '@vue/runtime-test'
|
||||
import {
|
||||
endBatch,
|
||||
onEffectCleanup,
|
||||
pauseTracking,
|
||||
resetTracking,
|
||||
startBatch,
|
||||
} from '../src/effect'
|
||||
import { type Dependency, endBatch, startBatch } from '../src/system'
|
||||
|
||||
describe('reactivity/effect', () => {
|
||||
it('should run the passed function once (wrapped by a effect)', () => {
|
||||
|
@ -1183,12 +1178,12 @@ describe('reactivity/effect', () => {
|
|||
})
|
||||
|
||||
describe('dep unsubscribe', () => {
|
||||
function getSubCount(dep: Dep | undefined) {
|
||||
function getSubCount(dep: Dependency | undefined) {
|
||||
let count = 0
|
||||
let sub = dep!.subs
|
||||
while (sub) {
|
||||
count++
|
||||
sub = sub.prevSub
|
||||
sub = sub.nextSub
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
type ComputedRef,
|
||||
computed,
|
||||
effect,
|
||||
effectScope,
|
||||
reactive,
|
||||
shallowRef as ref,
|
||||
toRaw,
|
||||
|
@ -19,7 +20,7 @@ describe.skipIf(!global.gc)('reactivity/gc', () => {
|
|||
}
|
||||
|
||||
// #9233
|
||||
it('should release computed cache', async () => {
|
||||
it.todo('should release computed cache', async () => {
|
||||
const src = ref<{} | undefined>({})
|
||||
// @ts-expect-error ES2021 API
|
||||
const srcRef = new WeakRef(src.value!)
|
||||
|
@ -34,7 +35,7 @@ describe.skipIf(!global.gc)('reactivity/gc', () => {
|
|||
expect(srcRef.deref()).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should release reactive property dep', async () => {
|
||||
it.todo('should release reactive property dep', async () => {
|
||||
const src = reactive({ foo: 1 })
|
||||
|
||||
let c: ComputedRef | undefined = computed(() => src.foo)
|
||||
|
@ -79,4 +80,36 @@ describe.skipIf(!global.gc)('reactivity/gc', () => {
|
|||
src.foo++
|
||||
expect(spy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should release computed that untrack by effect', async () => {
|
||||
const src = ref(0)
|
||||
// @ts-expect-error ES2021 API
|
||||
const c = new WeakRef(computed(() => src.value))
|
||||
const scope = effectScope()
|
||||
|
||||
scope.run(() => {
|
||||
effect(() => c.deref().value)
|
||||
})
|
||||
|
||||
expect(c.deref()).toBeDefined()
|
||||
scope.stop()
|
||||
await gc()
|
||||
expect(c.deref()).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should release computed that untrack by effectScope', async () => {
|
||||
const src = ref(0)
|
||||
// @ts-expect-error ES2021 API
|
||||
const c = new WeakRef(computed(() => src.value))
|
||||
const scope = effectScope()
|
||||
|
||||
scope.run(() => {
|
||||
c.deref().value
|
||||
})
|
||||
|
||||
expect(c.deref()).toBeDefined()
|
||||
scope.stop()
|
||||
await gc()
|
||||
expect(c.deref()).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { TrackOpTypes } from './constants'
|
||||
import { endBatch, pauseTracking, resetTracking, startBatch } from './effect'
|
||||
import { isProxy, isShallow, toRaw, toReactive } from './reactive'
|
||||
import { ARRAY_ITERATE_KEY, track } from './dep'
|
||||
import { isArray } from '@vue/shared'
|
||||
import { TrackOpTypes } from './constants'
|
||||
import { ARRAY_ITERATE_KEY, track } from './dep'
|
||||
import { pauseTracking, resetTracking } from './effect'
|
||||
import { isProxy, isShallow, toRaw, toReactive } from './reactive'
|
||||
import { endBatch, startBatch } from './system'
|
||||
|
||||
/**
|
||||
* Track array iteration and return:
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
import { isFunction } from '@vue/shared'
|
||||
import { hasChanged, isFunction } from '@vue/shared'
|
||||
import { ReactiveFlags, TrackOpTypes } from './constants'
|
||||
import { onTrack, setupOnTrigger } from './debug'
|
||||
import {
|
||||
type DebuggerEvent,
|
||||
type DebuggerOptions,
|
||||
EffectFlags,
|
||||
type Subscriber,
|
||||
activeSub,
|
||||
batch,
|
||||
refreshComputed,
|
||||
setActiveSub,
|
||||
} from './effect'
|
||||
import { activeEffectScope } from './effectScope'
|
||||
import type { Ref } from './ref'
|
||||
import {
|
||||
type Dependency,
|
||||
type Link,
|
||||
type Subscriber,
|
||||
SubscriberFlags,
|
||||
endTracking,
|
||||
link,
|
||||
processComputedUpdate,
|
||||
startTracking,
|
||||
updateDirtyFlag,
|
||||
} from './system'
|
||||
import { warn } from './warning'
|
||||
import { Dep, type Link, globalVersion } from './dep'
|
||||
import { ReactiveFlags, TrackOpTypes } from './constants'
|
||||
|
||||
declare const ComputedRefSymbol: unique symbol
|
||||
declare const WritableComputedRefSymbol: unique symbol
|
||||
|
@ -44,15 +53,21 @@ export interface WritableComputedOptions<T, S = T> {
|
|||
* @private exported by @vue/reactivity for Vue core use, but not exported from
|
||||
* the main vue package
|
||||
*/
|
||||
export class ComputedRefImpl<T = any> implements Subscriber {
|
||||
export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_value: any = undefined
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly dep: Dep = new Dep(this)
|
||||
_value: T | undefined = undefined
|
||||
|
||||
// Dependency
|
||||
subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
|
||||
// Subscriber
|
||||
deps: Link | undefined = undefined
|
||||
depsTail: Link | undefined = undefined
|
||||
flags: SubscriberFlags = SubscriberFlags.Computed | SubscriberFlags.Dirty
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -63,34 +78,35 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
|||
*/
|
||||
readonly __v_isReadonly: boolean
|
||||
// TODO isolatedDeclarations ReactiveFlags.IS_READONLY
|
||||
// A computed is also a subscriber that tracks other deps
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
deps?: Link = undefined
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
depsTail?: Link = undefined
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
flags: EffectFlags = EffectFlags.DIRTY
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
globalVersion: number = globalVersion - 1
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
isSSR: boolean
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
next?: Subscriber = undefined
|
||||
|
||||
// for backwards compat
|
||||
effect: this = this
|
||||
get effect(): this {
|
||||
return this
|
||||
}
|
||||
// for backwards compat
|
||||
get dep(): Dependency {
|
||||
return this
|
||||
}
|
||||
// for backwards compat
|
||||
get _dirty(): boolean {
|
||||
const flags = this.flags
|
||||
if (
|
||||
flags & SubscriberFlags.Dirty ||
|
||||
(flags & SubscriberFlags.PendingComputed &&
|
||||
updateDirtyFlag(this, this.flags))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
set _dirty(v: boolean) {
|
||||
if (v) {
|
||||
this.flags |= SubscriberFlags.Dirty
|
||||
} else {
|
||||
this.flags &= ~(SubscriberFlags.Dirty | SubscriberFlags.PendingComputed)
|
||||
}
|
||||
}
|
||||
|
||||
// dev only
|
||||
onTrack?: (event: DebuggerEvent) => void
|
||||
// dev only
|
||||
|
@ -105,43 +121,28 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
|||
constructor(
|
||||
public fn: ComputedGetter<T>,
|
||||
private readonly setter: ComputedSetter<T> | undefined,
|
||||
isSSR: boolean,
|
||||
) {
|
||||
this[ReactiveFlags.IS_READONLY] = !setter
|
||||
this.isSSR = isSSR
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
notify(): true | void {
|
||||
this.flags |= EffectFlags.DIRTY
|
||||
if (
|
||||
!(this.flags & EffectFlags.NOTIFIED) &&
|
||||
// avoid infinite self recursion
|
||||
activeSub !== this
|
||||
) {
|
||||
batch(this, true)
|
||||
return true
|
||||
} else if (__DEV__) {
|
||||
// TODO warn
|
||||
}
|
||||
}
|
||||
|
||||
get value(): T {
|
||||
const link = __DEV__
|
||||
? this.dep.track({
|
||||
const flags = this.flags
|
||||
if (flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed)) {
|
||||
processComputedUpdate(this, flags)
|
||||
}
|
||||
if (activeSub !== undefined) {
|
||||
if (__DEV__) {
|
||||
onTrack(activeSub!, {
|
||||
target: this,
|
||||
type: TrackOpTypes.GET,
|
||||
key: 'value',
|
||||
})
|
||||
: this.dep.track()
|
||||
refreshComputed(this)
|
||||
// sync version after evaluation
|
||||
if (link) {
|
||||
link.version = this.dep.version
|
||||
}
|
||||
return this._value
|
||||
link(this, activeSub)
|
||||
} else if (activeEffectScope !== undefined) {
|
||||
link(this, activeEffectScope)
|
||||
}
|
||||
return this._value!
|
||||
}
|
||||
|
||||
set value(newValue) {
|
||||
|
@ -151,6 +152,28 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
|||
warn('Write operation failed: computed value is readonly')
|
||||
}
|
||||
}
|
||||
|
||||
update(): boolean {
|
||||
const prevSub = activeSub
|
||||
setActiveSub(this)
|
||||
startTracking(this)
|
||||
try {
|
||||
const oldValue = this._value
|
||||
const newValue = this.fn(oldValue)
|
||||
if (hasChanged(oldValue, newValue)) {
|
||||
this._value = newValue
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} finally {
|
||||
setActiveSub(prevSub)
|
||||
endTracking(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
setupOnTrigger(ComputedRefImpl)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,7 +232,7 @@ export function computed<T>(
|
|||
setter = getterOrOptions.set
|
||||
}
|
||||
|
||||
const cRef = new ComputedRefImpl(getter, setter, isSSR)
|
||||
const cRef = new ComputedRefImpl(getter, setter)
|
||||
|
||||
if (__DEV__ && debugOptions && !isSSR) {
|
||||
cRef.onTrack = debugOptions.onTrack
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import { extend } from '@vue/shared'
|
||||
import type { DebuggerEventExtraInfo, ReactiveEffectOptions } from './effect'
|
||||
import { type Link, type Subscriber, SubscriberFlags } from './system'
|
||||
|
||||
export const triggerEventInfos: DebuggerEventExtraInfo[] = []
|
||||
|
||||
export function onTrack(
|
||||
sub: Link['sub'],
|
||||
debugInfo: DebuggerEventExtraInfo,
|
||||
): void {
|
||||
if (!__DEV__) {
|
||||
throw new Error(
|
||||
`Internal error: onTrack should be called only in development.`,
|
||||
)
|
||||
}
|
||||
if ((sub as ReactiveEffectOptions).onTrack) {
|
||||
;(sub as ReactiveEffectOptions).onTrack!(
|
||||
extend(
|
||||
{
|
||||
effect: sub,
|
||||
},
|
||||
debugInfo,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function onTrigger(sub: Link['sub']): void {
|
||||
if (!__DEV__) {
|
||||
throw new Error(
|
||||
`Internal error: onTrigger should be called only in development.`,
|
||||
)
|
||||
}
|
||||
if ((sub as ReactiveEffectOptions).onTrigger) {
|
||||
const debugInfo = triggerEventInfos[triggerEventInfos.length - 1]
|
||||
;(sub as ReactiveEffectOptions).onTrigger!(
|
||||
extend(
|
||||
{
|
||||
effect: sub,
|
||||
},
|
||||
debugInfo,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function setupOnTrigger(target: { new (...args: any[]): any }): void {
|
||||
if (!__DEV__) {
|
||||
throw new Error(
|
||||
`Internal error: setupOnTrigger should be called only in development.`,
|
||||
)
|
||||
}
|
||||
Object.defineProperty(target.prototype, 'onTrigger', {
|
||||
get() {
|
||||
return this._onTrigger
|
||||
},
|
||||
set(val) {
|
||||
if (!this._onTrigger) setupFlagsHandler(this)
|
||||
this._onTrigger = val
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function setupFlagsHandler(target: Subscriber): void {
|
||||
;(target as any)._flags = target.flags
|
||||
Object.defineProperty(target, 'flags', {
|
||||
get() {
|
||||
return (target as any)._flags
|
||||
},
|
||||
set(value) {
|
||||
if (
|
||||
!(
|
||||
(target as any)._flags &
|
||||
(SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)
|
||||
) &&
|
||||
!!(value & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty))
|
||||
) {
|
||||
onTrigger(this)
|
||||
}
|
||||
;(target as any)._flags = value
|
||||
},
|
||||
})
|
||||
}
|
|
@ -1,227 +1,34 @@
|
|||
import { extend, isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
|
||||
import type { ComputedRefImpl } from './computed'
|
||||
import { isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
|
||||
import { type TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { onTrack, triggerEventInfos } from './debug'
|
||||
import { activeSub } from './effect'
|
||||
import {
|
||||
type DebuggerEventExtraInfo,
|
||||
EffectFlags,
|
||||
type Subscriber,
|
||||
activeSub,
|
||||
type Dependency,
|
||||
type Link,
|
||||
endBatch,
|
||||
shouldTrack,
|
||||
link,
|
||||
propagate,
|
||||
startBatch,
|
||||
} from './effect'
|
||||
} from './system'
|
||||
|
||||
/**
|
||||
* Incremented every time a reactive change happens
|
||||
* This is used to give computed a fast path to avoid re-compute when nothing
|
||||
* has changed.
|
||||
*/
|
||||
export let globalVersion = 0
|
||||
|
||||
/**
|
||||
* Represents a link between a source (Dep) and a subscriber (Effect or Computed).
|
||||
* Deps and subs have a many-to-many relationship - each link between a
|
||||
* dep and a sub is represented by a Link instance.
|
||||
*
|
||||
* A Link is also a node in two doubly-linked lists - one for the associated
|
||||
* sub to track all its deps, and one for the associated dep to track all its
|
||||
* subs.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class Link {
|
||||
/**
|
||||
* - Before each effect run, all previous dep links' version are reset to -1
|
||||
* - During the run, a link's version is synced with the source dep on access
|
||||
* - After the run, links with version -1 (that were never used) are cleaned
|
||||
* up
|
||||
*/
|
||||
version: number
|
||||
|
||||
/**
|
||||
* Pointers for doubly-linked lists
|
||||
*/
|
||||
nextDep?: Link
|
||||
prevDep?: Link
|
||||
nextSub?: Link
|
||||
prevSub?: Link
|
||||
prevActiveLink?: Link
|
||||
class Dep implements Dependency {
|
||||
_subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
|
||||
constructor(
|
||||
public sub: Subscriber,
|
||||
public dep: Dep,
|
||||
) {
|
||||
this.version = dep.version
|
||||
this.nextDep =
|
||||
this.prevDep =
|
||||
this.nextSub =
|
||||
this.prevSub =
|
||||
this.prevActiveLink =
|
||||
undefined
|
||||
}
|
||||
}
|
||||
private map: KeyToDepMap,
|
||||
private key: unknown,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class Dep {
|
||||
version = 0
|
||||
/**
|
||||
* Link between this dep and the current active effect
|
||||
*/
|
||||
activeLink?: Link = undefined
|
||||
|
||||
/**
|
||||
* Doubly linked list representing the subscribing effects (tail)
|
||||
*/
|
||||
subs?: Link = undefined
|
||||
|
||||
/**
|
||||
* Doubly linked list representing the subscribing effects (head)
|
||||
* DEV only, for invoking onTrigger hooks in correct order
|
||||
*/
|
||||
subsHead?: Link
|
||||
|
||||
/**
|
||||
* For object property deps cleanup
|
||||
*/
|
||||
map?: KeyToDepMap = undefined
|
||||
key?: unknown = undefined
|
||||
|
||||
/**
|
||||
* Subscriber counter
|
||||
*/
|
||||
sc: number = 0
|
||||
|
||||
constructor(public computed?: ComputedRefImpl | undefined) {
|
||||
if (__DEV__) {
|
||||
this.subsHead = undefined
|
||||
}
|
||||
get subs(): Link | undefined {
|
||||
return this._subs
|
||||
}
|
||||
|
||||
track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
|
||||
if (!activeSub || !shouldTrack || activeSub === this.computed) {
|
||||
return
|
||||
set subs(value: Link | undefined) {
|
||||
this._subs = value
|
||||
if (value === undefined) {
|
||||
this.map.delete(this.key)
|
||||
}
|
||||
|
||||
let link = this.activeLink
|
||||
if (link === undefined || link.sub !== activeSub) {
|
||||
link = this.activeLink = new Link(activeSub, this)
|
||||
|
||||
// add the link to the activeEffect as a dep (as tail)
|
||||
if (!activeSub.deps) {
|
||||
activeSub.deps = activeSub.depsTail = link
|
||||
} else {
|
||||
link.prevDep = activeSub.depsTail
|
||||
activeSub.depsTail!.nextDep = link
|
||||
activeSub.depsTail = link
|
||||
}
|
||||
|
||||
addSub(link)
|
||||
} else if (link.version === -1) {
|
||||
// reused from last run - already a sub, just sync version
|
||||
link.version = this.version
|
||||
|
||||
// If this dep has a next, it means it's not at the tail - move it to the
|
||||
// tail. This ensures the effect's dep list is in the order they are
|
||||
// accessed during evaluation.
|
||||
if (link.nextDep) {
|
||||
const next = link.nextDep
|
||||
next.prevDep = link.prevDep
|
||||
if (link.prevDep) {
|
||||
link.prevDep.nextDep = next
|
||||
}
|
||||
|
||||
link.prevDep = activeSub.depsTail
|
||||
link.nextDep = undefined
|
||||
activeSub.depsTail!.nextDep = link
|
||||
activeSub.depsTail = link
|
||||
|
||||
// this was the head - point to the new head
|
||||
if (activeSub.deps === link) {
|
||||
activeSub.deps = next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__ && activeSub.onTrack) {
|
||||
activeSub.onTrack(
|
||||
extend(
|
||||
{
|
||||
effect: activeSub,
|
||||
},
|
||||
debugInfo,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return link
|
||||
}
|
||||
|
||||
trigger(debugInfo?: DebuggerEventExtraInfo): void {
|
||||
this.version++
|
||||
globalVersion++
|
||||
this.notify(debugInfo)
|
||||
}
|
||||
|
||||
notify(debugInfo?: DebuggerEventExtraInfo): void {
|
||||
startBatch()
|
||||
try {
|
||||
if (__DEV__) {
|
||||
// subs are notified and batched in reverse-order and then invoked in
|
||||
// original order at the end of the batch, but onTrigger hooks should
|
||||
// be invoked in original order here.
|
||||
for (let head = this.subsHead; head; head = head.nextSub) {
|
||||
if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
|
||||
head.sub.onTrigger(
|
||||
extend(
|
||||
{
|
||||
effect: head.sub,
|
||||
},
|
||||
debugInfo,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let link = this.subs; link; link = link.prevSub) {
|
||||
if (link.sub.notify()) {
|
||||
// if notify() returns `true`, this is a computed. Also call notify
|
||||
// on its dep - it's called here instead of inside computed's notify
|
||||
// in order to reduce call stack depth.
|
||||
;(link.sub as ComputedRefImpl).dep.notify()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
endBatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addSub(link: Link) {
|
||||
link.dep.sc++
|
||||
if (link.sub.flags & EffectFlags.TRACKING) {
|
||||
const computed = link.dep.computed
|
||||
// computed getting its first subscriber
|
||||
// enable tracking + lazily subscribe to all its deps
|
||||
if (computed && !link.dep.subs) {
|
||||
computed.flags |= EffectFlags.TRACKING | EffectFlags.DIRTY
|
||||
for (let l = computed.deps; l; l = l.nextDep) {
|
||||
addSub(l)
|
||||
}
|
||||
}
|
||||
|
||||
const currentTail = link.dep.subs
|
||||
if (currentTail !== link) {
|
||||
link.prevSub = currentTail
|
||||
if (currentTail) currentTail.nextSub = link
|
||||
}
|
||||
|
||||
if (__DEV__ && link.dep.subsHead === undefined) {
|
||||
link.dep.subsHead = link
|
||||
}
|
||||
|
||||
link.dep.subs = link
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,26 +61,23 @@ export const ARRAY_ITERATE_KEY: unique symbol = Symbol(
|
|||
* @param key - Identifier of the reactive property to track.
|
||||
*/
|
||||
export function track(target: object, type: TrackOpTypes, key: unknown): void {
|
||||
if (shouldTrack && activeSub) {
|
||||
if (activeSub !== undefined) {
|
||||
let depsMap = targetMap.get(target)
|
||||
if (!depsMap) {
|
||||
targetMap.set(target, (depsMap = new Map()))
|
||||
}
|
||||
let dep = depsMap.get(key)
|
||||
if (!dep) {
|
||||
depsMap.set(key, (dep = new Dep()))
|
||||
dep.map = depsMap
|
||||
dep.key = key
|
||||
depsMap.set(key, (dep = new Dep(depsMap, key)))
|
||||
}
|
||||
if (__DEV__) {
|
||||
dep.track({
|
||||
onTrack(activeSub!, {
|
||||
target,
|
||||
type,
|
||||
key,
|
||||
})
|
||||
} else {
|
||||
dep.track()
|
||||
}
|
||||
link(dep, activeSub!)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,14 +100,13 @@ export function trigger(
|
|||
const depsMap = targetMap.get(target)
|
||||
if (!depsMap) {
|
||||
// never been tracked
|
||||
globalVersion++
|
||||
return
|
||||
}
|
||||
|
||||
const run = (dep: Dep | undefined) => {
|
||||
if (dep) {
|
||||
const run = (dep: Dependency | undefined) => {
|
||||
if (dep !== undefined && dep.subs !== undefined) {
|
||||
if (__DEV__) {
|
||||
dep.trigger({
|
||||
triggerEventInfos.push({
|
||||
target,
|
||||
type,
|
||||
key,
|
||||
|
@ -311,8 +114,10 @@ export function trigger(
|
|||
oldValue,
|
||||
oldTarget,
|
||||
})
|
||||
} else {
|
||||
dep.trigger()
|
||||
}
|
||||
propagate(dep.subs)
|
||||
if (__DEV__) {
|
||||
triggerEventInfos.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -385,7 +190,7 @@ export function trigger(
|
|||
export function getDepFromReactive(
|
||||
object: any,
|
||||
key: string | number | symbol,
|
||||
): Dep | undefined {
|
||||
): Dependency | undefined {
|
||||
const depMap = targetMap.get(object)
|
||||
return depMap && depMap.get(key)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import { extend, hasChanged } from '@vue/shared'
|
||||
import type { ComputedRefImpl } from './computed'
|
||||
import { extend } from '@vue/shared'
|
||||
import type { TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { type Link, globalVersion } from './dep'
|
||||
import { setupOnTrigger } from './debug'
|
||||
import { activeEffectScope } from './effectScope'
|
||||
import {
|
||||
type Link,
|
||||
type Subscriber,
|
||||
SubscriberFlags,
|
||||
endTracking,
|
||||
startTracking,
|
||||
updateDirtyFlag,
|
||||
} from './system'
|
||||
import { warn } from './warning'
|
||||
|
||||
export type EffectScheduler = (...args: any[]) => any
|
||||
|
@ -27,7 +34,6 @@ export interface DebuggerOptions {
|
|||
|
||||
export interface ReactiveEffectOptions extends DebuggerOptions {
|
||||
scheduler?: EffectScheduler
|
||||
allowRecurse?: boolean
|
||||
onStop?: () => void
|
||||
}
|
||||
|
||||
|
@ -36,78 +42,27 @@ export interface ReactiveEffectRunner<T = any> {
|
|||
effect: ReactiveEffect
|
||||
}
|
||||
|
||||
export let activeSub: Subscriber | undefined
|
||||
|
||||
export enum EffectFlags {
|
||||
/**
|
||||
* ReactiveEffect only
|
||||
*/
|
||||
ACTIVE = 1 << 0,
|
||||
RUNNING = 1 << 1,
|
||||
TRACKING = 1 << 2,
|
||||
NOTIFIED = 1 << 3,
|
||||
DIRTY = 1 << 4,
|
||||
ALLOW_RECURSE = 1 << 5,
|
||||
PAUSED = 1 << 6,
|
||||
ALLOW_RECURSE = 1 << 7,
|
||||
PAUSED = 1 << 8,
|
||||
NOTIFIED = 1 << 9,
|
||||
STOP = 1 << 10,
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscriber is a type that tracks (or subscribes to) a list of deps.
|
||||
*/
|
||||
export interface Subscriber extends DebuggerOptions {
|
||||
/**
|
||||
* Head of the doubly linked list representing the deps
|
||||
* @internal
|
||||
*/
|
||||
deps?: Link
|
||||
/**
|
||||
* Tail of the same list
|
||||
* @internal
|
||||
*/
|
||||
depsTail?: Link
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
flags: EffectFlags
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
next?: Subscriber
|
||||
/**
|
||||
* returning `true` indicates it's a computed that needs to call notify
|
||||
* on its dep too
|
||||
* @internal
|
||||
*/
|
||||
notify(): true | void
|
||||
}
|
||||
export class ReactiveEffect<T = any> implements ReactiveEffectOptions {
|
||||
// Subscriber
|
||||
deps: Link | undefined = undefined
|
||||
depsTail: Link | undefined = undefined
|
||||
flags: number = SubscriberFlags.Effect
|
||||
|
||||
const pausedQueueEffects = new WeakSet<ReactiveEffect>()
|
||||
|
||||
export class ReactiveEffect<T = any>
|
||||
implements Subscriber, ReactiveEffectOptions
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
deps?: Link = undefined
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
depsTail?: Link = undefined
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
next?: Subscriber = undefined
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
cleanup?: () => void = undefined
|
||||
|
||||
scheduler?: EffectScheduler = undefined
|
||||
onStop?: () => void
|
||||
onTrack?: (event: DebuggerEvent) => void
|
||||
onTrigger?: (event: DebuggerEvent) => void
|
||||
|
@ -118,50 +73,53 @@ export class ReactiveEffect<T = any>
|
|||
}
|
||||
}
|
||||
|
||||
get active(): boolean {
|
||||
return !(this.flags & EffectFlags.STOP)
|
||||
}
|
||||
|
||||
pause(): void {
|
||||
if (!(this.flags & EffectFlags.PAUSED)) {
|
||||
this.flags |= EffectFlags.PAUSED
|
||||
}
|
||||
}
|
||||
|
||||
resume(): void {
|
||||
if (this.flags & EffectFlags.PAUSED) {
|
||||
const flags = this.flags
|
||||
if (flags & EffectFlags.PAUSED) {
|
||||
this.flags &= ~EffectFlags.PAUSED
|
||||
if (pausedQueueEffects.has(this)) {
|
||||
pausedQueueEffects.delete(this)
|
||||
this.trigger()
|
||||
}
|
||||
if (flags & EffectFlags.NOTIFIED) {
|
||||
this.flags &= ~EffectFlags.NOTIFIED
|
||||
this.notify()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
notify(): void {
|
||||
if (
|
||||
this.flags & EffectFlags.RUNNING &&
|
||||
!(this.flags & EffectFlags.ALLOW_RECURSE)
|
||||
) {
|
||||
return
|
||||
const flags = this.flags
|
||||
if (!(flags & EffectFlags.PAUSED)) {
|
||||
this.scheduler()
|
||||
} else {
|
||||
this.flags |= EffectFlags.NOTIFIED
|
||||
}
|
||||
if (!(this.flags & EffectFlags.NOTIFIED)) {
|
||||
batch(this)
|
||||
}
|
||||
|
||||
scheduler(): void {
|
||||
if (this.dirty) {
|
||||
this.run()
|
||||
}
|
||||
}
|
||||
|
||||
run(): T {
|
||||
// TODO cleanupEffect
|
||||
|
||||
if (!(this.flags & EffectFlags.ACTIVE)) {
|
||||
if (!this.active) {
|
||||
// stopped during cleanup
|
||||
return this.fn()
|
||||
}
|
||||
|
||||
this.flags |= EffectFlags.RUNNING
|
||||
cleanupEffect(this)
|
||||
prepareDeps(this)
|
||||
const prevEffect = activeSub
|
||||
const prevShouldTrack = shouldTrack
|
||||
activeSub = this
|
||||
shouldTrack = true
|
||||
const prevSub = activeSub
|
||||
setActiveSub(this)
|
||||
startTracking(this)
|
||||
|
||||
try {
|
||||
return this.fn()
|
||||
|
@ -172,300 +130,42 @@ export class ReactiveEffect<T = any>
|
|||
'this is likely a Vue internal bug.',
|
||||
)
|
||||
}
|
||||
cleanupDeps(this)
|
||||
activeSub = prevEffect
|
||||
shouldTrack = prevShouldTrack
|
||||
this.flags &= ~EffectFlags.RUNNING
|
||||
setActiveSub(prevSub)
|
||||
endTracking(this)
|
||||
if (
|
||||
this.flags & SubscriberFlags.Recursed &&
|
||||
this.flags & EffectFlags.ALLOW_RECURSE
|
||||
) {
|
||||
this.flags &= ~SubscriberFlags.Recursed
|
||||
this.notify()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.flags & EffectFlags.ACTIVE) {
|
||||
for (let link = this.deps; link; link = link.nextDep) {
|
||||
removeSub(link)
|
||||
}
|
||||
this.deps = this.depsTail = undefined
|
||||
if (this.active) {
|
||||
startTracking(this)
|
||||
endTracking(this)
|
||||
cleanupEffect(this)
|
||||
this.onStop && this.onStop()
|
||||
this.flags &= ~EffectFlags.ACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
trigger(): void {
|
||||
if (this.flags & EffectFlags.PAUSED) {
|
||||
pausedQueueEffects.add(this)
|
||||
} else if (this.scheduler) {
|
||||
this.scheduler()
|
||||
} else {
|
||||
this.runIfDirty()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
runIfDirty(): void {
|
||||
if (isDirty(this)) {
|
||||
this.run()
|
||||
this.flags |= EffectFlags.STOP
|
||||
}
|
||||
}
|
||||
|
||||
get dirty(): boolean {
|
||||
return isDirty(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For debugging
|
||||
*/
|
||||
// function printDeps(sub: Subscriber) {
|
||||
// let d = sub.deps
|
||||
// let ds = []
|
||||
// while (d) {
|
||||
// ds.push(d)
|
||||
// d = d.nextDep
|
||||
// }
|
||||
// return ds.map(d => ({
|
||||
// id: d.id,
|
||||
// prev: d.prevDep?.id,
|
||||
// next: d.nextDep?.id,
|
||||
// }))
|
||||
// }
|
||||
|
||||
let batchDepth = 0
|
||||
let batchedSub: Subscriber | undefined
|
||||
let batchedComputed: Subscriber | undefined
|
||||
|
||||
export function batch(sub: Subscriber, isComputed = false): void {
|
||||
sub.flags |= EffectFlags.NOTIFIED
|
||||
if (isComputed) {
|
||||
sub.next = batchedComputed
|
||||
batchedComputed = sub
|
||||
return
|
||||
}
|
||||
sub.next = batchedSub
|
||||
batchedSub = sub
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function startBatch(): void {
|
||||
batchDepth++
|
||||
}
|
||||
|
||||
/**
|
||||
* Run batched effects when all batches have ended
|
||||
* @internal
|
||||
*/
|
||||
export function endBatch(): void {
|
||||
if (--batchDepth > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (batchedComputed) {
|
||||
let e: Subscriber | undefined = batchedComputed
|
||||
batchedComputed = undefined
|
||||
while (e) {
|
||||
const next: Subscriber | undefined = e.next
|
||||
e.next = undefined
|
||||
e.flags &= ~EffectFlags.NOTIFIED
|
||||
e = next
|
||||
}
|
||||
}
|
||||
|
||||
let error: unknown
|
||||
while (batchedSub) {
|
||||
let e: Subscriber | undefined = batchedSub
|
||||
batchedSub = undefined
|
||||
while (e) {
|
||||
const next: Subscriber | undefined = e.next
|
||||
e.next = undefined
|
||||
e.flags &= ~EffectFlags.NOTIFIED
|
||||
if (e.flags & EffectFlags.ACTIVE) {
|
||||
try {
|
||||
// ACTIVE flag is effect-only
|
||||
;(e as ReactiveEffect).trigger()
|
||||
} catch (err) {
|
||||
if (!error) error = err
|
||||
}
|
||||
}
|
||||
e = next
|
||||
}
|
||||
}
|
||||
|
||||
if (error) throw error
|
||||
}
|
||||
|
||||
function prepareDeps(sub: Subscriber) {
|
||||
// Prepare deps for tracking, starting from the head
|
||||
for (let link = sub.deps; link; link = link.nextDep) {
|
||||
// set all previous deps' (if any) version to -1 so that we can track
|
||||
// which ones are unused after the run
|
||||
link.version = -1
|
||||
// store previous active sub if link was being used in another context
|
||||
link.prevActiveLink = link.dep.activeLink
|
||||
link.dep.activeLink = link
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupDeps(sub: Subscriber) {
|
||||
// Cleanup unsued deps
|
||||
let head
|
||||
let tail = sub.depsTail
|
||||
let link = tail
|
||||
while (link) {
|
||||
const prev = link.prevDep
|
||||
if (link.version === -1) {
|
||||
if (link === tail) tail = prev
|
||||
// unused - remove it from the dep's subscribing effect list
|
||||
removeSub(link)
|
||||
// also remove it from this effect's dep list
|
||||
removeDep(link)
|
||||
} else {
|
||||
// The new head is the last node seen which wasn't removed
|
||||
// from the doubly-linked list
|
||||
head = link
|
||||
}
|
||||
|
||||
// restore previous active link if any
|
||||
link.dep.activeLink = link.prevActiveLink
|
||||
link.prevActiveLink = undefined
|
||||
link = prev
|
||||
}
|
||||
// set the new head & tail
|
||||
sub.deps = head
|
||||
sub.depsTail = tail
|
||||
}
|
||||
|
||||
function isDirty(sub: Subscriber): boolean {
|
||||
for (let link = sub.deps; link; link = link.nextDep) {
|
||||
const flags = this.flags
|
||||
if (
|
||||
link.dep.version !== link.version ||
|
||||
(link.dep.computed &&
|
||||
(refreshComputed(link.dep.computed) ||
|
||||
link.dep.version !== link.version))
|
||||
flags & SubscriberFlags.Dirty ||
|
||||
(flags & SubscriberFlags.PendingComputed && updateDirtyFlag(this, flags))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// @ts-expect-error only for backwards compatibility where libs manually set
|
||||
// this flag - e.g. Pinia's testing module
|
||||
if (sub._dirty) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returning false indicates the refresh failed
|
||||
* @internal
|
||||
*/
|
||||
export function refreshComputed(computed: ComputedRefImpl): undefined {
|
||||
if (
|
||||
computed.flags & EffectFlags.TRACKING &&
|
||||
!(computed.flags & EffectFlags.DIRTY)
|
||||
) {
|
||||
return
|
||||
}
|
||||
computed.flags &= ~EffectFlags.DIRTY
|
||||
|
||||
// Global version fast path when no reactive changes has happened since
|
||||
// last refresh.
|
||||
if (computed.globalVersion === globalVersion) {
|
||||
return
|
||||
}
|
||||
computed.globalVersion = globalVersion
|
||||
|
||||
const dep = computed.dep
|
||||
computed.flags |= EffectFlags.RUNNING
|
||||
// In SSR there will be no render effect, so the computed has no subscriber
|
||||
// and therefore tracks no deps, thus we cannot rely on the dirty check.
|
||||
// Instead, computed always re-evaluate and relies on the globalVersion
|
||||
// fast path above for caching.
|
||||
if (
|
||||
dep.version > 0 &&
|
||||
!computed.isSSR &&
|
||||
computed.deps &&
|
||||
!isDirty(computed)
|
||||
) {
|
||||
computed.flags &= ~EffectFlags.RUNNING
|
||||
return
|
||||
}
|
||||
|
||||
const prevSub = activeSub
|
||||
const prevShouldTrack = shouldTrack
|
||||
activeSub = computed
|
||||
shouldTrack = true
|
||||
|
||||
try {
|
||||
prepareDeps(computed)
|
||||
const value = computed.fn(computed._value)
|
||||
if (dep.version === 0 || hasChanged(value, computed._value)) {
|
||||
computed._value = value
|
||||
dep.version++
|
||||
}
|
||||
} catch (err) {
|
||||
dep.version++
|
||||
throw err
|
||||
} finally {
|
||||
activeSub = prevSub
|
||||
shouldTrack = prevShouldTrack
|
||||
cleanupDeps(computed)
|
||||
computed.flags &= ~EffectFlags.RUNNING
|
||||
}
|
||||
}
|
||||
|
||||
function removeSub(link: Link, soft = false) {
|
||||
const { dep, prevSub, nextSub } = link
|
||||
if (prevSub) {
|
||||
prevSub.nextSub = nextSub
|
||||
link.prevSub = undefined
|
||||
}
|
||||
if (nextSub) {
|
||||
nextSub.prevSub = prevSub
|
||||
link.nextSub = undefined
|
||||
}
|
||||
if (__DEV__ && dep.subsHead === link) {
|
||||
// was previous head, point new head to next
|
||||
dep.subsHead = nextSub
|
||||
}
|
||||
|
||||
if (dep.subs === link) {
|
||||
// was previous tail, point new tail to prev
|
||||
dep.subs = prevSub
|
||||
|
||||
if (!prevSub && dep.computed) {
|
||||
// if computed, unsubscribe it from all its deps so this computed and its
|
||||
// value can be GCed
|
||||
dep.computed.flags &= ~EffectFlags.TRACKING
|
||||
for (let l = dep.computed.deps; l; l = l.nextDep) {
|
||||
// here we are only "soft" unsubscribing because the computed still keeps
|
||||
// referencing the deps and the dep should not decrease its sub count
|
||||
removeSub(l, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!soft && !--dep.sc && dep.map) {
|
||||
// #11979
|
||||
// property dep no longer has effect subscribers, delete it
|
||||
// this mostly is for the case where an object is kept in memory but only a
|
||||
// subset of its properties is tracked at one time
|
||||
dep.map.delete(dep.key)
|
||||
}
|
||||
}
|
||||
|
||||
function removeDep(link: Link) {
|
||||
const { prevDep, nextDep } = link
|
||||
if (prevDep) {
|
||||
prevDep.nextDep = nextDep
|
||||
link.prevDep = undefined
|
||||
}
|
||||
if (nextDep) {
|
||||
nextDep.prevDep = prevDep
|
||||
link.nextDep = undefined
|
||||
}
|
||||
if (__DEV__) {
|
||||
setupOnTrigger(ReactiveEffect)
|
||||
}
|
||||
|
||||
export interface ReactiveEffectRunner<T = any> {
|
||||
|
@ -505,34 +205,53 @@ export function stop(runner: ReactiveEffectRunner): void {
|
|||
runner.effect.stop()
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export let shouldTrack = true
|
||||
const trackStack: boolean[] = []
|
||||
const resetTrackingStack: (Subscriber | undefined)[] = []
|
||||
|
||||
/**
|
||||
* Temporarily pauses tracking.
|
||||
*/
|
||||
export function pauseTracking(): void {
|
||||
trackStack.push(shouldTrack)
|
||||
shouldTrack = false
|
||||
resetTrackingStack.push(activeSub)
|
||||
activeSub = undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-enables effect tracking (if it was paused).
|
||||
*/
|
||||
export function enableTracking(): void {
|
||||
trackStack.push(shouldTrack)
|
||||
shouldTrack = true
|
||||
const isPaused = activeSub === undefined
|
||||
if (!isPaused) {
|
||||
// Add the current active effect to the trackResetStack so it can be
|
||||
// restored by calling resetTracking.
|
||||
resetTrackingStack.push(activeSub)
|
||||
} else {
|
||||
// Add a placeholder to the trackResetStack so we can it can be popped
|
||||
// to restore the previous active effect.
|
||||
resetTrackingStack.push(undefined)
|
||||
for (let i = resetTrackingStack.length - 1; i >= 0; i--) {
|
||||
if (resetTrackingStack[i] !== undefined) {
|
||||
activeSub = resetTrackingStack[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the previous global effect tracking state.
|
||||
*/
|
||||
export function resetTracking(): void {
|
||||
const last = trackStack.pop()
|
||||
shouldTrack = last === undefined ? true : last
|
||||
if (__DEV__ && resetTrackingStack.length === 0) {
|
||||
warn(
|
||||
`resetTracking() was called when there was no active tracking ` +
|
||||
`to reset.`,
|
||||
)
|
||||
}
|
||||
if (resetTrackingStack.length) {
|
||||
activeSub = resetTrackingStack.pop()!
|
||||
} else {
|
||||
activeSub = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -561,7 +280,7 @@ export function onEffectCleanup(fn: () => void, failSilently = false): void {
|
|||
function cleanupEffect(e: ReactiveEffect) {
|
||||
const { cleanup } = e
|
||||
e.cleanup = undefined
|
||||
if (cleanup) {
|
||||
if (cleanup !== undefined) {
|
||||
// run cleanup without active effect
|
||||
const prevSub = activeSub
|
||||
activeSub = undefined
|
||||
|
@ -572,3 +291,9 @@ function cleanupEffect(e: ReactiveEffect) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export let activeSub: Subscriber | undefined = undefined
|
||||
|
||||
export function setActiveSub(sub: Subscriber | undefined): void {
|
||||
activeSub = sub
|
||||
}
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
import type { ReactiveEffect } from './effect'
|
||||
import { EffectFlags, type ReactiveEffect } from './effect'
|
||||
import {
|
||||
type Link,
|
||||
type Subscriber,
|
||||
endTracking,
|
||||
startTracking,
|
||||
} from './system'
|
||||
import { warn } from './warning'
|
||||
|
||||
export let activeEffectScope: EffectScope | undefined
|
||||
|
||||
export class EffectScope {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
private _active = true
|
||||
export class EffectScope implements Subscriber {
|
||||
// Subscriber: In order to collect orphans computeds
|
||||
deps: Link | undefined = undefined
|
||||
depsTail: Link | undefined = undefined
|
||||
flags: number = 0
|
||||
|
||||
/**
|
||||
* @internal track `on` calls, allow `on` call multiple times
|
||||
*/
|
||||
|
@ -21,8 +28,6 @@ export class EffectScope {
|
|||
*/
|
||||
cleanups: (() => void)[] = []
|
||||
|
||||
private _isPaused = false
|
||||
|
||||
/**
|
||||
* only assigned by undetached scope
|
||||
* @internal
|
||||
|
@ -51,12 +56,12 @@ export class EffectScope {
|
|||
}
|
||||
|
||||
get active(): boolean {
|
||||
return this._active
|
||||
return !(this.flags & EffectFlags.STOP)
|
||||
}
|
||||
|
||||
pause(): void {
|
||||
if (this._active) {
|
||||
this._isPaused = true
|
||||
if (!(this.flags & EffectFlags.PAUSED)) {
|
||||
this.flags |= EffectFlags.PAUSED
|
||||
let i, l
|
||||
if (this.scopes) {
|
||||
for (i = 0, l = this.scopes.length; i < l; i++) {
|
||||
|
@ -73,9 +78,8 @@ export class EffectScope {
|
|||
* Resumes the effect scope, including all child scopes and effects.
|
||||
*/
|
||||
resume(): void {
|
||||
if (this._active) {
|
||||
if (this._isPaused) {
|
||||
this._isPaused = false
|
||||
if (this.flags & EffectFlags.PAUSED) {
|
||||
this.flags &= ~EffectFlags.PAUSED
|
||||
let i, l
|
||||
if (this.scopes) {
|
||||
for (i = 0, l = this.scopes.length; i < l; i++) {
|
||||
|
@ -87,16 +91,15 @@ export class EffectScope {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run<T>(fn: () => T): T | undefined {
|
||||
if (this._active) {
|
||||
const currentEffectScope = activeEffectScope
|
||||
if (this.active) {
|
||||
const prevEffectScope = activeEffectScope
|
||||
try {
|
||||
activeEffectScope = this
|
||||
return fn()
|
||||
} finally {
|
||||
activeEffectScope = currentEffectScope
|
||||
activeEffectScope = prevEffectScope
|
||||
}
|
||||
} else if (__DEV__) {
|
||||
warn(`cannot run an inactive effect scope.`)
|
||||
|
@ -127,8 +130,10 @@ export class EffectScope {
|
|||
}
|
||||
|
||||
stop(fromParent?: boolean): void {
|
||||
if (this._active) {
|
||||
this._active = false
|
||||
if (this.active) {
|
||||
this.flags |= EffectFlags.STOP
|
||||
startTracking(this)
|
||||
endTracking(this)
|
||||
let i, l
|
||||
for (i = 0, l = this.effects.length; i < l; i++) {
|
||||
this.effects[i].stop()
|
||||
|
|
|
@ -5,7 +5,11 @@ import {
|
|||
isFunction,
|
||||
isObject,
|
||||
} from '@vue/shared'
|
||||
import { Dep, getDepFromReactive } from './dep'
|
||||
import type { ComputedRef, WritableComputedRef } from './computed'
|
||||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { onTrack, triggerEventInfos } from './debug'
|
||||
import { getDepFromReactive } from './dep'
|
||||
import { activeSub } from './effect'
|
||||
import {
|
||||
type Builtin,
|
||||
type ShallowReactiveMarker,
|
||||
|
@ -15,8 +19,7 @@ import {
|
|||
toRaw,
|
||||
toReactive,
|
||||
} from './reactive'
|
||||
import type { ComputedRef, WritableComputedRef } from './computed'
|
||||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { type Dependency, type Link, link, propagate } from './system'
|
||||
|
||||
declare const RefSymbol: unique symbol
|
||||
export declare const RawSymbol: unique symbol
|
||||
|
@ -103,12 +106,14 @@ function createRef(rawValue: unknown, shallow: boolean) {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RefImpl<T = any> {
|
||||
class RefImpl<T = any> implements Dependency {
|
||||
// Dependency
|
||||
subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
|
||||
_value: T
|
||||
private _rawValue: T
|
||||
|
||||
dep: Dep = new Dep()
|
||||
|
||||
public readonly [ReactiveFlags.IS_REF] = true
|
||||
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false
|
||||
|
||||
|
@ -118,16 +123,12 @@ class RefImpl<T = any> {
|
|||
this[ReactiveFlags.IS_SHALLOW] = isShallow
|
||||
}
|
||||
|
||||
get value() {
|
||||
if (__DEV__) {
|
||||
this.dep.track({
|
||||
target: this,
|
||||
type: TrackOpTypes.GET,
|
||||
key: 'value',
|
||||
})
|
||||
} else {
|
||||
this.dep.track()
|
||||
get dep() {
|
||||
return this
|
||||
}
|
||||
|
||||
get value() {
|
||||
trackRef(this)
|
||||
return this._value
|
||||
}
|
||||
|
||||
|
@ -142,15 +143,17 @@ class RefImpl<T = any> {
|
|||
this._rawValue = newValue
|
||||
this._value = useDirectValue ? newValue : toReactive(newValue)
|
||||
if (__DEV__) {
|
||||
this.dep.trigger({
|
||||
triggerEventInfos.push({
|
||||
target: this,
|
||||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
newValue,
|
||||
oldValue,
|
||||
})
|
||||
} else {
|
||||
this.dep.trigger()
|
||||
}
|
||||
triggerRef(this as unknown as Ref)
|
||||
if (__DEV__) {
|
||||
triggerEventInfos.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,17 +186,22 @@ class RefImpl<T = any> {
|
|||
*/
|
||||
export function triggerRef(ref: Ref): void {
|
||||
// ref may be an instance of ObjectRefImpl
|
||||
if ((ref as unknown as RefImpl).dep) {
|
||||
if (__DEV__) {
|
||||
;(ref as unknown as RefImpl).dep.trigger({
|
||||
target: ref,
|
||||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
newValue: (ref as unknown as RefImpl)._value,
|
||||
})
|
||||
} else {
|
||||
;(ref as unknown as RefImpl).dep.trigger()
|
||||
const dep = (ref as unknown as RefImpl).dep
|
||||
if (dep !== undefined && dep.subs !== undefined) {
|
||||
propagate(dep.subs)
|
||||
}
|
||||
}
|
||||
|
||||
function trackRef(dep: Dependency) {
|
||||
if (activeSub !== undefined) {
|
||||
if (__DEV__) {
|
||||
onTrack(activeSub!, {
|
||||
target: dep,
|
||||
type: TrackOpTypes.GET,
|
||||
key: 'value',
|
||||
})
|
||||
}
|
||||
link(dep, activeSub!)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,8 +293,10 @@ export type CustomRefFactory<T> = (
|
|||
set: (value: T) => void
|
||||
}
|
||||
|
||||
class CustomRefImpl<T> {
|
||||
public dep: Dep
|
||||
class CustomRefImpl<T> implements Dependency {
|
||||
// Dependency
|
||||
subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
|
||||
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
|
||||
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
|
||||
|
@ -296,12 +306,18 @@ class CustomRefImpl<T> {
|
|||
public _value: T = undefined!
|
||||
|
||||
constructor(factory: CustomRefFactory<T>) {
|
||||
const dep = (this.dep = new Dep())
|
||||
const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep))
|
||||
const { get, set } = factory(
|
||||
() => trackRef(this),
|
||||
() => triggerRef(this as unknown as Ref),
|
||||
)
|
||||
this._get = get
|
||||
this._set = set
|
||||
}
|
||||
|
||||
get dep() {
|
||||
return this
|
||||
}
|
||||
|
||||
get value() {
|
||||
return (this._value = this._get())
|
||||
}
|
||||
|
@ -361,7 +377,7 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
|
|||
this._object[this._key] = newVal
|
||||
}
|
||||
|
||||
get dep(): Dep | undefined {
|
||||
get dep(): Dependency | undefined {
|
||||
return getDepFromReactive(toRaw(this._object), this._key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,438 @@
|
|||
/* eslint-disable */
|
||||
// Ported from https://github.com/stackblitz/alien-signals/blob/v1.0.0/src/system.ts
|
||||
import type { ComputedRefImpl as Computed } from './computed.js'
|
||||
import type { ReactiveEffect as Effect } from './effect.js'
|
||||
|
||||
export interface Dependency {
|
||||
subs: Link | undefined
|
||||
subsTail: Link | undefined
|
||||
}
|
||||
|
||||
export interface Subscriber {
|
||||
flags: SubscriberFlags
|
||||
deps: Link | undefined
|
||||
depsTail: Link | undefined
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
dep: Dependency | Computed
|
||||
sub: Subscriber | Computed | Effect
|
||||
prevSub: Link | undefined
|
||||
nextSub: Link | undefined
|
||||
nextDep: Link | undefined
|
||||
}
|
||||
|
||||
export const enum SubscriberFlags {
|
||||
Computed = 1 << 0,
|
||||
Effect = 1 << 1,
|
||||
Tracking = 1 << 2,
|
||||
Recursed = 1 << 4,
|
||||
Dirty = 1 << 5,
|
||||
PendingComputed = 1 << 6,
|
||||
Propagated = Dirty | PendingComputed,
|
||||
}
|
||||
|
||||
let batchDepth = 0
|
||||
let queuedEffects: Effect | undefined
|
||||
let queuedEffectsTail: Effect | undefined
|
||||
let linkPool: Link | undefined
|
||||
|
||||
export function startBatch(): void {
|
||||
++batchDepth
|
||||
}
|
||||
|
||||
export function endBatch(): void {
|
||||
if (!--batchDepth) {
|
||||
processEffectNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
export function link(dep: Dependency, sub: Subscriber): Link | undefined {
|
||||
const currentDep = sub.depsTail
|
||||
if (currentDep !== undefined && currentDep.dep === dep) {
|
||||
return
|
||||
}
|
||||
const nextDep = currentDep !== undefined ? currentDep.nextDep : sub.deps
|
||||
if (nextDep !== undefined && nextDep.dep === dep) {
|
||||
sub.depsTail = nextDep
|
||||
return
|
||||
}
|
||||
const depLastSub = dep.subsTail
|
||||
if (
|
||||
depLastSub !== undefined &&
|
||||
depLastSub.sub === sub &&
|
||||
isValidLink(depLastSub, sub)
|
||||
) {
|
||||
return
|
||||
}
|
||||
return linkNewDep(dep, sub, nextDep, currentDep)
|
||||
}
|
||||
|
||||
export function propagate(link: Link): void {
|
||||
let targetFlag = SubscriberFlags.Dirty
|
||||
let subs = link
|
||||
let stack = 0
|
||||
|
||||
top: do {
|
||||
const sub = link.sub
|
||||
const subFlags = sub.flags
|
||||
|
||||
if (
|
||||
(!(
|
||||
subFlags &
|
||||
(SubscriberFlags.Tracking |
|
||||
SubscriberFlags.Recursed |
|
||||
SubscriberFlags.Propagated)
|
||||
) &&
|
||||
((sub.flags = subFlags | targetFlag), true)) ||
|
||||
(subFlags & SubscriberFlags.Recursed &&
|
||||
!(subFlags & SubscriberFlags.Tracking) &&
|
||||
((sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag),
|
||||
true)) ||
|
||||
(!(subFlags & SubscriberFlags.Propagated) &&
|
||||
isValidLink(link, sub) &&
|
||||
((sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag),
|
||||
(sub as Dependency).subs !== undefined))
|
||||
) {
|
||||
const subSubs = (sub as Dependency).subs
|
||||
if (subSubs !== undefined) {
|
||||
if (subSubs.nextSub !== undefined) {
|
||||
subSubs.prevSub = subs
|
||||
link = subs = subSubs
|
||||
targetFlag = SubscriberFlags.PendingComputed
|
||||
++stack
|
||||
} else {
|
||||
link = subSubs
|
||||
targetFlag = SubscriberFlags.PendingComputed
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (subFlags & SubscriberFlags.Effect) {
|
||||
if (queuedEffectsTail !== undefined) {
|
||||
queuedEffectsTail.depsTail!.nextDep = sub.deps
|
||||
} else {
|
||||
queuedEffects = sub as Effect
|
||||
}
|
||||
queuedEffectsTail = sub as Effect
|
||||
}
|
||||
} else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) {
|
||||
sub.flags = subFlags | targetFlag
|
||||
} else if (
|
||||
!(subFlags & targetFlag) &&
|
||||
subFlags & SubscriberFlags.Propagated &&
|
||||
isValidLink(link, sub)
|
||||
) {
|
||||
sub.flags = subFlags | targetFlag
|
||||
}
|
||||
|
||||
if ((link = subs.nextSub!) !== undefined) {
|
||||
subs = link
|
||||
targetFlag = stack
|
||||
? SubscriberFlags.PendingComputed
|
||||
: SubscriberFlags.Dirty
|
||||
continue
|
||||
}
|
||||
|
||||
while (stack) {
|
||||
--stack
|
||||
const dep = subs.dep
|
||||
const depSubs = dep.subs!
|
||||
subs = depSubs.prevSub!
|
||||
depSubs.prevSub = undefined
|
||||
if ((link = subs.nextSub!) !== undefined) {
|
||||
subs = link
|
||||
targetFlag = stack
|
||||
? SubscriberFlags.PendingComputed
|
||||
: SubscriberFlags.Dirty
|
||||
continue top
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
} while (true)
|
||||
|
||||
if (!batchDepth) {
|
||||
processEffectNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
export function startTracking(sub: Subscriber): void {
|
||||
sub.depsTail = undefined
|
||||
sub.flags =
|
||||
(sub.flags & ~(SubscriberFlags.Recursed | SubscriberFlags.Propagated)) |
|
||||
SubscriberFlags.Tracking
|
||||
}
|
||||
|
||||
export function endTracking(sub: Subscriber): void {
|
||||
const depsTail = sub.depsTail
|
||||
if (depsTail !== undefined) {
|
||||
const nextDep = depsTail.nextDep
|
||||
if (nextDep !== undefined) {
|
||||
clearTracking(nextDep)
|
||||
depsTail.nextDep = undefined
|
||||
}
|
||||
} else if (sub.deps !== undefined) {
|
||||
clearTracking(sub.deps)
|
||||
sub.deps = undefined
|
||||
}
|
||||
sub.flags &= ~SubscriberFlags.Tracking
|
||||
}
|
||||
|
||||
export function updateDirtyFlag(
|
||||
sub: Subscriber,
|
||||
flags: SubscriberFlags,
|
||||
): boolean {
|
||||
if (checkDirty(sub.deps!)) {
|
||||
sub.flags = flags | SubscriberFlags.Dirty
|
||||
return true
|
||||
} else {
|
||||
sub.flags = flags & ~SubscriberFlags.PendingComputed
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function processComputedUpdate(
|
||||
computed: Computed,
|
||||
flags: SubscriberFlags,
|
||||
): void {
|
||||
if (flags & SubscriberFlags.Dirty) {
|
||||
if (computed.update()) {
|
||||
const subs = computed.subs
|
||||
if (subs !== undefined) {
|
||||
shallowPropagate(subs)
|
||||
}
|
||||
}
|
||||
} else if (flags & SubscriberFlags.PendingComputed) {
|
||||
if (checkDirty(computed.deps!)) {
|
||||
if (computed.update()) {
|
||||
const subs = computed.subs
|
||||
if (subs !== undefined) {
|
||||
shallowPropagate(subs)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
computed.flags = flags & ~SubscriberFlags.PendingComputed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function processEffectNotifications(): void {
|
||||
while (queuedEffects !== undefined) {
|
||||
const effect = queuedEffects
|
||||
const depsTail = effect.depsTail!
|
||||
const queuedNext = depsTail.nextDep
|
||||
if (queuedNext !== undefined) {
|
||||
depsTail.nextDep = undefined
|
||||
queuedEffects = queuedNext.sub as Effect
|
||||
} else {
|
||||
queuedEffects = undefined
|
||||
queuedEffectsTail = undefined
|
||||
}
|
||||
effect.notify()
|
||||
}
|
||||
}
|
||||
|
||||
function linkNewDep(
|
||||
dep: Dependency,
|
||||
sub: Subscriber,
|
||||
nextDep: Link | undefined,
|
||||
depsTail: Link | undefined,
|
||||
): Link {
|
||||
let newLink: Link
|
||||
|
||||
if (linkPool !== undefined) {
|
||||
newLink = linkPool
|
||||
linkPool = newLink.nextDep
|
||||
newLink.nextDep = nextDep
|
||||
newLink.dep = dep
|
||||
newLink.sub = sub
|
||||
} else {
|
||||
newLink = {
|
||||
dep,
|
||||
sub,
|
||||
nextDep,
|
||||
prevSub: undefined,
|
||||
nextSub: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
if (depsTail === undefined) {
|
||||
sub.deps = newLink
|
||||
} else {
|
||||
depsTail.nextDep = newLink
|
||||
}
|
||||
|
||||
if (dep.subs === undefined) {
|
||||
dep.subs = newLink
|
||||
} else {
|
||||
const oldTail = dep.subsTail!
|
||||
newLink.prevSub = oldTail
|
||||
oldTail.nextSub = newLink
|
||||
}
|
||||
|
||||
sub.depsTail = newLink
|
||||
dep.subsTail = newLink
|
||||
|
||||
return newLink
|
||||
}
|
||||
|
||||
function checkDirty(link: Link): boolean {
|
||||
let stack = 0
|
||||
let dirty: boolean
|
||||
|
||||
top: do {
|
||||
dirty = false
|
||||
const dep = link.dep
|
||||
|
||||
if ('flags' in dep) {
|
||||
const depFlags = dep.flags
|
||||
if (
|
||||
(depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) ===
|
||||
(SubscriberFlags.Computed | SubscriberFlags.Dirty)
|
||||
) {
|
||||
if ((dep as Computed).update()) {
|
||||
const subs = dep.subs!
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs)
|
||||
}
|
||||
dirty = true
|
||||
}
|
||||
} else if (
|
||||
(depFlags &
|
||||
(SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) ===
|
||||
(SubscriberFlags.Computed | SubscriberFlags.PendingComputed)
|
||||
) {
|
||||
const depSubs = dep.subs!
|
||||
if (depSubs.nextSub !== undefined) {
|
||||
depSubs.prevSub = link
|
||||
}
|
||||
link = dep.deps!
|
||||
++stack
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!dirty && link.nextDep !== undefined) {
|
||||
link = link.nextDep
|
||||
continue
|
||||
}
|
||||
|
||||
if (stack) {
|
||||
let sub = link.sub as Computed
|
||||
do {
|
||||
--stack
|
||||
const subSubs = sub.subs!
|
||||
|
||||
if (dirty) {
|
||||
if (sub.update()) {
|
||||
if ((link = subSubs.prevSub!) !== undefined) {
|
||||
subSubs.prevSub = undefined
|
||||
shallowPropagate(sub.subs!)
|
||||
sub = link.sub as Computed
|
||||
} else {
|
||||
sub = subSubs.sub as Computed
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
sub.flags &= ~SubscriberFlags.PendingComputed
|
||||
}
|
||||
|
||||
if ((link = subSubs.prevSub!) !== undefined) {
|
||||
subSubs.prevSub = undefined
|
||||
if (link.nextDep !== undefined) {
|
||||
link = link.nextDep
|
||||
continue top
|
||||
}
|
||||
sub = link.sub as Computed
|
||||
} else {
|
||||
if ((link = subSubs.nextDep!) !== undefined) {
|
||||
continue top
|
||||
}
|
||||
sub = subSubs.sub as Computed
|
||||
}
|
||||
|
||||
dirty = false
|
||||
} while (stack)
|
||||
}
|
||||
|
||||
return dirty
|
||||
} while (true)
|
||||
}
|
||||
|
||||
function shallowPropagate(link: Link): void {
|
||||
do {
|
||||
const sub = link.sub
|
||||
const subFlags = sub.flags
|
||||
if (
|
||||
(subFlags & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)) ===
|
||||
SubscriberFlags.PendingComputed
|
||||
) {
|
||||
sub.flags = subFlags | SubscriberFlags.Dirty
|
||||
}
|
||||
link = link.nextSub!
|
||||
} while (link !== undefined)
|
||||
}
|
||||
|
||||
function isValidLink(checkLink: Link, sub: Subscriber): boolean {
|
||||
const depsTail = sub.depsTail
|
||||
if (depsTail !== undefined) {
|
||||
let link = sub.deps!
|
||||
do {
|
||||
if (link === checkLink) {
|
||||
return true
|
||||
}
|
||||
if (link === depsTail) {
|
||||
break
|
||||
}
|
||||
link = link.nextDep!
|
||||
} while (link !== undefined)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function clearTracking(link: Link): void {
|
||||
do {
|
||||
const dep = link.dep
|
||||
const nextDep = link.nextDep
|
||||
const nextSub = link.nextSub
|
||||
const prevSub = link.prevSub
|
||||
|
||||
if (nextSub !== undefined) {
|
||||
nextSub.prevSub = prevSub
|
||||
link.nextSub = undefined
|
||||
} else {
|
||||
dep.subsTail = prevSub
|
||||
}
|
||||
|
||||
if (prevSub !== undefined) {
|
||||
prevSub.nextSub = nextSub
|
||||
link.prevSub = undefined
|
||||
} else {
|
||||
dep.subs = nextSub
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
link.dep = undefined
|
||||
// @ts-expect-error
|
||||
link.sub = undefined
|
||||
link.nextDep = linkPool
|
||||
linkPool = link
|
||||
|
||||
if (dep.subs === undefined && 'deps' in dep) {
|
||||
const depFlags = dep.flags
|
||||
if (!(depFlags & SubscriberFlags.Dirty)) {
|
||||
dep.flags = depFlags | SubscriberFlags.Dirty
|
||||
}
|
||||
const depDeps = dep.deps
|
||||
if (depDeps !== undefined) {
|
||||
link = depDeps
|
||||
dep.depsTail!.nextDep = nextDep
|
||||
dep.deps = undefined
|
||||
dep.depsTail = undefined
|
||||
continue
|
||||
}
|
||||
}
|
||||
link = nextDep!
|
||||
} while (link !== undefined)
|
||||
}
|
|
@ -10,20 +10,19 @@ import {
|
|||
isSet,
|
||||
remove,
|
||||
} from '@vue/shared'
|
||||
import { warn } from './warning'
|
||||
import type { ComputedRef } from './computed'
|
||||
import { ReactiveFlags } from './constants'
|
||||
import {
|
||||
type DebuggerOptions,
|
||||
EffectFlags,
|
||||
type EffectScheduler,
|
||||
ReactiveEffect,
|
||||
pauseTracking,
|
||||
resetTracking,
|
||||
} from './effect'
|
||||
import { getCurrentScope } from './effectScope'
|
||||
import { isReactive, isShallow } from './reactive'
|
||||
import { type Ref, isRef } from './ref'
|
||||
import { getCurrentScope } from './effectScope'
|
||||
import { warn } from './warning'
|
||||
|
||||
// These errors were transferred from `packages/runtime-core/src/errorHandling.ts`
|
||||
// to @vue/reactivity to allow co-location with the moved base watch logic, hence
|
||||
|
@ -231,10 +230,7 @@ export function watch(
|
|||
: INITIAL_WATCHER_VALUE
|
||||
|
||||
const job = (immediateFirstRun?: boolean) => {
|
||||
if (
|
||||
!(effect.flags & EffectFlags.ACTIVE) ||
|
||||
(!effect.dirty && !immediateFirstRun)
|
||||
) {
|
||||
if (!effect.active || (!immediateFirstRun && !effect.dirty)) {
|
||||
return
|
||||
}
|
||||
if (cb) {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import {
|
||||
type ComputedRefImpl,
|
||||
type ReactiveEffectRunner,
|
||||
effect,
|
||||
} from '@vue/reactivity'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
type SetupContext,
|
||||
|
@ -25,8 +30,6 @@ import {
|
|||
withAsyncContext,
|
||||
withDefaults,
|
||||
} from '../src/apiSetupHelpers'
|
||||
import type { ComputedRefImpl } from '../../reactivity/src/computed'
|
||||
import { EffectFlags, type ReactiveEffectRunner, effect } from '@vue/reactivity'
|
||||
|
||||
describe('SFC <script setup> helpers', () => {
|
||||
test('should warn runtime usage', () => {
|
||||
|
@ -450,12 +453,12 @@ describe('SFC <script setup> helpers', () => {
|
|||
app.mount(root)
|
||||
|
||||
await ready
|
||||
expect(e!.effect.flags & EffectFlags.ACTIVE).toBeTruthy()
|
||||
expect(c!.flags & EffectFlags.TRACKING).toBeTruthy()
|
||||
expect(e!.effect.active).toBeTruthy()
|
||||
expect(c!.flags & 2 /* SubscriberFlags.Tracking */).toBe(0)
|
||||
|
||||
app.unmount()
|
||||
expect(e!.effect.flags & EffectFlags.ACTIVE).toBeFalsy()
|
||||
expect(c!.flags & EffectFlags.TRACKING).toBeFalsy()
|
||||
expect(e!.effect.active).toBeFalsy()
|
||||
expect(c!.flags & 2 /* SubscriberFlags.Tracking */).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
TrackOpTypes,
|
||||
TriggerOpTypes,
|
||||
effectScope,
|
||||
onScopeDispose,
|
||||
shallowReactive,
|
||||
shallowRef,
|
||||
toRef,
|
||||
|
@ -1982,4 +1983,31 @@ describe('api: watch', () => {
|
|||
expect(spy1).toHaveBeenCalled()
|
||||
expect(spy2).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// #12631
|
||||
test('this.$watch w/ onScopeDispose', () => {
|
||||
const onCleanup = vi.fn()
|
||||
const toggle = ref(true)
|
||||
|
||||
const Comp = defineComponent({
|
||||
render() {},
|
||||
created(this: any) {
|
||||
this.$watch(
|
||||
() => 1,
|
||||
function () {},
|
||||
)
|
||||
onScopeDispose(onCleanup)
|
||||
},
|
||||
})
|
||||
|
||||
const App = defineComponent({
|
||||
render() {
|
||||
return toggle.value ? h(Comp) : null
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
createApp(App).mount(root)
|
||||
expect(onCleanup).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -531,6 +531,9 @@ describe('error handling', () => {
|
|||
caughtError = caught
|
||||
}
|
||||
expect(fn).toHaveBeenCalledWith(err, 'setup function')
|
||||
expect(
|
||||
`Active effect was not restored correctly - this is likely a Vue internal bug.`,
|
||||
).toHaveBeenWarned()
|
||||
expect(
|
||||
`Unhandled error during execution of setup function`,
|
||||
).toHaveBeenWarned()
|
||||
|
|
|
@ -38,10 +38,12 @@ export function injectHook(
|
|||
// This assumes the hook does not synchronously trigger other hooks, which
|
||||
// can only be false when the user does something really funky.
|
||||
const reset = setCurrentInstance(target)
|
||||
const res = callWithAsyncErrorHandling(hook, target, type, args)
|
||||
try {
|
||||
return callWithAsyncErrorHandling(hook, target, type, args)
|
||||
} finally {
|
||||
reset()
|
||||
resetTracking()
|
||||
return res
|
||||
}
|
||||
})
|
||||
if (prepend) {
|
||||
hooks.unshift(wrappedHook)
|
||||
|
|
|
@ -416,20 +416,20 @@ interface LegacyOptions<
|
|||
extends?: Extends
|
||||
|
||||
// lifecycle
|
||||
beforeCreate?(): void
|
||||
created?(): void
|
||||
beforeMount?(): void
|
||||
mounted?(): void
|
||||
beforeUpdate?(): void
|
||||
updated?(): void
|
||||
activated?(): void
|
||||
deactivated?(): void
|
||||
beforeCreate?(): any
|
||||
created?(): any
|
||||
beforeMount?(): any
|
||||
mounted?(): any
|
||||
beforeUpdate?(): any
|
||||
updated?(): any
|
||||
activated?(): any
|
||||
deactivated?(): any
|
||||
/** @deprecated use `beforeUnmount` instead */
|
||||
beforeDestroy?(): void
|
||||
beforeUnmount?(): void
|
||||
beforeDestroy?(): any
|
||||
beforeUnmount?(): any
|
||||
/** @deprecated use `unmounted` instead */
|
||||
destroyed?(): void
|
||||
unmounted?(): void
|
||||
destroyed?(): any
|
||||
unmounted?(): any
|
||||
renderTracked?: DebuggerHook
|
||||
renderTriggered?: DebuggerHook
|
||||
errorCaptured?: ErrorCapturedHook
|
||||
|
|
|
@ -1565,7 +1565,8 @@ function baseCreateRenderer(
|
|||
instance.scope.off()
|
||||
|
||||
const update = (instance.update = effect.run.bind(effect))
|
||||
const job: SchedulerJob = (instance.job = effect.runIfDirty.bind(effect))
|
||||
const job: SchedulerJob = (instance.job = () =>
|
||||
effect.dirty && effect.run())
|
||||
job.i = instance
|
||||
job.id = instance.uid
|
||||
effect.scheduler = () => queueJob(job)
|
||||
|
|
|
@ -48,7 +48,7 @@ export function renderEffect(fn: () => void, noLifecycle = false): void {
|
|||
}
|
||||
|
||||
const effect = new ReactiveEffect(renderEffectFn)
|
||||
const job: SchedulerJob = effect.runIfDirty.bind(effect)
|
||||
const job: SchedulerJob = () => effect.dirty && effect.run()
|
||||
|
||||
if (instance) {
|
||||
if (__DEV__) {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import puppeteer, {
|
||||
type Browser,
|
||||
type ClickOptions,
|
||||
type LaunchOptions,
|
||||
type Page,
|
||||
type PuppeteerLaunchOptions,
|
||||
} from 'puppeteer'
|
||||
|
||||
export const E2E_TIMEOUT: number = 30 * 1000
|
||||
|
||||
const puppeteerOptions: PuppeteerLaunchOptions = {
|
||||
const puppeteerOptions: LaunchOptions = {
|
||||
args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
|
||||
headless: true,
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ describe('e2e: todomvc', () => {
|
|||
classList,
|
||||
enterValue,
|
||||
clearValue,
|
||||
timeout,
|
||||
} = setupPuppeteer()
|
||||
|
||||
async function removeItemAt(n: number) {
|
||||
|
@ -101,6 +102,7 @@ describe('e2e: todomvc', () => {
|
|||
|
||||
// active filter
|
||||
await click('.filters li:nth-child(2) a')
|
||||
await timeout(1)
|
||||
expect(await count('.todo')).toBe(1)
|
||||
expect(await count('.todo.completed')).toBe(0)
|
||||
// add item with filter active
|
||||
|
@ -109,6 +111,7 @@ describe('e2e: todomvc', () => {
|
|||
|
||||
// completed filter
|
||||
await click('.filters li:nth-child(3) a')
|
||||
await timeout(1)
|
||||
expect(await count('.todo')).toBe(2)
|
||||
expect(await count('.todo.completed')).toBe(2)
|
||||
|
||||
|
@ -128,12 +131,14 @@ describe('e2e: todomvc', () => {
|
|||
await click('.todo .toggle')
|
||||
expect(await count('.todo')).toBe(1)
|
||||
await click('.filters li:nth-child(2) a')
|
||||
await timeout(1)
|
||||
expect(await count('.todo')).toBe(3)
|
||||
await click('.todo .toggle')
|
||||
expect(await count('.todo')).toBe(2)
|
||||
|
||||
// editing triggered by blur
|
||||
await click('.filters li:nth-child(1) a')
|
||||
await timeout(1)
|
||||
await click('.todo:nth-child(1) label', { clickCount: 2 })
|
||||
expect(await count('.todo.editing')).toBe(1)
|
||||
expect(await isFocused('.todo:nth-child(1) .edit')).toBe(true)
|
||||
|
|
2045
pnpm-lock.yaml
2045
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,9 @@
|
|||
import { configDefaults, defineConfig } from 'vitest/config'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import { entries } from './scripts/aliases.js'
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
__DEV__: true,
|
||||
__DEV__: process.env.MODE !== 'benchmark',
|
||||
__TEST__: true,
|
||||
__VERSION__: '"test"',
|
||||
__BROWSER__: false,
|
||||
|
@ -25,6 +25,11 @@ export default defineConfig({
|
|||
test: {
|
||||
globals: true,
|
||||
pool: 'threads',
|
||||
poolOptions: {
|
||||
forks: {
|
||||
execArgv: ['--expose-gc'],
|
||||
},
|
||||
},
|
||||
setupFiles: 'scripts/setup-vitest.ts',
|
||||
environmentMatchGlobs: [
|
||||
['packages/{vue,vue-compat,runtime-dom,runtime-vapor}/**', 'jsdom'],
|
||||
|
|
Loading…
Reference in New Issue