Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: vuejs/core
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.1.2
Choose a base ref
...
head repository: vuejs/core
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v3.1.3
Choose a head ref

Commits on Jun 22, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a137da8 View commit details
  2. chore: add missing snapshot

    yyx990803 committed Jun 22, 2021
    Copy the full SHA
    65f8c82 View commit details

Commits on Jun 23, 2021

  1. Copy the full SHA
    6f6f0cf View commit details
  2. feat(sfc): useAttrs + useSlots

    Deprecate useContext
    yyx990803 committed Jun 23, 2021
    Copy the full SHA
    63e9e2e View commit details
  3. Copy the full SHA
    0b8b576 View commit details
  4. chore: comments

    yyx990803 committed Jun 23, 2021
    Copy the full SHA
    075889e View commit details
  5. Copy the full SHA
    ac853ff View commit details

Commits on Jun 24, 2021

  1. Copy the full SHA
    a5a66c5 View commit details

Commits on Jun 25, 2021

  1. Copy the full SHA
    b0203a3 View commit details
  2. feat(sfc): defineExpose

    yyx990803 committed Jun 25, 2021
    Copy the full SHA
    be2b1d3 View commit details
  3. Copy the full SHA
    691d354 View commit details
  4. Copy the full SHA
    be0f614 View commit details
  5. Copy the full SHA
    1675b6d View commit details
  6. Copy the full SHA
    3ffc7be View commit details

Commits on Jun 27, 2021

  1. Copy the full SHA
    4c5844a View commit details
  2. Copy the full SHA
    004bd18 View commit details
  3. chore: fix dts test

    yyx990803 committed Jun 27, 2021
    Copy the full SHA
    f680074 View commit details
  4. Copy the full SHA
    fddef8b View commit details
  5. Copy the full SHA
    ef5c415 View commit details

Commits on Jun 28, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d069796 View commit details
  2. Copy the full SHA
    2f91db3 View commit details
  3. Copy the full SHA
    afdd2f2 View commit details
  4. Copy the full SHA
    2973b6c View commit details
  5. Copy the full SHA
    62c1b2f View commit details
  6. Copy the full SHA
    0245c98 View commit details
  7. Copy the full SHA
    d35e0b1 View commit details
  8. Copy the full SHA
    2e10261 View commit details
  9. Copy the full SHA
    211793d View commit details
  10. Copy the full SHA
    fd7fa6f View commit details

Commits on Jun 29, 2021

  1. Copy the full SHA
    0240e82 View commit details
  2. Copy the full SHA
    9ee41e1 View commit details
  3. 1
    Copy the full SHA
    96cc335 View commit details
  4. feat(compiler-sfc): compileScript parseOnly mode

    This is an internal feature meant for IDE support
    yyx990803 committed Jun 29, 2021
    Copy the full SHA
    601a290 View commit details

Commits on Jun 30, 2021

  1. Copy the full SHA
    f8a6b57 View commit details
  2. Copy the full SHA
    1ffd48a View commit details

Commits on Jul 1, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    81e69b2 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8ed3ed6 View commit details
  3. build(deps-dev): bump typescript from 4.2.4 to 4.3.5 (#4038)

    Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.2.4 to 4.3.5.
    - [Release notes](https://github.com/Microsoft/TypeScript/releases)
    - [Commits](microsoft/TypeScript@v4.2.4...v4.3.5)
    
    ---
    updated-dependencies:
    - dependency-name: typescript
      dependency-type: direct:development
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4779f90 View commit details
  4. build(deps-dev): bump eslint from 7.25.0 to 7.29.0 (#4040)

    Bumps [eslint](https://github.com/eslint/eslint) from 7.25.0 to 7.29.0.
    - [Release notes](https://github.com/eslint/eslint/releases)
    - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
    - [Commits](eslint/eslint@v7.25.0...v7.29.0)
    
    ---
    updated-dependencies:
    - dependency-name: eslint
      dependency-type: direct:development
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    598fe71 View commit details
  5. build(deps-dev): bump @typescript-eslint/parser from 4.26.0 to 4.28.1 (

    …#4042)
    
    Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.26.0 to 4.28.1.
    - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
    - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
    - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.1/packages/parser)
    
    ---
    updated-dependencies:
    - dependency-name: "@typescript-eslint/parser"
      dependency-type: direct:development
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    020c417 View commit details
  6. build(deps): bump @babel/parser from 7.14.4 to 7.14.7 (#4041)

    Bumps [@babel/parser](https://github.com/babel/babel/tree/HEAD/packages/babel-parser) from 7.14.4 to 7.14.7.
    - [Release notes](https://github.com/babel/babel/releases)
    - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/babel/babel/commits/v7.14.7/packages/babel-parser)
    
    ---
    updated-dependencies:
    - dependency-name: "@babel/parser"
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5158f20 View commit details
  7. build(deps): bump postcss-modules from 4.0.0 to 4.1.3 (#4043)

    Bumps [postcss-modules](https://github.com/css-modules/postcss-modules) from 4.0.0 to 4.1.3.
    - [Release notes](https://github.com/css-modules/postcss-modules/releases)
    - [Changelog](https://github.com/madyankin/postcss-modules/blob/master/CHANGELOG.md)
    - [Commits](madyankin/postcss-modules@v4.0.0...v4.1.3)
    
    ---
    updated-dependencies:
    - dependency-name: postcss-modules
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    144c0e7 View commit details
  8. build(deps-dev): bump vite from 2.3.7 to 2.3.8 (#4044)

    Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 2.3.7 to 2.3.8.
    - [Release notes](https://github.com/vitejs/vite/releases)
    - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
    - [Commits](https://github.com/vitejs/vite/commits/v2.3.8/packages/vite)
    
    ---
    updated-dependencies:
    - dependency-name: vite
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    27276cd View commit details
  9. build(deps): bump @babel/types from 7.14.4 to 7.14.5 (#4045)

    Bumps [@babel/types](https://github.com/babel/babel/tree/HEAD/packages/babel-types) from 7.14.4 to 7.14.5.
    - [Release notes](https://github.com/babel/babel/releases)
    - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/babel/babel/commits/v7.14.5/packages/babel-types)
    
    ---
    updated-dependencies:
    - dependency-name: "@babel/types"
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    bd7c239 View commit details
  10. build(deps-dev): bump sass from 1.32.12 to 1.35.1 (#4047)

    Bumps [sass](https://github.com/sass/dart-sass) from 1.32.12 to 1.35.1.
    - [Release notes](https://github.com/sass/dart-sass/releases)
    - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
    - [Commits](sass/dart-sass@1.32.12...1.35.1)
    
    ---
    updated-dependencies:
    - dependency-name: sass
      dependency-type: direct:development
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    489db4f View commit details
  11. chore(puppeteer): upgrade puppeteer (#4019)

    * chore(puppeteer): upgrade puppeteer
    
    * fix(tests): fix typescript error with puppeteer 5.X types
    
    Co-authored-by: bas <bas@planning.nl>
    basvanmeurs and basvanmeurs authored Jul 1, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    69b74a8 View commit details
  12. Copy the full SHA
    127ed1b View commit details
  13. Copy the full SHA
    d2df28d View commit details
  14. Copy the full SHA
    fded1e8 View commit details
  15. Copy the full SHA
    34d4991 View commit details
Showing with 2,967 additions and 1,336 deletions.
  1. +44 −0 CHANGELOG.md
  2. +3 −3 package.json
  3. +2 −2 packages/compiler-core/package.json
  4. +9 −4 packages/compiler-core/src/parse.ts
  5. +2 −1 packages/compiler-core/src/transforms/transformElement.ts
  6. +3 −3 packages/compiler-dom/package.json
  7. +313 −231 packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
  8. +4 −4 packages/compiler-sfc/__tests__/__snapshots__/cssVars.spec.ts.snap
  9. +338 −322 packages/compiler-sfc/__tests__/compileScript.spec.ts
  10. +77 −0 packages/compiler-sfc/__tests__/compileScriptParseOnlyMode.spec.ts
  11. +277 −0 packages/compiler-sfc/__tests__/compileScriptRefSugar.ts
  12. +6 −6 packages/compiler-sfc/package.json
  13. +529 −194 packages/compiler-sfc/src/compileScript.ts
  14. +19 −0 packages/compiler-sfc/src/parse.ts
  15. +3 −3 packages/compiler-ssr/package.json
  16. +2 −2 packages/reactivity/package.json
  17. +35 −30 packages/reactivity/src/baseHandlers.ts
  18. +102 −86 packages/reactivity/src/collectionHandlers.ts
  19. +1 −1 packages/reactivity/src/ref.ts
  20. +23 −1 packages/runtime-core/__tests__/apiExpose.spec.ts
  21. +228 −20 packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
  22. +35 −1 packages/runtime-core/__tests__/hydration.spec.ts
  23. +3 −3 packages/runtime-core/package.json
  24. +220 −35 packages/runtime-core/src/apiSetupHelpers.ts
  25. +2 −2 packages/runtime-core/src/apiWatch.ts
  26. +34 −8 packages/runtime-core/src/component.ts
  27. +8 −8 packages/runtime-core/src/componentOptions.ts
  28. +3 −2 packages/runtime-core/src/componentProps.ts
  29. +33 −17 packages/runtime-core/src/componentPublicInstance.ts
  30. +1 −0 packages/runtime-core/src/errorHandling.ts
  31. +19 −10 packages/runtime-core/src/hydration.ts
  32. +18 −3 packages/runtime-core/src/index.ts
  33. +11 −4 packages/runtime-core/src/renderer.ts
  34. +3 −2 packages/runtime-core/src/vnode.ts
  35. +13 −0 packages/runtime-core/types/scriptSetupHelpers.d.ts
  36. +16 −24 packages/runtime-dom/__tests__/nodeOps.spec.ts
  37. +3 −3 packages/runtime-dom/package.json
  38. +17 −16 packages/runtime-dom/src/nodeOps.ts
  39. +2 −4 packages/runtime-dom/types/jsx.d.ts
  40. +3 −3 packages/runtime-test/package.json
  41. +4 −4 packages/server-renderer/package.json
  42. +3 −2 packages/sfc-playground/package.json
  43. +48 −47 packages/sfc-playground/src/Header.vue
  44. +2 −2 packages/sfc-playground/src/codemirror/CodeMirror.vue
  45. +7 −1 packages/sfc-playground/src/output/Preview.vue
  46. +36 −14 packages/sfc-playground/src/sfcCompiler.ts
  47. +1 −1 packages/shared/package.json
  48. +1 −1 packages/size-check/package.json
  49. +1 −1 packages/template-explorer/package.json
  50. +2 −2 packages/vue-compat/package.json
  51. +1 −1 packages/vue/__tests__/Transition.spec.ts
  52. +1 −1 packages/vue/__tests__/TransitionGroup.spec.ts
  53. +1 −1 packages/vue/__tests__/e2eUtils.ts
  54. +1 −1 packages/vue/examples/__tests__/commits.spec.ts
  55. +1 −1 packages/vue/examples/__tests__/grid.spec.ts
  56. +1 −1 packages/vue/examples/__tests__/svg.spec.ts
  57. +4 −4 packages/vue/package.json
  58. +2 −5 test-dts/index.d.ts
  59. +7 −0 test-dts/ref.test-d.ts
  60. +44 −6 test-dts/setupHelpers.test-d.ts
  61. +335 −182 yarn.lock
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
## [3.1.3](https://github.com/vuejs/vue-next/compare/v3.1.2...v3.1.3) (2021-07-01)


### Bug Fixes

* **compiler-core:** properly exit self-closing pre tag ([d2df28d](https://github.com/vuejs/vue-next/commit/d2df28dca42f6679766033f8986b5637dfe64e1e)), closes [#4030](https://github.com/vuejs/vue-next/issues/4030)
* **compiler-sfc:** avoid script setup marker showing up in devtools ([211793d](https://github.com/vuejs/vue-next/commit/211793d3767b12dd457de62160b672af24b921e7))
* **compiler-sfc:** fix defineProps() call on imported identifier ([691d354](https://github.com/vuejs/vue-next/commit/691d354af9e3a66c781494656b367950fcd8faec))
* **compiler-sfc:** fix defineProps/defineEmits usage in multi-variable declarations ([62c1b2f](https://github.com/vuejs/vue-next/commit/62c1b2f7dc4d2dd22a1b1ab1897f0ce765008d59)), closes [#3739](https://github.com/vuejs/vue-next/issues/3739)
* **compiler-sfc:** fix script setup hidden flag codegen ([a5a66c5](https://github.com/vuejs/vue-next/commit/a5a66c5196f5e00e8cbf7f6008d350d6eabcee71))
* **compiler-sfc:** support method signature in defineProps ([afdd2f2](https://github.com/vuejs/vue-next/commit/afdd2f28354ce8cea647279ed25d61e7b9946cf5)), closes [#2983](https://github.com/vuejs/vue-next/issues/2983)
* **compiler-sfc:** support TS runtime enum in `<script setup>` ([1ffd48a](https://github.com/vuejs/vue-next/commit/1ffd48a2f5fd3eead3ea29dae668b7ed1c6f6130))
* **runtime-core:** add missing serverPrefetch hook error string ([#4014](https://github.com/vuejs/vue-next/issues/4014)) ([d069796](https://github.com/vuejs/vue-next/commit/d069796b8f0cf8df9aa77d781c4b5429b9411204))
* **runtime-core:** fix mouting of detached static vnode ([fded1e8](https://github.com/vuejs/vue-next/commit/fded1e8dfa22ca7fecd300c4cbffd6a37b887be8)), closes [#4023](https://github.com/vuejs/vue-next/issues/4023)
* **runtime-dom:** fix static node content caching edge cases ([ba89ca9](https://github.com/vuejs/vue-next/commit/ba89ca9ecafe86292e3adf751671ed5e9ca6e928)), closes [#4023](https://github.com/vuejs/vue-next/issues/4023) [#4031](https://github.com/vuejs/vue-next/issues/4031) [#4037](https://github.com/vuejs/vue-next/issues/4037)
* **sfc:** allow variables that start with _ or $ in `<script setup>` ([0b8b576](https://github.com/vuejs/vue-next/commit/0b8b5764287b4814a37034ad4bc6f2b8ac8f8700))
* **ssr:** ensure behavior consistency between prod/dev when mounting SSR app to empty containers ([33708e8](https://github.com/vuejs/vue-next/commit/33708e8bf44a037070af5c8eabdfe1ccad22bbc2)), closes [#4034](https://github.com/vuejs/vue-next/issues/4034)
* **ssr:** properly hydrate non-string value bindings ([34d4991](https://github.com/vuejs/vue-next/commit/34d4991dd5876325eb8747afa9a835929bde3974)), closes [#4006](https://github.com/vuejs/vue-next/issues/4006)
* **types:** improve type of unref() ([127ed1b](https://github.com/vuejs/vue-next/commit/127ed1b969cb2d237d0f588aab726e04f4732641)), closes [#3954](https://github.com/vuejs/vue-next/issues/3954)
* defineExpose type definition and runtime warning ([1675b6d](https://github.com/vuejs/vue-next/commit/1675b6d723829d1f61e697735e3da7b16aa1362d))
* prevent withAsyncContext currentInstance leak in edge cases ([9ee41e1](https://github.com/vuejs/vue-next/commit/9ee41e14d2d173866300e75758468c6788180277))


### Features

* **compiler-sfc:** compileScript parseOnly mode ([601a290](https://github.com/vuejs/vue-next/commit/601a290caaf7fa29c58c88ac79fc2f1d2c57e337))
* **expose:** always expose $ instance properties on child refs ([b0203a3](https://github.com/vuejs/vue-next/commit/b0203a30929e4e7f59e035574e43d72ed3b9d7fd))
* **sfc:** add `defineEmits` and deprecate `defineEmit` ([#3725](https://github.com/vuejs/vue-next/issues/3725)) ([a137da8](https://github.com/vuejs/vue-next/commit/a137da8a9f728edacd50d288bce281e32597197b))
* **sfc:** auto restore current instance after await statements in async setup() ([0240e82](https://github.com/vuejs/vue-next/commit/0240e82a38e2e0c5f0b63c228fd02b059a19073d))
* **sfc:** change `<script setup>` directive resolution to require v prefix ([d35e0b1](https://github.com/vuejs/vue-next/commit/d35e0b1468ce3c22b713020ed29f81aba40dd039)), closes [#3543](https://github.com/vuejs/vue-next/issues/3543)
* **sfc:** defineExpose ([be2b1d3](https://github.com/vuejs/vue-next/commit/be2b1d3c2f16de8dc6e2a22f65fefaa2d25ec3ee))
* **sfc:** make ref sugar disabled by default ([96cc335](https://github.com/vuejs/vue-next/commit/96cc335aa7050b6bf2ae53cc209d0032a8d59d0e))
* **sfc:** remove `<template inherit-attrs>` support ([6f6f0cf](https://github.com/vuejs/vue-next/commit/6f6f0cf5dcc02f4a648fab86439eb29a4b5596d2))
* **sfc:** support referenced types for defineEmits ([2973b6c](https://github.com/vuejs/vue-next/commit/2973b6c30ae5b3ff65aeb71a26a6de1c7789537d))
* **sfc:** support using declared interface or type alias with defineProps() ([2f91db3](https://github.com/vuejs/vue-next/commit/2f91db30cda5c315ed3e4d20800b55721b0cb17c))
* **sfc:** useAttrs + useSlots ([63e9e2e](https://github.com/vuejs/vue-next/commit/63e9e2e9aae07c701548f3350ea83535bea22066))
* **sfc:** withDefaults helper ([4c5844a](https://github.com/vuejs/vue-next/commit/4c5844a9ca0acc4ea45565a0dc9a21c2502d64a4))
* **sfc-playground:** support lang=ts ([be0f614](https://github.com/vuejs/vue-next/commit/be0f614ac096bdfe44cfddb04c859c9747dcd6dd))
* **sfc/types:** make `<script setup>` helper types available globally ([004bd18](https://github.com/vuejs/vue-next/commit/004bd18cf75526bd79f68ccea8102aa94a8a28e2))
* **types:** support IDE renaming for props ([#3656](https://github.com/vuejs/vue-next/issues/3656)) ([81e69b2](https://github.com/vuejs/vue-next/commit/81e69b29ecf992d215d8ddc56bf7e40661144595))
* **types/ide:** support find definition for jsx tags, events ([#3570](https://github.com/vuejs/vue-next/issues/3570)) ([8ed3ed6](https://github.com/vuejs/vue-next/commit/8ed3ed6c27b0fb9a1b6994eddc967e42d4b3d4e1))



## [3.1.2](https://github.com/vuejs/vue-next/compare/v3.1.1...v3.1.2) (2021-06-22)


6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"private": true,
"version": "3.1.2",
"version": "3.1.3",
"workspaces": [
"packages/*"
],
@@ -52,7 +52,7 @@
"@types/hash-sum": "^1.0.0",
"@types/jest": "^26.0.16",
"@types/node": "^14.10.1",
"@types/puppeteer": "^2.0.0",
"@types/puppeteer": "^5.0.0",
"@typescript-eslint/parser": "^4.1.1",
"brotli": "^1.3.2",
"chalk": "^4.1.0",
@@ -67,7 +67,7 @@
"minimist": "^1.2.0",
"npm-run-all": "^4.1.5",
"prettier": "~1.14.0",
"puppeteer": "^2.0.0",
"puppeteer": "^10.0.0",
"rollup": "~2.38.5",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0",
4 changes: 2 additions & 2 deletions packages/compiler-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
@@ -32,7 +32,7 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-core#readme",
"dependencies": {
"@vue/shared": "3.1.2",
"@vue/shared": "3.1.3",
"@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0",
"estree-walker": "^2.0.1",
13 changes: 9 additions & 4 deletions packages/compiler-core/src/parse.ts
Original file line number Diff line number Diff line change
@@ -425,6 +425,10 @@ function parseElement(
const isVPreBoundary = context.inVPre && !wasInVPre

if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
// #4030 self-closing <pre> tag
if (context.options.isPreTag(element.tag)) {
context.inPre = false
}
return element
}

@@ -528,14 +532,15 @@ function parseTag(
const cursor = getCursor(context)
const currentSource = context.source

// Attributes.
let props = parseAttributes(context, type)

// check <pre> tag
if (context.options.isPreTag(tag)) {
const isPreTag = context.options.isPreTag(tag)
if (isPreTag) {
context.inPre = true
}

// Attributes.
let props = parseAttributes(context, type)

// check v-pre
if (
type === TagType.Start &&
3 changes: 2 additions & 1 deletion packages/compiler-core/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
@@ -739,7 +739,8 @@ function buildDirectiveArgs(
} else {
// user directive.
// see if we have directives exposed via <script setup>
const fromSetup = !__BROWSER__ && resolveSetupReference(dir.name, context)
const fromSetup =
!__BROWSER__ && resolveSetupReference('v-' + dir.name, context)
if (fromSetup) {
dirArgs.push(fromSetup)
} else {
6 changes: 3 additions & 3 deletions packages/compiler-dom/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
@@ -37,7 +37,7 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-dom#readme",
"dependencies": {
"@vue/shared": "3.1.2",
"@vue/compiler-core": "3.1.2"
"@vue/shared": "3.1.3",
"@vue/compiler-core": "3.1.3"
}
}
544 changes: 313 additions & 231 deletions packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -53,8 +53,8 @@ exports[`CSS vars injection codegen w/ <script setup> 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
export default {
expose: [],
setup(__props) {
setup(__props, { expose }) {
expose()
_useCssVars(_ctx => ({
\\"xxxxxxxx-color\\": (color)
@@ -88,11 +88,11 @@ exports[`CSS vars injection w/ <script setup> binding analysis 1`] = `
import { ref } from 'vue'
export default {
expose: [],
props: {
foo: String
},
setup(__props) {
setup(__props, { expose }) {
expose()
_useCssVars(_ctx => ({
\\"xxxxxxxx-color\\": (color),
660 changes: 338 additions & 322 deletions packages/compiler-sfc/__tests__/compileScript.spec.ts

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions packages/compiler-sfc/__tests__/compileScriptParseOnlyMode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { TextRange } from '../src/parse'
import { compileSFCScript } from './utils'

describe('compileScript parseOnly mode', () => {
function compile(src: string) {
return compileSFCScript(src, { parseOnly: true })
}

function getRange(src: string, range: TextRange) {
return src.slice(range.start, range.end)
}

test('bindings', () => {
const scriptSrc = `
import { foo } from './x'
`
const scriptSetupSrc = `
import { bar } from './x'
const a = 123
function b() {}
class c {}
`
const src = `
<script>${scriptSrc}</script>
<script setup>${scriptSetupSrc}</script>
`
const { ranges } = compile(src)

expect(getRange(scriptSrc, ranges!.scriptBindings[0])).toBe('foo')
expect(
ranges!.scriptSetupBindings.map(r => getRange(scriptSetupSrc, r))
).toMatchObject(['bar', 'a', 'b', 'c'])
})

test('defineProps', () => {
const src = `
defineProps({ foo: String })
`
const { ranges } = compile(`<script setup>${src}</script>`)
expect(getRange(src, ranges!.propsRuntimeArg!)).toBe(`{ foo: String }`)
})

test('defineProps (type)', () => {
const src = `
interface Props { x?: number }
defineProps<Props>()
`
const { ranges } = compile(`<script setup lang="ts">${src}</script>`)
expect(getRange(src, ranges!.propsTypeArg!)).toBe(`Props`)
})

test('withDefaults', () => {
const src = `
interface Props { x?: number }
withDefaults(defineProps<Props>(), { x: 1 })
`
const { ranges } = compile(`<script setup lang="ts">${src}</script>`)
expect(getRange(src, ranges!.withDefaultsArg!)).toBe(`{ x: 1 }`)
})

test('defineEmits', () => {
const src = `
defineEmits(['foo'])
`
const { ranges } = compile(`<script setup>${src}</script>`)
expect(getRange(src, ranges!.emitsRuntimeArg!)).toBe(`['foo']`)
})

test('defineEmits (type)', () => {
const src = `
defineEmits<{ (e: 'x'): void }>()
`
const { ranges } = compile(`<script setup lang="ts">${src}</script>`)
expect(getRange(src, ranges!.emitsTypeArg!)).toBe(`{ (e: 'x'): void }`)
})
})
277 changes: 277 additions & 0 deletions packages/compiler-sfc/__tests__/compileScriptRefSugar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
import { BindingTypes } from '@vue/compiler-core'
import { compileSFCScript as compile, assertCode } from './utils'

describe('<script setup> ref sugar', () => {
function compileWithRefSugar(src: string) {
return compile(src, { refSugar: true })
}

test('convert ref declarations', () => {
const { content, bindings } = compileWithRefSugar(`<script setup>
ref: foo
ref: a = 1
ref: b = {
count: 0
}
let c = () => {}
let d
</script>`)
expect(content).toMatch(`import { ref as _ref } from 'vue'`)
expect(content).not.toMatch(`ref: foo`)
expect(content).not.toMatch(`ref: a`)
expect(content).not.toMatch(`ref: b`)
expect(content).toMatch(`const foo = _ref()`)
expect(content).toMatch(`const a = _ref(1)`)
expect(content).toMatch(`
const b = _ref({
count: 0
})
`)
// normal declarations left untouched
expect(content).toMatch(`let c = () => {}`)
expect(content).toMatch(`let d`)
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_REF,
a: BindingTypes.SETUP_REF,
b: BindingTypes.SETUP_REF,
c: BindingTypes.SETUP_LET,
d: BindingTypes.SETUP_LET
})
})

test('multi ref declarations', () => {
const { content, bindings } = compileWithRefSugar(`<script setup>
ref: a = 1, b = 2, c = {
count: 0
}
</script>`)
expect(content).toMatch(`
const a = _ref(1), b = _ref(2), c = _ref({
count: 0
})
`)
expect(content).toMatch(`return { a, b, c }`)
assertCode(content)
expect(bindings).toStrictEqual({
a: BindingTypes.SETUP_REF,
b: BindingTypes.SETUP_REF,
c: BindingTypes.SETUP_REF
})
})

test('should not convert non ref labels', () => {
const { content } = compileWithRefSugar(`<script setup>
foo: a = 1, b = 2, c = {
count: 0
}
</script>`)
expect(content).toMatch(`foo: a = 1, b = 2`)
assertCode(content)
})

test('accessing ref binding', () => {
const { content } = compileWithRefSugar(`<script setup>
ref: a = 1
console.log(a)
function get() {
return a + 1
}
</script>`)
expect(content).toMatch(`console.log(a.value)`)
expect(content).toMatch(`return a.value + 1`)
assertCode(content)
})

test('cases that should not append .value', () => {
const { content } = compileWithRefSugar(`<script setup>
ref: a = 1
console.log(b.a)
function get(a) {
return a + 1
}
</script>`)
expect(content).not.toMatch(`a.value`)
})

test('mutating ref binding', () => {
const { content } = compileWithRefSugar(`<script setup>
ref: a = 1
ref: b = { count: 0 }
function inc() {
a++
a = a + 1
b.count++
b.count = b.count + 1
;({ a } = { a: 2 })
;[a] = [1]
}
</script>`)
expect(content).toMatch(`a.value++`)
expect(content).toMatch(`a.value = a.value + 1`)
expect(content).toMatch(`b.value.count++`)
expect(content).toMatch(`b.value.count = b.value.count + 1`)
expect(content).toMatch(`;({ a: a.value } = { a: 2 })`)
expect(content).toMatch(`;[a.value] = [1]`)
assertCode(content)
})

test('using ref binding in property shorthand', () => {
const { content } = compileWithRefSugar(`<script setup>
ref: a = 1
const b = { a }
function test() {
const { a } = b
}
</script>`)
expect(content).toMatch(`const b = { a: a.value }`)
// should not convert destructure
expect(content).toMatch(`const { a } = b`)
assertCode(content)
})

test('should not rewrite scope variable', () => {
const { content } = compileWithRefSugar(`
<script setup>
ref: a = 1
ref: b = 1
ref: d = 1
const e = 1
function test() {
const a = 2
console.log(a)
console.log(b)
let c = { c: 3 }
console.log(c)
let $d
console.log($d)
console.log(d)
console.log(e)
}
</script>`)
expect(content).toMatch('console.log(a)')
expect(content).toMatch('console.log(b.value)')
expect(content).toMatch('console.log(c)')
expect(content).toMatch('console.log($d)')
expect(content).toMatch('console.log(d.value)')
expect(content).toMatch('console.log(e)')
assertCode(content)
})

test('object destructure', () => {
const { content, bindings } = compileWithRefSugar(`<script setup>
ref: n = 1, ({ a, b: c, d = 1, e: f = 2, ...g } = useFoo())
ref: ({ foo } = useSomthing(() => 1));
console.log(n, a, c, d, f, g, foo)
</script>`)
expect(content).toMatch(
`const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()`
)
expect(content).toMatch(`const { foo: __foo } = useSomthing(() => 1)`)
expect(content).toMatch(`\nconst a = _ref(__a);`)
expect(content).not.toMatch(`\nconst b = _ref(__b);`)
expect(content).toMatch(`\nconst c = _ref(__c);`)
expect(content).toMatch(`\nconst d = _ref(__d);`)
expect(content).not.toMatch(`\nconst e = _ref(__e);`)
expect(content).toMatch(`\nconst f = _ref(__f);`)
expect(content).toMatch(`\nconst g = _ref(__g);`)
expect(content).toMatch(`\nconst foo = _ref(__foo);`)
expect(content).toMatch(
`console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)`
)
expect(content).toMatch(`return { n, a, c, d, f, g, foo }`)
expect(bindings).toStrictEqual({
n: BindingTypes.SETUP_REF,
a: BindingTypes.SETUP_REF,
c: BindingTypes.SETUP_REF,
d: BindingTypes.SETUP_REF,
f: BindingTypes.SETUP_REF,
g: BindingTypes.SETUP_REF,
foo: BindingTypes.SETUP_REF
})
assertCode(content)
})

test('array destructure', () => {
const { content, bindings } = compileWithRefSugar(`<script setup>
ref: n = 1, [a, b = 1, ...c] = useFoo()
console.log(n, a, b, c)
</script>`)
expect(content).toMatch(
`const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()`
)
expect(content).toMatch(`\nconst a = _ref(__a);`)
expect(content).toMatch(`\nconst b = _ref(__b);`)
expect(content).toMatch(`\nconst c = _ref(__c);`)
expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
expect(content).toMatch(`return { n, a, b, c }`)
expect(bindings).toStrictEqual({
n: BindingTypes.SETUP_REF,
a: BindingTypes.SETUP_REF,
b: BindingTypes.SETUP_REF,
c: BindingTypes.SETUP_REF
})
assertCode(content)
})

test('nested destructure', () => {
const { content, bindings } = compileWithRefSugar(`<script setup>
ref: [{ a: { b }}] = useFoo()
ref: ({ c: [d, e] } = useBar())
console.log(b, d, e)
</script>`)
expect(content).toMatch(`const [{ a: { b: __b }}] = useFoo()`)
expect(content).toMatch(`const { c: [__d, __e] } = useBar()`)
expect(content).not.toMatch(`\nconst a = _ref(__a);`)
expect(content).not.toMatch(`\nconst c = _ref(__c);`)
expect(content).toMatch(`\nconst b = _ref(__b);`)
expect(content).toMatch(`\nconst d = _ref(__d);`)
expect(content).toMatch(`\nconst e = _ref(__e);`)
expect(content).toMatch(`return { b, d, e }`)
expect(bindings).toStrictEqual({
b: BindingTypes.SETUP_REF,
d: BindingTypes.SETUP_REF,
e: BindingTypes.SETUP_REF
})
assertCode(content)
})

describe('errors', () => {
test('ref: non-assignment expressions', () => {
expect(() =>
compile(
`<script setup>
ref: a = 1, foo()
</script>`,
{ refSugar: true }
)
).toThrow(`ref: statements can only contain assignment expressions`)
})

test('defineProps/Emit() referencing ref declarations', () => {
expect(() =>
compile(
`<script setup>
ref: bar = 1
defineProps({
bar
})
</script>`,
{ refSugar: true }
)
).toThrow(`cannot reference locally declared variables`)

expect(() =>
compile(
`<script setup>
ref: bar = 1
defineEmits({
bar
})
</script>`,
{ refSugar: true }
)
).toThrow(`cannot reference locally declared variables`)
})
})
})
12 changes: 6 additions & 6 deletions packages/compiler-sfc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"types": "dist/compiler-sfc.d.ts",
@@ -31,16 +31,16 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-sfc#readme",
"peerDependencies": {
"vue": "3.1.2"
"vue": "3.1.3"
},
"dependencies": {
"@babel/parser": "^7.13.9",
"@babel/types": "^7.13.0",
"@types/estree": "^0.0.48",
"@vue/compiler-core": "3.1.2",
"@vue/compiler-dom": "3.1.2",
"@vue/compiler-ssr": "3.1.2",
"@vue/shared": "3.1.2",
"@vue/compiler-core": "3.1.3",
"@vue/compiler-dom": "3.1.3",
"@vue/compiler-ssr": "3.1.3",
"@vue/shared": "3.1.3",
"consolidate": "^0.16.0",
"estree-walker": "^2.0.1",
"hash-sum": "^2.0.0",
723 changes: 529 additions & 194 deletions packages/compiler-sfc/src/compileScript.ts

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions packages/compiler-sfc/src/parse.ts
Original file line number Diff line number Diff line change
@@ -42,6 +42,25 @@ export interface SFCScriptBlock extends SFCBlock {
bindings?: BindingMetadata
scriptAst?: Statement[]
scriptSetupAst?: Statement[]
ranges?: ScriptSetupTextRanges
}

/**
* Text range data for IDE support
*/
export interface ScriptSetupTextRanges {
scriptBindings: TextRange[]
scriptSetupBindings: TextRange[]
propsTypeArg?: TextRange
propsRuntimeArg?: TextRange
emitsTypeArg?: TextRange
emitsRuntimeArg?: TextRange
withDefaultsArg?: TextRange
}

export interface TextRange {
start: number
end: number
}

export interface SFCStyleBlock extends SFCBlock {
6 changes: 3 additions & 3 deletions packages/compiler-ssr/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-ssr",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",
@@ -28,7 +28,7 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-ssr#readme",
"dependencies": {
"@vue/shared": "3.1.2",
"@vue/compiler-dom": "3.1.2"
"@vue/shared": "3.1.3",
"@vue/compiler-dom": "3.1.3"
}
}
4 changes: 2 additions & 2 deletions packages/reactivity/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
@@ -36,6 +36,6 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/reactivity#readme",
"dependencies": {
"@vue/shared": "3.1.2"
"@vue/shared": "3.1.3"
}
}
65 changes: 35 additions & 30 deletions packages/reactivity/src/baseHandlers.ts
Original file line number Diff line number Diff line change
@@ -42,37 +42,42 @@ const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)

const arrayInstrumentations: Record<string, Function> = {}
// instrument identity-sensitive Array methods to account for possible reactive
// values
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
const method = Array.prototype[key] as any
arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
const arr = toRaw(this)
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()

function createArrayInstrumentations() {
const instrumentations: Record<string, Function> = {}
// instrument identity-sensitive Array methods to account for possible reactive
// values
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
const method = Array.prototype[key] as any
instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
const arr = toRaw(this)
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// we run the method using the original args first (which may be reactive)
const res = method.apply(arr, args)
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
return method.apply(arr, args.map(toRaw))
} else {
return res
}
}
// we run the method using the original args first (which may be reactive)
const res = method.apply(arr, args)
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
return method.apply(arr, args.map(toRaw))
} else {
})
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
const method = Array.prototype[key] as any
instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
pauseTracking()
const res = method.apply(this, args)
resetTracking()
return res
}
}
})
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
const method = Array.prototype[key] as any
arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
pauseTracking()
const res = method.apply(this, args)
resetTracking()
return res
}
})
})
return instrumentations
}

function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
@@ -224,7 +229,7 @@ export const readonlyHandlers: ProxyHandler<object> = {
}
}

export const shallowReactiveHandlers: ProxyHandler<object> = extend(
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
{},
mutableHandlers,
{
@@ -236,7 +241,7 @@ export const shallowReactiveHandlers: ProxyHandler<object> = extend(
// Props handlers are special in the sense that it should not unwrap top-level
// refs (in order to allow refs to be explicitly passed down), but should
// retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers: ProxyHandler<object> = extend(
export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
{},
readonlyHandlers,
{
188 changes: 102 additions & 86 deletions packages/reactivity/src/collectionHandlers.ts
Original file line number Diff line number Diff line change
@@ -236,93 +236,109 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
}
}

const mutableInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key)
},
get size() {
return size((this as unknown) as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, false)
}
function createInstrumentations() {
const mutableInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key)
},
get size() {
return size((this as unknown) as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, false)
}

const shallowInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, false, true)
},
get size() {
return size((this as unknown) as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, true)
}
const shallowInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, false, true)
},
get size() {
return size((this as unknown) as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, true)
}

const readonlyInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, true)
},
get size() {
return size((this as unknown) as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, false)
}
const readonlyInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, true)
},
get size() {
return size((this as unknown) as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, false)
}

const shallowReadonlyInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, true, true)
},
get size() {
return size((this as unknown) as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, true)
const shallowReadonlyInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, true, true)
},
get size() {
return size((this as unknown) as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, true)
}

const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
iteratorMethods.forEach(method => {
mutableInstrumentations[method as string] = createIterableMethod(
method,
false,
false
)
readonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
false
)
shallowInstrumentations[method as string] = createIterableMethod(
method,
false,
true
)
shallowReadonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
true
)
})

return [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations
]
}

const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
iteratorMethods.forEach(method => {
mutableInstrumentations[method as string] = createIterableMethod(
method,
false,
false
)
readonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
false
)
shallowInstrumentations[method as string] = createIterableMethod(
method,
false,
true
)
shallowReadonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
true
)
})
const [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations
] = /* #__PURE__*/ createInstrumentations()

function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = shallow
@@ -357,21 +373,21 @@ function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
}

export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false, false)
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}

export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false, true)
get: /*#__PURE__*/ createInstrumentationGetter(false, true)
}

export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(true, false)
get: /*#__PURE__*/ createInstrumentationGetter(true, false)
}

export const shallowReadonlyCollectionHandlers: ProxyHandler<
CollectionTypes
> = {
get: createInstrumentationGetter(true, true)
get: /*#__PURE__*/ createInstrumentationGetter(true, true)
}

function checkIdentityKeys(
2 changes: 1 addition & 1 deletion packages/reactivity/src/ref.ts
Original file line number Diff line number Diff line change
@@ -85,7 +85,7 @@ export function triggerRef(ref: Ref) {
trigger(toRaw(ref), TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)
}

export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
export function unref<T>(ref: T | Ref<T>): T {
return isRef(ref) ? (ref.value as any) : ref
}

24 changes: 23 additions & 1 deletion packages/runtime-core/__tests__/apiExpose.spec.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ describe('api: expose', () => {
render() {},
setup(_, { expose }) {
expose({
foo: ref(1),
foo: 1,
bar: ref(2)
})
return {
@@ -169,4 +169,26 @@ describe('api: expose', () => {
const root = nodeOps.createElement('div')
render(h(Parent), root)
})

test('expose should allow access to built-in instance properties', () => {
const Child = defineComponent({
render() {
return h('div')
},
setup(_, { expose }) {
expose()
return {}
}
})

const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
}
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(childRef.value.$el.tag).toBe('div')
})
})
248 changes: 228 additions & 20 deletions packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,257 @@
import {
ComponentInternalInstance,
createApp,
defineComponent,
getCurrentInstance,
h,
nodeOps,
onMounted,
render,
SetupContext
serializeInner,
SetupContext,
Suspense
} from '@vue/runtime-test'
import { defineEmit, defineProps, useContext } from '../src/apiSetupHelpers'
import {
defineEmits,
defineProps,
defineExpose,
withDefaults,
useAttrs,
useSlots,
mergeDefaults,
withAsyncContext
} from '../src/apiSetupHelpers'

describe('SFC <script setup> helpers', () => {
test('should warn runtime usage', () => {
defineProps()
expect(`defineProps() is a compiler-hint`).toHaveBeenWarned()

defineEmit()
expect(`defineEmit() is a compiler-hint`).toHaveBeenWarned()
defineEmits()
expect(`defineEmits() is a compiler-hint`).toHaveBeenWarned()

defineExpose()
expect(`defineExpose() is a compiler-hint`).toHaveBeenWarned()

withDefaults({}, {})
expect(`withDefaults() is a compiler-hint`).toHaveBeenWarned()
})

test('useContext (no args)', () => {
let ctx: SetupContext | undefined
test('useSlots / useAttrs (no args)', () => {
let slots: SetupContext['slots'] | undefined
let attrs: SetupContext['attrs'] | undefined
const Comp = {
setup() {
ctx = useContext()
slots = useSlots()
attrs = useAttrs()
return () => {}
}
}
render(h(Comp), nodeOps.createElement('div'))
expect(ctx).toMatchObject({
attrs: {},
slots: {}
})
expect(typeof ctx!.emit).toBe('function')
const passedAttrs = { id: 'foo' }
const passedSlots = {
default: () => {},
x: () => {}
}
render(h(Comp, passedAttrs, passedSlots), nodeOps.createElement('div'))
expect(typeof slots!.default).toBe('function')
expect(typeof slots!.x).toBe('function')
expect(attrs).toMatchObject(passedAttrs)
})

test('useContext (with args)', () => {
test('useSlots / useAttrs (with args)', () => {
let slots: SetupContext['slots'] | undefined
let attrs: SetupContext['attrs'] | undefined
let ctx: SetupContext | undefined
let ctxArg: SetupContext | undefined
const Comp = defineComponent({
setup(_, _ctxArg) {
ctx = useContext()
ctxArg = _ctxArg
setup(_, _ctx) {
slots = useSlots()
attrs = useAttrs()
ctx = _ctx
return () => {}
}
})
render(h(Comp), nodeOps.createElement('div'))
expect(ctx).toBeDefined()
expect(ctx).toBe(ctxArg)
expect(slots).toBe(ctx!.slots)
expect(attrs).toBe(ctx!.attrs)
})

test('mergeDefaults', () => {
const merged = mergeDefaults(
{
foo: null,
bar: { type: String, required: false }
},
{
foo: 1,
bar: 'baz'
}
)
expect(merged).toMatchObject({
foo: { default: 1 },
bar: { type: String, required: false, default: 'baz' }
})

mergeDefaults({}, { foo: 1 })
expect(
`props default key "foo" has no corresponding declaration`
).toHaveBeenWarned()
})

describe('withAsyncContext', () => {
// disable options API because applyOptions() also resets currentInstance
// and we want to ensure the logic works even with Options API disabled.
beforeEach(() => {
__FEATURE_OPTIONS_API__ = false
})

afterEach(() => {
__FEATURE_OPTIONS_API__ = true
})

test('basic', async () => {
const spy = jest.fn()

let beforeInstance: ComponentInternalInstance | null = null
let afterInstance: ComponentInternalInstance | null = null
let resolve: (msg: string) => void

const Comp = defineComponent({
async setup() {
beforeInstance = getCurrentInstance()
const msg = await withAsyncContext(
new Promise(r => {
resolve = r
})
)
// register the lifecycle after an await statement
onMounted(spy)
afterInstance = getCurrentInstance()
return () => msg
}
})

const root = nodeOps.createElement('div')
render(h(() => h(Suspense, () => h(Comp))), root)

expect(spy).not.toHaveBeenCalled()
resolve!('hello')
// wait a macro task tick for all micro ticks to resolve
await new Promise(r => setTimeout(r))
// mount hook should have been called
expect(spy).toHaveBeenCalled()
// should retain same instance before/after the await call
expect(beforeInstance).toBe(afterInstance)
expect(serializeInner(root)).toBe('hello')
})

test('error handling', async () => {
const spy = jest.fn()

let beforeInstance: ComponentInternalInstance | null = null
let afterInstance: ComponentInternalInstance | null = null
let reject: () => void

const Comp = defineComponent({
async setup() {
beforeInstance = getCurrentInstance()
try {
await withAsyncContext(
new Promise((r, rj) => {
reject = rj
})
)
} catch (e) {
// ignore
}
// register the lifecycle after an await statement
onMounted(spy)
afterInstance = getCurrentInstance()
return () => ''
}
})

const root = nodeOps.createElement('div')
render(h(() => h(Suspense, () => h(Comp))), root)

expect(spy).not.toHaveBeenCalled()
reject!()
// wait a macro task tick for all micro ticks to resolve
await new Promise(r => setTimeout(r))
// mount hook should have been called
expect(spy).toHaveBeenCalled()
// should retain same instance before/after the await call
expect(beforeInstance).toBe(afterInstance)
})

test('should not leak instance on multiple awaits', async () => {
let resolve: (val?: any) => void
let beforeInstance: ComponentInternalInstance | null = null
let afterInstance: ComponentInternalInstance | null = null
let inBandInstance: ComponentInternalInstance | null = null
let outOfBandInstance: ComponentInternalInstance | null = null

const ready = new Promise(r => {
resolve = r
})

async function doAsyncWork() {
// should still have instance
inBandInstance = getCurrentInstance()
await Promise.resolve()
// should not leak instance
outOfBandInstance = getCurrentInstance()
}

const Comp = defineComponent({
async setup() {
beforeInstance = getCurrentInstance()
// first await
await withAsyncContext(Promise.resolve())
// setup exit, instance set to null, then resumed
await withAsyncContext(doAsyncWork())
afterInstance = getCurrentInstance()
return () => {
resolve()
return ''
}
}
})

const root = nodeOps.createElement('div')
render(h(() => h(Suspense, () => h(Comp))), root)

await ready
expect(inBandInstance).toBe(beforeInstance)
expect(outOfBandInstance).toBeNull()
expect(afterInstance).toBe(beforeInstance)
expect(getCurrentInstance()).toBeNull()
})

test('should not leak on multiple awaits + error', async () => {
let resolve: (val?: any) => void
const ready = new Promise(r => {
resolve = r
})

const Comp = defineComponent({
async setup() {
await withAsyncContext(Promise.resolve())
await withAsyncContext(Promise.reject())
},
render() {}
})

const app = createApp(() => h(Suspense, () => h(Comp)))
app.config.errorHandler = () => {
resolve()
return false
}

const root = nodeOps.createElement('div')
app.mount(root)

await ready
expect(getCurrentInstance()).toBeNull()
})
})
})
36 changes: 35 additions & 1 deletion packages/runtime-core/__tests__/hydration.spec.ts
Original file line number Diff line number Diff line change
@@ -10,9 +10,13 @@ import {
onMounted,
defineAsyncComponent,
defineComponent,
createTextVNode
createTextVNode,
createVNode,
withDirectives,
vModelCheckbox
} from '@vue/runtime-dom'
import { renderToString, SSRContext } from '@vue/server-renderer'
import { PatchFlags } from '../../shared/src'

function mountWithHydration(html: string, render: () => any) {
const container = document.createElement('div')
@@ -761,6 +765,36 @@ describe('SSR hydration', () => {
)
})

test('force hydrate input v-model with non-string value bindings', () => {
const { container } = mountWithHydration(
'<input type="checkbox" value="true">',
() =>
withDirectives(
createVNode(
'input',
{ type: 'checkbox', 'true-value': true },
null,
PatchFlags.PROPS,
['true-value']
),
[[vModelCheckbox, true]]
)
)
expect((container.firstChild as any)._trueValue).toBe(true)
})

test('force hydrate select option with non-string value bindings', () => {
const { container } = mountWithHydration(
'<select><option :value="true">ok</option></select>',
() =>
h('select', [
// hoisted because bound value is a constant...
createVNode('option', { value: true }, null, -1 /* HOISTED */)
])
)
expect((container.firstChild!.firstChild as any)._value).toBe(true)
})

describe('mismatch handling', () => {
test('text node', () => {
const { container } = mountWithHydration(`foo`, () => 'bar')
6 changes: 3 additions & 3 deletions packages/runtime-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
@@ -32,7 +32,7 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/runtime-core#readme",
"dependencies": {
"@vue/shared": "3.1.2",
"@vue/reactivity": "3.1.2"
"@vue/shared": "3.1.3",
"@vue/reactivity": "3.1.3"
}
}
255 changes: 220 additions & 35 deletions packages/runtime-core/src/apiSetupHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,250 @@
import {
getCurrentInstance,
SetupContext,
createSetupContext
createSetupContext,
setCurrentInstance
} from './component'
import { EmitFn, EmitsOptions } from './componentEmits'
import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
import {
ComponentObjectPropsOptions,
PropOptions,
ExtractPropTypes
} from './componentProps'
import { warn } from './warning'

// dev only
const warnRuntimeUsage = (method: string) =>
warn(
`${method}() is a compiler-hint helper that is only usable inside ` +
`<script setup> of a single file component. Its arguments should be ` +
`compiled away and passing it at runtime has no effect.`
)

/**
* Compile-time-only helper used for declaring props inside `<script setup>`.
* This is stripped away in the compiled code and should never be actually
* called at runtime.
* Vue `<script setup>` compiler macro for declaring component props. The
* expected argument is the same as the component `props` option.
*
* Example runtime declaration:
* ```js
* // using Array syntax
* const props = defineProps(['foo', 'bar'])
* // using Object syntax
* const props = defineProps({
* foo: String,
* bar: {
* type: Number,
* required: true
* }
* })
* ```
*
* Equivalent type-based decalration:
* ```ts
* // will be compiled into equivalent runtime declarations
* const props = defineProps<{
* foo?: string
* bar: number
* }>()
* ```
*
* This is only usable inside `<script setup>`, is compiled away in the
* output and should **not** be actually called at runtime.
*/
// overload 1: string props
// overload 1: runtime props w/ array
export function defineProps<PropNames extends string = string>(
props: PropNames[]
): Readonly<{ [key in PropNames]?: any }>
// overload 2: runtime props w/ object
export function defineProps<
TypeProps = undefined,
PropNames extends string = string,
InferredProps = { [key in PropNames]?: any }
>(
props?: PropNames[]
): Readonly<TypeProps extends undefined ? InferredProps : TypeProps>
// overload 2: object props
export function defineProps<
TypeProps = undefined,
PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions,
InferredProps = ExtractPropTypes<PP>
>(props?: PP): Readonly<TypeProps extends undefined ? InferredProps : TypeProps>
PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions
>(props: PP): Readonly<ExtractPropTypes<PP>>
// overload 3: typed-based declaration
export function defineProps<TypeProps>(): Readonly<TypeProps>
// implementation
export function defineProps() {
if (__DEV__) {
warn(
`defineProps() is a compiler-hint helper that is only usable inside ` +
`<script setup> of a single file component. Its arguments should be ` +
`compiled away and passing it at runtime has no effect.`
)
warnRuntimeUsage(`defineProps`)
}
return null as any
}

export function defineEmit<
TypeEmit = undefined,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
InferredEmit = EmitFn<E>
>(emitOptions?: E | EE[]): TypeEmit extends undefined ? InferredEmit : TypeEmit
/**
* Vue `<script setup>` compiler macro for declaring a component's emitted
* events. The expected argument is the same as the component `emits` option.
*
* Example runtime declaration:
* ```js
* const emit = defineEmits(['change', 'update'])
* ```
*
* Example type-based decalration:
* ```ts
* const emit = defineEmits<{
* (event: 'change'): void
* (event: 'update', id: number): void
* }>()
*
* emit('change')
* emit('update', 1)
* ```
*
* This is only usable inside `<script setup>`, is compiled away in the
* output and should **not** be actually called at runtime.
*/
// overload 1: runtime emits w/ array
export function defineEmits<EE extends string = string>(
emitOptions: EE[]
): EmitFn<EE[]>
export function defineEmits<E extends EmitsOptions = EmitsOptions>(
emitOptions: E
): EmitFn<E>
export function defineEmits<TypeEmit>(): TypeEmit
// implementation
export function defineEmit() {
export function defineEmits() {
if (__DEV__) {
warn(
`defineEmit() is a compiler-hint helper that is only usable inside ` +
`<script setup> of a single file component. Its arguments should be ` +
`compiled away and passing it at runtime has no effect.`
)
warnRuntimeUsage(`defineEmits`)
}
return null as any
}

/**
* @deprecated use `defineEmits` instead.
*/
export const defineEmit = defineEmits

/**
* Vue `<script setup>` compiler macro for declaring a component's exposed
* instance properties when it is accessed by a parent component via template
* refs.
*
* `<script setup>` components are closed by default - i.e. varaibles inside
* the `<script setup>` scope is not exposed to parent unless explicitly exposed
* via `defineExpose`.
*
* This is only usable inside `<script setup>`, is compiled away in the
* output and should **not** be actually called at runtime.
*/
export function defineExpose(exposed?: Record<string, any>) {
if (__DEV__) {
warnRuntimeUsage(`defineExpose`)
}
}

type NotUndefined<T> = T extends undefined ? never : T

type InferDefaults<T> = {
[K in keyof T]?: NotUndefined<T[K]> extends (
| number
| string
| boolean
| symbol
| Function)
? NotUndefined<T[K]>
: (props: T) => NotUndefined<T[K]>
}

type PropsWithDefaults<Base, Defaults> = Base &
{
[K in keyof Defaults]: K extends keyof Base ? NotUndefined<Base[K]> : never
}

/**
* Vue `<script setup>` compiler macro for providing props default values when
* using type-based `defineProps` decalration.
*
* Example usage:
* ```ts
* withDefaults(defineProps<{
* size?: number
* labels?: string[]
* }>(), {
* size: 3,
* labels: () => ['default label']
* })
* ```
*
* This is only usable inside `<script setup>`, is compiled away in the output
* and should **not** be actually called at runtime.
*/
export function withDefaults<Props, Defaults extends InferDefaults<Props>>(
props: Props,
defaults: Defaults
): PropsWithDefaults<Props, Defaults> {
if (__DEV__) {
warnRuntimeUsage(`withDefaults`)
}
return null as any
}

/**
* @deprecated use `useSlots` and `useAttrs` instead.
*/
export function useContext(): SetupContext {
if (__DEV__) {
warn(
`\`useContext()\` has been deprecated and will be removed in the ` +
`next minor release. Use \`useSlots()\` and \`useAttrs()\` instead.`
)
}
return getContext()
}

export function useSlots(): SetupContext['slots'] {
return getContext().slots
}

export function useAttrs(): SetupContext['attrs'] {
return getContext().attrs
}

function getContext(): SetupContext {
const i = getCurrentInstance()!
if (__DEV__ && !i) {
warn(`useContext() called without active instance.`)
}
return i.setupContext || (i.setupContext = createSetupContext(i))
}

/**
* Runtime helper for merging default declarations. Imported by compiled code
* only.
* @internal
*/
export function mergeDefaults(
// the base props is compiler-generated and guaranteed to be in this shape.
props: Record<string, PropOptions | null>,
defaults: Record<string, any>
) {
for (const key in defaults) {
const val = props[key]
if (val) {
val.default = defaults[key]
} else if (val === null) {
props[key] = { default: defaults[key] }
} else if (__DEV__) {
warn(`props default key "${key}" has no corresponding declaration.`)
}
}
return props
}

/**
* Runtime helper for storing and resuming current instance context in
* async setup().
*/
export async function withAsyncContext<T>(
awaitable: T | Promise<T>
): Promise<T> {
const ctx = getCurrentInstance()
setCurrentInstance(null) // unset after storing instance
if (__DEV__ && !ctx) {
warn(`withAsyncContext() called when there is no active context instance.`)
}
let res: T
try {
res = await awaitable
} finally {
setCurrentInstance(ctx)
}
return res
}
4 changes: 2 additions & 2 deletions packages/runtime-core/src/apiWatch.ts
Original file line number Diff line number Diff line change
@@ -175,8 +175,8 @@ function doWatch(
let isMultiSource = false

if (isRef(source)) {
getter = () => (source as Ref).value
forceTrigger = !!(source as Ref)._shallow
getter = () => source.value
forceTrigger = !!source._shallow
} else if (isReactive(source)) {
getter = () => source
deep = true
42 changes: 34 additions & 8 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,8 @@ import {
createRenderContext,
exposePropsOnRenderContext,
exposeSetupStateOnRenderContext,
ComponentPublicInstanceConstructor
ComponentPublicInstanceConstructor,
publicPropertiesMap
} from './componentPublicInstance'
import {
ComponentPropsOptions,
@@ -169,7 +170,7 @@ export interface SetupContext<E = EmitsOptions> {
attrs: Data
slots: Slots
emit: EmitFn<E>
expose: (exposed: Record<string, any>) => void
expose: (exposed?: Record<string, any>) => void
}

/**
@@ -291,6 +292,7 @@ export interface ComponentInternalInstance {

// exposed properties via expose()
exposed: Record<string, any> | null
exposeProxy: Record<string, any> | null

/**
* alternative proxy used only for runtime-compiled render functions using
@@ -447,6 +449,7 @@ export function createComponentInstance(
render: null,
proxy: null,
exposed: null,
exposeProxy: null,
withProxy: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
@@ -620,6 +623,11 @@ function setupStatefulComponent(
currentInstance = null

if (isPromise(setupResult)) {
const unsetInstance = () => {
currentInstance = null
}
setupResult.then(unsetInstance, unsetInstance)

if (isSSR) {
// return the promise so server-renderer can wait on it
return setupResult
@@ -815,11 +823,9 @@ export function finishComponentSetup(
}
}

const attrHandlers: ProxyHandler<Data> = {
const attrDevProxyHandlers: ProxyHandler<Data> = {
get: (target, key: string) => {
if (__DEV__) {
markAttrsAccessed()
}
markAttrsAccessed()
return target[key]
},
set: () => {
@@ -839,15 +845,18 @@ export function createSetupContext(
if (__DEV__ && instance.exposed) {
warn(`expose() should be called only once per setup().`)
}
instance.exposed = proxyRefs(exposed)
instance.exposed = exposed || {}
}

if (__DEV__) {
let attrs: Data
// We use getters in dev in case libs like test-utils overwrite instance
// properties (overwrites should not be done in prod)
return Object.freeze({
get attrs() {
return new Proxy(instance.attrs, attrHandlers)
return (
attrs || (attrs = new Proxy(instance.attrs, attrDevProxyHandlers))
)
},
get slots() {
return shallowReadonly(instance.slots)
@@ -867,6 +876,23 @@ export function createSetupContext(
}
}

export function getExposeProxy(instance: ComponentInternalInstance) {
if (instance.exposed) {
return (
instance.exposeProxy ||
(instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
get(target, key: string) {
if (key in target) {
return target[key]
} else if (key in publicPropertiesMap) {
return publicPropertiesMap[key](instance)
}
}
}))
)
}
}

// record effects created during a component's setup() so that they can be
// stopped when the component unmounts
export function recordInstanceBoundEffect(
16 changes: 8 additions & 8 deletions packages/runtime-core/src/componentOptions.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ import {
isString,
isObject,
isArray,
EMPTY_OBJ,
NOOP,
isPromise
} from '@vue/shared'
@@ -45,9 +44,7 @@ import {
import {
reactive,
ComputedGetter,
WritableComputedOptions,
proxyRefs,
toRef
WritableComputedOptions
} from '@vue/reactivity'
import {
ComponentObjectPropsOptions,
@@ -540,7 +537,7 @@ export let shouldCacheAccess = true

export function applyOptions(instance: ComponentInternalInstance) {
const options = resolveMergedOptions(instance)
const publicThis = instance.proxy!
const publicThis = instance.proxy! as any
const ctx = instance.ctx

// do not cache property access on public proxy during state initialization
@@ -773,12 +770,15 @@ export function applyOptions(instance: ComponentInternalInstance) {

if (isArray(expose)) {
if (expose.length) {
const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
const exposed = instance.exposed || (instance.exposed = {})
expose.forEach(key => {
exposed[key] = toRef(publicThis, key as any)
Object.defineProperty(exposed, key, {
get: () => publicThis[key],
set: val => (publicThis[key] = val)
})
})
} else if (!instance.exposed) {
instance.exposed = EMPTY_OBJ
instance.exposed = {}
}
}

5 changes: 3 additions & 2 deletions packages/runtime-core/src/componentProps.ts
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>

type DefaultFactory<T> = (props: Data) => T | null | undefined

interface PropOptions<T = any, D = T> {
export interface PropOptions<T = any, D = T> {
type?: PropType<T> | true | null
required?: boolean
default?: D | DefaultFactory<D> | null | undefined | object
@@ -109,7 +109,8 @@ type InferPropType<T> = [T] extends [null]
: T

export type ExtractPropTypes<O> = O extends object
? { [K in RequiredKeys<O>]: InferPropType<O[K]> } &
? { [K in keyof O]?: unknown } & // This is needed to keep the relation between the option prop and the props, allowing to use ctrl+click to navigate to the prop options. see: #3656
{ [K in RequiredKeys<O>]: InferPropType<O[K]> } &
{ [K in OptionalKeys<O>]?: InferPropType<O[K]> }
: { [K in string]: any }

50 changes: 33 additions & 17 deletions packages/runtime-core/src/componentPublicInstance.ts
Original file line number Diff line number Diff line change
@@ -221,22 +221,25 @@ const getPublicInstance = (
return getPublicInstance(i.parent)
}

const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
$props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
$attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
$slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
$refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
$parent: i => getPublicInstance(i.parent),
$root: i => getPublicInstance(i.root),
$emit: i => i.emit,
$options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
$forceUpdate: i => () => queueJob(i.update),
$nextTick: i => nextTick.bind(i.proxy!),
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
} as PublicPropertiesMap)
export const publicPropertiesMap: PublicPropertiesMap = extend(
Object.create(null),
{
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
$props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
$attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
$slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
$refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
$parent: i => getPublicInstance(i.parent),
$root: i => getPublicInstance(i.root),
$emit: i => i.emit,
$options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
$forceUpdate: i => () => queueJob(i.update),
$nextTick: i => nextTick.bind(i.proxy!),
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
} as PublicPropertiesMap
)

if (__COMPAT__) {
installCompatInstanceProperties(publicPropertiesMap)
@@ -272,6 +275,19 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
return true
}

// prioritize <script setup> bindings during dev.
// this allows even properties that start with _ or $ to be used - so that
// it aligns with the production behavior where the render fn is inlined and
// indeed has access to all declared variables.
if (
__DEV__ &&
setupState !== EMPTY_OBJ &&
setupState.__isScriptSetup &&
hasOwn(setupState, key)
) {
return setupState[key]
}

// data / props / ctx
// This getter gets called for every property access on the render context
// during render and is a major hotspot. The most expensive part of this
@@ -526,7 +542,7 @@ export function exposeSetupStateOnRenderContext(
) {
const { ctx, setupState } = instance
Object.keys(toRaw(setupState)).forEach(key => {
if (key[0] === '$' || key[0] === '_') {
if (!setupState.__isScriptSetup && (key[0] === '$' || key[0] === '_')) {
warn(
`setup() return property ${JSON.stringify(
key
1 change: 1 addition & 0 deletions packages/runtime-core/src/errorHandling.ts
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ export const enum ErrorCodes {
}

export const ErrorTypeStrings: Record<number | string, string> = {
[LifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
[LifecycleHooks.BEFORE_CREATE]: 'beforeCreate hook',
[LifecycleHooks.CREATED]: 'created hook',
[LifecycleHooks.BEFORE_MOUNT]: 'beforeMount hook',
29 changes: 19 additions & 10 deletions packages/runtime-core/src/hydration.ts
Original file line number Diff line number Diff line change
@@ -57,12 +57,14 @@ export function createHydrationFunctions(
} = rendererInternals

const hydrate: RootHydrateFunction = (vnode, container) => {
if (__DEV__ && !container.hasChildNodes()) {
warn(
`Attempting to hydrate existing markup but container is empty. ` +
`Performing full mount instead.`
)
if (!container.hasChildNodes()) {
__DEV__ &&
warn(
`Attempting to hydrate existing markup but container is empty. ` +
`Performing full mount instead.`
)
patch(null, vnode, container)
flushPostFlushCbs()
return
}
hasMismatch = false
@@ -132,10 +134,10 @@ export function createHydrationFunctions(
// if the static vnode has its content stripped during build,
// adopt it from the server-rendered HTML.
const needToAdoptContent = !(vnode.children as string).length
for (let i = 0; i < vnode.staticCount; i++) {
for (let i = 0; i < vnode.staticCount!; i++) {
if (needToAdoptContent)
vnode.children += (nextNode as Element).outerHTML
if (i === vnode.staticCount - 1) {
if (i === vnode.staticCount! - 1) {
vnode.anchor = nextNode
}
nextNode = nextSibling(nextNode)!
@@ -264,21 +266,28 @@ export function createHydrationFunctions(
optimized: boolean
) => {
optimized = optimized || !!vnode.dynamicChildren
const { props, patchFlag, shapeFlag, dirs } = vnode
const { type, props, patchFlag, shapeFlag, dirs } = vnode
// #4006 for form elements with non-string v-model value bindings
// e.g. <option :value="obj">, <input type="checkbox" :true-value="1">
const forcePatchValue = (type === 'input' && dirs) || type === 'option'
// skip props & children if this is hoisted static nodes
if (patchFlag !== PatchFlags.HOISTED) {
if (forcePatchValue || patchFlag !== PatchFlags.HOISTED) {
if (dirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'created')
}
// props
if (props) {
if (
forcePatchValue ||
!optimized ||
(patchFlag & PatchFlags.FULL_PROPS ||
patchFlag & PatchFlags.HYDRATE_EVENTS)
) {
for (const key in props) {
if (!isReservedProp(key) && isOn(key)) {
if (
(forcePatchValue && key.endsWith('value')) ||
(isOn(key) && !isReservedProp(key))
) {
patchProp(el, key, null, props[key])
}
}
21 changes: 18 additions & 3 deletions packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -44,7 +44,22 @@ export { provide, inject } from './apiInject'
export { nextTick } from './scheduler'
export { defineComponent } from './apiDefineComponent'
export { defineAsyncComponent } from './apiAsyncComponent'
export { defineProps, defineEmit, useContext } from './apiSetupHelpers'

// <script setup> API ----------------------------------------------------------

export {
// macros runtime, for warnings only
defineProps,
defineEmits,
defineExpose,
withDefaults,
// internal
mergeDefaults,
withAsyncContext,
// deprecated
defineEmit,
useContext
} from './apiSetupHelpers'

// Advanced API ----------------------------------------------------------------

@@ -135,7 +150,6 @@ export {
DeepReadonly
} from '@vue/reactivity'
export {
// types
WatchEffect,
WatchOptions,
WatchOptionsBase,
@@ -186,7 +200,8 @@ export {
export { EmitsOptions, ObjectEmitsOptions } from './componentEmits'
export {
ComponentPublicInstance,
ComponentCustomProperties
ComponentCustomProperties,
CreateComponentPublicInstance
} from './componentPublicInstance'
export {
Renderer,
15 changes: 11 additions & 4 deletions packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import {
ComponentOptions,
createComponentInstance,
Data,
getExposeProxy,
setupComponent
} from './component'
import {
@@ -142,7 +143,7 @@ export interface RendererOptions<
parent: HostElement,
anchor: HostNode | null,
isSVG: boolean,
cached?: [HostNode, HostNode | null] | null
cached?: HostNode[] | null
): HostElement[]
}

@@ -335,7 +336,7 @@ export const setRef = (

const refValue =
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
? vnode.component!.exposed || vnode.component!.proxy
? getExposeProxy(vnode.component!) || vnode.component!.proxy
: vnode.el
const value = isUnmount ? null : refValue

@@ -632,16 +633,22 @@ function baseCreateRenderer(
) => {
// static nodes are only present when used with compiler-dom/runtime-dom
// which guarantees presence of hostInsertStaticContent.
;[n2.el, n2.anchor] = hostInsertStaticContent!(
const nodes = hostInsertStaticContent!(
n2.children as string,
container,
anchor,
isSVG,
// pass cached nodes if the static node is being mounted multiple times
// so that runtime-dom can simply cloneNode() instead of inserting new
// HTML
n2.el && [n2.el, n2.anchor]
n2.staticCache
)
// first mount - this is the orignal hoisted vnode. cache nodes.
if (!n2.el) {
n2.staticCache = nodes
}
n2.el = nodes[0]
n2.anchor = nodes[nodes.length - 1]
}

/**
5 changes: 3 additions & 2 deletions packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
@@ -167,7 +167,8 @@ export interface VNode<
anchor: HostNode | null // fragment anchor
target: HostElement | null // teleport target
targetAnchor: HostNode | null // teleport target anchor
staticCount: number // number of elements contained in a static vnode
staticCount?: number // number of elements contained in a static vnode
staticCache?: HostNode[] // cache of parsed static nodes for faster repeated insertions

// suspense
suspense: SuspenseBoundary | null
@@ -439,7 +440,6 @@ function _createVNode(
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
@@ -521,6 +521,7 @@ export function cloneVNode<T, U>(
target: vnode.target,
targetAnchor: vnode.targetAnchor,
staticCount: vnode.staticCount,
staticCache: vnode.staticCache,
shapeFlag: vnode.shapeFlag,
// if the vnode is cloned with extra props, we can no longer assume its
// existing patch flag to be reliable and need to add the FULL_PROPS flag.
13 changes: 13 additions & 0 deletions packages/runtime-core/types/scriptSetupHelpers.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Note: this file is auto concatenated to the end of the bundled d.ts during
// build.
type _defineProps = typeof defineProps
type _defineEmits = typeof defineEmits
type _defineExpose = typeof defineExpose
type _withDefaults = typeof withDefaults

declare global {
const defineProps: _defineProps
const defineEmits: _defineEmits
const defineExpose: _defineExpose
const withDefaults: _withDefaults
}
40 changes: 16 additions & 24 deletions packages/runtime-dom/__tests__/nodeOps.spec.ts
Original file line number Diff line number Diff line change
@@ -30,15 +30,11 @@ describe('runtime-dom: node-ops', () => {
test('fresh insertion', () => {
const content = `<div>one</div><div>two</div>three`
const parent = document.createElement('div')
const [first, last] = nodeOps.insertStaticContent!(
content,
parent,
null,
false
)
const nodes = nodeOps.insertStaticContent!(content, parent, null, false)
expect(parent.innerHTML).toBe(content)
expect(first).toBe(parent.firstChild)
expect(last).toBe(parent.lastChild)
expect(nodes.length).toBe(3)
expect(nodes[0]).toBe(parent.firstChild)
expect(nodes[nodes.length - 1]).toBe(parent.lastChild)
})

test('fresh insertion with anchor', () => {
@@ -47,15 +43,13 @@ describe('runtime-dom: node-ops', () => {
const parent = document.createElement('div')
parent.innerHTML = existing
const anchor = parent.firstChild
const [first, last] = nodeOps.insertStaticContent!(
content,
parent,
anchor,
false
)
const nodes = nodeOps.insertStaticContent!(content, parent, anchor, false)
expect(parent.innerHTML).toBe(content + existing)
expect(first).toBe(parent.firstChild)
expect(last).toBe(parent.childNodes[parent.childNodes.length - 2])
expect(nodes.length).toBe(3)
expect(nodes[0]).toBe(parent.firstChild)
expect(nodes[nodes.length - 1]).toBe(
parent.childNodes[parent.childNodes.length - 2]
)
})

test('fresh insertion as svg', () => {
@@ -97,7 +91,7 @@ describe('runtime-dom: node-ops', () => {
const content = `<div>one</div><div>two</div>three`

const cacheParent = document.createElement('div')
const [cachedFirst, cachedLast] = nodeOps.insertStaticContent!(
const nodes = nodeOps.insertStaticContent!(
content,
cacheParent,
null,
@@ -106,20 +100,18 @@ describe('runtime-dom: node-ops', () => {

const parent = document.createElement('div')

const [first, last] = nodeOps.insertStaticContent!(
const clonedNodes = nodeOps.insertStaticContent!(
``,
parent,
null,
false,
[cachedFirst, cachedLast]
nodes
)

expect(parent.innerHTML).toBe(content)
expect(first).toBe(parent.firstChild)
expect(last).toBe(parent.lastChild)

expect(first).not.toBe(cachedFirst)
expect(last).not.toBe(cachedLast)
expect(clonedNodes[0]).toBe(parent.firstChild)
expect(clonedNodes[clonedNodes.length - 1]).toBe(parent.lastChild)
expect(clonedNodes[0]).not.toBe(nodes[0])
})
})
})
6 changes: 3 additions & 3 deletions packages/runtime-dom/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-dom",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
@@ -35,8 +35,8 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/runtime-dom#readme",
"dependencies": {
"@vue/shared": "3.1.2",
"@vue/runtime-core": "3.1.2",
"@vue/shared": "3.1.3",
"@vue/runtime-core": "3.1.3",
"csstype": "^2.6.8"
}
}
33 changes: 17 additions & 16 deletions packages/runtime-dom/src/nodeOps.ts
Original file line number Diff line number Diff line change
@@ -73,17 +73,15 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
// As long as the user only uses trusted templates, this is safe.
insertStaticContent(content, parent, anchor, isSVG, cached) {
if (cached) {
let [cachedFirst, cachedLast] = cached
let first, last
while (true) {
let node = cachedFirst.cloneNode(true)
if (!first) first = node
let first
let last
let i = 0
let l = cached.length
for (; i < l; i++) {
const node = cached[i].cloneNode(true)
if (i === 0) first = node
if (i === l - 1) last = node
parent.insertBefore(node, anchor)
if (cachedFirst === cachedLast) {
last = node
break
}
cachedFirst = cachedFirst.nextSibling!
}
return [first, last] as any
}
@@ -111,11 +109,14 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
} else {
parent.insertAdjacentHTML('beforeend', content)
}
return [
// first
before ? before.nextSibling : parent.firstChild,
// last
anchor ? anchor.previousSibling : parent.lastChild
]
let first = before ? before.nextSibling : parent.firstChild
const last = anchor ? anchor.previousSibling : parent.lastChild
const ret = []
while (first) {
ret.push(first)
if (first === last) break
first = first.nextSibling
}
return ret
}
}
6 changes: 2 additions & 4 deletions packages/runtime-dom/types/jsx.d.ts
Original file line number Diff line number Diff line change
@@ -1296,10 +1296,8 @@ export interface Events {
onTransitionstart: TransitionEvent
}

type StringKeyOf<T> = Extract<keyof T, string>

type EventHandlers<E> = {
[K in StringKeyOf<E>]?: E[K] extends Function ? E[K] : (payload: E[K]) => void
[K in keyof E]?: E[K] extends Function ? E[K] : (payload: E[K]) => void
}

// use namespace import to avoid collision with generated types which use
@@ -1317,7 +1315,7 @@ type ReservedProps = {
type ElementAttrs<T> = T & ReservedProps

type NativeElements = {
[K in StringKeyOf<IntrinsicElementAttributes>]: ElementAttrs<
[K in keyof IntrinsicElementAttributes]: ElementAttrs<
IntrinsicElementAttributes[K]
>
}
6 changes: 3 additions & 3 deletions packages/runtime-test/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-test",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/runtime-test",
"private": true,
"main": "index.js",
@@ -25,7 +25,7 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/runtime-test#readme",
"dependencies": {
"@vue/shared": "3.1.2",
"@vue/runtime-core": "3.1.2"
"@vue/shared": "3.1.3",
"@vue/runtime-core": "3.1.3"
}
}
8 changes: 4 additions & 4 deletions packages/server-renderer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/server-renderer",
"version": "3.1.2",
"version": "3.1.3",
"description": "@vue/server-renderer",
"main": "index.js",
"types": "dist/server-renderer.d.ts",
@@ -28,10 +28,10 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/server-renderer#readme",
"peerDependencies": {
"vue": "3.1.2"
"vue": "3.1.3"
},
"dependencies": {
"@vue/shared": "3.1.2",
"@vue/compiler-ssr": "3.1.2"
"@vue/shared": "3.1.3",
"@vue/compiler-ssr": "3.1.3"
}
}
5 changes: 3 additions & 2 deletions packages/sfc-playground/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/sfc-playground",
"version": "3.1.2",
"version": "3.1.3",
"private": true,
"scripts": {
"dev": "vite",
@@ -22,6 +22,7 @@
},
"dependencies": {
"file-saver": "^2.0.5",
"jszip": "^3.6.0"
"jszip": "^3.6.0",
"sucrase": "^3.19.0"
}
}
95 changes: 48 additions & 47 deletions packages/sfc-playground/src/Header.vue
Original file line number Diff line number Diff line change
@@ -1,50 +1,3 @@
<template>
<nav>
<h1>
<img alt="logo" src="/logo.svg">
<span>Vue SFC Playground</span>
</h1>
<div class="links">
<div class="version" @click.stop>
<span class="active-version" @click="toggle">
Version: {{ activeVersion }}
</span>
<ul class="versions" :class="{ expanded }">
<li v-if="!publishedVersions"><a>loading versions...</a></li>
<li v-for="version of publishedVersions">
<a @click="setVueVersion(version)">v{{ version }}</a>
</li>
<li><a @click="resetVueVersion">This Commit ({{ currentCommit }})</a></li>
<li>
<a href="https://app.netlify.com/sites/vue-sfc-playground/deploys" target="_blank">Commits History</a>
</li>
</ul>
</div>
<button class="share" @click="copyLink">
<svg width="1.4em" height="1.4em" viewBox="0 0 24 24">
<g fill="none" stroke="#626262" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="5" r="3"/>
<circle cx="6" cy="12" r="3"/>
<circle cx="18" cy="19" r="3"/>
<path d="M8.59 13.51l6.83 3.98"/>
<path d="M15.41 6.51l-6.82 3.98"/>
</g>
</svg>
</button>
<button class="download" @click="downloadProject">
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
<g fill="#626262">
<rect x="4" y="18" width="16" height="2" rx="1" ry="1"/>
<rect x="3" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 5 18)"/>
<rect x="17" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 19 18)"/>
<path d="M12 15a1 1 0 0 1-.58-.18l-4-2.82a1 1 0 0 1-.24-1.39a1 1 0 0 1 1.4-.24L12 12.76l3.4-2.56a1 1 0 0 1 1.2 1.6l-4 3a1 1 0 0 1-.6.2z"/>
<path d="M12 13a1 1 0 0 1-1-1V4a1 1 0 0 1 2 0v8a1 1 0 0 1-1 1z"/>
</g>
</svg>
</button>
</div>
</nav>
</template>

<script setup lang="ts">
import { downloadProject } from './download/download'
@@ -100,6 +53,54 @@ async function fetchVersions(): Promise<string[]> {
}
</script>

<template>
<nav>
<h1>
<img alt="logo" src="/logo.svg">
<span>Vue SFC Playground</span>
</h1>
<div class="links">
<div class="version" @click.stop>
<span class="active-version" @click="toggle">
Version: {{ activeVersion }}
</span>
<ul class="versions" :class="{ expanded }">
<li v-if="!publishedVersions"><a>loading versions...</a></li>
<li v-for="version of publishedVersions">
<a @click="setVueVersion(version)">v{{ version }}</a>
</li>
<li><a @click="resetVueVersion">This Commit ({{ currentCommit }})</a></li>
<li>
<a href="https://app.netlify.com/sites/vue-sfc-playground/deploys" target="_blank">Commits History</a>
</li>
</ul>
</div>
<button class="share" @click="copyLink">
<svg width="1.4em" height="1.4em" viewBox="0 0 24 24">
<g fill="none" stroke="#626262" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="5" r="3"/>
<circle cx="6" cy="12" r="3"/>
<circle cx="18" cy="19" r="3"/>
<path d="M8.59 13.51l6.83 3.98"/>
<path d="M15.41 6.51l-6.82 3.98"/>
</g>
</svg>
</button>
<button class="download" @click="downloadProject">
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
<g fill="#626262">
<rect x="4" y="18" width="16" height="2" rx="1" ry="1"/>
<rect x="3" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 5 18)"/>
<rect x="17" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 19 18)"/>
<path d="M12 15a1 1 0 0 1-.58-.18l-4-2.82a1 1 0 0 1-.24-1.39a1 1 0 0 1 1.4-.24L12 12.76l3.4-2.56a1 1 0 0 1 1.2 1.6l-4 3a1 1 0 0 1-.6.2z"/>
<path d="M12 13a1 1 0 0 1-1-1V4a1 1 0 0 1 2 0v8a1 1 0 0 1-1 1z"/>
</g>
</svg>
</button>
</div>
</nav>
</template>

<style>
nav {
height: var(--nav-height);
4 changes: 2 additions & 2 deletions packages/sfc-playground/src/codemirror/CodeMirror.vue
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
</template>

<script setup lang="ts">
import { ref, onMounted, defineProps, defineEmit, watchEffect } from 'vue'
import { ref, onMounted, defineProps, defineEmits, watchEffect } from 'vue'
import { debounce } from '../utils'
import CodeMirror from './codemirror'
@@ -24,7 +24,7 @@ const props = defineProps({
}
})
const emit = defineEmit<(e: 'change', value: string) => void>()
const emit = defineEmits<(e: 'change', value: string) => void>()
onMounted(() => {
const addonOptions = {
8 changes: 7 additions & 1 deletion packages/sfc-playground/src/output/Preview.vue
Original file line number Diff line number Diff line change
@@ -123,6 +123,9 @@ function createSandbox() {
runtimeError.value = 'Uncaught (in promise): ' + error.message
},
on_console: (log: any) => {
if (log.duplicate) {
return
}
if (log.level === 'error') {
if (log.args[0] instanceof Error) {
runtimeError.value = log.args[0].message
@@ -156,7 +159,10 @@ function createSandbox() {
}
async function updatePreview() {
console.clear()
// @ts-ignore
if (import.meta.env.PROD) {
console.clear()
}
runtimeError.value = null
runtimeWarning.value = null
try {
50 changes: 36 additions & 14 deletions packages/sfc-playground/src/sfcCompiler.ts
Original file line number Diff line number Diff line change
@@ -59,17 +59,24 @@ export async function compileFile({ filename, code, compiled }: File) {
}

if (
(descriptor.script && descriptor.script.lang) ||
(descriptor.scriptSetup && descriptor.scriptSetup.lang) ||
descriptor.styles.some(s => s.lang) ||
(descriptor.template && descriptor.template.lang)
) {
store.errors = [
'lang="x" pre-processors are not supported in the in-browser playground.'
`lang="x" pre-processors for <template> or <style> are currently not ` +
`supported.`
]
return
}

const scriptLang =
(descriptor.script && descriptor.script.lang) ||
(descriptor.scriptSetup && descriptor.scriptSetup.lang)
if (scriptLang && scriptLang !== 'ts') {
store.errors = [`Only lang="ts" is supported for <script> blocks.`]
return
}

const hasScoped = descriptor.styles.some(s => s.scoped)
let clientCode = ''
let ssrCode = ''
@@ -79,7 +86,7 @@ export async function compileFile({ filename, code, compiled }: File) {
ssrCode += code
}

const clientScriptResult = doCompileScript(descriptor, id, false)
const clientScriptResult = await doCompileScript(descriptor, id, false)
if (!clientScriptResult) {
return
}
@@ -89,11 +96,12 @@ export async function compileFile({ filename, code, compiled }: File) {
// script ssr only needs to be performed if using <script setup> where
// the render fn is inlined.
if (descriptor.scriptSetup) {
const ssrScriptResult = doCompileScript(descriptor, id, true)
if (!ssrScriptResult) {
return
const ssrScriptResult = await doCompileScript(descriptor, id, true)
if (ssrScriptResult) {
ssrCode += ssrScriptResult[0]
} else {
ssrCode = `/* SSR compile error: ${store.errors[0]} */`
}
ssrCode += ssrScriptResult[0]
} else {
// when no <script setup> is used, the script result will be identical.
ssrCode += clientScript
@@ -114,10 +122,12 @@ export async function compileFile({ filename, code, compiled }: File) {
clientCode += clientTemplateResult

const ssrTemplateResult = doCompileTemplate(descriptor, id, bindings, true)
if (!ssrTemplateResult) {
return
if (ssrTemplateResult) {
// ssr compile failure is fine
ssrCode += ssrTemplateResult
} else {
ssrCode = `/* SSR compile error: ${store.errors[0]} */`
}
ssrCode += ssrTemplateResult
}

if (hasScoped) {
@@ -171,11 +181,11 @@ export async function compileFile({ filename, code, compiled }: File) {
store.errors = []
}

function doCompileScript(
async function doCompileScript(
descriptor: SFCDescriptor,
id: string,
ssr: boolean
): [string, BindingMetadata | undefined] | undefined {
): Promise<[string, BindingMetadata | undefined] | undefined> {
if (descriptor.script || descriptor.scriptSetup) {
try {
const compiledScript = SFCCompiler.compileScript(descriptor, {
@@ -198,9 +208,21 @@ function doCompileScript(
code +=
`\n` +
SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER)

if ((descriptor.script || descriptor.scriptSetup)!.lang === 'ts') {
code = (await import('sucrase')).transform(code, {
transforms: ['typescript']
}).code
}

return [code, compiledScript.bindings]
} catch (e) {
store.errors = [e]
store.errors = [
e.stack
.split('\n')
.slice(0, 12)
.join('\n')
]
return
}
} else {
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/shared",
"version": "3.1.2",
"version": "3.1.3",
"description": "internal utils shared across @vue packages",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
2 changes: 1 addition & 1 deletion packages/size-check/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/size-check",
"version": "3.1.2",
"version": "3.1.3",
"private": true,
"buildOptions": {
"name": "Vue",
2 changes: 1 addition & 1 deletion packages/template-explorer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/template-explorer",
"version": "3.1.2",
"version": "3.1.3",
"private": true,
"buildOptions": {
"formats": [
4 changes: 2 additions & 2 deletions packages/vue-compat/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vue/compat",
"version": "3.1.2",
"version": "3.1.3",
"description": "Vue 3 compatibility build for Vue 2",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",
@@ -38,6 +38,6 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/vue-compat#readme",
"peerDependencies": {
"vue": "3.1.2"
"vue": "3.1.3"
}
}
2 changes: 1 addition & 1 deletion packages/vue/__tests__/Transition.spec.ts
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ describe('e2e: Transition', () => {

beforeEach(async () => {
await page().goto(baseUrl)
await page().waitFor('#app')
await page().waitForSelector('#app')
})

describe('transition with v-if', () => {
2 changes: 1 addition & 1 deletion packages/vue/__tests__/TransitionGroup.spec.ts
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ describe('e2e: TransitionGroup', () => {

beforeEach(async () => {
await page().goto(baseUrl)
await page().waitFor('#app')
await page().waitForSelector('#app')
})

test(
2 changes: 1 addition & 1 deletion packages/vue/__tests__/e2eUtils.ts
Original file line number Diff line number Diff line change
@@ -108,7 +108,7 @@ export function setupPuppeteer() {
await page.$eval(
selector,
(node, value) => {
;(node as HTMLInputElement).value = value
;(node as HTMLInputElement).value = value as string
node.dispatchEvent(new Event('input'))
},
value
2 changes: 1 addition & 1 deletion packages/vue/examples/__tests__/commits.spec.ts
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ describe('e2e: commits', () => {
})

await page().goto(baseUrl)
await page().waitFor('li')
await page().waitForSelector('li')
expect(await count('input')).toBe(2)
expect(await count('label')).toBe(2)
expect(await text('label[for="master"]')).toBe('master')
2 changes: 1 addition & 1 deletion packages/vue/examples/__tests__/grid.spec.ts
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ describe('e2e: grid', () => {
)}`

await page().goto(baseUrl)
await page().waitFor('table')
await page().waitForSelector('table')
expect(await count('th')).toBe(2)
expect(await count('th.active')).toBe(0)
expect(await text('th:nth-child(1)')).toContain('Name')
2 changes: 1 addition & 1 deletion packages/vue/examples/__tests__/svg.spec.ts
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ describe('e2e: svg', () => {
)}`

await page().goto(baseUrl)
await page().waitFor('svg')
await page().waitForSelector('svg')
expect(await count('g')).toBe(1)
expect(await count('polygon')).toBe(1)
expect(await count('circle')).toBe(1)
8 changes: 4 additions & 4 deletions packages/vue/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue",
"version": "3.1.2",
"version": "3.1.3",
"description": "The progressive JavaScript framework for buiding modern web UI.",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",
@@ -37,9 +37,9 @@
},
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/vue#readme",
"dependencies": {
"@vue/shared": "3.1.2",
"@vue/compiler-dom": "3.1.2",
"@vue/runtime-dom": "3.1.2"
"@vue/shared": "3.1.3",
"@vue/compiler-dom": "3.1.3",
"@vue/runtime-dom": "3.1.3"
},
"devDependencies": {
"lodash": "^4.17.15",
7 changes: 2 additions & 5 deletions test-dts/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// This directory contains a number of d.ts assertions using tsd:
// https://github.com/SamVerschueren/tsd
// The tests checks type errors and will probably show up red in VSCode, and
// it's intended. We cannot use directives like @ts-ignore or @ts-nocheck since
// that would suppress the errors that should be caught.
// This directory contains a number of d.ts assertions
// use \@ts-expect-error where errors are expected.

export * from '@vue/runtime-dom'

7 changes: 7 additions & 0 deletions test-dts/ref.test-d.ts
Original file line number Diff line number Diff line change
@@ -203,3 +203,10 @@ switch (data.state.value) {
data.state.value = 'state1'
break
}

// #3954
function testUnrefGenerics<T>(p: T | Ref<T>) {
expectType<T>(unref(p))
}

testUnrefGenerics(1)
50 changes: 44 additions & 6 deletions test-dts/setupHelpers.test-d.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,9 @@ import {
expectType,
defineProps,
defineEmit,
defineEmits,
useContext,
withDefaults,
Slots,
describe
} from './index'
@@ -18,6 +20,31 @@ describe('defineProps w/ type declaration', () => {
props.bar
})

describe('defineProps w/ type declaration + withDefaults', () => {
const res = withDefaults(
defineProps<{
number?: number
arr?: string[]
obj?: { x: number }
fn?: (e: string) => void
x?: string
}>(),
{
number: 123,
arr: () => [],
obj: () => ({ x: 123 }),
fn: () => {}
}
)

res.number + 1
res.arr.push('hi')
res.obj.x
res.fn('hi')
// @ts-expect-error
res.x.slice()
})

describe('defineProps w/ runtime declaration', () => {
// runtime declaration
const props = defineProps({
@@ -49,16 +76,16 @@ describe('defineProps w/ runtime declaration', () => {
props2.baz
})

describe('defineEmit w/ type declaration', () => {
const emit = defineEmit<(e: 'change') => void>()
describe('defineEmits w/ type declaration', () => {
const emit = defineEmits<(e: 'change') => void>()
emit('change')
// @ts-expect-error
emit()
// @ts-expect-error
emit('bar')

type Emits = { (e: 'foo' | 'bar'): void; (e: 'baz', id: number): void }
const emit2 = defineEmit<Emits>()
const emit2 = defineEmits<Emits>()

emit2('foo')
emit2('bar')
@@ -67,8 +94,8 @@ describe('defineEmit w/ type declaration', () => {
emit2('baz')
})

describe('defineEmit w/ runtime declaration', () => {
const emit = defineEmit({
describe('defineEmits w/ runtime declaration', () => {
const emit = defineEmits({
foo: () => {},
bar: null
})
@@ -77,13 +104,24 @@ describe('defineEmit w/ runtime declaration', () => {
// @ts-expect-error
emit('baz')

const emit2 = defineEmit(['foo', 'bar'])
const emit2 = defineEmits(['foo', 'bar'])
emit2('foo')
emit2('bar', 123)
// @ts-expect-error
emit2('baz')
})

describe('deprecated defineEmit', () => {
const emit = defineEmit({
foo: () => {},
bar: null
})
emit('foo')
emit('bar', 123)
// @ts-expect-error
emit('baz')
})

describe('useContext', () => {
const { attrs, emit, slots } = useContext()
expectType<Record<string, unknown>>(attrs)
517 changes: 335 additions & 182 deletions yarn.lock

Large diffs are not rendered by default.