Parcourir la source

Merge remote-tracking branch 'upstream/minor'

三咲智子 Kevin Deng il y a 2 ans
Parent
commit
3d3d08d0f1

+ 28 - 0
CHANGELOG.md

@@ -1,3 +1,31 @@
+## [3.4.2](https://github.com/vuejs/core/compare/v3.4.1...v3.4.2) (2023-12-30)
+
+
+### Bug Fixes
+
+* **compiler-sfc:** fix dev regression for dot / namespace component usage ([dce99c1](https://github.com/vuejs/core/commit/dce99c12df981ca45a4d848c37ba8b16496025f0)), closes [#9947](https://github.com/vuejs/core/issues/9947)
+* **runtime-core:** support deep: false when watch reactive ([#9928](https://github.com/vuejs/core/issues/9928)) ([4f703d1](https://github.com/vuejs/core/commit/4f703d120d76d711084346f73ea295c73e6ef6b6)), closes [#9916](https://github.com/vuejs/core/issues/9916)
+* **ssr:** fix hydration error for slot outlet inside transition-group ([#9937](https://github.com/vuejs/core/issues/9937)) ([6cb00ed](https://github.com/vuejs/core/commit/6cb00ed0f9b64428ec18fada0f68467d6a813fde)), closes [#9933](https://github.com/vuejs/core/issues/9933)
+
+
+
+## [3.4.1](https://github.com/vuejs/core/compare/v3.4.0...v3.4.1) (2023-12-30)
+
+
+### Bug Fixes
+
+* **compat:** correct enum value for COMPILER_FILTERS feature ([#9875](https://github.com/vuejs/core/issues/9875)) ([77d33e2](https://github.com/vuejs/core/commit/77d33e263cf19983caf4e5c53a0eb0bee374843c))
+* **defineModel:** always default modifiers to empty object ([9bc3c7e](https://github.com/vuejs/core/commit/9bc3c7e29cf15f5ca96703542d10cfd786a3fc55)), closes [#9945](https://github.com/vuejs/core/issues/9945)
+* **defineModel:** support local mutation when only prop but no listener is passed ([97ce041](https://github.com/vuejs/core/commit/97ce041910b6ca4bef10f939493d6b5a06ea5b07))
+* **types:** fix defineModel watch type error ([#9942](https://github.com/vuejs/core/issues/9942)) ([4af8583](https://github.com/vuejs/core/commit/4af85835f7e593a7dffa7dc7e99f14877eb70fd1)), closes [#9939](https://github.com/vuejs/core/issues/9939)
+
+
+### Features
+
+* **compiler-sfc:** support passing template parsing options when parsing sfc ([6fab855](https://github.com/vuejs/core/commit/6fab8551e4aeef4610987640de8b435b1ae321bb)) (necessary to fix https://github.com/vitejs/vite-plugin-vue/issues/322)
+
+
+
 # [3.4.0 Slam Dunk](https://github.com/vuejs/core/compare/v3.4.0-rc.3...v3.4.0) (2023-12-29)
 
 > Read [this blog post](https://blog.vuejs.org/posts/vue-3-4) for an overview of the release highlights.

+ 1 - 1
packages/compiler-core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-core",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "@vue/compiler-core",
   "main": "index.js",
   "module": "dist/compiler-core.esm-bundler.js",

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

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-dom",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "@vue/compiler-dom",
   "main": "index.js",
   "module": "dist/compiler-dom.esm-bundler.js",

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

@@ -639,190 +639,6 @@ return { foo, bar, baz, y, z }
 }"
 `;
 
-exports[`SFC compile <script setup> > dev mode import usage check > TS annotations 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { Foo, Bar, Baz, Qux, Fred } from './x'
-        const a = 1
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-        function b() {}
-        
-return { a, b, get Baz() { return Baz } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > attribute expressions 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { bar, baz } from './x'
-        const cond = true
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-        
-return { cond, get bar() { return bar }, get baz() { return baz } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > components 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { FooBar, FooBaz, FooQux, foo } from './x'
-        const fooBar: FooBar = 1
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-        
-return { fooBar, get FooBaz() { return FooBaz }, get FooQux() { return FooQux }, get foo() { return foo } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > directive 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { vMyDir } from './x'
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-        
-return { get vMyDir() { return vMyDir } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > dynamic arguments 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { FooBar, foo, bar, unused, baz } from './x'
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-        
-return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar }, get baz() { return baz } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > js template string interpolations 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { VAR, VAR2, VAR3 } from './x'
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-        
-return { get VAR() { return VAR }, get VAR3() { return VAR3 } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > last tag 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { FooBaz, Last } from './x'
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-        
-return { get FooBaz() { return FooBaz }, get Last() { return Last } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > property access (whitespace) 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { Foo, Bar, Baz } from './foo'
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-          
-return { get Foo() { return Foo } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > property access 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { Foo, Bar, Baz } from './foo'
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-          
-return { get Foo() { return Foo } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > spread operator 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { Foo, Bar, Baz } from './foo'
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-          
-return { get Foo() { return Foo } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > template ref 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { foo, bar, Baz } from './foo'
-        
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-          
-return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> > dev mode import usage check > vue interpolations 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import { x, y, z, x$y } from './x'
-      
-export default /*#__PURE__*/_defineComponent({
-  setup(__props, { expose: __expose }) {
-  __expose();
-
-      
-return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }
-}
-
-})"
-`;
-
 exports[`SFC compile <script setup> > errors > should allow defineProps/Emit() referencing imported binding 1`] = `
 "import { bar } from './bar'
         

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

@@ -339,227 +339,6 @@ describe('SFC compile <script setup>', () => {
     })
   })
 
-  // in dev mode, declared bindings are returned as an object from setup()
-  // when using TS, users may import types which should not be returned as
-  // values, so we need to check import usage in the template to determine
-  // what to be returned.
-  describe('dev mode import usage check', () => {
-    test('components', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-        import { FooBar, FooBaz, FooQux, foo } from './x'
-        const fooBar: FooBar = 1
-        </script>
-        <template>
-          <FooBaz></FooBaz>
-          <foo-qux/>
-          <foo/>
-          FooBar
-        </template>
-        `)
-      // FooBar: should not be matched by plain text or incorrect case
-      // FooBaz: used as PascalCase component
-      // FooQux: used as kebab-case component
-      // foo: lowercase component
-      expect(content).toMatch(
-        `return { fooBar, get FooBaz() { return FooBaz }, ` +
-          `get FooQux() { return FooQux }, get foo() { return foo } }`,
-      )
-      assertCode(content)
-    })
-
-    test('directive', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-        import { vMyDir } from './x'
-        </script>
-        <template>
-          <div v-my-dir></div>
-        </template>
-        `)
-      expect(content).toMatch(`return { get vMyDir() { return vMyDir } }`)
-      assertCode(content)
-    })
-
-    test('dynamic arguments', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-        import { FooBar, foo, bar, unused, baz } from './x'
-        </script>
-        <template>
-          <FooBar #[foo.slotName] />
-          <FooBar #unused />
-          <div :[bar.attrName]="15"></div>
-          <div unused="unused"></div>
-          <div #[\`item:\${baz.key}\`]="{ value }"></div>
-        </template>
-        `)
-      expect(content).toMatch(
-        `return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
-          `get bar() { return bar }, get baz() { return baz } }`,
-      )
-      assertCode(content)
-    })
-
-    // https://github.com/vuejs/core/issues/4599
-    test('attribute expressions', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-        import { bar, baz } from './x'
-        const cond = true
-        </script>
-        <template>
-          <div :class="[cond ? '' : bar(), 'default']" :style="baz"></div>
-        </template>
-        `)
-      expect(content).toMatch(
-        `return { cond, get bar() { return bar }, get baz() { return baz } }`,
-      )
-      assertCode(content)
-    })
-
-    test('vue interpolations', () => {
-      const { content } = compile(`
-      <script setup lang="ts">
-      import { x, y, z, x$y } from './x'
-      </script>
-      <template>
-        <div :id="z + 'y'">{{ x }} {{ yy }} {{ x$y }}</div>
-      </template>
-      `)
-      // x: used in interpolation
-      // y: should not be matched by {{ yy }} or 'y' in binding exps
-      // x$y: #4274 should escape special chars when creating Regex
-      expect(content).toMatch(
-        `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`,
-      )
-      assertCode(content)
-    })
-
-    // #4340 interpolations in template strings
-    test('js template string interpolations', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-        import { VAR, VAR2, VAR3 } from './x'
-        </script>
-        <template>
-          {{ \`\${VAR}VAR2\${VAR3}\` }}
-        </template>
-        `)
-      // VAR2 should not be matched
-      expect(content).toMatch(
-        `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`,
-      )
-      assertCode(content)
-    })
-
-    // edge case: last tag in template
-    test('last tag', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-        import { FooBaz, Last } from './x'
-        </script>
-        <template>
-          <FooBaz></FooBaz>
-          <Last/>
-        </template>
-        `)
-      expect(content).toMatch(
-        `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`,
-      )
-      assertCode(content)
-    })
-
-    test('TS annotations', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-        import { Foo, Bar, Baz, Qux, Fred } from './x'
-        const a = 1
-        function b() {}
-        </script>
-        <template>
-          {{ a as Foo }}
-          {{ b<Bar>() }}
-          {{ Baz }}
-          <Comp v-slot="{ data }: Qux">{{ data }}</Comp>
-          <div v-for="{ z = x as Qux } in list as Fred"/>
-        </template>
-        `)
-      expect(content).toMatch(`return { a, b, get Baz() { return Baz } }`)
-      assertCode(content)
-    })
-
-    // vuejs/vue#12591
-    test('v-on inline statement', () => {
-      // should not error
-      compile(`
-      <script setup lang="ts">
-        import { foo } from './foo'
-      </script>
-      <template>
-        <div @click="$emit('update:a');"></div>
-      </template>
-      `)
-    })
-
-    test('template ref', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-          import { foo, bar, Baz } from './foo'
-        </script>
-        <template>
-          <div ref="foo"></div>
-          <div ref=""></div>
-          <Baz ref="bar" />
-        </template>
-        `)
-      expect(content).toMatch(
-        'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }',
-      )
-      assertCode(content)
-    })
-
-    // https://github.com/nuxt/nuxt/issues/22416
-    test('property access', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-          import { Foo, Bar, Baz } from './foo'
-        </script>
-        <template>
-          <div>{{ Foo.Bar.Baz }}</div>
-        </template>
-        `)
-      expect(content).toMatch('return { get Foo() { return Foo } }')
-      assertCode(content)
-    })
-
-    test('spread operator', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-          import { Foo, Bar, Baz } from './foo'
-        </script>
-        <template>
-          <div v-bind="{ ...Foo.Bar.Baz }"></div>
-        </template>
-        `)
-      expect(content).toMatch('return { get Foo() { return Foo } }')
-      assertCode(content)
-    })
-
-    test('property access (whitespace)', () => {
-      const { content } = compile(`
-        <script setup lang="ts">
-          import { Foo, Bar, Baz } from './foo'
-        </script>
-        <template>
-          <div>{{ Foo . Bar . Baz }}</div>
-        </template>
-        `)
-      expect(content).toMatch('return { get Foo() { return Foo } }')
-      assertCode(content)
-    })
-  })
-
   describe('inlineTemplate mode', () => {
     test('should work', () => {
       const { content } = compile(

+ 215 - 0
packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap

@@ -0,0 +1,215 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`TS annotations 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { Foo, Bar, Baz, Qux, Fred } from './x'
+    const a = 1
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+    function b() {}
+    
+return { a, b, get Baz() { return Baz } }
+}
+
+})"
+`;
+
+exports[`attribute expressions 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { bar, baz } from './x'
+    const cond = true
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+    
+return { cond, get bar() { return bar }, get baz() { return baz } }
+}
+
+})"
+`;
+
+exports[`components 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { FooBar, FooBaz, FooQux, foo } from './x'
+    const fooBar: FooBar = 1
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+    
+return { fooBar, get FooBaz() { return FooBaz }, get FooQux() { return FooQux }, get foo() { return foo } }
+}
+
+})"
+`;
+
+exports[`directive 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { vMyDir } from './x'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+    
+return { get vMyDir() { return vMyDir } }
+}
+
+})"
+`;
+
+exports[`dynamic arguments 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { FooBar, foo, bar, unused, baz } from './x'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+    
+return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar }, get baz() { return baz } }
+}
+
+})"
+`;
+
+exports[`import namespace 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import * as Foo from './foo'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+return { get Foo() { return Foo } }
+}
+
+})"
+`;
+
+exports[`js template string interpolations 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { VAR, VAR2, VAR3 } from './x'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+    
+return { get VAR() { return VAR }, get VAR3() { return VAR3 } }
+}
+
+})"
+`;
+
+exports[`last tag 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { FooBaz, Last } from './x'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+    
+return { get FooBaz() { return FooBaz }, get Last() { return Last } }
+}
+
+})"
+`;
+
+exports[`namespace / dot component usage 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import * as Foo from './foo'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+return { get Foo() { return Foo } }
+}
+
+})"
+`;
+
+exports[`property access (whitespace) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { Foo, Bar, Baz } from './foo'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+return { get Foo() { return Foo } }
+}
+
+})"
+`;
+
+exports[`property access 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { Foo, Bar, Baz } from './foo'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+return { get Foo() { return Foo } }
+}
+
+})"
+`;
+
+exports[`spread operator 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { Foo, Bar, Baz } from './foo'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+return { get Foo() { return Foo } }
+}
+
+})"
+`;
+
+exports[`template ref 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { foo, bar, Baz } from './foo'
+    
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }
+}
+
+})"
+`;
+
+exports[`vue interpolations 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { x, y, z, x$y } from './x'
+  
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+  
+return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }
+}
+
+})"
+`;

+ 235 - 0
packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts

@@ -0,0 +1,235 @@
+import { assertCode, compileSFCScript as compile } from '../utils'
+
+// in dev mode, declared bindings are returned as an object from setup()
+// when using TS, users may import types which should not be returned as
+// values, so we need to check import usage in the template to determine
+// what to be returned.
+
+test('components', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+    import { FooBar, FooBaz, FooQux, foo } from './x'
+    const fooBar: FooBar = 1
+    </script>
+    <template>
+      <FooBaz></FooBaz>
+      <foo-qux/>
+      <foo/>
+      FooBar
+    </template>
+    `)
+  // FooBar: should not be matched by plain text or incorrect case
+  // FooBaz: used as PascalCase component
+  // FooQux: used as kebab-case component
+  // foo: lowercase component
+  expect(content).toMatch(
+    `return { fooBar, get FooBaz() { return FooBaz }, ` +
+      `get FooQux() { return FooQux }, get foo() { return foo } }`,
+  )
+  assertCode(content)
+})
+
+test('directive', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+    import { vMyDir } from './x'
+    </script>
+    <template>
+      <div v-my-dir></div>
+    </template>
+    `)
+  expect(content).toMatch(`return { get vMyDir() { return vMyDir } }`)
+  assertCode(content)
+})
+
+test('dynamic arguments', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+    import { FooBar, foo, bar, unused, baz } from './x'
+    </script>
+    <template>
+      <FooBar #[foo.slotName] />
+      <FooBar #unused />
+      <div :[bar.attrName]="15"></div>
+      <div unused="unused"></div>
+      <div #[\`item:\${baz.key}\`]="{ value }"></div>
+    </template>
+    `)
+  expect(content).toMatch(
+    `return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
+      `get bar() { return bar }, get baz() { return baz } }`,
+  )
+  assertCode(content)
+})
+
+// https://github.com/vuejs/core/issues/4599
+test('attribute expressions', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+    import { bar, baz } from './x'
+    const cond = true
+    </script>
+    <template>
+      <div :class="[cond ? '' : bar(), 'default']" :style="baz"></div>
+    </template>
+    `)
+  expect(content).toMatch(
+    `return { cond, get bar() { return bar }, get baz() { return baz } }`,
+  )
+  assertCode(content)
+})
+
+test('vue interpolations', () => {
+  const { content } = compile(`
+  <script setup lang="ts">
+  import { x, y, z, x$y } from './x'
+  </script>
+  <template>
+    <div :id="z + 'y'">{{ x }} {{ yy }} {{ x$y }}</div>
+  </template>
+  `)
+  // x: used in interpolation
+  // y: should not be matched by {{ yy }} or 'y' in binding exps
+  // x$y: #4274 should escape special chars when creating Regex
+  expect(content).toMatch(
+    `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`,
+  )
+  assertCode(content)
+})
+
+// #4340 interpolations in template strings
+test('js template string interpolations', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+    import { VAR, VAR2, VAR3 } from './x'
+    </script>
+    <template>
+      {{ \`\${VAR}VAR2\${VAR3}\` }}
+    </template>
+    `)
+  // VAR2 should not be matched
+  expect(content).toMatch(
+    `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`,
+  )
+  assertCode(content)
+})
+
+// edge case: last tag in template
+test('last tag', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+    import { FooBaz, Last } from './x'
+    </script>
+    <template>
+      <FooBaz></FooBaz>
+      <Last/>
+    </template>
+    `)
+  expect(content).toMatch(
+    `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`,
+  )
+  assertCode(content)
+})
+
+test('TS annotations', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+    import { Foo, Bar, Baz, Qux, Fred } from './x'
+    const a = 1
+    function b() {}
+    </script>
+    <template>
+      {{ a as Foo }}
+      {{ b<Bar>() }}
+      {{ Baz }}
+      <Comp v-slot="{ data }: Qux">{{ data }}</Comp>
+      <div v-for="{ z = x as Qux } in list as Fred"/>
+    </template>
+    `)
+  expect(content).toMatch(`return { a, b, get Baz() { return Baz } }`)
+  assertCode(content)
+})
+
+// vuejs/vue#12591
+test('v-on inline statement', () => {
+  // should not error
+  compile(`
+  <script setup lang="ts">
+    import { foo } from './foo'
+  </script>
+  <template>
+    <div @click="$emit('update:a');"></div>
+  </template>
+  `)
+})
+
+test('template ref', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+      import { foo, bar, Baz } from './foo'
+    </script>
+    <template>
+      <div ref="foo"></div>
+      <div ref=""></div>
+      <Baz ref="bar" />
+    </template>
+    `)
+  expect(content).toMatch(
+    'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }',
+  )
+  assertCode(content)
+})
+
+// https://github.com/nuxt/nuxt/issues/22416
+test('property access', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+      import { Foo, Bar, Baz } from './foo'
+    </script>
+    <template>
+      <div>{{ Foo.Bar.Baz }}</div>
+    </template>
+    `)
+  expect(content).toMatch('return { get Foo() { return Foo } }')
+  assertCode(content)
+})
+
+test('spread operator', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+      import { Foo, Bar, Baz } from './foo'
+    </script>
+    <template>
+      <div v-bind="{ ...Foo.Bar.Baz }"></div>
+    </template>
+    `)
+  expect(content).toMatch('return { get Foo() { return Foo } }')
+  assertCode(content)
+})
+
+test('property access (whitespace)', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+      import { Foo, Bar, Baz } from './foo'
+    </script>
+    <template>
+      <div>{{ Foo . Bar . Baz }}</div>
+    </template>
+    `)
+  expect(content).toMatch('return { get Foo() { return Foo } }')
+  assertCode(content)
+})
+
+// #9974
+test('namespace / dot component usage', () => {
+  const { content } = compile(`
+    <script setup lang="ts">
+      import * as Foo from './foo'
+    </script>
+    <template>
+      <Foo.Bar />
+    </template>
+    `)
+  expect(content).toMatch('return { get Foo() { return Foo } }')
+  assertCode(content)
+})

+ 32 - 1
packages/compiler-sfc/__tests__/parse.spec.ts

@@ -1,5 +1,10 @@
 import { parse } from '../src'
-import { baseCompile, createRoot } from '@vue/compiler-core'
+import {
+  ElementTypes,
+  NodeTypes,
+  baseCompile,
+  createRoot,
+} from '@vue/compiler-core'
 import { SourceMapConsumer } from 'source-map-js'
 
 describe('compiler:sfc', () => {
@@ -350,6 +355,32 @@ h1 { color: red }
     expect(descriptor.customBlocks[0].content).toBe(` <-& `)
   })
 
+  test('should accept parser options', () => {
+    const { errors, descriptor } = parse(`<template><hello/></template>`, {
+      templateParseOptions: {
+        isCustomElement: t => t === 'hello',
+      },
+    })
+    expect(errors.length).toBe(0)
+    expect(descriptor.template!.ast!.children[0]).toMatchObject({
+      type: NodeTypes.ELEMENT,
+      tag: 'hello',
+      tagType: ElementTypes.ELEMENT,
+    })
+
+    // test cache invalidation on different options
+    const { descriptor: d2 } = parse(`<template><hello/></template>`, {
+      templateParseOptions: {
+        isCustomElement: t => t !== 'hello',
+      },
+    })
+    expect(d2.template!.ast!.children[0]).toMatchObject({
+      type: NodeTypes.ELEMENT,
+      tag: 'hello',
+      tagType: ElementTypes.COMPONENT,
+    })
+  })
+
   describe('warnings', () => {
     function assertWarning(errors: Error[], msg: string) {
       expect(errors.some(e => e.message.match(msg))).toBe(true)

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

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-sfc",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "@vue/compiler-sfc",
   "main": "dist/compiler-sfc.cjs.js",
   "module": "dist/compiler-sfc.esm-browser.js",

+ 31 - 9
packages/compiler-sfc/src/parse.ts

@@ -3,6 +3,7 @@ import {
   type CompilerError,
   type ElementNode,
   NodeTypes,
+  type ParserOptions,
   type RootNode,
   type SourceLocation,
   createRoot,
@@ -24,6 +25,11 @@ export interface SFCParseOptions {
   pad?: boolean | 'line' | 'space'
   ignoreEmpty?: boolean
   compiler?: TemplateCompiler
+  templateParseOptions?: ParserOptions
+  /**
+   * TODO remove in 3.5
+   * @deprecated use `templateParseOptions: { prefixIdentifiers: false }` instead
+   */
   parseExpressions?: boolean
 }
 
@@ -97,24 +103,39 @@ export interface SFCParseResult {
 
 export const parseCache = createCache<SFCParseResult>()
 
+function genCacheKey(source: string, options: SFCParseOptions): string {
+  return (
+    source +
+    JSON.stringify(
+      {
+        ...options,
+        compiler: { parse: options.compiler?.parse },
+      },
+      (_, val) => (typeof val === 'function' ? val.toString() : val),
+    )
+  )
+}
+
 export function parse(
   source: string,
-  {
+  options: SFCParseOptions = {},
+): SFCParseResult {
+  const sourceKey = genCacheKey(source, options)
+  const cache = parseCache.get(sourceKey)
+  if (cache) {
+    return cache
+  }
+
+  const {
     sourceMap = true,
     filename = DEFAULT_FILENAME,
     sourceRoot = '',
     pad = false,
     ignoreEmpty = true,
     compiler = CompilerDOM,
+    templateParseOptions = {},
     parseExpressions = true,
-  }: SFCParseOptions = {},
-): SFCParseResult {
-  const sourceKey =
-    source + sourceMap + filename + sourceRoot + pad + compiler.parse
-  const cache = parseCache.get(sourceKey)
-  if (cache) {
-    return cache
-  }
+  } = options
 
   const descriptor: SFCDescriptor = {
     filename,
@@ -133,6 +154,7 @@ export function parse(
   const ast = compiler.parse(source, {
     parseMode: 'sfc',
     prefixIdentifiers: parseExpressions,
+    ...templateParseOptions,
     onError: e => {
       errors.push(e)
     },

+ 8 - 6
packages/compiler-sfc/src/script/importUsageCheck.ts

@@ -16,12 +16,12 @@ import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
  * when not using inline mode.
  */
 export function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
-  return resolveTemplateUsageCheckString(sfc).has(local)
+  return resolveTemplateUsedIdentifiers(sfc).has(local)
 }
 
 const templateUsageCheckCache = createCache<Set<string>>()
 
-function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
+function resolveTemplateUsedIdentifiers(sfc: SFCDescriptor): Set<string> {
   const { content, ast } = sfc.template!
   const cached = templateUsageCheckCache.get(content)
   if (cached) {
@@ -35,12 +35,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
   function walk(node: TemplateChildNode) {
     switch (node.type) {
       case NodeTypes.ELEMENT:
+        let tag = node.tag
+        if (tag.includes('.')) tag = tag.split('.')[0].trim()
         if (
-          !parserOptions.isNativeTag!(node.tag) &&
-          !parserOptions.isBuiltInComponent!(node.tag)
+          !parserOptions.isNativeTag!(tag) &&
+          !parserOptions.isBuiltInComponent!(tag)
         ) {
-          ids.add(camelize(node.tag))
-          ids.add(capitalize(camelize(node.tag)))
+          ids.add(camelize(tag))
+          ids.add(capitalize(camelize(tag)))
         }
         for (let i = 0; i < node.props.length; i++) {
           const prop = node.props[i]

+ 16 - 0
packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts

@@ -127,4 +127,20 @@ describe('ssr: <slot>', () => {
       }"
     `)
   })
+
+  test('inside transition-group', () => {
+    const { code } = compile(
+      `<TransitionGroup tag="div"><slot/></TransitionGroup>`,
+    )
+    expect(code).toMatch(ssrHelpers[SSR_RENDER_SLOT_INNER])
+    expect(code).toMatchInlineSnapshot(`
+      "const { ssrRenderSlotInner: _ssrRenderSlotInner, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${_ssrRenderAttrs(_attrs)}>\`)
+        _ssrRenderSlotInner(_ctx.$slots, "default", {}, null, _push, _parent, null, true)
+        _push(\`</div>\`)
+      }"
+    `)
+  })
 })

+ 1 - 1
packages/compiler-ssr/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-ssr",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "@vue/compiler-ssr",
   "main": "dist/compiler-ssr.cjs.js",
   "types": "dist/compiler-ssr.d.ts",

+ 7 - 3
packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts

@@ -4,6 +4,7 @@ import {
   NodeTypes,
   type SlotOutletNode,
   TRANSITION,
+  TRANSITION_GROUP,
   createCallExpression,
   createFunctionExpression,
   isSlotOutlet,
@@ -37,16 +38,19 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
 
     let method = SSR_RENDER_SLOT
 
-    // #3989
+    // #3989, #9933
     // check if this is a single slot inside a transition wrapper - since
-    // transition will unwrap the slot fragment into a single vnode at runtime,
+    // transition/transition-group will unwrap the slot fragment into vnode(s) at runtime,
     // we need to avoid rendering the slot as a fragment.
     const parent = context.parent
+    let componentType
     if (
       parent &&
       parent.type === NodeTypes.ELEMENT &&
       parent.tagType === ElementTypes.COMPONENT &&
-      resolveComponentType(parent, context, true) === TRANSITION &&
+      ((componentType = resolveComponentType(parent, context, true)) ===
+        TRANSITION ||
+        componentType === TRANSITION_GROUP) &&
       parent.children.filter(c => c.type === NodeTypes.ELEMENT).length === 1
     ) {
       method = SSR_RENDER_SLOT_INNER

+ 36 - 1
packages/dts-test/watch.test-d.ts

@@ -1,4 +1,11 @@
-import { computed, defineComponent, ref, shallowRef, watch } from 'vue'
+import {
+  computed,
+  defineComponent,
+  defineModel,
+  ref,
+  shallowRef,
+  watch,
+} from 'vue'
 import { expectType } from './utils'
 
 const source = ref('foo')
@@ -106,3 +113,31 @@ defineComponent({
     expectType<Steps>(value)
   })
 }
+
+{
+  // defineModel
+  const bool = defineModel({ default: false })
+  watch(bool, value => {
+    expectType<boolean>(value)
+  })
+
+  const bool1 = defineModel<boolean>()
+  watch(bool1, value => {
+    expectType<boolean | undefined>(value)
+  })
+
+  const msg = defineModel<string>({ required: true })
+  watch(msg, value => {
+    expectType<string>(value)
+  })
+
+  const arr = defineModel<string[]>({ required: true })
+  watch(arr, value => {
+    expectType<string[]>(value)
+  })
+
+  const obj = defineModel<{ foo: string }>({ required: true })
+  watch(obj, value => {
+    expectType<{ foo: string }>(value)
+  })
+}

+ 1 - 1
packages/reactivity/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/reactivity",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "@vue/reactivity",
   "main": "index.js",
   "module": "dist/reactivity.esm-bundler.js",

+ 35 - 0
packages/runtime-core/__tests__/apiSetupHelpers.spec.ts

@@ -279,6 +279,41 @@ describe('SFC <script setup> helpers', () => {
       expect(serializeInner(root)).toBe('bar')
     })
 
+    test('without parent listener (local mutation)', async () => {
+      let foo: any
+      const update = () => {
+        foo.value = 'bar'
+      }
+
+      const compRender = vi.fn()
+      const Comp = defineComponent({
+        props: ['foo'],
+        emits: ['update:foo'],
+        setup(props) {
+          foo = useModel(props, 'foo')
+          return () => {
+            compRender()
+            return foo.value
+          }
+        },
+      })
+
+      const root = nodeOps.createElement('div')
+      // provide initial value
+      render(h(Comp, { foo: 'initial' }), root)
+      expect(compRender).toBeCalledTimes(1)
+      expect(serializeInner(root)).toBe('initial')
+
+      expect(foo.value).toBe('initial')
+      update()
+      // when parent didn't provide value, local mutation is enabled
+      expect(foo.value).toBe('bar')
+
+      await nextTick()
+      expect(compRender).toBeCalledTimes(2)
+      expect(serializeInner(root)).toBe('bar')
+    })
+
     test('default value', async () => {
       let count: any
       const inc = () => {

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

@@ -25,9 +25,11 @@ import {
   type DebuggerEvent,
   ITERATE_KEY,
   type Ref,
+  type ShallowRef,
   TrackOpTypes,
   TriggerOpTypes,
   effectScope,
+  shallowReactive,
   shallowRef,
   toRef,
   triggerRef,
@@ -156,6 +158,59 @@ describe('api: watch', () => {
     expect(dummy).toBe(1)
   })
 
+  it('directly watching reactive object with explicit deep: false', async () => {
+    const src = reactive({
+      state: {
+        count: 0,
+      },
+    })
+    let dummy
+    watch(
+      src,
+      ({ state }) => {
+        dummy = state?.count
+      },
+      {
+        deep: false,
+      },
+    )
+
+    // nested should not trigger
+    src.state.count++
+    await nextTick()
+    expect(dummy).toBe(undefined)
+
+    // root level should trigger
+    src.state = { count: 1 }
+    await nextTick()
+    expect(dummy).toBe(1)
+  })
+
+  // #9916
+  it('directly watching shallow reactive array', async () => {
+    class foo {
+      prop1: ShallowRef<string> = shallowRef('')
+      prop2: string = ''
+    }
+
+    const obj1 = new foo()
+    const obj2 = new foo()
+
+    const collection = shallowReactive([obj1, obj2])
+    const cb = vi.fn()
+    watch(collection, cb)
+
+    collection[0].prop1.value = 'foo'
+    await nextTick()
+    // should not trigger
+    expect(cb).toBeCalledTimes(0)
+
+    collection.push(new foo())
+    await nextTick()
+    // should trigger on array self mutation
+    expect(cb).toBeCalledTimes(1)
+  })
+
   it('watching multiple sources', async () => {
     const state = reactive({ count: 1 })
     const count = ref(1)

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

@@ -1,6 +1,6 @@
 {
   "name": "@vue/runtime-core",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "@vue/runtime-core",
   "main": "index.js",
   "module": "dist/runtime-core.esm-bundler.js",

+ 14 - 2
packages/runtime-core/src/apiSetupHelpers.ts

@@ -3,6 +3,7 @@ import {
   type LooseRequired,
   type Prettify,
   type UnionToIntersection,
+  camelize,
   extend,
   hasChanged,
   isArray,
@@ -380,6 +381,8 @@ export function useModel(
     return ref() as any
   }
 
+  const camelizedName = camelize(name)
+
   const res = customRef((track, trigger) => {
     let localValue: any
     watchSyncEffect(() => {
@@ -396,7 +399,16 @@ export function useModel(
       },
       set(value) {
         const rawProps = i.vnode!.props
-        if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
+        if (
+          !(
+            rawProps &&
+            // check if parent has passed v-model
+            (name in rawProps || camelizedName in rawProps) &&
+            (`onUpdate:${name}` in rawProps ||
+              `onUpdate:${camelizedName}` in rawProps)
+          ) &&
+          hasChanged(value, localValue)
+        ) {
           localValue = value
           trigger()
         }
@@ -414,7 +426,7 @@ export function useModel(
     return {
       next() {
         if (i < 2) {
-          return { value: i++ ? props[modifierKey] : res, done: false }
+          return { value: i++ ? props[modifierKey] || {} : res, done: false }
         } else {
           return { done: true }
         }

+ 32 - 16
packages/runtime-core/src/apiWatch.ts

@@ -42,7 +42,7 @@ import { warn } from './warning'
 import { DeprecationTypes } from './compat/compatConfig'
 import { checkCompatEnabled, isCompatEnabled } from './compat/compatConfig'
 import type { ObjectWatchOptionItem } from './componentOptions'
-import { useSSRContext } from '@vue/runtime-core'
+import { useSSRContext } from './helpers/useSsrContext'
 
 export type WatchEffect = (onCleanup: OnCleanup) => void
 
@@ -115,6 +115,13 @@ const INITIAL_WATCHER_VALUE = {}
 
 type MultiWatchSources = (WatchSource<unknown> | object)[]
 
+// overload: single source + cb
+export function watch<T, Immediate extends Readonly<boolean> = false>(
+  source: WatchSource<T>,
+  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
+  options?: WatchOptions<Immediate>,
+): WatchStopHandle
+
 // overload: array of multiple sources + cb
 export function watch<
   T extends MultiWatchSources,
@@ -137,13 +144,6 @@ export function watch<
   options?: WatchOptions<Immediate>,
 ): WatchStopHandle
 
-// overload: single source + cb
-export function watch<T, Immediate extends Readonly<boolean> = false>(
-  source: WatchSource<T>,
-  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
-  options?: WatchOptions<Immediate>,
-): WatchStopHandle
-
 // overload: watching reactive object w/ cb
 export function watch<
   T extends object,
@@ -231,8 +231,11 @@ function doWatch(
     getter = () => source.value
     forceTrigger = isShallow(source)
   } else if (isReactive(source)) {
-    getter = () => source
-    deep = true
+    getter =
+      isShallow(source) || deep === false
+        ? () => traverse(source, 1)
+        : () => traverse(source)
+    forceTrigger = true
   } else if (isArray(source)) {
     isMultiSource = true
     forceTrigger = source.some(s => isReactive(s) || isShallow(s))
@@ -241,7 +244,7 @@ function doWatch(
         if (isRef(s)) {
           return s.value
         } else if (isReactive(s)) {
-          return traverse(s)
+          return traverse(s, isShallow(s) || deep === false ? 1 : undefined)
         } else if (isFunction(s)) {
           return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
         } else {
@@ -460,28 +463,41 @@ export function createPathGetter(ctx: any, path: string) {
   }
 }
 
-export function traverse(value: unknown, seen?: Set<unknown>) {
+export function traverse(
+  value: unknown,
+  depth?: number,
+  currentDepth = 0,
+  seen?: Set<unknown>,
+) {
   if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
     return value
   }
+
+  if (depth && depth > 0) {
+    if (currentDepth >= depth) {
+      return value
+    }
+    currentDepth++
+  }
+
   seen = seen || new Set()
   if (seen.has(value)) {
     return value
   }
   seen.add(value)
   if (isRef(value)) {
-    traverse(value.value, seen)
+    traverse(value.value, depth, currentDepth, seen)
   } else if (isArray(value)) {
     for (let i = 0; i < value.length; i++) {
-      traverse(value[i], seen)
+      traverse(value[i], depth, currentDepth, seen)
     }
   } else if (isSet(value) || isMap(value)) {
     value.forEach((v: any) => {
-      traverse(v, seen)
+      traverse(v, depth, currentDepth, seen)
     })
   } else if (isPlainObject(value)) {
     for (const key in value) {
-      traverse(value[key], seen)
+      traverse(value[key], depth, currentDepth, seen)
     }
   }
   return value

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

@@ -1,6 +1,6 @@
 {
   "name": "@vue/runtime-dom",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "@vue/runtime-dom",
   "main": "index.js",
   "module": "dist/runtime-dom.esm-bundler.js",

+ 1 - 1
packages/server-renderer/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/server-renderer",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "@vue/server-renderer",
   "main": "index.js",
   "module": "dist/server-renderer.esm-bundler.js",

+ 1 - 1
packages/shared/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/shared",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "internal utils shared across @vue packages",
   "main": "index.js",
   "module": "dist/shared.esm-bundler.js",

+ 1 - 1
packages/vue-compat/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compat",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "Vue 3 compatibility build for Vue 2",
   "main": "index.js",
   "module": "dist/vue.runtime.esm-bundler.js",

+ 1 - 1
packages/vue/package.json

@@ -1,6 +1,6 @@
 {
   "name": "vue",
-  "version": "3.4.0",
+  "version": "3.4.2",
   "description": "The progressive JavaScript framework for building modern web UI.",
   "main": "index.js",
   "module": "dist/vue.runtime.esm-bundler.js",

Fichier diff supprimé car celui-ci est trop grand
+ 86 - 403
pnpm-lock.yaml


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff