Browse Source

chore: Merge branch 'main' into minor

Evan You 2 years ago
parent
commit
982a145d38
44 changed files with 1456 additions and 355 deletions
  1. 25 0
      .github/workflows/ci.yml
  2. 17 68
      CHANGELOG.md
  3. 14 12
      package.json
  4. 41 0
      packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap
  5. 31 0
      packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
  6. 4 0
      packages/compiler-sfc/src/compileScript.ts
  7. 9 0
      packages/compiler-sfc/src/script/context.ts
  8. 11 0
      packages/compiler-sfc/src/script/defineProps.ts
  9. 4 0
      packages/compiler-sfc/src/script/resolveType.ts
  10. 11 1
      packages/dts-test/ref.test-d.ts
  11. 96 0
      packages/dts-test/setupHelpers.test-d.ts
  12. 8 0
      packages/dts-test/tsx.test-d.tsx
  13. 41 0
      packages/reactivity-transform/package.json
  14. 126 0
      packages/reactivity/__tests__/computed.bench.ts
  15. 26 4
      packages/reactivity/__tests__/reactive.spec.ts
  16. 92 0
      packages/reactivity/__tests__/reactiveArray.bench.ts
  17. 9 0
      packages/reactivity/__tests__/reactiveArray.spec.ts
  18. 143 0
      packages/reactivity/__tests__/reactiveMap.bench.ts
  19. 114 0
      packages/reactivity/__tests__/reactiveObject.bench.ts
  20. 33 0
      packages/reactivity/__tests__/ref.bench.ts
  21. 26 18
      packages/reactivity/src/baseHandlers.ts
  22. 0 1
      packages/reactivity/src/ref.ts
  23. 58 0
      packages/runtime-core/__tests__/apiWatch.bench.ts
  24. 92 0
      packages/runtime-core/__tests__/apiWatch.spec.ts
  25. 66 0
      packages/runtime-core/__tests__/components/Suspense.spec.ts
  26. 4 3
      packages/runtime-core/src/apiSetupHelpers.ts
  27. 8 9
      packages/runtime-core/src/componentEmits.ts
  28. 1 1
      packages/runtime-core/src/componentSlots.ts
  29. 3 2
      packages/runtime-core/src/components/Suspense.ts
  30. 1 1
      packages/runtime-core/src/renderer.ts
  31. 4 0
      packages/runtime-core/src/scheduler.ts
  32. 7 0
      packages/runtime-dom/__tests__/patchProps.spec.ts
  33. 1 1
      packages/runtime-dom/package.json
  34. 1 0
      packages/runtime-dom/src/jsx.ts
  35. 5 3
      packages/runtime-dom/src/patchProp.ts
  36. 1 1
      packages/sfc-playground/package.json
  37. 1 1
      packages/sfc-playground/src/Header.vue
  38. 6 2
      packages/sfc-playground/src/VersionSelect.vue
  39. 45 0
      packages/shared/__tests__/toDisplayString.spec.ts
  40. 1 1
      packages/shared/src/domAttrConfig.ts
  41. 16 6
      packages/shared/src/toDisplayString.ts
  42. 1 1
      packages/template-explorer/package.json
  43. 249 215
      pnpm-lock.yaml
  44. 4 4
      vitest.config.ts

+ 25 - 0
.github/workflows/ci.yml

@@ -58,6 +58,31 @@ jobs:
       - name: Run ssr unit tests
         run: pnpm run test-unit server-renderer
 
+  benchmarks:
+    runs-on: ubuntu-latest
+    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
+    env:
+      PUPPETEER_SKIP_DOWNLOAD: 'true'
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install pnpm
+        uses: pnpm/action-setup@v2
+
+      - name: Install Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version-file: '.node-version'
+          cache: 'pnpm'
+
+      - run: pnpm install
+
+      - name: Run benchmarks
+        uses: CodSpeedHQ/action@v2
+        with:
+          run: pnpm vitest bench --run
+          token: ${{ secrets.CODSPEED_TOKEN }}
+
   e2e-test:
     runs-on: ubuntu-latest
     if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository

+ 17 - 68
CHANGELOG.md

@@ -1,3 +1,20 @@
+## [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)
 
 
@@ -19,74 +36,6 @@
 
 
 
-# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
-
-
-### Bug Fixes
-
-* **parser:** directive arg should be undefined on shorthands with no arg ([e49dffc](https://github.com/vuejs/core/commit/e49dffc9ece86bddf094b9ad4ad15eb4856d6277))
-
-
-### Features
-
-* **dx:** link errors to docs in prod build ([#9165](https://github.com/vuejs/core/issues/9165)) ([9f8ba98](https://github.com/vuejs/core/commit/9f8ba9821fe166f77e63fa940e9e7e13ec3344fa))
-
-
-
-# [3.4.0-alpha.2](https://github.com/vuejs/core/compare/v3.3.9...v3.4.0-alpha.2) (2023-11-27)
-
-
-### Bug Fixes
-
-* avoid confusing breakage in @vitejs/plugin-vue ([ceec69c](https://github.com/vuejs/core/commit/ceec69c8ccb96c433a4a506ad2e85e276998bade))
-* **compiler-core:** fix line/column tracking when fast forwarding ([2e65ea4](https://github.com/vuejs/core/commit/2e65ea481f74db8649df8110a031cbdc98f98c84))
-* **compiler-sfc:** fix ast reuse for ssr ([fb619cf](https://github.com/vuejs/core/commit/fb619cf9a440239f0ba88e327d10001a6a3c8171))
-* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([c6083dc](https://github.com/vuejs/core/commit/c6083dcad31f3e9292c687fada9e32f287e2317f))
-* **compiler-sfc:** use correct compiler when re-parsing in ssr mode ([678378a](https://github.com/vuejs/core/commit/678378afd559481badb486b243722b6287862e09))
-
-
-* feat!: remove reactivity transform (#9321) ([79b8a09](https://github.com/vuejs/core/commit/79b8a0905bf363bf82edd2096fef10c3db6d9c3c)), closes [#9321](https://github.com/vuejs/core/issues/9321)
-
-
-### Features
-
-* **compiler-core:** support specifying root namespace when parsing ([40f72d5](https://github.com/vuejs/core/commit/40f72d5e50b389cb11b7ca13461aa2a75ddacdb4))
-* **compiler-core:** support v-bind shorthand for key and value with the same name ([#9451](https://github.com/vuejs/core/issues/9451)) ([26399aa](https://github.com/vuejs/core/commit/26399aa6fac1596b294ffeba06bb498d86f5508c))
-* **compiler:** improve parsing tolerance for language-tools ([41ff68e](https://github.com/vuejs/core/commit/41ff68ea579d933333392146625560359acb728a))
-* **reactivity:** expose last result for computed getter ([#9497](https://github.com/vuejs/core/issues/9497)) ([48b47a1](https://github.com/vuejs/core/commit/48b47a1ab63577e2dbd91947eea544e3ef185b85))
-
-
-### Performance Improvements
-
-* avoid sfc source map unnecessary serialization and parsing ([f15d2f6](https://github.com/vuejs/core/commit/f15d2f6cf69c0c39f8dfb5c33122790c68bf92e2))
-* **codegen:** optimize line / column calculation during codegen ([3be53d9](https://github.com/vuejs/core/commit/3be53d9b974dae1a10eb795cade71ae765e17574))
-* **codegen:** optimize source map generation ([c11002f](https://github.com/vuejs/core/commit/c11002f16afd243a2b15b546816e73882eea9e4d))
-* **compiler-sfc:** remove magic-string trim on script ([e8e3ec6](https://github.com/vuejs/core/commit/e8e3ec6ca7392e43975c75b56eaaa711d5ea9410))
-* **compiler-sfc:** use faster source map addMapping ([50cde7c](https://github.com/vuejs/core/commit/50cde7cfbcc49022ba88f5f69fa9b930b483c282))
-* optimize away isBuiltInType ([66c0ed0](https://github.com/vuejs/core/commit/66c0ed0a3c1c6f37dafc6b1c52b75c6bf60e3136))
-* optimize makeMap ([ae6fba9](https://github.com/vuejs/core/commit/ae6fba94954bac6430902f77b0d1113a98a75b18))
-* optimize position cloning ([2073236](https://github.com/vuejs/core/commit/20732366b9b3530d33b842cf1fc985919afb9317))
-
-
-### BREAKING CHANGES
-
-* Reactivity Transform was marked deprecated in 3.3 and is now removed in 3.4. This change does not require a major due to the feature being experimental. Users who wish to continue using the feature can do so via the external plugin at https://vue-macros.dev/features/reactivity-transform.html
-
-
-
-# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
-
-
-### Features
-
-* **compiler-core:** export error message ([#8729](https://github.com/vuejs/core/issues/8729)) ([f7e80ee](https://github.com/vuejs/core/commit/f7e80ee4a065a9eaba98720abf415d9e87756cbd))
-* **compiler-sfc:** expose resolve type-based props and emits ([#8874](https://github.com/vuejs/core/issues/8874)) ([9e77580](https://github.com/vuejs/core/commit/9e77580c0c2f0d977bd0031a1d43cc334769d433))
-* export runtime error strings ([#9301](https://github.com/vuejs/core/issues/9301)) ([feb2f2e](https://github.com/vuejs/core/commit/feb2f2edce2d91218a5e9a52c81e322e4033296b))
-* **reactivity:** more efficient reactivity system ([#5912](https://github.com/vuejs/core/issues/5912)) ([16e06ca](https://github.com/vuejs/core/commit/16e06ca08f5a1e2af3fc7fb35de153dbe0c3087d)), closes [#311](https://github.com/vuejs/core/issues/311) [#1811](https://github.com/vuejs/core/issues/1811) [#6018](https://github.com/vuejs/core/issues/6018) [#7160](https://github.com/vuejs/core/issues/7160) [#8714](https://github.com/vuejs/core/issues/8714) [#9149](https://github.com/vuejs/core/issues/9149) [#9419](https://github.com/vuejs/core/issues/9419) [#9464](https://github.com/vuejs/core/issues/9464)
-* **runtime-core:** add `once` option to watch ([#9034](https://github.com/vuejs/core/issues/9034)) ([a645e7a](https://github.com/vuejs/core/commit/a645e7aa51006516ba668b3a4365d296eb92ee7d))
-
-
-
 ## [3.3.10](https://github.com/vuejs/core/compare/v3.3.9...v3.3.10) (2023-12-04)
 
 

+ 14 - 12
package.json

@@ -1,7 +1,7 @@
 {
   "private": true,
   "version": "3.4.0-alpha.4",
-  "packageManager": "pnpm@8.11.0",
+  "packageManager": "pnpm@8.12.0",
   "type": "module",
   "scripts": {
     "dev": "node scripts/dev.js",
@@ -22,6 +22,7 @@
     "test-dts": "run-s build-dts test-dts-only",
     "test-dts-only": "tsc -p ./packages/dts-test/tsconfig.test.json",
     "test-coverage": "vitest -c vitest.unit.config.ts --coverage",
+    "test-bench": "vitest bench",
     "release": "node scripts/release.js",
     "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
     "dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
@@ -60,6 +61,7 @@
   "devDependencies": {
     "@babel/parser": "^7.23.5",
     "@babel/types": "^7.23.5",
+    "@codspeed/vitest-plugin": "^2.3.1",
     "@rollup/plugin-alias": "^5.0.1",
     "@rollup/plugin-commonjs": "^25.0.7",
     "@rollup/plugin-json": "^6.0.1",
@@ -68,33 +70,33 @@
     "@rollup/plugin-terser": "^0.4.4",
     "@types/hash-sum": "^1.0.2",
     "@types/minimist": "^1.2.5",
-    "@types/node": "^20.10.3",
+    "@types/node": "^20.10.4",
     "@types/semver": "^7.5.5",
-    "@typescript-eslint/parser": "^6.13.0",
-    "@vitest/coverage-istanbul": "^0.34.6",
+    "@typescript-eslint/parser": "^6.13.2",
+    "@vitest/coverage-istanbul": "^1.0.4",
     "@vue/consolidate": "0.17.3",
     "conventional-changelog-cli": "^4.1.0",
     "enquirer": "^2.4.1",
     "esbuild": "^0.19.5",
     "esbuild-plugin-polyfill-node": "^0.3.0",
-    "eslint": "^8.54.0",
+    "eslint": "^8.55.0",
     "eslint-define-config": "^1.24.1",
     "eslint-plugin-jest": "^27.6.0",
     "estree-walker": "^2.0.2",
     "execa": "^8.0.1",
-    "jsdom": "^22.1.0",
-    "lint-staged": "^15.1.0",
+    "jsdom": "^23.0.1",
+    "lint-staged": "^15.2.0",
     "lodash": "^4.17.21",
     "magic-string": "^0.30.5",
     "markdown-table": "^3.0.3",
-    "marked": "^9.1.6",
+    "marked": "^11.0.1",
     "minimist": "^1.2.8",
     "npm-run-all": "^4.1.5",
     "picocolors": "^1.0.0",
-    "prettier": "^3.1.0",
+    "prettier": "^3.1.1",
     "pretty-bytes": "^6.1.1",
     "pug": "^3.0.2",
-    "puppeteer": "~21.5.2",
+    "puppeteer": "~21.6.0",
     "rimraf": "^5.0.5",
     "rollup": "^4.1.4",
     "rollup-plugin-dts": "^6.1.0",
@@ -108,7 +110,7 @@
     "tslib": "^2.6.2",
     "tsx": "^4.6.2",
     "typescript": "^5.2.2",
-    "vite": "^5.0.0",
-    "vitest": "^1.0.0"
+    "vite": "^5.0.5",
+    "vitest": "^1.0.4"
   }
 }

+ 41 - 0
packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap

@@ -18,6 +18,47 @@ return { props, bar }
 }"
 `;
 
+exports[`defineProps > custom element retains the props type & default value & production mode 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Props { 
+          foo?: number;
+      }
+      
+export default /*#__PURE__*/_defineComponent({
+  __name: 'app.ce',
+  props: {
+    foo: { default: 5.5, type: Number }
+  },
+  setup(__props: any, { expose: __expose }) {
+  __expose();
+
+      const props = __props;
+      
+return { props }
+}
+
+})"
+`;
+
+exports[`defineProps > custom element retains the props type & production mode 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  __name: 'app.ce',
+  props: {
+    foo: {type: Number}
+  },
+  setup(__props: any, { expose: __expose }) {
+  __expose();
+
+      const props = __props
+      
+return { props }
+}
+
+})"
+`;
+
 exports[`defineProps > defineProps w/ runtime options 1`] = `
 "import { defineComponent as _defineComponent } from 'vue'
 

+ 31 - 0
packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts

@@ -710,4 +710,35 @@ const props = defineProps({ foo: String })
       'da-sh': BindingTypes.PROPS
     })
   })
+
+  // #8989
+  test('custom element retains the props type & production mode', () => {
+    const { content } = compile(
+      `<script setup lang="ts">
+      const props = defineProps<{ foo: number}>()
+      </script>`,
+      { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
+      { filename: 'app.ce.vue' }
+    )
+
+    expect(content).toMatch(`foo: {type: Number}`)
+    assertCode(content)
+  })
+
+  test('custom element retains the props type & default value & production mode', () => {
+    const { content } = compile(
+      `<script setup lang="ts">
+      interface Props { 
+          foo?: number;
+      }
+      const props = withDefaults(defineProps<Props>(), {
+          foo: 5.5,
+      });
+      </script>`,
+      { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
+      { filename: 'app.ce.vue' }
+    )
+    expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
+    assertCode(content)
+  })
 })

+ 4 - 0
packages/compiler-sfc/src/compileScript.ts

@@ -117,6 +117,10 @@ export interface SFCScriptCompileOptions {
     fileExists(file: string): boolean
     readFile(file: string): string | undefined
   }
+  /**
+   * Transform Vue SFCs into custom elements.
+   */
+  customElement?: boolean | ((filename: string) => boolean)
 }
 
 export interface ImportBinding {

+ 9 - 0
packages/compiler-sfc/src/script/context.ts

@@ -12,6 +12,7 @@ import { TypeScope } from './resolveType'
 export class ScriptCompileContext {
   isJS: boolean
   isTS: boolean
+  isCE = false
 
   scriptAst: Program | null
   scriptSetupAst: Program | null
@@ -95,6 +96,14 @@ export class ScriptCompileContext {
       scriptSetupLang === 'ts' ||
       scriptSetupLang === 'tsx'
 
+    const customElement = options.customElement
+    const filename = this.descriptor.filename
+    if (customElement) {
+      this.isCE =
+        typeof customElement === 'boolean'
+          ? customElement
+          : customElement(filename)
+    }
     // resolve parser plugins
     const plugins: ParserPlugin[] = resolveParserPlugins(
       (scriptLang || scriptSetupLang)!,

+ 11 - 0
packages/compiler-sfc/src/script/defineProps.ts

@@ -281,6 +281,17 @@ function genRuntimePropFromType(
       defaultString
     ])} }`
   } else {
+    // #8989 for custom element, should keep the type
+    if (ctx.isCE) {
+      if (defaultString) {
+        return `${finalKey}: ${`{ ${defaultString}, type: ${toRuntimeTypeString(
+          type
+        )} }`}`
+      } else {
+        return `${finalKey}: {type: ${toRuntimeTypeString(type)}}`
+      }
+    }
+
     // production: checks are useless
     return `${finalKey}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
   }

+ 4 - 0
packages/compiler-sfc/src/script/resolveType.ts

@@ -83,6 +83,9 @@ export type SimpleTypeResolveContext = Pick<
 
   // emits
   | 'emitsTypeDecl'
+
+  // customElement
+  | 'isCE'
 > &
   Partial<
     Pick<ScriptCompileContext, 'scope' | 'globalScopes' | 'deps' | 'fs'>
@@ -1475,6 +1478,7 @@ export function inferRuntimeType(
             scope
           )
         }
+        break
       case 'TSMethodSignature':
       case 'TSFunctionType':
         return ['Function']

+ 11 - 1
packages/dts-test/ref.test-d.ts

@@ -18,7 +18,7 @@ import {
   computed,
   ShallowRef
 } from 'vue'
-import { expectType, describe, IsUnion } from './utils'
+import { expectType, describe, IsUnion, IsAny } from './utils'
 
 function plainType(arg: number | Ref<number>) {
   // ref coercing
@@ -79,6 +79,10 @@ function plainType(arg: number | Ref<number>) {
   // should still unwrap in objects nested in arrays
   const arr2 = ref([{ a: ref(1) }]).value
   expectType<number>(arr2[0].a)
+
+  // any value should return Ref<any>, not any
+  const a = ref(1 as any)
+  expectType<IsAny<typeof a>>(false)
 }
 
 plainType(1)
@@ -191,6 +195,12 @@ if (refStatus.value === 'initial') {
   expectType<IsUnion<typeof shallowUnionAsCast>>(false)
 }
 
+{
+  // any value should return Ref<any>, not any
+  const a = shallowRef(1 as any)
+  expectType<IsAny<typeof a>>(false)
+}
+
 describe('shallowRef with generic', <T>() => {
   const r = ref({}) as MaybeRef<T>
   expectType<ShallowRef<T> | Ref<T>>(shallowRef(r))

+ 96 - 0
packages/dts-test/setupHelpers.test-d.ts

@@ -260,6 +260,30 @@ describe('defineSlots', () => {
   expectType<Slots>(slotsUntype)
 })
 
+describe('defineSlots generic', <T extends Record<string, any>>() => {
+  const props = defineProps<{
+    item: T
+  }>()
+
+  const slots = defineSlots<
+    {
+      [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
+    } & {
+      label?: (props: { item: T }) => any
+    }
+  >()
+
+  for (const key of Object.keys(props.item) as (keyof T & string)[]) {
+    slots[`slot-${String(key)}`]?.({
+      item: props.item
+    })
+  }
+  slots.label?.({ item: props.item })
+
+  // @ts-expect-error calling wrong slot
+  slots.foo({})
+})
+
 describe('defineModel', () => {
   // overload 1
   const modelValueRequired = defineModel<boolean>({ required: true })
@@ -336,6 +360,78 @@ describe('useSlots', () => {
   expectType<Slots>(slots)
 })
 
+describe('defineSlots generic', <T extends Record<string, any>>() => {
+  const props = defineProps<{
+    item: T
+  }>()
+
+  const slots = defineSlots<
+    {
+      [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
+    } & {
+      label?: (props: { item: T }) => any
+    }
+  >()
+
+  // @ts-expect-error slots should be readonly
+  slots.label = () => {}
+
+  // @ts-expect-error non existing slot
+  slots['foo-asdas']?.({
+    item: props.item
+  })
+  for (const key in props.item) {
+    slots[`slot-${String(key)}`]?.({
+      item: props.item
+    })
+    slots[`slot-${String(key as keyof T)}`]?.({
+      item: props.item
+    })
+  }
+
+  for (const key of Object.keys(props.item) as (keyof T)[]) {
+    slots[`slot-${String(key)}`]?.({
+      item: props.item
+    })
+  }
+  slots.label?.({ item: props.item })
+
+  // @ts-expect-error calling wrong slot
+  slots.foo({})
+})
+
+describe('defineSlots generic strict', <T extends {
+  foo: 'foo'
+  bar: 'bar'
+}>() => {
+  const props = defineProps<{
+    item: T
+  }>()
+
+  const slots = defineSlots<
+    {
+      [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
+    } & {
+      label?: (props: { item: T }) => any
+    }
+  >()
+
+  // slot-bar/foo should be automatically inferred
+  slots['slot-bar']?.({ item: props.item })
+  slots['slot-foo']?.({ item: props.item })
+
+  slots.label?.({ item: props.item })
+
+  // @ts-expect-error not part of the extends
+  slots['slot-RANDOM']?.({ item: props.item })
+
+  // @ts-expect-error slots should be readonly
+  slots.label = () => {}
+
+  // @ts-expect-error calling wrong slot
+  slots.foo({})
+})
+
 // #6420
 describe('toRefs w/ type declaration', () => {
   const props = defineProps<{

+ 8 - 0
packages/dts-test/tsx.test-d.tsx

@@ -112,3 +112,11 @@ expectType<JSX.Element>(
 )
 // @ts-expect-error
 ;<Suspense onResolve={123} />
+
+// svg
+expectType<JSX.Element>(
+  <svg
+    xmlnsXlink="http://www.w3.org/1999/xlink"
+    xmlns="http://www.w3.org/2000/svg"
+  />
+)

+ 41 - 0
packages/reactivity-transform/package.json

@@ -0,0 +1,41 @@
+{
+  "name": "@vue/reactivity-transform",
+  "version": "3.3.11",
+  "description": "@vue/reactivity-transform",
+  "main": "dist/reactivity-transform.cjs.js",
+  "files": [
+    "dist"
+  ],
+  "buildOptions": {
+    "formats": [
+      "cjs"
+    ],
+    "prod": false
+  },
+  "types": "dist/reactivity-transform.d.ts",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/vuejs/core.git",
+    "directory": "packages/reactivity-transform"
+  },
+  "keywords": [
+    "vue"
+  ],
+  "author": "Evan You",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/vuejs/core/issues"
+  },
+  "homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
+  "dependencies": {
+    "@babel/parser": "^7.23.5",
+    "@vue/compiler-core": "workspace:*",
+    "@vue/shared": "workspace:*",
+    "estree-walker": "^2.0.2",
+    "magic-string": "^0.30.5"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.23.5",
+    "@babel/types": "^7.23.5"
+  }
+}

+ 126 - 0
packages/reactivity/__tests__/computed.bench.ts

@@ -0,0 +1,126 @@
+import { describe, bench } from 'vitest'
+import { ComputedRef, Ref, computed, ref } from '../src/index'
+
+describe('computed', () => {
+  bench('create computed', () => {
+    computed(() => 100)
+  })
+
+  {
+    let i = 0
+    const o = ref(100)
+    bench('write independent ref dep', () => {
+      o.value = i++
+    })
+  }
+
+  {
+    const v = ref(100)
+    computed(() => v.value * 2)
+    let i = 0
+    bench("write ref, don't read computed (never invoked)", () => {
+      v.value = i++
+    })
+  }
+
+  {
+    const v = ref(100)
+    computed(() => {
+      return v.value * 2
+    })
+    let i = 0
+    bench("write ref, don't read computed (never invoked)", () => {
+      v.value = i++
+    })
+  }
+
+  {
+    const v = ref(100)
+    const c = computed(() => {
+      return v.value * 2
+    })
+    c.value
+    let i = 0
+    bench("write ref, don't read computed (invoked)", () => {
+      v.value = i++
+    })
+  }
+
+  {
+    const v = ref(100)
+    const c = computed(() => {
+      return v.value * 2
+    })
+    let i = 0
+    bench('write ref, read computed', () => {
+      v.value = i++
+      c.value
+    })
+  }
+
+  {
+    const v = ref(100)
+    const computeds = []
+    for (let i = 0, n = 1000; i < n; i++) {
+      const c = computed(() => {
+        return v.value * 2
+      })
+      computeds.push(c)
+    }
+    let i = 0
+    bench("write ref, don't read 1000 computeds (never invoked)", () => {
+      v.value = i++
+    })
+  }
+
+  {
+    const v = ref(100)
+    const computeds = []
+    for (let i = 0, n = 1000; i < n; i++) {
+      const c = computed(() => {
+        return v.value * 2
+      })
+      c.value
+      computeds.push(c)
+    }
+    let i = 0
+    bench("write ref, don't read 1000 computeds (invoked)", () => {
+      v.value = i++
+    })
+  }
+
+  {
+    const v = ref(100)
+    const computeds: ComputedRef<number>[] = []
+    for (let i = 0, n = 1000; i < n; i++) {
+      const c = computed(() => {
+        return v.value * 2
+      })
+      c.value
+      computeds.push(c)
+    }
+    let i = 0
+    bench('write ref, read 1000 computeds', () => {
+      v.value = i++
+      computeds.forEach(c => c.value)
+    })
+  }
+
+  {
+    const refs: Ref<number>[] = []
+    for (let i = 0, n = 1000; i < n; i++) {
+      refs.push(ref(i))
+    }
+    const c = computed(() => {
+      let total = 0
+      refs.forEach(ref => (total += ref.value))
+      return total
+    })
+    let i = 0
+    const n = refs.length
+    bench('1000 refs, 1 computed', () => {
+      refs[i++ % n].value++
+      c.value
+    })
+  }
+})

+ 26 - 4
packages/reactivity/__tests__/reactive.spec.ts

@@ -158,6 +158,21 @@ describe('reactivity/reactive', () => {
     expect(original.bar).toBe(original2)
   })
 
+  // #1246
+  test('mutation on objects using reactive as prototype should not trigger', () => {
+    const observed = reactive({ foo: 1 })
+    const original = Object.create(observed)
+    let dummy
+    effect(() => (dummy = original.foo))
+    expect(dummy).toBe(1)
+    observed.foo = 2
+    expect(dummy).toBe(2)
+    original.foo = 3
+    expect(dummy).toBe(2)
+    original.foo = 4
+    expect(dummy).toBe(2)
+  })
+
   test('toRaw', () => {
     const original = { foo: 1 }
     const observed = reactive(original)
@@ -166,11 +181,18 @@ describe('reactivity/reactive', () => {
   })
 
   test('toRaw on object using reactive as prototype', () => {
-    const original = reactive({})
-    const obj = Object.create(original)
+    const original = { foo: 1 }
+    const observed = reactive(original)
+    const inherted = Object.create(observed)
+    expect(toRaw(inherted)).toBe(inherted)
+  })
+
+  test('toRaw on user Proxy wrapping reactive', () => {
+    const original = {}
+    const re = reactive(original)
+    const obj = new Proxy(re, {})
     const raw = toRaw(obj)
-    expect(raw).toBe(obj)
-    expect(raw).not.toBe(toRaw(original))
+    expect(raw).toBe(original)
   })
 
   test('should not unwrap Ref<T>', () => {

+ 92 - 0
packages/reactivity/__tests__/reactiveArray.bench.ts

@@ -0,0 +1,92 @@
+import { bench } from 'vitest'
+import { computed, reactive, readonly, shallowRef, triggerRef } from '../src'
+
+for (let amount = 1e1; amount < 1e4; amount *= 10) {
+  {
+    const rawArray = []
+    for (let i = 0, n = amount; i < n; i++) {
+      rawArray.push(i)
+    }
+    const r = reactive(rawArray)
+    const c = computed(() => {
+      return r.reduce((v, a) => a + v, 0)
+    })
+
+    bench(`reduce *reactive* array, ${amount} elements`, () => {
+      for (let i = 0, n = r.length; i < n; i++) {
+        r[i]++
+      }
+      c.value
+    })
+  }
+
+  {
+    const rawArray = []
+    for (let i = 0, n = amount; i < n; i++) {
+      rawArray.push(i)
+    }
+    const r = reactive(rawArray)
+    const c = computed(() => {
+      return r.reduce((v, a) => a + v, 0)
+    })
+
+    bench(
+      `reduce *reactive* array, ${amount} elements, only change first value`,
+      () => {
+        r[0]++
+        c.value
+      }
+    )
+  }
+
+  {
+    const rawArray = []
+    for (let i = 0, n = amount; i < n; i++) {
+      rawArray.push(i)
+    }
+    const r = reactive({ arr: readonly(rawArray) })
+    const c = computed(() => {
+      return r.arr.reduce((v, a) => a + v, 0)
+    })
+
+    bench(`reduce *readonly* array, ${amount} elements`, () => {
+      r.arr = r.arr.map(v => v + 1)
+      c.value
+    })
+  }
+
+  {
+    const rawArray = []
+    for (let i = 0, n = amount; i < n; i++) {
+      rawArray.push(i)
+    }
+    const r = shallowRef(rawArray)
+    const c = computed(() => {
+      return r.value.reduce((v, a) => a + v, 0)
+    })
+
+    bench(`reduce *raw* array, copied, ${amount} elements`, () => {
+      r.value = r.value.map(v => v + 1)
+      c.value
+    })
+  }
+
+  {
+    const rawArray: number[] = []
+    for (let i = 0, n = amount; i < n; i++) {
+      rawArray.push(i)
+    }
+    const r = shallowRef(rawArray)
+    const c = computed(() => {
+      return r.value.reduce((v, a) => a + v, 0)
+    })
+
+    bench(`reduce *raw* array, manually triggered, ${amount} elements`, () => {
+      for (let i = 0, n = rawArray.length; i < n; i++) {
+        rawArray[i]++
+      }
+      triggerRef(r)
+      c.value
+    })
+  }
+}

+ 9 - 0
packages/reactivity/__tests__/reactiveArray.spec.ts

@@ -175,6 +175,15 @@ describe('reactivity/reactive/Array', () => {
     expect(length).toBe('01')
   })
 
+  // #9742
+  test('mutation on user proxy of reactive Array', () => {
+    const array = reactive<number[]>([])
+    const proxy = new Proxy(array, {})
+    proxy.push(1)
+    expect(array).toHaveLength(1)
+    expect(proxy).toHaveLength(1)
+  })
+
   describe('Array methods w/ refs', () => {
     let original: any[]
     beforeEach(() => {

+ 143 - 0
packages/reactivity/__tests__/reactiveMap.bench.ts

@@ -0,0 +1,143 @@
+import { bench } from 'vitest'
+import { reactive, computed, ComputedRef } from '../src'
+
+function createMap(obj: Record<string, any>) {
+  const map = new Map()
+  for (const key in obj) {
+    if (obj.hasOwnProperty(key)) {
+      map.set(key, obj[key])
+    }
+  }
+  return map
+}
+
+bench('create reactive map', () => {
+  reactive(createMap({ a: 1 }))
+})
+
+{
+  let i = 0
+  const r = reactive(createMap({ a: 1 }))
+  bench('write reactive map property', () => {
+    r.set('a', i++)
+  })
+}
+
+{
+  const r = reactive(createMap({ a: 1 }))
+  computed(() => {
+    return r.get('a') * 2
+  })
+  let i = 0
+  bench("write reactive map, don't read computed (never invoked)", () => {
+    r.set('a', i++)
+  })
+}
+
+{
+  const r = reactive(createMap({ a: 1 }))
+  const c = computed(() => {
+    return r.get('a') * 2
+  })
+  c.value
+  let i = 0
+  bench("write reactive map, don't read computed (invoked)", () => {
+    r.set('a', i++)
+  })
+}
+
+{
+  const r = reactive(createMap({ a: 1 }))
+  const c = computed(() => {
+    return r.get('a') * 2
+  })
+  let i = 0
+  bench('write reactive map, read computed', () => {
+    r.set('a', i++)
+    c.value
+  })
+}
+
+{
+  const _m = new Map()
+  for (let i = 0; i < 10000; i++) {
+    _m.set(i, i)
+  }
+  const r = reactive(_m)
+  const c = computed(() => {
+    let total = 0
+    r.forEach((value, key) => {
+      total += value
+    })
+    return total
+  })
+  bench("write reactive map (10'000 items), read computed", () => {
+    r.set(5000, r.get(5000) + 1)
+    c.value
+  })
+}
+
+{
+  const r = reactive(createMap({ a: 1 }))
+  const computeds = []
+  for (let i = 0, n = 1000; i < n; i++) {
+    const c = computed(() => {
+      return r.get('a') * 2
+    })
+    computeds.push(c)
+  }
+  let i = 0
+  bench("write reactive map, don't read 1000 computeds (never invoked)", () => {
+    r.set('a', i++)
+  })
+}
+
+{
+  const r = reactive(createMap({ a: 1 }))
+  const computeds = []
+  for (let i = 0, n = 1000; i < n; i++) {
+    const c = computed(() => {
+      return r.get('a') * 2
+    })
+    c.value
+    computeds.push(c)
+  }
+  let i = 0
+  bench("write reactive map, don't read 1000 computeds (invoked)", () => {
+    r.set('a', i++)
+  })
+}
+
+{
+  const r = reactive(createMap({ a: 1 }))
+  const computeds: ComputedRef<number>[] = []
+  for (let i = 0, n = 1000; i < n; i++) {
+    const c = computed(() => {
+      return r.get('a') * 2
+    })
+    computeds.push(c)
+  }
+  let i = 0
+  bench('write reactive map, read 1000 computeds', () => {
+    r.set('a', i++)
+    computeds.forEach(c => c.value)
+  })
+}
+
+{
+  const reactives: Map<any, any>[] = []
+  for (let i = 0, n = 1000; i < n; i++) {
+    reactives.push(reactive(createMap({ a: i })))
+  }
+  const c = computed(() => {
+    let total = 0
+    reactives.forEach(r => (total += r.get('a')))
+    return total
+  })
+  let i = 0
+  const n = reactives.length
+  bench('1000 reactive maps, 1 computed', () => {
+    reactives[i++ % n].set('a', reactives[i++ % n].get('a') + 1)
+    c.value
+  })
+}

+ 114 - 0
packages/reactivity/__tests__/reactiveObject.bench.ts

@@ -0,0 +1,114 @@
+import { bench } from 'vitest'
+import { ComputedRef, computed, reactive } from '../src'
+
+bench('create reactive obj', () => {
+  reactive({ a: 1 })
+})
+
+{
+  let i = 0
+  const r = reactive({ a: 1 })
+  bench('write reactive obj property', () => {
+    r.a = i++
+  })
+}
+
+{
+  const r = reactive({ a: 1 })
+  computed(() => {
+    return r.a * 2
+  })
+  let i = 0
+  bench("write reactive obj, don't read computed (never invoked)", () => {
+    r.a = i++
+  })
+}
+
+{
+  const r = reactive({ a: 1 })
+  const c = computed(() => {
+    return r.a * 2
+  })
+  c.value
+  let i = 0
+  bench("write reactive obj, don't read computed (invoked)", () => {
+    r.a = i++
+  })
+}
+
+{
+  const r = reactive({ a: 1 })
+  const c = computed(() => {
+    return r.a * 2
+  })
+  let i = 0
+  bench('write reactive obj, read computed', () => {
+    r.a = i++
+    c.value
+  })
+}
+
+{
+  const r = reactive({ a: 1 })
+  const computeds = []
+  for (let i = 0, n = 1000; i < n; i++) {
+    const c = computed(() => {
+      return r.a * 2
+    })
+    computeds.push(c)
+  }
+  let i = 0
+  bench("write reactive obj, don't read 1000 computeds (never invoked)", () => {
+    r.a = i++
+  })
+}
+
+{
+  const r = reactive({ a: 1 })
+  const computeds = []
+  for (let i = 0, n = 1000; i < n; i++) {
+    const c = computed(() => {
+      return r.a * 2
+    })
+    c.value
+    computeds.push(c)
+  }
+  let i = 0
+  bench("write reactive obj, don't read 1000 computeds (invoked)", () => {
+    r.a = i++
+  })
+}
+
+{
+  const r = reactive({ a: 1 })
+  const computeds: ComputedRef<number>[] = []
+  for (let i = 0, n = 1000; i < n; i++) {
+    const c = computed(() => {
+      return r.a * 2
+    })
+    computeds.push(c)
+  }
+  let i = 0
+  bench('write reactive obj, read 1000 computeds', () => {
+    r.a = i++
+    computeds.forEach(c => c.value)
+  })
+}
+
+{
+  const reactives: Record<string, number>[] = []
+  for (let i = 0, n = 1000; i < n; i++) {
+    reactives.push(reactive({ a: i }))
+  }
+  const c = computed(() => {
+    let total = 0
+    reactives.forEach(r => (total += r.a))
+    return total
+  })
+  let i = 0
+  const n = reactives.length
+  bench('1000 reactive objs, 1 computed', () => {
+    reactives[i++ % n].a++
+    c.value
+  })
+}

+ 33 - 0
packages/reactivity/__tests__/ref.bench.ts

@@ -0,0 +1,33 @@
+import { describe, bench } from 'vitest'
+import { ref } from '../src/index'
+
+describe('ref', () => {
+  bench('create ref', () => {
+    ref(100)
+  })
+
+  {
+    let i = 0
+    const v = ref(100)
+    bench('write ref', () => {
+      v.value = i++
+    })
+  }
+
+  {
+    const v = ref(100)
+    bench('read ref', () => {
+      v.value
+    })
+  }
+
+  {
+    let i = 0
+    const v = ref(100)
+    bench('write/read ref', () => {
+      v.value = i++
+
+      v.value
+    })
+  }
+})

+ 26 - 18
packages/reactivity/src/baseHandlers.ts

@@ -101,19 +101,25 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
       return isReadonly
     } else if (key === ReactiveFlags.IS_SHALLOW) {
       return shallow
-    } else if (
-      key === ReactiveFlags.RAW &&
-      receiver ===
-        (isReadonly
-          ? shallow
-            ? shallowReadonlyMap
-            : readonlyMap
-          : shallow
-            ? shallowReactiveMap
-            : reactiveMap
-        ).get(target)
-    ) {
-      return target
+    } else if (key === ReactiveFlags.RAW) {
+      if (
+        receiver ===
+          (isReadonly
+            ? shallow
+              ? shallowReadonlyMap
+              : readonlyMap
+            : shallow
+              ? shallowReactiveMap
+              : reactiveMap
+          ).get(target) ||
+        // receiver is not the reactive proxy, but has the same prototype
+        // this means the reciever is a user proxy of the reactive proxy
+        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
+      ) {
+        return target
+      }
+      // early return undefined
+      return
     }
 
     const targetIsArray = isArray(target)
@@ -169,17 +175,19 @@ class MutableReactiveHandler extends BaseReactiveHandler {
     receiver: object
   ): boolean {
     let oldValue = (target as any)[key]
-    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
-      return false
-    }
     if (!this._shallow) {
+      const isOldValueReadonly = isReadonly(oldValue)
       if (!isShallow(value) && !isReadonly(value)) {
         oldValue = toRaw(oldValue)
         value = toRaw(value)
       }
       if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
-        oldValue.value = value
-        return true
+        if (isOldValueReadonly) {
+          return false
+        } else {
+          oldValue.value = value
+          return true
+        }
       }
     } else {
       // in shallow mode, objects are set as-is regardless of reactive or not

+ 0 - 1
packages/reactivity/src/ref.ts

@@ -100,7 +100,6 @@ export function isRef(r: any): r is Ref {
  * @param value - The object to wrap in the ref.
  * @see {@link https://vuejs.org/api/reactivity-core.html#ref}
  */
-export function ref<T extends Ref>(value: T): T
 export function ref<T>(value: T): Ref<UnwrapRef<T>>
 export function ref<T = any>(): Ref<T | undefined>
 export function ref(value?: unknown) {

+ 58 - 0
packages/runtime-core/__tests__/apiWatch.bench.ts

@@ -0,0 +1,58 @@
+import { nextTick, ref, watch, watchEffect } from '../src'
+import { bench } from 'vitest'
+
+bench('create watcher', () => {
+  const v = ref(100)
+  watch(v, v => {})
+})
+
+{
+  const v = ref(100)
+  watch(v, v => {})
+  let i = 0
+  bench('update ref to trigger watcher (scheduled but not executed)', () => {
+    v.value = i++
+  })
+}
+
+{
+  const v = ref(100)
+  watch(v, v => {})
+  let i = 0
+  bench('update ref to trigger watcher (executed)', async () => {
+    v.value = i++
+    return nextTick()
+  })
+}
+
+{
+  bench('create watchEffect', () => {
+    watchEffect(() => {})
+  })
+}
+
+{
+  const v = ref(100)
+  watchEffect(() => {
+    v.value
+  })
+  let i = 0
+  bench(
+    'update ref to trigger watchEffect (scheduled but not executed)',
+    () => {
+      v.value = i++
+    }
+  )
+}
+
+{
+  const v = ref(100)
+  watchEffect(() => {
+    v.value
+  })
+  let i = 0
+  bench('update ref to trigger watchEffect (executed)', async () => {
+    v.value = i++
+    await nextTick()
+  })
+}

+ 92 - 0
packages/runtime-core/__tests__/apiWatch.spec.ts

@@ -549,6 +549,98 @@ describe('api: watch', () => {
     expect(cb).not.toHaveBeenCalled()
   })
 
+  // #7030
+  it('should not fire on child component unmount w/ flush: pre', async () => {
+    const visible = ref(true)
+    const cb = vi.fn()
+    const Parent = defineComponent({
+      props: ['visible'],
+      render() {
+        return visible.value ? h(Comp) : null
+      }
+    })
+    const Comp = {
+      setup() {
+        watch(visible, cb, { flush: 'pre' })
+      },
+      render() {}
+    }
+    const App = {
+      render() {
+        return h(Parent, {
+          visible: visible.value
+        })
+      }
+    }
+    render(h(App), nodeOps.createElement('div'))
+    expect(cb).not.toHaveBeenCalled()
+    visible.value = false
+    await nextTick()
+    expect(cb).not.toHaveBeenCalled()
+  })
+
+  // #7030
+  it('flush: pre watcher in child component should not fire before parent update', async () => {
+    const b = ref(0)
+    const calls: string[] = []
+
+    const Comp = {
+      setup() {
+        watch(
+          () => b.value,
+          val => {
+            calls.push('watcher child')
+          },
+          { flush: 'pre' }
+        )
+        return () => {
+          b.value
+          calls.push('render child')
+        }
+      }
+    }
+
+    const Parent = {
+      props: ['a'],
+      setup() {
+        watch(
+          () => b.value,
+          val => {
+            calls.push('watcher parent')
+          },
+          { flush: 'pre' }
+        )
+        return () => {
+          b.value
+          calls.push('render parent')
+          return h(Comp)
+        }
+      }
+    }
+
+    const App = {
+      render() {
+        return h(Parent, {
+          a: b.value
+        })
+      }
+    }
+
+    render(h(App), nodeOps.createElement('div'))
+    expect(calls).toEqual(['render parent', 'render child'])
+
+    b.value++
+    await nextTick()
+    expect(calls).toEqual([
+      'render parent',
+      'render child',
+      'watcher parent',
+      'render parent',
+      'watcher child',
+      'render child'
+    ])
+  })
+
   // #1763
   it('flush: pre watcher watching props should fire before child update', async () => {
     const a = ref(0)

+ 66 - 0
packages/runtime-core/__tests__/components/Suspense.spec.ts

@@ -1185,6 +1185,72 @@ describe('Suspense', () => {
     expect(calls).toEqual([`one mounted`, `one unmounted`, `two mounted`])
   })
 
+  test('mount the fallback content is in the correct position', async () => {
+    const makeComp = (name: string, delay = 0) =>
+      defineAsyncComponent(
+        {
+          setup() {
+            return () => h('div', [name])
+          }
+        },
+        delay
+      )
+
+    const One = makeComp('one')
+    const Two = makeComp('two', 20)
+
+    const view = shallowRef(One)
+
+    const Comp = {
+      setup() {
+        return () =>
+          h('div', [
+            h(
+              Suspense,
+              {
+                timeout: 10
+              },
+              {
+                default: h(view.value),
+                fallback: h('div', 'fallback')
+              }
+            ),
+            h('div', 'three')
+          ])
+      }
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(Comp), root)
+    expect(serializeInner(root)).toBe(
+      `<div><div>fallback</div><div>three</div></div>`
+    )
+
+    await deps[0]
+    await nextTick()
+    expect(serializeInner(root)).toBe(
+      `<div><div>one</div><div>three</div></div>`
+    )
+
+    view.value = Two
+    await nextTick()
+    expect(serializeInner(root)).toBe(
+      `<div><div>one</div><div>three</div></div>`
+    )
+
+    await new Promise(r => setTimeout(r, 10))
+    await nextTick()
+    expect(serializeInner(root)).toBe(
+      `<div><div>fallback</div><div>three</div></div>`
+    )
+
+    await deps[1]
+    await nextTick()
+    expect(serializeInner(root)).toBe(
+      `<div><div>two</div><div>three</div></div>`
+    )
+  })
+
   // #2214
   // Since suspense renders its own root like a component, it should not patch
   // its content in optimized mode.

+ 4 - 3
packages/runtime-core/src/apiSetupHelpers.ts

@@ -66,9 +66,9 @@ const warnRuntimeUsage = (method: string) =>
  *   foo?: string
  *   bar: number
  * }>()
+ * ```
  *
  * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
- * ```
  *
  * This is only usable inside `<script setup>`, is compiled away in the
  * output and should **not** be actually called at runtime.
@@ -116,8 +116,9 @@ type BooleanKey<T, K extends keyof T = keyof T> = K extends any
  * Example type-based declaration:
  * ```ts
  * const emit = defineEmits<{
- *   (event: 'change'): void
- *   (event: 'update', id: number): void
+ *   // <eventName>: <expected arguments>
+ *   change: []
+ *   update: [value: string] // named tuple syntax
  * }>()
  *
  * emit('change')

+ 8 - 9
packages/runtime-core/src/componentEmits.ts

@@ -38,19 +38,18 @@ export type EmitsOptions = ObjectEmitsOptions | string[]
 
 export type EmitsToProps<T extends EmitsOptions> = T extends string[]
   ? {
-      [K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
+      [K in `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
     }
   : T extends ObjectEmitsOptions
     ? {
-        [K in string &
-          `on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
-          ? T[Uncapitalize<C>] extends null
-            ? (...args: any[]) => any
-            : (
-                ...args: T[Uncapitalize<C>] extends (...args: infer P) => any
-                  ? P
+        [K in `on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
+          ? (
+              ...args: T[Uncapitalize<C>] extends (...args: infer P) => any
+                ? P
+                : T[Uncapitalize<C>] extends null
+                  ? any[]
                   : never
-              ) => any
+            ) => any
           : never
       }
     : {}

+ 1 - 1
packages/runtime-core/src/componentSlots.ts

@@ -44,7 +44,7 @@ export type SlotsType<T extends Record<string, any> = Record<string, any>> = {
 export type StrictUnwrapSlotsType<
   S extends SlotsType,
   T = NonNullable<S[typeof SlotSymbol]>
-> = [keyof S] extends [never] ? Slots : Readonly<T>
+> = [keyof S] extends [never] ? Slots : Readonly<T> & T
 
 export type UnwrapSlotsType<
   S extends SlotsType,

+ 3 - 2
packages/runtime-core/src/components/Suspense.ts

@@ -465,7 +465,7 @@ function createSuspenseBoundary(
     timeout: typeof timeout === 'number' ? timeout : -1,
     activeBranch: null,
     pendingBranch: null,
-    isInFallback: true,
+    isInFallback: !isHydrating,
     isHydrating,
     isUnmounted: false,
     effects: [],
@@ -583,6 +583,7 @@ function createSuspenseBoundary(
       // invoke @fallback event
       triggerEvent(vnode, 'onFallback')
 
+      const anchor = next(activeBranch!)
       const mountFallback = () => {
         if (!suspense.isInFallback) {
           return
@@ -592,7 +593,7 @@ function createSuspenseBoundary(
           null,
           fallbackVNode,
           container,
-          next(activeBranch!),
+          anchor,
           parentComponent,
           null, // fallback tree will not have suspense context
           namespace,

+ 1 - 1
packages/runtime-core/src/renderer.ts

@@ -1594,7 +1594,7 @@ function baseCreateRenderer(
     pauseTracking()
     // props update may have triggered pre-flush watchers.
     // flush them before the render update.
-    flushPreFlushCbs()
+    flushPreFlushCbs(instance)
     resetTracking()
   }
 

+ 4 - 0
packages/runtime-core/src/scheduler.ts

@@ -139,6 +139,7 @@ export function queuePostFlushCb(cb: SchedulerJobs) {
 }
 
 export function flushPreFlushCbs(
+  instance?: ComponentInternalInstance,
   seen?: CountMap,
   // if currently flushing, skip the current job itself
   i = isFlushing ? flushIndex + 1 : 0
@@ -149,6 +150,9 @@ export function flushPreFlushCbs(
   for (; i < queue.length; i++) {
     const cb = queue[i]
     if (cb && cb.pre) {
+      if (instance && cb.id !== instance.uid) {
+        continue
+      }
       if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
         continue
       }

+ 7 - 0
packages/runtime-dom/__tests__/patchProps.spec.ts

@@ -300,6 +300,13 @@ describe('runtime-dom: props patching', () => {
     expect(el.getAttribute('width')).toBe('24px')
   })
 
+  // # 9762 should fallthrough to `key in el` logic for non embedded tags
+  test('width and height on custom elements', () => {
+    const el = document.createElement('foobar')
+    patchProp(el, 'width', null, '24px')
+    expect(el.getAttribute('width')).toBe('24px')
+  })
+
   test('translate attribute', () => {
     const el = document.createElement('div')
     patchProp(el, 'translate', null, 'no')

+ 1 - 1
packages/runtime-dom/package.json

@@ -37,6 +37,6 @@
   "dependencies": {
     "@vue/shared": "workspace:*",
     "@vue/runtime-core": "workspace:*",
-    "csstype": "^3.1.2"
+    "csstype": "^3.1.3"
   }
 }

+ 1 - 0
packages/runtime-dom/src/jsx.ts

@@ -1077,6 +1077,7 @@ export interface SVGAttributes extends AriaAttributes, EventHandlers<Events> {
   xlinkTitle?: string
   xlinkType?: string
   xmlns?: string
+  xmlnsXlink?: string
   y1?: Numberish
   y2?: Numberish
   y?: Numberish

+ 5 - 3
packages/runtime-dom/src/patchProp.ts

@@ -111,15 +111,17 @@ function shouldSetAsProp(
     return false
   }
 
-  // #8780 the width or heigth of embedded tags must be set as attribute
+  // #8780 the width or height of embedded tags must be set as attribute
   if (key === 'width' || key === 'height') {
     const tag = el.tagName
-    return !(
+    if (
       tag === 'IMG' ||
       tag === 'VIDEO' ||
       tag === 'CANVAS' ||
       tag === 'SOURCE'
-    )
+    ) {
+      return false
+    }
   }
 
   // native onclick with string value, must be set as attribute

+ 1 - 1
packages/sfc-playground/package.json

@@ -10,7 +10,7 @@
   },
   "devDependencies": {
     "@vitejs/plugin-vue": "^4.4.0",
-    "vite": "^5.0.0"
+    "vite": "^5.0.5"
   },
   "dependencies": {
     "@vue/repl": "^3.0.0",

+ 1 - 1
packages/sfc-playground/src/Header.vue

@@ -24,7 +24,7 @@ const vueVersion = ref(`@${currentCommit}`)
 async function setVueVersion(v: string) {
   vueVersion.value = `loading...`
   await store.setVueVersion(v)
-  vueVersion.value = `v${v}`
+  vueVersion.value = v
 }
 
 function resetVueVersion() {

+ 6 - 2
packages/sfc-playground/src/VersionSelect.vue

@@ -74,8 +74,8 @@ onMounted(() => {
 
     <ul class="versions" :class="{ expanded }">
       <li v-if="!versions"><a>loading versions...</a></li>
-      <li v-for="version of versions">
-        <a @click="setVersion(version)">v{{ version }}</a>
+      <li v-for="ver of versions" :class="{ active: ver === version }">
+        <a @click="setVersion(ver)">v{{ ver }}</a>
       </li>
       <div @click="expanded = false">
         <slot />
@@ -111,4 +111,8 @@ onMounted(() => {
   border-top: 6px solid #aaa;
   margin-left: 8px;
 }
+
+.versions .active a {
+  color: var(--green);
+}
 </style>

+ 45 - 0
packages/shared/__tests__/toDisplayString.spec.ts

@@ -171,4 +171,49 @@ describe('toDisplayString', () => {
       }"
     `)
   })
+
+  //#9727
+  test('Map with Symbol keys', () => {
+    const m = new Map<any, any>([
+      [Symbol(), 'foo'],
+      [Symbol(), 'bar'],
+      [Symbol('baz'), 'baz']
+    ])
+    expect(toDisplayString(m)).toMatchInlineSnapshot(`
+      "{
+        "Map(3)": {
+          "Symbol(0) =>": "foo",
+          "Symbol(1) =>": "bar",
+          "Symbol(baz) =>": "baz"
+        }
+      }"
+    `)
+    // confirming the symbol renders Symbol(foo)
+    expect(toDisplayString(new Map([[Symbol('foo'), 'foo']]))).toContain(
+      String(Symbol('foo'))
+    )
+  })
+
+  test('Set with Symbol values', () => {
+    const s = new Set([Symbol('foo'), Symbol('bar'), Symbol()])
+    expect(toDisplayString(s)).toMatchInlineSnapshot(`
+      "{
+        "Set(3)": [
+          "Symbol(foo)",
+          "Symbol(bar)",
+          "Symbol()"
+        ]
+      }"
+    `)
+  })
+
+  test('Object with Symbol values', () => {
+    expect(toDisplayString({ foo: Symbol('x'), bar: Symbol() }))
+      .toMatchInlineSnapshot(`
+      "{
+        "foo": "Symbol(x)",
+        "bar": "Symbol()"
+      }"
+    `)
+  })
 })

+ 1 - 1
packages/shared/src/domAttrConfig.ts

@@ -118,6 +118,6 @@ export const isKnownSvgAttr = /*#__PURE__*/ makeMap(
     `v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,` +
     `vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,` +
     `writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,` +
-    `xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,` +
+    `xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,` +
     `xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`
 )

+ 16 - 6
packages/shared/src/toDisplayString.ts

@@ -6,7 +6,8 @@ import {
   isPlainObject,
   isSet,
   objectToString,
-  isString
+  isString,
+  isSymbol
 } from './general'
 
 /**
@@ -31,17 +32,26 @@ const replacer = (_key: string, val: any): any => {
     return replacer(_key, val.value)
   } else if (isMap(val)) {
     return {
-      [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val]) => {
-        ;(entries as any)[`${key} =>`] = val
-        return entries
-      }, {})
+      [`Map(${val.size})`]: [...val.entries()].reduce(
+        (entries, [key, val], i) => {
+          entries[stringifySymbol(key, i) + ' =>'] = val
+          return entries
+        },
+        {} as Record<string, any>
+      )
     }
   } else if (isSet(val)) {
     return {
-      [`Set(${val.size})`]: [...val.values()]
+      [`Set(${val.size})`]: [...val.values()].map(v => stringifySymbol(v))
     }
+  } else if (isSymbol(val)) {
+    return stringifySymbol(val)
   } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {
+    // native elements
     return String(val)
   }
   return val
 }
+
+const stringifySymbol = (v: unknown, i: number | string = ''): any =>
+  isSymbol(v) ? `Symbol(${v.description ?? i})` : v

+ 1 - 1
packages/template-explorer/package.json

@@ -11,7 +11,7 @@
     "enableNonBrowserBranches": true
   },
   "dependencies": {
-    "monaco-editor": "^0.44.0",
+    "monaco-editor": "^0.45.0",
     "source-map-js": "^1.0.2"
   }
 }

File diff suppressed because it is too large
+ 249 - 215
pnpm-lock.yaml


+ 4 - 4
vitest.config.ts

@@ -1,5 +1,6 @@
-import { configDefaults, defineConfig, UserConfig } from 'vitest/config'
+import { configDefaults, defineConfig } from 'vitest/config'
 import { entries } from './scripts/aliases.js'
+import codspeedPlugin from '@codspeed/vitest-plugin'
 
 export default defineConfig({
   define: {
@@ -21,10 +22,9 @@ export default defineConfig({
   resolve: {
     alias: entries
   },
+  plugins: [codspeedPlugin()],
   test: {
     globals: true,
-    // disable threads on GH actions to speed it up
-    threads: !process.env.GITHUB_ACTIONS,
     setupFiles: 'scripts/setupVitest.ts',
     environmentMatchGlobs: [
       ['packages/{vue,vue-compat,runtime-dom}/**', 'jsdom']
@@ -44,4 +44,4 @@ export default defineConfig({
       ]
     }
   }
-}) as UserConfig
+})

Some files were not shown because too many files changed in this diff