mirror of https://github.com/vuejs/core.git
chore: Merge branch 'main' into minor
This commit is contained in:
commit
982a145d38
|
@ -58,6 +58,31 @@ jobs:
|
|||
- name: Run ssr unit tests
|
||||
run: pnpm run test-unit server-renderer
|
||||
|
||||
benchmarks:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- name: Run benchmarks
|
||||
uses: CodSpeedHQ/action@v2
|
||||
with:
|
||||
run: pnpm vitest bench --run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
e2e-test:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
|
|
85
CHANGELOG.md
85
CHANGELOG.md
|
@ -1,3 +1,20 @@
|
|||
## [3.3.11](https://github.com/vuejs/core/compare/v3.3.10...v3.3.11) (2023-12-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **custom-element:** correctly handle number type props in prod ([#8989](https://github.com/vuejs/core/issues/8989)) ([d74d364](https://github.com/vuejs/core/commit/d74d364d62db8e48881af6b5a75ce4fb5f36cc35))
|
||||
* **reactivity:** fix mutation on user proxy of reactive Array ([6ecbd5c](https://github.com/vuejs/core/commit/6ecbd5ce2a7f59314a8326a1d193874b87f4d8c8)), closes [#9742](https://github.com/vuejs/core/issues/9742) [#9751](https://github.com/vuejs/core/issues/9751) [#9750](https://github.com/vuejs/core/issues/9750)
|
||||
* **runtime-dom:** fix width and height prop check condition ([5b00286](https://github.com/vuejs/core/commit/5b002869c533220706f9788b496b8ca8d8e98609)), closes [#9762](https://github.com/vuejs/core/issues/9762)
|
||||
* **shared:** handle Map with symbol keys in toDisplayString ([#9731](https://github.com/vuejs/core/issues/9731)) ([364821d](https://github.com/vuejs/core/commit/364821d6bdb1775e2f55a69bcfb9f40f7acf1506)), closes [#9727](https://github.com/vuejs/core/issues/9727)
|
||||
* **shared:** handle more Symbol cases in toDisplayString ([983d45d](https://github.com/vuejs/core/commit/983d45d4f8eb766b5a16b7ea93b86d3c51618fa6))
|
||||
* **Suspense:** properly get anchor when mount fallback vnode ([#9770](https://github.com/vuejs/core/issues/9770)) ([b700328](https://github.com/vuejs/core/commit/b700328342e17dc16b19316c2e134a26107139d2)), closes [#9769](https://github.com/vuejs/core/issues/9769)
|
||||
* **types:** ref() return type should not be any when initial value is any ([#9768](https://github.com/vuejs/core/issues/9768)) ([cdac121](https://github.com/vuejs/core/commit/cdac12161ec27b45ded48854c3d749664b6d4a6d))
|
||||
* **watch:** should not fire pre watcher on child component unmount ([#7181](https://github.com/vuejs/core/issues/7181)) ([6784f0b](https://github.com/vuejs/core/commit/6784f0b1f8501746ea70d87d18ed63a62cf6b76d)), closes [#7030](https://github.com/vuejs/core/issues/7030)
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.4](https://github.com/vuejs/core/compare/v3.3.10...v3.4.0-alpha.4) (2023-12-04)
|
||||
|
||||
|
||||
|
@ -19,74 +36,6 @@
|
|||
|
||||
|
||||
|
||||
# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **parser:** directive arg should be undefined on shorthands with no arg ([e49dffc](https://github.com/vuejs/core/commit/e49dffc9ece86bddf094b9ad4ad15eb4856d6277))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **dx:** link errors to docs in prod build ([#9165](https://github.com/vuejs/core/issues/9165)) ([9f8ba98](https://github.com/vuejs/core/commit/9f8ba9821fe166f77e63fa940e9e7e13ec3344fa))
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.2](https://github.com/vuejs/core/compare/v3.3.9...v3.4.0-alpha.2) (2023-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid confusing breakage in @vitejs/plugin-vue ([ceec69c](https://github.com/vuejs/core/commit/ceec69c8ccb96c433a4a506ad2e85e276998bade))
|
||||
* **compiler-core:** fix line/column tracking when fast forwarding ([2e65ea4](https://github.com/vuejs/core/commit/2e65ea481f74db8649df8110a031cbdc98f98c84))
|
||||
* **compiler-sfc:** fix ast reuse for ssr ([fb619cf](https://github.com/vuejs/core/commit/fb619cf9a440239f0ba88e327d10001a6a3c8171))
|
||||
* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([c6083dc](https://github.com/vuejs/core/commit/c6083dcad31f3e9292c687fada9e32f287e2317f))
|
||||
* **compiler-sfc:** use correct compiler when re-parsing in ssr mode ([678378a](https://github.com/vuejs/core/commit/678378afd559481badb486b243722b6287862e09))
|
||||
|
||||
|
||||
* feat!: remove reactivity transform (#9321) ([79b8a09](https://github.com/vuejs/core/commit/79b8a0905bf363bf82edd2096fef10c3db6d9c3c)), closes [#9321](https://github.com/vuejs/core/issues/9321)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-core:** support specifying root namespace when parsing ([40f72d5](https://github.com/vuejs/core/commit/40f72d5e50b389cb11b7ca13461aa2a75ddacdb4))
|
||||
* **compiler-core:** support v-bind shorthand for key and value with the same name ([#9451](https://github.com/vuejs/core/issues/9451)) ([26399aa](https://github.com/vuejs/core/commit/26399aa6fac1596b294ffeba06bb498d86f5508c))
|
||||
* **compiler:** improve parsing tolerance for language-tools ([41ff68e](https://github.com/vuejs/core/commit/41ff68ea579d933333392146625560359acb728a))
|
||||
* **reactivity:** expose last result for computed getter ([#9497](https://github.com/vuejs/core/issues/9497)) ([48b47a1](https://github.com/vuejs/core/commit/48b47a1ab63577e2dbd91947eea544e3ef185b85))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* avoid sfc source map unnecessary serialization and parsing ([f15d2f6](https://github.com/vuejs/core/commit/f15d2f6cf69c0c39f8dfb5c33122790c68bf92e2))
|
||||
* **codegen:** optimize line / column calculation during codegen ([3be53d9](https://github.com/vuejs/core/commit/3be53d9b974dae1a10eb795cade71ae765e17574))
|
||||
* **codegen:** optimize source map generation ([c11002f](https://github.com/vuejs/core/commit/c11002f16afd243a2b15b546816e73882eea9e4d))
|
||||
* **compiler-sfc:** remove magic-string trim on script ([e8e3ec6](https://github.com/vuejs/core/commit/e8e3ec6ca7392e43975c75b56eaaa711d5ea9410))
|
||||
* **compiler-sfc:** use faster source map addMapping ([50cde7c](https://github.com/vuejs/core/commit/50cde7cfbcc49022ba88f5f69fa9b930b483c282))
|
||||
* optimize away isBuiltInType ([66c0ed0](https://github.com/vuejs/core/commit/66c0ed0a3c1c6f37dafc6b1c52b75c6bf60e3136))
|
||||
* optimize makeMap ([ae6fba9](https://github.com/vuejs/core/commit/ae6fba94954bac6430902f77b0d1113a98a75b18))
|
||||
* optimize position cloning ([2073236](https://github.com/vuejs/core/commit/20732366b9b3530d33b842cf1fc985919afb9317))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Reactivity Transform was marked deprecated in 3.3 and is now removed in 3.4. This change does not require a major due to the feature being experimental. Users who wish to continue using the feature can do so via the external plugin at https://vue-macros.dev/features/reactivity-transform.html
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-core:** export error message ([#8729](https://github.com/vuejs/core/issues/8729)) ([f7e80ee](https://github.com/vuejs/core/commit/f7e80ee4a065a9eaba98720abf415d9e87756cbd))
|
||||
* **compiler-sfc:** expose resolve type-based props and emits ([#8874](https://github.com/vuejs/core/issues/8874)) ([9e77580](https://github.com/vuejs/core/commit/9e77580c0c2f0d977bd0031a1d43cc334769d433))
|
||||
* export runtime error strings ([#9301](https://github.com/vuejs/core/issues/9301)) ([feb2f2e](https://github.com/vuejs/core/commit/feb2f2edce2d91218a5e9a52c81e322e4033296b))
|
||||
* **reactivity:** more efficient reactivity system ([#5912](https://github.com/vuejs/core/issues/5912)) ([16e06ca](https://github.com/vuejs/core/commit/16e06ca08f5a1e2af3fc7fb35de153dbe0c3087d)), closes [#311](https://github.com/vuejs/core/issues/311) [#1811](https://github.com/vuejs/core/issues/1811) [#6018](https://github.com/vuejs/core/issues/6018) [#7160](https://github.com/vuejs/core/issues/7160) [#8714](https://github.com/vuejs/core/issues/8714) [#9149](https://github.com/vuejs/core/issues/9149) [#9419](https://github.com/vuejs/core/issues/9419) [#9464](https://github.com/vuejs/core/issues/9464)
|
||||
* **runtime-core:** add `once` option to watch ([#9034](https://github.com/vuejs/core/issues/9034)) ([a645e7a](https://github.com/vuejs/core/commit/a645e7aa51006516ba668b3a4365d296eb92ee7d))
|
||||
|
||||
|
||||
|
||||
## [3.3.10](https://github.com/vuejs/core/compare/v3.3.9...v3.3.10) (2023-12-04)
|
||||
|
||||
|
||||
|
|
26
package.json
26
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.4.0-alpha.4",
|
||||
"packageManager": "pnpm@8.11.0",
|
||||
"packageManager": "pnpm@8.12.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
@ -22,6 +22,7 @@
|
|||
"test-dts": "run-s build-dts test-dts-only",
|
||||
"test-dts-only": "tsc -p ./packages/dts-test/tsconfig.test.json",
|
||||
"test-coverage": "vitest -c vitest.unit.config.ts --coverage",
|
||||
"test-bench": "vitest bench",
|
||||
"release": "node scripts/release.js",
|
||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||
|
@ -60,6 +61,7 @@
|
|||
"devDependencies": {
|
||||
"@babel/parser": "^7.23.5",
|
||||
"@babel/types": "^7.23.5",
|
||||
"@codspeed/vitest-plugin": "^2.3.1",
|
||||
"@rollup/plugin-alias": "^5.0.1",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.0.1",
|
||||
|
@ -68,33 +70,33 @@
|
|||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.10.3",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/semver": "^7.5.5",
|
||||
"@typescript-eslint/parser": "^6.13.0",
|
||||
"@vitest/coverage-istanbul": "^0.34.6",
|
||||
"@typescript-eslint/parser": "^6.13.2",
|
||||
"@vitest/coverage-istanbul": "^1.0.4",
|
||||
"@vue/consolidate": "0.17.3",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.19.5",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-define-config": "^1.24.1",
|
||||
"eslint-plugin-jest": "^27.6.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^8.0.1",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.1.0",
|
||||
"jsdom": "^23.0.1",
|
||||
"lint-staged": "^15.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.5",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^9.1.6",
|
||||
"marked": "^11.0.1",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.1.1",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~21.5.2",
|
||||
"puppeteer": "~21.6.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.1.4",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
|
@ -108,7 +110,7 @@
|
|||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.0",
|
||||
"vitest": "^1.0.0"
|
||||
"vite": "^5.0.5",
|
||||
"vitest": "^1.0.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,47 @@ return { props, bar }
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`defineProps > custom element retains the props type & default value & production mode 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Props {
|
||||
foo?: number;
|
||||
}
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
__name: 'app.ce',
|
||||
props: {
|
||||
foo: { default: 5.5, type: Number }
|
||||
},
|
||||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineProps > custom element retains the props type & production mode 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
__name: 'app.ce',
|
||||
props: {
|
||||
foo: {type: Number}
|
||||
},
|
||||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineProps > defineProps w/ runtime options 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
|
|
|
@ -710,4 +710,35 @@ const props = defineProps({ foo: String })
|
|||
'da-sh': BindingTypes.PROPS
|
||||
})
|
||||
})
|
||||
|
||||
// #8989
|
||||
test('custom element retains the props type & production mode', () => {
|
||||
const { content } = compile(
|
||||
`<script setup lang="ts">
|
||||
const props = defineProps<{ foo: number}>()
|
||||
</script>`,
|
||||
{ isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
|
||||
{ filename: 'app.ce.vue' }
|
||||
)
|
||||
|
||||
expect(content).toMatch(`foo: {type: Number}`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('custom element retains the props type & default value & production mode', () => {
|
||||
const { content } = compile(
|
||||
`<script setup lang="ts">
|
||||
interface Props {
|
||||
foo?: number;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
foo: 5.5,
|
||||
});
|
||||
</script>`,
|
||||
{ isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
|
||||
{ filename: 'app.ce.vue' }
|
||||
)
|
||||
expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
|
||||
assertCode(content)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -117,6 +117,10 @@ export interface SFCScriptCompileOptions {
|
|||
fileExists(file: string): boolean
|
||||
readFile(file: string): string | undefined
|
||||
}
|
||||
/**
|
||||
* Transform Vue SFCs into custom elements.
|
||||
*/
|
||||
customElement?: boolean | ((filename: string) => boolean)
|
||||
}
|
||||
|
||||
export interface ImportBinding {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { TypeScope } from './resolveType'
|
|||
export class ScriptCompileContext {
|
||||
isJS: boolean
|
||||
isTS: boolean
|
||||
isCE = false
|
||||
|
||||
scriptAst: Program | null
|
||||
scriptSetupAst: Program | null
|
||||
|
@ -95,6 +96,14 @@ export class ScriptCompileContext {
|
|||
scriptSetupLang === 'ts' ||
|
||||
scriptSetupLang === 'tsx'
|
||||
|
||||
const customElement = options.customElement
|
||||
const filename = this.descriptor.filename
|
||||
if (customElement) {
|
||||
this.isCE =
|
||||
typeof customElement === 'boolean'
|
||||
? customElement
|
||||
: customElement(filename)
|
||||
}
|
||||
// resolve parser plugins
|
||||
const plugins: ParserPlugin[] = resolveParserPlugins(
|
||||
(scriptLang || scriptSetupLang)!,
|
||||
|
|
|
@ -281,6 +281,17 @@ function genRuntimePropFromType(
|
|||
defaultString
|
||||
])} }`
|
||||
} else {
|
||||
// #8989 for custom element, should keep the type
|
||||
if (ctx.isCE) {
|
||||
if (defaultString) {
|
||||
return `${finalKey}: ${`{ ${defaultString}, type: ${toRuntimeTypeString(
|
||||
type
|
||||
)} }`}`
|
||||
} else {
|
||||
return `${finalKey}: {type: ${toRuntimeTypeString(type)}}`
|
||||
}
|
||||
}
|
||||
|
||||
// production: checks are useless
|
||||
return `${finalKey}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
|
||||
}
|
||||
|
|
|
@ -83,6 +83,9 @@ export type SimpleTypeResolveContext = Pick<
|
|||
|
||||
// emits
|
||||
| 'emitsTypeDecl'
|
||||
|
||||
// customElement
|
||||
| 'isCE'
|
||||
> &
|
||||
Partial<
|
||||
Pick<ScriptCompileContext, 'scope' | 'globalScopes' | 'deps' | 'fs'>
|
||||
|
@ -1475,6 +1478,7 @@ export function inferRuntimeType(
|
|||
scope
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'TSMethodSignature':
|
||||
case 'TSFunctionType':
|
||||
return ['Function']
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
computed,
|
||||
ShallowRef
|
||||
} from 'vue'
|
||||
import { expectType, describe, IsUnion } from './utils'
|
||||
import { expectType, describe, IsUnion, IsAny } from './utils'
|
||||
|
||||
function plainType(arg: number | Ref<number>) {
|
||||
// ref coercing
|
||||
|
@ -79,6 +79,10 @@ function plainType(arg: number | Ref<number>) {
|
|||
// should still unwrap in objects nested in arrays
|
||||
const arr2 = ref([{ a: ref(1) }]).value
|
||||
expectType<number>(arr2[0].a)
|
||||
|
||||
// any value should return Ref<any>, not any
|
||||
const a = ref(1 as any)
|
||||
expectType<IsAny<typeof a>>(false)
|
||||
}
|
||||
|
||||
plainType(1)
|
||||
|
@ -191,6 +195,12 @@ if (refStatus.value === 'initial') {
|
|||
expectType<IsUnion<typeof shallowUnionAsCast>>(false)
|
||||
}
|
||||
|
||||
{
|
||||
// any value should return Ref<any>, not any
|
||||
const a = shallowRef(1 as any)
|
||||
expectType<IsAny<typeof a>>(false)
|
||||
}
|
||||
|
||||
describe('shallowRef with generic', <T>() => {
|
||||
const r = ref({}) as MaybeRef<T>
|
||||
expectType<ShallowRef<T> | Ref<T>>(shallowRef(r))
|
||||
|
|
|
@ -260,6 +260,30 @@ describe('defineSlots', () => {
|
|||
expectType<Slots>(slotsUntype)
|
||||
})
|
||||
|
||||
describe('defineSlots generic', <T extends Record<string, any>>() => {
|
||||
const props = defineProps<{
|
||||
item: T
|
||||
}>()
|
||||
|
||||
const slots = defineSlots<
|
||||
{
|
||||
[K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
|
||||
} & {
|
||||
label?: (props: { item: T }) => any
|
||||
}
|
||||
>()
|
||||
|
||||
for (const key of Object.keys(props.item) as (keyof T & string)[]) {
|
||||
slots[`slot-${String(key)}`]?.({
|
||||
item: props.item
|
||||
})
|
||||
}
|
||||
slots.label?.({ item: props.item })
|
||||
|
||||
// @ts-expect-error calling wrong slot
|
||||
slots.foo({})
|
||||
})
|
||||
|
||||
describe('defineModel', () => {
|
||||
// overload 1
|
||||
const modelValueRequired = defineModel<boolean>({ required: true })
|
||||
|
@ -336,6 +360,78 @@ describe('useSlots', () => {
|
|||
expectType<Slots>(slots)
|
||||
})
|
||||
|
||||
describe('defineSlots generic', <T extends Record<string, any>>() => {
|
||||
const props = defineProps<{
|
||||
item: T
|
||||
}>()
|
||||
|
||||
const slots = defineSlots<
|
||||
{
|
||||
[K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
|
||||
} & {
|
||||
label?: (props: { item: T }) => any
|
||||
}
|
||||
>()
|
||||
|
||||
// @ts-expect-error slots should be readonly
|
||||
slots.label = () => {}
|
||||
|
||||
// @ts-expect-error non existing slot
|
||||
slots['foo-asdas']?.({
|
||||
item: props.item
|
||||
})
|
||||
for (const key in props.item) {
|
||||
slots[`slot-${String(key)}`]?.({
|
||||
item: props.item
|
||||
})
|
||||
slots[`slot-${String(key as keyof T)}`]?.({
|
||||
item: props.item
|
||||
})
|
||||
}
|
||||
|
||||
for (const key of Object.keys(props.item) as (keyof T)[]) {
|
||||
slots[`slot-${String(key)}`]?.({
|
||||
item: props.item
|
||||
})
|
||||
}
|
||||
slots.label?.({ item: props.item })
|
||||
|
||||
// @ts-expect-error calling wrong slot
|
||||
slots.foo({})
|
||||
})
|
||||
|
||||
describe('defineSlots generic strict', <T extends {
|
||||
foo: 'foo'
|
||||
bar: 'bar'
|
||||
}>() => {
|
||||
const props = defineProps<{
|
||||
item: T
|
||||
}>()
|
||||
|
||||
const slots = defineSlots<
|
||||
{
|
||||
[K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
|
||||
} & {
|
||||
label?: (props: { item: T }) => any
|
||||
}
|
||||
>()
|
||||
|
||||
// slot-bar/foo should be automatically inferred
|
||||
slots['slot-bar']?.({ item: props.item })
|
||||
slots['slot-foo']?.({ item: props.item })
|
||||
|
||||
slots.label?.({ item: props.item })
|
||||
|
||||
// @ts-expect-error not part of the extends
|
||||
slots['slot-RANDOM']?.({ item: props.item })
|
||||
|
||||
// @ts-expect-error slots should be readonly
|
||||
slots.label = () => {}
|
||||
|
||||
// @ts-expect-error calling wrong slot
|
||||
slots.foo({})
|
||||
})
|
||||
|
||||
// #6420
|
||||
describe('toRefs w/ type declaration', () => {
|
||||
const props = defineProps<{
|
||||
|
|
|
@ -112,3 +112,11 @@ expectType<JSX.Element>(
|
|||
)
|
||||
// @ts-expect-error
|
||||
;<Suspense onResolve={123} />
|
||||
|
||||
// svg
|
||||
expectType<JSX.Element>(
|
||||
<svg
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "@vue/reactivity-transform",
|
||||
"version": "3.3.11",
|
||||
"description": "@vue/reactivity-transform",
|
||||
"main": "dist/reactivity-transform.cjs.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"buildOptions": {
|
||||
"formats": [
|
||||
"cjs"
|
||||
],
|
||||
"prod": false
|
||||
},
|
||||
"types": "dist/reactivity-transform.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/reactivity-transform"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.5",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.5",
|
||||
"@babel/types": "^7.23.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
import { describe, bench } from 'vitest'
|
||||
import { ComputedRef, Ref, computed, ref } from '../src/index'
|
||||
|
||||
describe('computed', () => {
|
||||
bench('create computed', () => {
|
||||
computed(() => 100)
|
||||
})
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const o = ref(100)
|
||||
bench('write independent ref dep', () => {
|
||||
o.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
computed(() => v.value * 2)
|
||||
let i = 0
|
||||
bench("write ref, don't read computed (never invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
let i = 0
|
||||
bench("write ref, don't read computed (never invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
c.value
|
||||
let i = 0
|
||||
bench("write ref, don't read computed (invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
let i = 0
|
||||
bench('write ref, read computed', () => {
|
||||
v.value = i++
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write ref, don't read 1000 computeds (never invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
c.value
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write ref, don't read 1000 computeds (invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const computeds: ComputedRef<number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
c.value
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench('write ref, read 1000 computeds', () => {
|
||||
v.value = i++
|
||||
computeds.forEach(c => c.value)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const refs: Ref<number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
refs.push(ref(i))
|
||||
}
|
||||
const c = computed(() => {
|
||||
let total = 0
|
||||
refs.forEach(ref => (total += ref.value))
|
||||
return total
|
||||
})
|
||||
let i = 0
|
||||
const n = refs.length
|
||||
bench('1000 refs, 1 computed', () => {
|
||||
refs[i++ % n].value++
|
||||
c.value
|
||||
})
|
||||
}
|
||||
})
|
|
@ -158,6 +158,21 @@ describe('reactivity/reactive', () => {
|
|||
expect(original.bar).toBe(original2)
|
||||
})
|
||||
|
||||
// #1246
|
||||
test('mutation on objects using reactive as prototype should not trigger', () => {
|
||||
const observed = reactive({ foo: 1 })
|
||||
const original = Object.create(observed)
|
||||
let dummy
|
||||
effect(() => (dummy = original.foo))
|
||||
expect(dummy).toBe(1)
|
||||
observed.foo = 2
|
||||
expect(dummy).toBe(2)
|
||||
original.foo = 3
|
||||
expect(dummy).toBe(2)
|
||||
original.foo = 4
|
||||
expect(dummy).toBe(2)
|
||||
})
|
||||
|
||||
test('toRaw', () => {
|
||||
const original = { foo: 1 }
|
||||
const observed = reactive(original)
|
||||
|
@ -166,11 +181,18 @@ describe('reactivity/reactive', () => {
|
|||
})
|
||||
|
||||
test('toRaw on object using reactive as prototype', () => {
|
||||
const original = reactive({})
|
||||
const obj = Object.create(original)
|
||||
const original = { foo: 1 }
|
||||
const observed = reactive(original)
|
||||
const inherted = Object.create(observed)
|
||||
expect(toRaw(inherted)).toBe(inherted)
|
||||
})
|
||||
|
||||
test('toRaw on user Proxy wrapping reactive', () => {
|
||||
const original = {}
|
||||
const re = reactive(original)
|
||||
const obj = new Proxy(re, {})
|
||||
const raw = toRaw(obj)
|
||||
expect(raw).toBe(obj)
|
||||
expect(raw).not.toBe(toRaw(original))
|
||||
expect(raw).toBe(original)
|
||||
})
|
||||
|
||||
test('should not unwrap Ref<T>', () => {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import { bench } from 'vitest'
|
||||
import { computed, reactive, readonly, shallowRef, triggerRef } from '../src'
|
||||
|
||||
for (let amount = 1e1; amount < 1e4; amount *= 10) {
|
||||
{
|
||||
const rawArray = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = reactive(rawArray)
|
||||
const c = computed(() => {
|
||||
return r.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(`reduce *reactive* array, ${amount} elements`, () => {
|
||||
for (let i = 0, n = r.length; i < n; i++) {
|
||||
r[i]++
|
||||
}
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const rawArray = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = reactive(rawArray)
|
||||
const c = computed(() => {
|
||||
return r.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(
|
||||
`reduce *reactive* array, ${amount} elements, only change first value`,
|
||||
() => {
|
||||
r[0]++
|
||||
c.value
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
const rawArray = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = reactive({ arr: readonly(rawArray) })
|
||||
const c = computed(() => {
|
||||
return r.arr.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(`reduce *readonly* array, ${amount} elements`, () => {
|
||||
r.arr = r.arr.map(v => v + 1)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const rawArray = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = shallowRef(rawArray)
|
||||
const c = computed(() => {
|
||||
return r.value.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(`reduce *raw* array, copied, ${amount} elements`, () => {
|
||||
r.value = r.value.map(v => v + 1)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const rawArray: number[] = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = shallowRef(rawArray)
|
||||
const c = computed(() => {
|
||||
return r.value.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(`reduce *raw* array, manually triggered, ${amount} elements`, () => {
|
||||
for (let i = 0, n = rawArray.length; i < n; i++) {
|
||||
rawArray[i]++
|
||||
}
|
||||
triggerRef(r)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
}
|
|
@ -175,6 +175,15 @@ describe('reactivity/reactive/Array', () => {
|
|||
expect(length).toBe('01')
|
||||
})
|
||||
|
||||
// #9742
|
||||
test('mutation on user proxy of reactive Array', () => {
|
||||
const array = reactive<number[]>([])
|
||||
const proxy = new Proxy(array, {})
|
||||
proxy.push(1)
|
||||
expect(array).toHaveLength(1)
|
||||
expect(proxy).toHaveLength(1)
|
||||
})
|
||||
|
||||
describe('Array methods w/ refs', () => {
|
||||
let original: any[]
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
import { bench } from 'vitest'
|
||||
import { reactive, computed, ComputedRef } from '../src'
|
||||
|
||||
function createMap(obj: Record<string, any>) {
|
||||
const map = new Map()
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
map.set(key, obj[key])
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
bench('create reactive map', () => {
|
||||
reactive(createMap({ a: 1 }))
|
||||
})
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
bench('write reactive map property', () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
let i = 0
|
||||
bench("write reactive map, don't read computed (never invoked)", () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
c.value
|
||||
let i = 0
|
||||
bench("write reactive map, don't read computed (invoked)", () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
let i = 0
|
||||
bench('write reactive map, read computed', () => {
|
||||
r.set('a', i++)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const _m = new Map()
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
_m.set(i, i)
|
||||
}
|
||||
const r = reactive(_m)
|
||||
const c = computed(() => {
|
||||
let total = 0
|
||||
r.forEach((value, key) => {
|
||||
total += value
|
||||
})
|
||||
return total
|
||||
})
|
||||
bench("write reactive map (10'000 items), read computed", () => {
|
||||
r.set(5000, r.get(5000) + 1)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write reactive map, don't read 1000 computeds (never invoked)", () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
c.value
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write reactive map, don't read 1000 computeds (invoked)", () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const computeds: ComputedRef<number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench('write reactive map, read 1000 computeds', () => {
|
||||
r.set('a', i++)
|
||||
computeds.forEach(c => c.value)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const reactives: Map<any, any>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
reactives.push(reactive(createMap({ a: i })))
|
||||
}
|
||||
const c = computed(() => {
|
||||
let total = 0
|
||||
reactives.forEach(r => (total += r.get('a')))
|
||||
return total
|
||||
})
|
||||
let i = 0
|
||||
const n = reactives.length
|
||||
bench('1000 reactive maps, 1 computed', () => {
|
||||
reactives[i++ % n].set('a', reactives[i++ % n].get('a') + 1)
|
||||
c.value
|
||||
})
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
import { bench } from 'vitest'
|
||||
import { ComputedRef, computed, reactive } from '../src'
|
||||
|
||||
bench('create reactive obj', () => {
|
||||
reactive({ a: 1 })
|
||||
})
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const r = reactive({ a: 1 })
|
||||
bench('write reactive obj property', () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
let i = 0
|
||||
bench("write reactive obj, don't read computed (never invoked)", () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
c.value
|
||||
let i = 0
|
||||
bench("write reactive obj, don't read computed (invoked)", () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
let i = 0
|
||||
bench('write reactive obj, read computed', () => {
|
||||
r.a = i++
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write reactive obj, don't read 1000 computeds (never invoked)", () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
c.value
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write reactive obj, don't read 1000 computeds (invoked)", () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const computeds: ComputedRef<number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench('write reactive obj, read 1000 computeds', () => {
|
||||
r.a = i++
|
||||
computeds.forEach(c => c.value)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const reactives: Record<string, number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
reactives.push(reactive({ a: i }))
|
||||
}
|
||||
const c = computed(() => {
|
||||
let total = 0
|
||||
reactives.forEach(r => (total += r.a))
|
||||
return total
|
||||
})
|
||||
let i = 0
|
||||
const n = reactives.length
|
||||
bench('1000 reactive objs, 1 computed', () => {
|
||||
reactives[i++ % n].a++
|
||||
c.value
|
||||
})
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { describe, bench } from 'vitest'
|
||||
import { ref } from '../src/index'
|
||||
|
||||
describe('ref', () => {
|
||||
bench('create ref', () => {
|
||||
ref(100)
|
||||
})
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const v = ref(100)
|
||||
bench('write ref', () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
bench('read ref', () => {
|
||||
v.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const v = ref(100)
|
||||
bench('write/read ref', () => {
|
||||
v.value = i++
|
||||
|
||||
v.value
|
||||
})
|
||||
}
|
||||
})
|
|
@ -101,19 +101,25 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
|||
return isReadonly
|
||||
} else if (key === ReactiveFlags.IS_SHALLOW) {
|
||||
return shallow
|
||||
} else if (
|
||||
key === ReactiveFlags.RAW &&
|
||||
receiver ===
|
||||
(isReadonly
|
||||
? shallow
|
||||
? shallowReadonlyMap
|
||||
: readonlyMap
|
||||
: shallow
|
||||
? shallowReactiveMap
|
||||
: reactiveMap
|
||||
).get(target)
|
||||
) {
|
||||
return target
|
||||
} else if (key === ReactiveFlags.RAW) {
|
||||
if (
|
||||
receiver ===
|
||||
(isReadonly
|
||||
? shallow
|
||||
? shallowReadonlyMap
|
||||
: readonlyMap
|
||||
: shallow
|
||||
? shallowReactiveMap
|
||||
: reactiveMap
|
||||
).get(target) ||
|
||||
// receiver is not the reactive proxy, but has the same prototype
|
||||
// this means the reciever is a user proxy of the reactive proxy
|
||||
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
|
||||
) {
|
||||
return target
|
||||
}
|
||||
// early return undefined
|
||||
return
|
||||
}
|
||||
|
||||
const targetIsArray = isArray(target)
|
||||
|
@ -169,17 +175,19 @@ class MutableReactiveHandler extends BaseReactiveHandler {
|
|||
receiver: object
|
||||
): boolean {
|
||||
let oldValue = (target as any)[key]
|
||||
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
|
||||
return false
|
||||
}
|
||||
if (!this._shallow) {
|
||||
const isOldValueReadonly = isReadonly(oldValue)
|
||||
if (!isShallow(value) && !isReadonly(value)) {
|
||||
oldValue = toRaw(oldValue)
|
||||
value = toRaw(value)
|
||||
}
|
||||
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
||||
oldValue.value = value
|
||||
return true
|
||||
if (isOldValueReadonly) {
|
||||
return false
|
||||
} else {
|
||||
oldValue.value = value
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// in shallow mode, objects are set as-is regardless of reactive or not
|
||||
|
|
|
@ -100,7 +100,6 @@ export function isRef(r: any): r is Ref {
|
|||
* @param value - The object to wrap in the ref.
|
||||
* @see {@link https://vuejs.org/api/reactivity-core.html#ref}
|
||||
*/
|
||||
export function ref<T extends Ref>(value: T): T
|
||||
export function ref<T>(value: T): Ref<UnwrapRef<T>>
|
||||
export function ref<T = any>(): Ref<T | undefined>
|
||||
export function ref(value?: unknown) {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import { nextTick, ref, watch, watchEffect } from '../src'
|
||||
import { bench } from 'vitest'
|
||||
|
||||
bench('create watcher', () => {
|
||||
const v = ref(100)
|
||||
watch(v, v => {})
|
||||
})
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
watch(v, v => {})
|
||||
let i = 0
|
||||
bench('update ref to trigger watcher (scheduled but not executed)', () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
watch(v, v => {})
|
||||
let i = 0
|
||||
bench('update ref to trigger watcher (executed)', async () => {
|
||||
v.value = i++
|
||||
return nextTick()
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
bench('create watchEffect', () => {
|
||||
watchEffect(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
watchEffect(() => {
|
||||
v.value
|
||||
})
|
||||
let i = 0
|
||||
bench(
|
||||
'update ref to trigger watchEffect (scheduled but not executed)',
|
||||
() => {
|
||||
v.value = i++
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
watchEffect(() => {
|
||||
v.value
|
||||
})
|
||||
let i = 0
|
||||
bench('update ref to trigger watchEffect (executed)', async () => {
|
||||
v.value = i++
|
||||
await nextTick()
|
||||
})
|
||||
}
|
|
@ -549,6 +549,98 @@ describe('api: watch', () => {
|
|||
expect(cb).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// #7030
|
||||
it('should not fire on child component unmount w/ flush: pre', async () => {
|
||||
const visible = ref(true)
|
||||
const cb = vi.fn()
|
||||
const Parent = defineComponent({
|
||||
props: ['visible'],
|
||||
render() {
|
||||
return visible.value ? h(Comp) : null
|
||||
}
|
||||
})
|
||||
const Comp = {
|
||||
setup() {
|
||||
watch(visible, cb, { flush: 'pre' })
|
||||
},
|
||||
render() {}
|
||||
}
|
||||
const App = {
|
||||
render() {
|
||||
return h(Parent, {
|
||||
visible: visible.value
|
||||
})
|
||||
}
|
||||
}
|
||||
render(h(App), nodeOps.createElement('div'))
|
||||
expect(cb).not.toHaveBeenCalled()
|
||||
visible.value = false
|
||||
await nextTick()
|
||||
expect(cb).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// #7030
|
||||
it('flush: pre watcher in child component should not fire before parent update', async () => {
|
||||
const b = ref(0)
|
||||
const calls: string[] = []
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
watch(
|
||||
() => b.value,
|
||||
val => {
|
||||
calls.push('watcher child')
|
||||
},
|
||||
{ flush: 'pre' }
|
||||
)
|
||||
return () => {
|
||||
b.value
|
||||
calls.push('render child')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Parent = {
|
||||
props: ['a'],
|
||||
setup() {
|
||||
watch(
|
||||
() => b.value,
|
||||
val => {
|
||||
calls.push('watcher parent')
|
||||
},
|
||||
{ flush: 'pre' }
|
||||
)
|
||||
return () => {
|
||||
b.value
|
||||
calls.push('render parent')
|
||||
return h(Comp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const App = {
|
||||
render() {
|
||||
return h(Parent, {
|
||||
a: b.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render(h(App), nodeOps.createElement('div'))
|
||||
expect(calls).toEqual(['render parent', 'render child'])
|
||||
|
||||
b.value++
|
||||
await nextTick()
|
||||
expect(calls).toEqual([
|
||||
'render parent',
|
||||
'render child',
|
||||
'watcher parent',
|
||||
'render parent',
|
||||
'watcher child',
|
||||
'render child'
|
||||
])
|
||||
})
|
||||
|
||||
// #1763
|
||||
it('flush: pre watcher watching props should fire before child update', async () => {
|
||||
const a = ref(0)
|
||||
|
|
|
@ -1185,6 +1185,72 @@ describe('Suspense', () => {
|
|||
expect(calls).toEqual([`one mounted`, `one unmounted`, `two mounted`])
|
||||
})
|
||||
|
||||
test('mount the fallback content is in the correct position', async () => {
|
||||
const makeComp = (name: string, delay = 0) =>
|
||||
defineAsyncComponent(
|
||||
{
|
||||
setup() {
|
||||
return () => h('div', [name])
|
||||
}
|
||||
},
|
||||
delay
|
||||
)
|
||||
|
||||
const One = makeComp('one')
|
||||
const Two = makeComp('two', 20)
|
||||
|
||||
const view = shallowRef(One)
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () =>
|
||||
h('div', [
|
||||
h(
|
||||
Suspense,
|
||||
{
|
||||
timeout: 10
|
||||
},
|
||||
{
|
||||
default: h(view.value),
|
||||
fallback: h('div', 'fallback')
|
||||
}
|
||||
),
|
||||
h('div', 'three')
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>fallback</div><div>three</div></div>`
|
||||
)
|
||||
|
||||
await deps[0]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>one</div><div>three</div></div>`
|
||||
)
|
||||
|
||||
view.value = Two
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>one</div><div>three</div></div>`
|
||||
)
|
||||
|
||||
await new Promise(r => setTimeout(r, 10))
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>fallback</div><div>three</div></div>`
|
||||
)
|
||||
|
||||
await deps[1]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>two</div><div>three</div></div>`
|
||||
)
|
||||
})
|
||||
|
||||
// #2214
|
||||
// Since suspense renders its own root like a component, it should not patch
|
||||
// its content in optimized mode.
|
||||
|
|
|
@ -66,9 +66,9 @@ const warnRuntimeUsage = (method: string) =>
|
|||
* foo?: string
|
||||
* bar: number
|
||||
* }>()
|
||||
* ```
|
||||
*
|
||||
* @see {@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
|
||||
* ```
|
||||
*
|
||||
* This is only usable inside `<script setup>`, is compiled away in the
|
||||
* output and should **not** be actually called at runtime.
|
||||
|
@ -116,8 +116,9 @@ type BooleanKey<T, K extends keyof T = keyof T> = K extends any
|
|||
* Example type-based declaration:
|
||||
* ```ts
|
||||
* const emit = defineEmits<{
|
||||
* (event: 'change'): void
|
||||
* (event: 'update', id: number): void
|
||||
* // <eventName>: <expected arguments>
|
||||
* change: []
|
||||
* update: [value: string] // named tuple syntax
|
||||
* }>()
|
||||
*
|
||||
* emit('change')
|
||||
|
|
|
@ -38,19 +38,18 @@ export type EmitsOptions = ObjectEmitsOptions | string[]
|
|||
|
||||
export type EmitsToProps<T extends EmitsOptions> = T extends string[]
|
||||
? {
|
||||
[K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
|
||||
[K in `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
|
||||
}
|
||||
: T extends ObjectEmitsOptions
|
||||
? {
|
||||
[K in string &
|
||||
`on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
|
||||
? T[Uncapitalize<C>] extends null
|
||||
? (...args: any[]) => any
|
||||
: (
|
||||
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
|
||||
? P
|
||||
[K in `on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
|
||||
? (
|
||||
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
|
||||
? P
|
||||
: T[Uncapitalize<C>] extends null
|
||||
? any[]
|
||||
: never
|
||||
) => any
|
||||
) => any
|
||||
: never
|
||||
}
|
||||
: {}
|
||||
|
|
|
@ -44,7 +44,7 @@ export type SlotsType<T extends Record<string, any> = Record<string, any>> = {
|
|||
export type StrictUnwrapSlotsType<
|
||||
S extends SlotsType,
|
||||
T = NonNullable<S[typeof SlotSymbol]>
|
||||
> = [keyof S] extends [never] ? Slots : Readonly<T>
|
||||
> = [keyof S] extends [never] ? Slots : Readonly<T> & T
|
||||
|
||||
export type UnwrapSlotsType<
|
||||
S extends SlotsType,
|
||||
|
|
|
@ -465,7 +465,7 @@ function createSuspenseBoundary(
|
|||
timeout: typeof timeout === 'number' ? timeout : -1,
|
||||
activeBranch: null,
|
||||
pendingBranch: null,
|
||||
isInFallback: true,
|
||||
isInFallback: !isHydrating,
|
||||
isHydrating,
|
||||
isUnmounted: false,
|
||||
effects: [],
|
||||
|
@ -583,6 +583,7 @@ function createSuspenseBoundary(
|
|||
// invoke @fallback event
|
||||
triggerEvent(vnode, 'onFallback')
|
||||
|
||||
const anchor = next(activeBranch!)
|
||||
const mountFallback = () => {
|
||||
if (!suspense.isInFallback) {
|
||||
return
|
||||
|
@ -592,7 +593,7 @@ function createSuspenseBoundary(
|
|||
null,
|
||||
fallbackVNode,
|
||||
container,
|
||||
next(activeBranch!),
|
||||
anchor,
|
||||
parentComponent,
|
||||
null, // fallback tree will not have suspense context
|
||||
namespace,
|
||||
|
|
|
@ -1594,7 +1594,7 @@ function baseCreateRenderer(
|
|||
pauseTracking()
|
||||
// props update may have triggered pre-flush watchers.
|
||||
// flush them before the render update.
|
||||
flushPreFlushCbs()
|
||||
flushPreFlushCbs(instance)
|
||||
resetTracking()
|
||||
}
|
||||
|
||||
|
|
|
@ -139,6 +139,7 @@ export function queuePostFlushCb(cb: SchedulerJobs) {
|
|||
}
|
||||
|
||||
export function flushPreFlushCbs(
|
||||
instance?: ComponentInternalInstance,
|
||||
seen?: CountMap,
|
||||
// if currently flushing, skip the current job itself
|
||||
i = isFlushing ? flushIndex + 1 : 0
|
||||
|
@ -149,6 +150,9 @@ export function flushPreFlushCbs(
|
|||
for (; i < queue.length; i++) {
|
||||
const cb = queue[i]
|
||||
if (cb && cb.pre) {
|
||||
if (instance && cb.id !== instance.uid) {
|
||||
continue
|
||||
}
|
||||
if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -300,6 +300,13 @@ describe('runtime-dom: props patching', () => {
|
|||
expect(el.getAttribute('width')).toBe('24px')
|
||||
})
|
||||
|
||||
// # 9762 should fallthrough to `key in el` logic for non embedded tags
|
||||
test('width and height on custom elements', () => {
|
||||
const el = document.createElement('foobar')
|
||||
patchProp(el, 'width', null, '24px')
|
||||
expect(el.getAttribute('width')).toBe('24px')
|
||||
})
|
||||
|
||||
test('translate attribute', () => {
|
||||
const el = document.createElement('div')
|
||||
patchProp(el, 'translate', null, 'no')
|
||||
|
|
|
@ -37,6 +37,6 @@
|
|||
"dependencies": {
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/runtime-core": "workspace:*",
|
||||
"csstype": "^3.1.2"
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1077,6 +1077,7 @@ export interface SVGAttributes extends AriaAttributes, EventHandlers<Events> {
|
|||
xlinkTitle?: string
|
||||
xlinkType?: string
|
||||
xmlns?: string
|
||||
xmlnsXlink?: string
|
||||
y1?: Numberish
|
||||
y2?: Numberish
|
||||
y?: Numberish
|
||||
|
|
|
@ -111,15 +111,17 @@ function shouldSetAsProp(
|
|||
return false
|
||||
}
|
||||
|
||||
// #8780 the width or heigth of embedded tags must be set as attribute
|
||||
// #8780 the width or height of embedded tags must be set as attribute
|
||||
if (key === 'width' || key === 'height') {
|
||||
const tag = el.tagName
|
||||
return !(
|
||||
if (
|
||||
tag === 'IMG' ||
|
||||
tag === 'VIDEO' ||
|
||||
tag === 'CANVAS' ||
|
||||
tag === 'SOURCE'
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// native onclick with string value, must be set as attribute
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"vite": "^5.0.0"
|
||||
"vite": "^5.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^3.0.0",
|
||||
|
|
|
@ -24,7 +24,7 @@ const vueVersion = ref(`@${currentCommit}`)
|
|||
async function setVueVersion(v: string) {
|
||||
vueVersion.value = `loading...`
|
||||
await store.setVueVersion(v)
|
||||
vueVersion.value = `v${v}`
|
||||
vueVersion.value = v
|
||||
}
|
||||
|
||||
function resetVueVersion() {
|
||||
|
|
|
@ -74,8 +74,8 @@ onMounted(() => {
|
|||
|
||||
<ul class="versions" :class="{ expanded }">
|
||||
<li v-if="!versions"><a>loading versions...</a></li>
|
||||
<li v-for="version of versions">
|
||||
<a @click="setVersion(version)">v{{ version }}</a>
|
||||
<li v-for="ver of versions" :class="{ active: ver === version }">
|
||||
<a @click="setVersion(ver)">v{{ ver }}</a>
|
||||
</li>
|
||||
<div @click="expanded = false">
|
||||
<slot />
|
||||
|
@ -111,4 +111,8 @@ onMounted(() => {
|
|||
border-top: 6px solid #aaa;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.versions .active a {
|
||||
color: var(--green);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -171,4 +171,49 @@ describe('toDisplayString', () => {
|
|||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
//#9727
|
||||
test('Map with Symbol keys', () => {
|
||||
const m = new Map<any, any>([
|
||||
[Symbol(), 'foo'],
|
||||
[Symbol(), 'bar'],
|
||||
[Symbol('baz'), 'baz']
|
||||
])
|
||||
expect(toDisplayString(m)).toMatchInlineSnapshot(`
|
||||
"{
|
||||
"Map(3)": {
|
||||
"Symbol(0) =>": "foo",
|
||||
"Symbol(1) =>": "bar",
|
||||
"Symbol(baz) =>": "baz"
|
||||
}
|
||||
}"
|
||||
`)
|
||||
// confirming the symbol renders Symbol(foo)
|
||||
expect(toDisplayString(new Map([[Symbol('foo'), 'foo']]))).toContain(
|
||||
String(Symbol('foo'))
|
||||
)
|
||||
})
|
||||
|
||||
test('Set with Symbol values', () => {
|
||||
const s = new Set([Symbol('foo'), Symbol('bar'), Symbol()])
|
||||
expect(toDisplayString(s)).toMatchInlineSnapshot(`
|
||||
"{
|
||||
"Set(3)": [
|
||||
"Symbol(foo)",
|
||||
"Symbol(bar)",
|
||||
"Symbol()"
|
||||
]
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('Object with Symbol values', () => {
|
||||
expect(toDisplayString({ foo: Symbol('x'), bar: Symbol() }))
|
||||
.toMatchInlineSnapshot(`
|
||||
"{
|
||||
"foo": "Symbol(x)",
|
||||
"bar": "Symbol()"
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -118,6 +118,6 @@ export const isKnownSvgAttr = /*#__PURE__*/ makeMap(
|
|||
`v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,` +
|
||||
`vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,` +
|
||||
`writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,` +
|
||||
`xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,` +
|
||||
`xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,` +
|
||||
`xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`
|
||||
)
|
||||
|
|
|
@ -6,7 +6,8 @@ import {
|
|||
isPlainObject,
|
||||
isSet,
|
||||
objectToString,
|
||||
isString
|
||||
isString,
|
||||
isSymbol
|
||||
} from './general'
|
||||
|
||||
/**
|
||||
|
@ -31,17 +32,26 @@ const replacer = (_key: string, val: any): any => {
|
|||
return replacer(_key, val.value)
|
||||
} else if (isMap(val)) {
|
||||
return {
|
||||
[`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val]) => {
|
||||
;(entries as any)[`${key} =>`] = val
|
||||
return entries
|
||||
}, {})
|
||||
[`Map(${val.size})`]: [...val.entries()].reduce(
|
||||
(entries, [key, val], i) => {
|
||||
entries[stringifySymbol(key, i) + ' =>'] = val
|
||||
return entries
|
||||
},
|
||||
{} as Record<string, any>
|
||||
)
|
||||
}
|
||||
} else if (isSet(val)) {
|
||||
return {
|
||||
[`Set(${val.size})`]: [...val.values()]
|
||||
[`Set(${val.size})`]: [...val.values()].map(v => stringifySymbol(v))
|
||||
}
|
||||
} else if (isSymbol(val)) {
|
||||
return stringifySymbol(val)
|
||||
} else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {
|
||||
// native elements
|
||||
return String(val)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
const stringifySymbol = (v: unknown, i: number | string = ''): any =>
|
||||
isSymbol(v) ? `Symbol(${v.description ?? i})` : v
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"enableNonBrowserBranches": true
|
||||
},
|
||||
"dependencies": {
|
||||
"monaco-editor": "^0.44.0",
|
||||
"monaco-editor": "^0.45.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
|
571
pnpm-lock.yaml
571
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,6 @@
|
|||
import { configDefaults, defineConfig, UserConfig } from 'vitest/config'
|
||||
import { configDefaults, defineConfig } from 'vitest/config'
|
||||
import { entries } from './scripts/aliases.js'
|
||||
import codspeedPlugin from '@codspeed/vitest-plugin'
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
|
@ -21,10 +22,9 @@ export default defineConfig({
|
|||
resolve: {
|
||||
alias: entries
|
||||
},
|
||||
plugins: [codspeedPlugin()],
|
||||
test: {
|
||||
globals: true,
|
||||
// disable threads on GH actions to speed it up
|
||||
threads: !process.env.GITHUB_ACTIONS,
|
||||
setupFiles: 'scripts/setupVitest.ts',
|
||||
environmentMatchGlobs: [
|
||||
['packages/{vue,vue-compat,runtime-dom}/**', 'jsdom']
|
||||
|
@ -44,4 +44,4 @@ export default defineConfig({
|
|||
]
|
||||
}
|
||||
}
|
||||
}) as UserConfig
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue