chore: Merge branch 'main' into minor

This commit is contained in:
Evan You 2024-02-26 10:13:44 +08:00
commit 58d827cb71
52 changed files with 903 additions and 712 deletions

View File

@ -50,5 +50,9 @@
// pinned // pinned
// https://github.com/vuejs/core/issues/10300#issuecomment-1940855364 // https://github.com/vuejs/core/issues/10300#issuecomment-1940855364
'lru-cache', 'lru-cache',
// pinned
// https://github.com/vuejs/core/commit/a012e39b373f1b6918e5c89856e8f902e1bfa14d
'@rollup/plugin-replace',
], ],
} }

View File

@ -1,3 +1,17 @@
## [3.4.20](https://github.com/vuejs/core/compare/v3.4.19...v3.4.20) (2024-02-26)
### Bug Fixes
* **parser:** should not treat uppercase components as special tags ([e0e0253](https://github.com/vuejs/core/commit/e0e02535cdea1aeb1cfaff0d61d4b2555e555c36)), closes [#10395](https://github.com/vuejs/core/issues/10395)
* **runtime-dom:** avoid always resetting nullish option value ([ff130c4](https://github.com/vuejs/core/commit/ff130c470204086edaa093fb8fdc1247c69cba69)), closes [#10396](https://github.com/vuejs/core/issues/10396)
* **runtime-dom:** fix nested v-show priority regression ([364f890](https://github.com/vuejs/core/commit/364f8902c8657faec7c3a4d70a5b2c856567e92d)), closes [#10338](https://github.com/vuejs/core/issues/10338)
* **runtime-dom:** v-bind style should clear previous css string value ([#10373](https://github.com/vuejs/core/issues/10373)) ([e2d3235](https://github.com/vuejs/core/commit/e2d323538e71d404e729148fd19a08bbc2e3da9b)), closes [#10352](https://github.com/vuejs/core/issues/10352)
* **suspense:** handle suspense switching with nested suspense ([#10184](https://github.com/vuejs/core/issues/10184)) ([0f3da05](https://github.com/vuejs/core/commit/0f3da05ea201761529bb95594df1e2cee20b7107)), closes [#10098](https://github.com/vuejs/core/issues/10098)
* **types:** better typing for direct setup signature of defineComponent ([#10357](https://github.com/vuejs/core/issues/10357)) ([eadce5b](https://github.com/vuejs/core/commit/eadce5b75356656fd2209ebdb406d34823c961b7)), closes [#8604](https://github.com/vuejs/core/issues/8604) [#8855](https://github.com/vuejs/core/issues/8855)
## [3.4.19](https://github.com/vuejs/core/compare/v3.4.18...v3.4.19) (2024-02-13) ## [3.4.19](https://github.com/vuejs/core/compare/v3.4.18...v3.4.19) (2024-02-13)

View File

@ -773,7 +773,7 @@ may cause build issues in projects still using TS 3.x.
- **types:** adjust type exports for manual render function and tooling usage ([e4dc03a](https://github.com/vuejs/core/commit/e4dc03a8b17d5e9f167de6a62a645878ac7ef3e2)), closes [#1329](https://github.com/vuejs/core/issues/1329) - **types:** adjust type exports for manual render function and tooling usage ([e4dc03a](https://github.com/vuejs/core/commit/e4dc03a8b17d5e9f167de6a62a645878ac7ef3e2)), closes [#1329](https://github.com/vuejs/core/issues/1329)
- **types:** mixins/extends support in TypeScript ([#626](https://github.com/vuejs/core/issues/626)) ([d3c436a](https://github.com/vuejs/core/commit/d3c436ae2e66b75b7f2ed574dadda3f0e1fdce73)) - **types:** mixins/extends support in TypeScript ([#626](https://github.com/vuejs/core/issues/626)) ([d3c436a](https://github.com/vuejs/core/commit/d3c436ae2e66b75b7f2ed574dadda3f0e1fdce73))
- **types:** support typing directive value via generic argument ([#1007](https://github.com/vuejs/core/issues/1007)) ([419b86d](https://github.com/vuejs/core/commit/419b86d1908f2a0521e6a7eafcbee764e9ee59a0)), closes [#998](https://github.com/vuejs/core/issues/998) - **types:** support typing directive value via generic argument ([#1007](https://github.com/vuejs/core/issues/1007)) ([419b86d](https://github.com/vuejs/core/commit/419b86d1908f2a0521e6a7eafcbee764e9ee59a0)), closes [#998](https://github.com/vuejs/core/issues/998)
- **types:** update to Typescript 3.9 ([#1106](https://github.com/vuejs/core/issues/1106)) ([97dedeb](https://github.com/vuejs/core/commit/97dedebd8097116a16209664a1ca38392b964da3)) - **types:** update to TypeScript 3.9 ([#1106](https://github.com/vuejs/core/issues/1106)) ([97dedeb](https://github.com/vuejs/core/commit/97dedebd8097116a16209664a1ca38392b964da3))
### Performance Improvements ### Performance Improvements

View File

@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"version": "3.4.19", "version": "3.4.20",
"packageManager": "pnpm@8.15.1", "packageManager": "pnpm@8.15.4",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "node scripts/dev.js", "dev": "node scripts/dev.js",
@ -70,49 +70,49 @@
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@types/hash-sum": "^1.0.2", "@types/hash-sum": "^1.0.2",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/node": "^20.11.16", "@types/node": "^20.11.20",
"@types/semver": "^7.5.6", "@types/semver": "^7.5.8",
"@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^6.18.1", "@typescript-eslint/parser": "^7.0.2",
"@vitest/coverage-istanbul": "^1.2.2", "@vitest/coverage-istanbul": "^1.3.1",
"@vue/consolidate": "0.17.3", "@vue/consolidate": "1.0.0",
"conventional-changelog-cli": "^4.1.0", "conventional-changelog-cli": "^4.1.0",
"enquirer": "^2.4.1", "enquirer": "^2.4.1",
"esbuild": "^0.20.0", "esbuild": "^0.20.1",
"esbuild-plugin-polyfill-node": "^0.3.0", "esbuild-plugin-polyfill-node": "^0.3.0",
"eslint": "^8.56.0", "eslint": "^8.57.0",
"eslint-define-config": "^1.24.1", "eslint-define-config": "^2.1.0",
"eslint-plugin-import": "npm:eslint-plugin-i@^2.29.1", "eslint-plugin-import": "npm:eslint-plugin-i@^2.29.1",
"eslint-plugin-jest": "^27.6.3", "eslint-plugin-jest": "^27.9.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"execa": "^8.0.1", "execa": "^8.0.1",
"jsdom": "^23.2.0", "jsdom": "^24.0.0",
"lint-staged": "^15.2.0", "lint-staged": "^15.2.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"magic-string": "^0.30.6", "magic-string": "^0.30.7",
"markdown-table": "^3.0.3", "markdown-table": "^3.0.3",
"marked": "^11.2.0", "marked": "^12.0.0",
"minimist": "^1.2.8", "minimist": "^1.2.8",
"npm-run-all2": "^6.1.2", "npm-run-all2": "^6.1.2",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"prettier": "^3.2.2", "prettier": "^3.2.5",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"pug": "^3.0.2", "pug": "^3.0.2",
"puppeteer": "~21.11.0", "puppeteer": "~22.2.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup": "4.9.2", "rollup": "^4.12.0",
"rollup-plugin-dts": "^6.1.0", "rollup-plugin-dts": "^6.1.0",
"rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-polyfill-node": "^0.13.0",
"semver": "^7.5.4", "semver": "^7.6.0",
"serve": "^14.2.1", "serve": "^14.2.1",
"simple-git-hooks": "^2.9.0", "simple-git-hooks": "^2.9.0",
"terser": "^5.27.0", "terser": "^5.28.1",
"todomvc-app-css": "^2.4.3", "todomvc-app-css": "^2.4.3",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"tsx": "^4.7.0", "tsx": "^4.7.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.0.12", "vite": "^5.1.4",
"vitest": "^1.2.2" "vitest": "^1.3.1"
} }
} }

View File

@ -271,7 +271,7 @@ describe('compiler: transform v-on', () => {
}) })
}) })
test('should NOT wrap as function if expression is already function expression (with Typescript)', () => { test('should NOT wrap as function if expression is already function expression (with TypeScript)', () => {
const { node } = parseWithVOn(`<div @click="(e: any): any => foo(e)"/>`) const { node } = parseWithVOn(`<div @click="(e: any): any => foo(e)"/>`)
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [

View File

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

View File

@ -553,12 +553,11 @@ export default class Tokenizer {
// HTML mode // HTML mode
// - <script>, <style> RAWTEXT // - <script>, <style> RAWTEXT
// - <title>, <textarea> RCDATA // - <title>, <textarea> RCDATA
const lower = c | 0x20 if (c === 116 /* t */) {
if (lower === 116 /* t */) {
this.state = State.BeforeSpecialT this.state = State.BeforeSpecialT
} else { } else {
this.state = this.state =
lower === 115 /* s */ ? State.BeforeSpecialS : State.InTagName c === 115 /* s */ ? State.BeforeSpecialS : State.InTagName
} }
} else { } else {
this.state = State.InTagName this.state = State.InTagName
@ -862,10 +861,9 @@ export default class Tokenizer {
} }
} }
private stateBeforeSpecialS(c: number): void { private stateBeforeSpecialS(c: number): void {
const lower = c | 0x20 if (c === Sequences.ScriptEnd[3]) {
if (lower === Sequences.ScriptEnd[3]) {
this.startSpecial(Sequences.ScriptEnd, 4) this.startSpecial(Sequences.ScriptEnd, 4)
} else if (lower === Sequences.StyleEnd[3]) { } else if (c === Sequences.StyleEnd[3]) {
this.startSpecial(Sequences.StyleEnd, 4) this.startSpecial(Sequences.StyleEnd, 4)
} else { } else {
this.state = State.InTagName this.state = State.InTagName
@ -873,10 +871,9 @@ export default class Tokenizer {
} }
} }
private stateBeforeSpecialT(c: number): void { private stateBeforeSpecialT(c: number): void {
const lower = c | 0x20 if (c === Sequences.TitleEnd[3]) {
if (lower === Sequences.TitleEnd[3]) {
this.startSpecial(Sequences.TitleEnd, 4) this.startSpecial(Sequences.TitleEnd, 4)
} else if (lower === Sequences.TextareaEnd[3]) { } else if (c === Sequences.TextareaEnd[3]) {
this.startSpecial(Sequences.TextareaEnd, 4) this.startSpecial(Sequences.TextareaEnd, 4)
} else { } else {
this.state = State.InTagName this.state = State.InTagName

View File

@ -20,7 +20,7 @@ describe('DOM parser', () => {
) )
const element = ast.children[0] as ElementNode const element = ast.children[0] as ElementNode
const text = element.children[0] as TextNode const text = element.children[0] as TextNode
expect(element.children.length).toBe(1)
expect(text).toStrictEqual({ expect(text).toStrictEqual({
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: 'some<div>text</div>and<!--comment-->', content: 'some<div>text</div>and<!--comment-->',
@ -32,6 +32,20 @@ describe('DOM parser', () => {
}) })
}) })
test('should not treat Uppercase component as special tag', () => {
const ast = parse(
'<TextArea>some<div>text</div>and<!--comment--></TextArea>',
parserOptions,
)
const element = ast.children[0] as ElementNode
expect(element.children.map(n => n.type)).toMatchObject([
NodeTypes.TEXT,
NodeTypes.ELEMENT,
NodeTypes.TEXT,
NodeTypes.COMMENT,
])
})
test('textarea handles entities', () => { test('textarea handles entities', () => {
const ast = parse('<textarea>&amp;</textarea>', parserOptions) const ast = parse('<textarea>&amp;</textarea>', parserOptions)
const element = ast.children[0] as ElementNode const element = ast.children[0] as ElementNode

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-sfc", "name": "@vue/compiler-sfc",
"version": "3.4.19", "version": "3.4.20",
"description": "@vue/compiler-sfc", "description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js", "main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js", "module": "dist/compiler-sfc.esm-browser.js",
@ -48,8 +48,8 @@
"@vue/compiler-ssr": "workspace:*", "@vue/compiler-ssr": "workspace:*",
"@vue/shared": "workspace:*", "@vue/shared": "workspace:*",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.6", "magic-string": "^0.30.7",
"postcss": "^8.4.33", "postcss": "^8.4.35",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
@ -62,6 +62,6 @@
"postcss-modules": "^6.0.0", "postcss-modules": "^6.0.0",
"postcss-selector-parser": "^6.0.15", "postcss-selector-parser": "^6.0.15",
"pug": "^3.0.2", "pug": "^3.0.2",
"sass": "^1.70.0" "sass": "^1.71.1"
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
# dts-test # dts-test
Tests Typescript types to ensure the types remain as expected. Tests TypeScript types to ensure the types remain as expected.
- This directory is included in the root `tsconfig.json`, where package imports are aliased to `src` directories, so in IDEs and the `pnpm check` script the types are validated against source code. - This directory is included in the root `tsconfig.json`, where package imports are aliased to `src` directories, so in IDEs and the `pnpm check` script the types are validated against source code.

View File

@ -21,6 +21,7 @@ describe.skipIf(!global.gc)('reactivity/gc', () => {
// #9233 // #9233
it('should release computed cache', async () => { it('should release computed cache', async () => {
const src = ref<{} | undefined>({}) const src = ref<{} | undefined>({})
// @ts-expect-error ES2021 API
const srcRef = new WeakRef(src.value!) const srcRef = new WeakRef(src.value!)
let c: ComputedRef | undefined = computed(() => src.value) let c: ComputedRef | undefined = computed(() => src.value)

View File

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

View File

@ -84,26 +84,26 @@ function hasOwnProperty(this: object, key: string) {
class BaseReactiveHandler implements ProxyHandler<Target> { class BaseReactiveHandler implements ProxyHandler<Target> {
constructor( constructor(
protected readonly _isReadonly = false, protected readonly _isReadonly = false,
protected readonly _shallow = false, protected readonly _isShallow = false,
) {} ) {}
get(target: Target, key: string | symbol, receiver: object) { get(target: Target, key: string | symbol, receiver: object) {
const isReadonly = this._isReadonly, const isReadonly = this._isReadonly,
shallow = this._shallow isShallow = this._isShallow
if (key === ReactiveFlags.IS_REACTIVE) { if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) { } else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) { } else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow return isShallow
} else if (key === ReactiveFlags.RAW) { } else if (key === ReactiveFlags.RAW) {
if ( if (
receiver === receiver ===
(isReadonly (isReadonly
? shallow ? isShallow
? shallowReadonlyMap ? shallowReadonlyMap
: readonlyMap : readonlyMap
: shallow : isShallow
? shallowReactiveMap ? shallowReactiveMap
: reactiveMap : reactiveMap
).get(target) || ).get(target) ||
@ -145,7 +145,7 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
track(target, TrackOpTypes.GET, key) track(target, TrackOpTypes.GET, key)
} }
if (shallow) { if (isShallow) {
return res return res
} }
@ -166,8 +166,8 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
} }
class MutableReactiveHandler extends BaseReactiveHandler { class MutableReactiveHandler extends BaseReactiveHandler {
constructor(shallow = false) { constructor(isShallow = false) {
super(false, shallow) super(false, isShallow)
} }
set( set(
@ -177,7 +177,7 @@ class MutableReactiveHandler extends BaseReactiveHandler {
receiver: object, receiver: object,
): boolean { ): boolean {
let oldValue = (target as any)[key] let oldValue = (target as any)[key]
if (!this._shallow) { if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue) const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) { if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue) oldValue = toRaw(oldValue)
@ -239,8 +239,8 @@ class MutableReactiveHandler extends BaseReactiveHandler {
} }
class ReadonlyReactiveHandler extends BaseReactiveHandler { class ReadonlyReactiveHandler extends BaseReactiveHandler {
constructor(shallow = false) { constructor(isShallow = false) {
super(true, shallow) super(true, isShallow)
} }
set(target: object, key: string | symbol) { set(target: object, key: string | symbol) {

View File

@ -2,6 +2,7 @@ import { toRaw, toReactive, toReadonly } from './reactive'
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep' import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared' import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared'
import { warn } from './warning'
type CollectionTypes = IterableCollections | WeakCollections type CollectionTypes = IterableCollections | WeakCollections
@ -218,7 +219,7 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
return function (this: CollectionTypes, ...args: unknown[]) { return function (this: CollectionTypes, ...args: unknown[]) {
if (__DEV__) { if (__DEV__) {
const key = args[0] ? `on key "${args[0]}" ` : `` const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn( warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`, `${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this), toRaw(this),
) )
@ -392,7 +393,7 @@ function checkIdentityKeys(
const rawKey = toRaw(key) const rawKey = toRaw(key)
if (rawKey !== key && has.call(target, rawKey)) { if (rawKey !== key && has.call(target, rawKey)) {
const type = toRawType(target) const type = toRawType(target)
console.warn( warn(
`Reactive ${type} contains both the raw and reactive ` + `Reactive ${type} contains both the raw and reactive ` +
`versions of the same object${type === `Map` ? ` as keys` : ``}, ` + `versions of the same object${type === `Map` ? ` as keys` : ``}, ` +
`which can lead to inconsistencies. ` + `which can lead to inconsistencies. ` +

View File

@ -60,6 +60,11 @@ export class ComputedRefImpl<T = any> implements Subscriber {
// dev only // dev only
onTrigger?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void
/**
* Dev only
*/
_warnRecursive?: boolean
constructor( constructor(
public fn: ComputedGetter<T>, public fn: ComputedGetter<T>,
private readonly setter: ComputedSetter<T> | undefined, private readonly setter: ComputedSetter<T> | undefined,

View File

@ -43,6 +43,7 @@ export {
type WritableComputedOptions, type WritableComputedOptions,
type ComputedGetter, type ComputedGetter,
type ComputedSetter, type ComputedSetter,
type ComputedRefImpl,
} from './computed' } from './computed'
export { export {
effect, effect,

View File

@ -13,6 +13,7 @@ import {
} from './collectionHandlers' } from './collectionHandlers'
import type { RawSymbol, Ref, UnwrapRefSimple } from './ref' import type { RawSymbol, Ref, UnwrapRefSimple } from './ref'
import { ReactiveFlags } from './constants' import { ReactiveFlags } from './constants'
import { warn } from './warning'
export interface Target { export interface Target {
[ReactiveFlags.SKIP]?: boolean [ReactiveFlags.SKIP]?: boolean
@ -247,7 +248,7 @@ function createReactiveObject(
) { ) {
if (!isObject(target)) { if (!isObject(target)) {
if (__DEV__) { if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`) warn(`value cannot be made reactive: ${String(target)}`)
} }
return target return target
} }

View File

@ -17,6 +17,7 @@ import {
} from './reactive' } from './reactive'
import type { ComputedRef } from './computed' import type { ComputedRef } from './computed'
import { TrackOpTypes, TriggerOpTypes } from './constants' import { TrackOpTypes, TriggerOpTypes } from './constants'
import { warn } from './warning'
declare const RefSymbol: unique symbol declare const RefSymbol: unique symbol
export declare const RawSymbol: unique symbol export declare const RawSymbol: unique symbol
@ -319,7 +320,7 @@ export type ToRefs<T = any> = {
*/ */
export function toRefs<T extends object>(object: T): ToRefs<T> { export function toRefs<T extends object>(object: T): ToRefs<T> {
if (__DEV__ && !isProxy(object)) { if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`) warn(`toRefs() expects a reactive object but received a plain one.`)
} }
const ret: any = isArray(object) ? new Array(object.length) : {} const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) { for (const key in object) {

View File

@ -143,10 +143,10 @@ describe('api: createApp', () => {
}, },
setup() { setup() {
// resolve in setup // resolve in setup
const FooBar = resolveComponent('foo-bar') as any const FooBar = resolveComponent('foo-bar')
return () => { return () => {
// resolve in render // resolve in render
const BarBaz = resolveComponent('bar-baz') as any const BarBaz = resolveComponent('bar-baz')
return h('div', [h(FooBar), h(BarBaz)]) return h('div', [h(FooBar), h(BarBaz)])
} }
}, },
@ -182,10 +182,10 @@ describe('api: createApp', () => {
}, },
setup() { setup() {
// resolve in setup // resolve in setup
const FooBar = resolveDirective('foo-bar')! const FooBar = resolveDirective('foo-bar')
return () => { return () => {
// resolve in render // resolve in render
const BarBaz = resolveDirective('bar-baz')! const BarBaz = resolveDirective('bar-baz')
return withDirectives(h('div'), [[FooBar], [BarBaz]]) return withDirectives(h('div'), [[FooBar], [BarBaz]])
} }
}, },
@ -350,7 +350,7 @@ describe('api: createApp', () => {
const handler = vi.fn((err, instance, info) => { const handler = vi.fn((err, instance, info) => {
expect(err).toBe(error) expect(err).toBe(error)
expect((instance as any).count).toBe(count.value) expect(instance.count).toBe(count.value)
expect(info).toBe(`render function`) expect(info).toBe(`render function`)
}) })
@ -450,11 +450,6 @@ describe('api: createApp', () => {
} }
const app = createApp(Root) const app = createApp(Root)
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
writable: false,
})
app.mount(nodeOps.createElement('div')) app.mount(nodeOps.createElement('div'))
expect( expect(
`Do not use built-in directive ids as custom directive id: bind`, `Do not use built-in directive ids as custom directive id: bind`,

View File

@ -790,10 +790,8 @@ describe('api: options', () => {
data() {}, data() {},
} }
defineComponent({ defineComponent({
// @ts-expect-error edge case after #7963, unlikely to happen in practice
// since the user will want to type the mixins themselves. // since the user will want to type the mixins themselves.
mixins: [defineComponent(MixinA), defineComponent(MixinB)], mixins: [defineComponent(MixinA), defineComponent(MixinB)],
// @ts-expect-error
data() {}, data() {},
}) })
}) })

View File

@ -22,7 +22,7 @@ import {
watch, watch,
watchEffect, watchEffect,
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { createApp, defineComponent } from 'vue' import { computed, createApp, defineComponent, inject, provide } from 'vue'
import type { RawSlots } from 'packages/runtime-core/src/componentSlots' import type { RawSlots } from 'packages/runtime-core/src/componentSlots'
import { resetSuspenseId } from '../../src/components/Suspense' import { resetSuspenseId } from '../../src/components/Suspense'
@ -1039,6 +1039,99 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`) expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
}) })
// #10098
test('switching branches w/ nested suspense', async () => {
const RouterView = {
setup(_: any, { slots }: any) {
const route = inject('route') as any
const depth = inject('depth', 0)
provide('depth', depth + 1)
return () => {
const current = route.value[depth]
return slots.default({ Component: current })[0]
}
},
}
const OuterB = defineAsyncComponent({
setup: () => {
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
})
const InnerB = defineAsyncComponent({
setup: () => {
return () => h('div', 'innerB')
},
})
const OuterA = defineAsyncComponent({
setup: () => {
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
})
const InnerA = defineAsyncComponent({
setup: () => {
return () => h('div', 'innerA')
},
})
const toggle = ref(true)
const route = computed(() => {
return toggle.value ? [OuterA, InnerA] : [OuterB, InnerB]
})
const Comp = {
setup() {
provide('route', route)
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<!---->`)
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
deps.length = 0
toggle.value = false
await nextTick()
// toggle again
toggle.value = true
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
})
test('branch switch to 3rd branch before resolve', async () => { test('branch switch to 3rd branch before resolve', async () => {
const calls: string[] = [] const calls: string[] = []

View File

@ -26,7 +26,7 @@ import {
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { type SSRContext, renderToString } from '@vue/server-renderer' import { type SSRContext, renderToString } from '@vue/server-renderer'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { vShowOldKey } from '../../runtime-dom/src/directives/vShow' import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
function mountWithHydration(html: string, render: () => any) { function mountWithHydration(html: string, render: () => any) {
const container = document.createElement('div') const container = document.createElement('div')
@ -1252,7 +1252,7 @@ describe('SSR hydration', () => {
foo foo
</div> </div>
`) `)
expect((container.firstChild as any)[vShowOldKey]).toBe('') expect((container.firstChild as any)[vShowOriginalDisplay]).toBe('')
expect(vnode.el).toBe(container.firstChild) expect(vnode.el).toBe(container.firstChild)
expect(`mismatch`).not.toHaveBeenWarned() expect(`mismatch`).not.toHaveBeenWarned()
}) })

View File

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

View File

@ -1,10 +1,17 @@
import { computed as _computed } from '@vue/reactivity' import { type ComputedRefImpl, computed as _computed } from '@vue/reactivity'
import { isInSSRComponentSetup } from './component' import { getCurrentInstance, isInSSRComponentSetup } from './component'
export const computed: typeof _computed = ( export const computed: typeof _computed = (
getterOrOptions: any, getterOrOptions: any,
debugOptions?: any, debugOptions?: any,
) => { ) => {
// @ts-expect-error // @ts-expect-error
return _computed(getterOrOptions, debugOptions, isInSSRComponentSetup) const c = _computed(getterOrOptions, debugOptions, isInSSRComponentSetup)
if (__DEV__) {
const i = getCurrentInstance()
if (i && i.appContext.config.warnRecursiveComputed) {
;(c as unknown as ComputedRefImpl<any>)._warnRecursive = true
}
}
return c
} }

View File

@ -84,7 +84,7 @@ export type OptionMergeFunction = (to: unknown, from: unknown) => any
export interface AppConfig { export interface AppConfig {
// @private // @private
readonly isNativeTag?: (tag: string) => boolean readonly isNativeTag: (tag: string) => boolean
performance: boolean performance: boolean
optionMergeStrategies: Record<string, OptionMergeFunction> optionMergeStrategies: Record<string, OptionMergeFunction>
@ -110,6 +110,12 @@ export interface AppConfig {
* @deprecated use config.compilerOptions.isCustomElement * @deprecated use config.compilerOptions.isCustomElement
*/ */
isCustomElement?: (tag: string) => boolean isCustomElement?: (tag: string) => boolean
/**
* TODO document for 3.5
* Enable warnings for computed getters that recursively trigger itself.
*/
warnRecursiveComputed?: boolean
} }
export interface AppContext { export interface AppContext {

View File

@ -89,6 +89,30 @@ export type DefineComponent<
> & > &
PP PP
export type DefineSetupFnComponent<
P extends Record<string, any>,
E extends EmitsOptions = {},
S extends SlotsType = SlotsType,
Props = P & EmitsToProps<E>,
PP = PublicProps,
> = new (
props: Props & PP,
) => CreateComponentPublicInstance<
Props,
{},
{},
{},
{},
ComponentOptionsMixin,
ComponentOptionsMixin,
E,
PP,
{},
false,
{},
S
>
// defineComponent is a utility that is primarily used for type inference // defineComponent is a utility that is primarily used for type inference
// when declaring components. Type inference is provided in the component // when declaring components. Type inference is provided in the component
// options (provided as the argument). The returned value has artificial types // options (provided as the argument). The returned value has artificial types
@ -111,7 +135,7 @@ export function defineComponent<
emits?: E | EE[] emits?: E | EE[]
slots?: S slots?: S
}, },
): (props: Props & EmitsToProps<E>) => any ): DefineSetupFnComponent<Props, E, S>
export function defineComponent< export function defineComponent<
Props extends Record<string, any>, Props extends Record<string, any>,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
@ -127,7 +151,7 @@ export function defineComponent<
emits?: E | EE[] emits?: E | EE[]
slots?: S slots?: S
}, },
): (props: Props & EmitsToProps<E>) => any ): DefineSetupFnComponent<Props, E, S>
// overload 2: object format with no props // overload 2: object format with no props
// (uses user defined props interface) // (uses user defined props interface)

View File

@ -61,7 +61,6 @@ import {
import { import {
EMPTY_OBJ, EMPTY_OBJ,
type IfAny, type IfAny,
NO,
NOOP, NOOP,
ShapeFlags, ShapeFlags,
extend, extend,
@ -712,9 +711,11 @@ export const unsetCurrentInstance = () => {
const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component') const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component')
export function validateComponentName(name: string, config: AppConfig) { export function validateComponentName(
const appIsNativeTag = config.isNativeTag || NO name: string,
if (isBuiltInTag(name) || appIsNativeTag(name)) { { isNativeTag }: AppConfig,
) {
if (isBuiltInTag(name) || isNativeTag(name)) {
warn( warn(
'Do not use built-in or reserved HTML elements as component id: ' + name, 'Do not use built-in or reserved HTML elements as component id: ' + name,
) )

View File

@ -144,8 +144,6 @@ const BaseTransitionImpl: ComponentOptions = {
const instance = getCurrentInstance()! const instance = getCurrentInstance()!
const state = useTransitionState() const state = useTransitionState()
let prevTransitionKey: any
return () => { return () => {
const children = const children =
slots.default && getTransitionRawChildren(slots.default(), true) slots.default && getTransitionRawChildren(slots.default(), true)
@ -211,23 +209,11 @@ const BaseTransitionImpl: ComponentOptions = {
const oldChild = instance.subTree const oldChild = instance.subTree
const oldInnerChild = oldChild && getKeepAliveChild(oldChild) const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
let transitionKeyChanged = false
const { getTransitionKey } = innerChild.type as any
if (getTransitionKey) {
const key = getTransitionKey()
if (prevTransitionKey === undefined) {
prevTransitionKey = key
} else if (key !== prevTransitionKey) {
prevTransitionKey = key
transitionKeyChanged = true
}
}
// handle mode // handle mode
if ( if (
oldInnerChild && oldInnerChild &&
oldInnerChild.type !== Comment && oldInnerChild.type !== Comment &&
(!isSameVNodeType(innerChild, oldInnerChild) || transitionKeyChanged) !isSameVNodeType(innerChild, oldInnerChild)
) { ) {
const leavingHooks = resolveTransitionHooks( const leavingHooks = resolveTransitionHooks(
oldInnerChild, oldInnerChild,

View File

@ -100,7 +100,9 @@ export const SuspenseImpl = {
// it is necessary to skip the current patch to avoid multiple mounts // it is necessary to skip the current patch to avoid multiple mounts
// of inner components. // of inner components.
if (parentSuspense && parentSuspense.deps > 0) { if (parentSuspense && parentSuspense.deps > 0) {
n2.suspense = n1.suspense n2.suspense = n1.suspense!
n2.suspense.vnode = n2
n2.el = n1.el
return return
} }
patchSuspense( patchSuspense(

View File

@ -250,7 +250,11 @@ export type {
AllowedComponentProps, AllowedComponentProps,
ComponentInstance, ComponentInstance,
} from './component' } from './component'
export type { DefineComponent, PublicProps } from './apiDefineComponent' export type {
DefineComponent,
DefineSetupFnComponent,
PublicProps,
} from './apiDefineComponent'
export type { export type {
ComponentOptions, ComponentOptions,
ComponentOptionsMixin, ComponentOptionsMixin,

View File

@ -158,4 +158,13 @@ describe(`runtime-dom: style patching`, () => {
) )
expect(el.style.display).toBe('flex') expect(el.style.display).toBe('flex')
}) })
it('should clear previous css string value', () => {
const el = document.createElement('div')
patchProp(el, 'style', {}, 'color:red')
expect(el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
patchProp(el, 'style', 'color:red', { fontSize: '12px' })
expect(el.style.cssText.replace(/\s/g, '')).toBe('font-size:12px;')
})
}) })

View File

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

View File

@ -209,25 +209,20 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
}, },
// set value in mounted & updated because <select> relies on its children // set value in mounted & updated because <select> relies on its children
// <option>s. // <option>s.
mounted(el, { value, oldValue, modifiers: { number } }) { mounted(el, { value, modifiers: { number } }) {
setSelected(el, value, oldValue, number) setSelected(el, value, number)
}, },
beforeUpdate(el, _binding, vnode) { beforeUpdate(el, _binding, vnode) {
el[assignKey] = getModelAssigner(vnode) el[assignKey] = getModelAssigner(vnode)
}, },
updated(el, { value, oldValue, modifiers: { number } }) { updated(el, { value, modifiers: { number } }) {
if (!el._assigning) { if (!el._assigning) {
setSelected(el, value, oldValue, number) setSelected(el, value, number)
} }
}, },
} }
function setSelected( function setSelected(el: HTMLSelectElement, value: any, number: boolean) {
el: HTMLSelectElement,
value: any,
oldValue: any,
number: boolean,
) {
const isMultiple = el.multiple const isMultiple = el.multiple
const isArrayValue = isArray(value) const isArrayValue = isArray(value)
if (isMultiple && !isArrayValue && !isSet(value)) { if (isMultiple && !isArrayValue && !isSet(value)) {
@ -256,11 +251,9 @@ function setSelected(
} else { } else {
option.selected = value.has(optionValue) option.selected = value.has(optionValue)
} }
} else { } else if (looseEqual(getValue(option), value)) {
if (looseEqual(getValue(option), value)) { if (el.selectedIndex !== i) el.selectedIndex = i
if (el.selectedIndex !== i) el.selectedIndex = i return
return
}
} }
} }
if (!isMultiple && el.selectedIndex !== -1) { if (!isMultiple && el.selectedIndex !== -1) {

View File

@ -1,15 +1,18 @@
import type { ObjectDirective } from '@vue/runtime-core' import type { ObjectDirective } from '@vue/runtime-core'
export const vShowOldKey = Symbol('_vod') export const vShowOriginalDisplay = Symbol('_vod')
export const vShowHidden = Symbol('_vsh')
interface VShowElement extends HTMLElement { export interface VShowElement extends HTMLElement {
// _vod = vue original display // _vod = vue original display
[vShowOldKey]: string [vShowOriginalDisplay]: string
[vShowHidden]: boolean
} }
export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = { export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
beforeMount(el, { value }, { transition }) { beforeMount(el, { value }, { transition }) {
el[vShowOldKey] = el.style.display === 'none' ? '' : el.style.display el[vShowOriginalDisplay] =
el.style.display === 'none' ? '' : el.style.display
if (transition && value) { if (transition && value) {
transition.beforeEnter(el) transition.beforeEnter(el)
} else { } else {
@ -22,11 +25,7 @@ export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
} }
}, },
updated(el, { value, oldValue }, { transition }) { updated(el, { value, oldValue }, { transition }) {
if ( if (!value === !oldValue) return
!value === !oldValue &&
(el.style.display === el[vShowOldKey] || !value)
)
return
if (transition) { if (transition) {
if (value) { if (value) {
transition.beforeEnter(el) transition.beforeEnter(el)
@ -51,7 +50,8 @@ if (__DEV__) {
} }
function setDisplay(el: VShowElement, value: unknown): void { function setDisplay(el: VShowElement, value: unknown): void {
el.style.display = value ? el[vShowOldKey] : 'none' el.style.display = value ? el[vShowOriginalDisplay] : 'none'
el[vShowHidden] = !value
} }
// SSR vnode transforms, only used when user includes client-oriented render // SSR vnode transforms, only used when user includes client-oriented render

View File

@ -39,7 +39,8 @@ export function patchDOMProp(
el._value = value el._value = value
// #4956: <option> value will fallback to its text content so we need to // #4956: <option> value will fallback to its text content so we need to
// compare against its attribute value instead. // compare against its attribute value instead.
const oldValue = tag === 'OPTION' ? el.getAttribute('value') : el.value const oldValue =
tag === 'OPTION' ? el.getAttribute('value') || '' : el.value
const newValue = value == null ? '' : value const newValue = value == null ? '' : value
if (oldValue !== newValue) { if (oldValue !== newValue) {
el.value = newValue el.value = newValue

View File

@ -1,6 +1,10 @@
import { capitalize, hyphenate, isArray, isString } from '@vue/shared' import { capitalize, hyphenate, isArray, isString } from '@vue/shared'
import { camelize, warn } from '@vue/runtime-core' import { camelize, warn } from '@vue/runtime-core'
import { vShowOldKey } from '../directives/vShow' import {
type VShowElement,
vShowHidden,
vShowOriginalDisplay,
} from '../directives/vShow'
import { CSS_VAR_TEXT } from '../helpers/useCssVars' import { CSS_VAR_TEXT } from '../helpers/useCssVars'
type Style = string | Record<string, string | string[]> | null type Style = string | Record<string, string | string[]> | null
@ -10,13 +14,21 @@ const displayRE = /(^|;)\s*display\s*:/
export function patchStyle(el: Element, prev: Style, next: Style) { export function patchStyle(el: Element, prev: Style, next: Style) {
const style = (el as HTMLElement).style const style = (el as HTMLElement).style
const isCssString = isString(next) const isCssString = isString(next)
const currentDisplay = style.display
let hasControlledDisplay = false let hasControlledDisplay = false
if (next && !isCssString) { if (next && !isCssString) {
if (prev && !isString(prev)) { if (prev) {
for (const key in prev) { if (!isString(prev)) {
if (next[key] == null) { for (const key in prev) {
setStyle(style, key, '') if (next[key] == null) {
setStyle(style, key, '')
}
}
} else {
for (const prevStyle of prev.split(';')) {
const key = prevStyle.slice(0, prevStyle.indexOf(':')).trim()
if (next[key] == null) {
setStyle(style, key, '')
}
} }
} }
} }
@ -41,12 +53,14 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
el.removeAttribute('style') el.removeAttribute('style')
} }
} }
// indicates that the `display` of the element is controlled by `v-show`, // indicates the element also has `v-show`.
// so we always keep the current `display` value regardless of the `style` if (vShowOriginalDisplay in el) {
// value, thus handing over control to `v-show`. // make v-show respect the current v-bind style display when shown
if (vShowOldKey in el) { el[vShowOriginalDisplay] = hasControlledDisplay ? style.display : ''
el[vShowOldKey] = hasControlledDisplay ? style.display : '' // if v-show is in hidden state, v-show has higher priority
style.display = currentDisplay if ((el as VShowElement)[vShowHidden]) {
style.display = 'none'
}
} }
} }

View File

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

View File

@ -7,8 +7,6 @@
<link rel="icon" type="image/svg" href="/logo.svg" /> <link rel="icon" type="image/svg" href="/logo.svg" />
<title>Vue SFC Playground</title> <title>Vue SFC Playground</title>
<script> <script>
// process shim for old versions of @vue/compiler-sfc dependency
window.process = { env: {} }
const savedPreferDark = localStorage.getItem('vue-sfc-playground-prefer-dark') const savedPreferDark = localStorage.getItem('vue-sfc-playground-prefer-dark')
if ( if (
savedPreferDark === 'true' || savedPreferDark === 'true' ||

View File

@ -9,11 +9,11 @@
"serve": "vite preview" "serve": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.3", "@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.0.12" "vite": "^5.1.4"
}, },
"dependencies": { "dependencies": {
"@vue/repl": "^3.1.1", "@vue/repl": "^4.1.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"vue": "workspace:*" "vue": "workspace:*"

View File

@ -1,22 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import Header from './Header.vue' import Header from './Header.vue'
import { Repl, ReplStore, SFCOptions } from '@vue/repl' import { Repl, useStore, SFCOptions, useVueImportMap } from '@vue/repl'
import type Monaco from '@vue/repl/monaco-editor' import Monaco from '@vue/repl/monaco-editor'
import type CodeMirror from '@vue/repl/codemirror-editor' import { ref, watchEffect, onMounted, computed } from 'vue'
import { ref, watchEffect, onMounted } from 'vue'
import { shallowRef } from 'vue'
const EditorComponent = shallowRef<typeof Monaco | typeof CodeMirror>()
if (import.meta.env.DEV) {
import('@vue/repl/codemirror-editor').then(
mod => (EditorComponent.value = mod.default),
)
} else {
import('@vue/repl/monaco-editor').then(
mod => (EditorComponent.value = mod.default),
)
}
const replRef = ref<InstanceType<typeof Repl>>() const replRef = ref<InstanceType<typeof Repl>>()
@ -26,78 +12,80 @@ const setVH = () => {
window.addEventListener('resize', setVH) window.addEventListener('resize', setVH)
setVH() setVH()
const useProdMode = ref(false)
const useSSRMode = ref(false) const useSSRMode = ref(false)
const { productionMode, vueVersion, importMap } = useVueImportMap({
runtimeDev: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.js`
: `${location.origin}/src/vue-dev-proxy`,
runtimeProd: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.prod.js`
: `${location.origin}/src/vue-dev-proxy-prod`,
serverRenderer: import.meta.env.PROD
? `${location.origin}/server-renderer.esm-browser.js`
: `${location.origin}/src/vue-server-renderer-dev-proxy`,
})
let hash = location.hash.slice(1) let hash = location.hash.slice(1)
if (hash.startsWith('__DEV__')) { if (hash.startsWith('__DEV__')) {
hash = hash.slice(7) hash = hash.slice(7)
useProdMode.value = false productionMode.value = false
} }
if (hash.startsWith('__PROD__')) { if (hash.startsWith('__PROD__')) {
hash = hash.slice(8) hash = hash.slice(8)
useProdMode.value = true productionMode.value = true
} }
if (hash.startsWith('__SSR__')) { if (hash.startsWith('__SSR__')) {
hash = hash.slice(7) hash = hash.slice(7)
useSSRMode.value = true useSSRMode.value = true
} }
const store = new ReplStore({
serializedState: hash,
productionMode: useProdMode.value,
defaultVueRuntimeURL: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.js`
: `${location.origin}/src/vue-dev-proxy`,
defaultVueRuntimeProdURL: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.prod.js`
: `${location.origin}/src/vue-dev-proxy-prod`,
defaultVueServerRendererURL: import.meta.env.PROD
? `${location.origin}/server-renderer.esm-browser.js`
: `${location.origin}/src/vue-server-renderer-dev-proxy`,
})
// enable experimental features // enable experimental features
const sfcOptions: SFCOptions = { const sfcOptions = computed(
script: { (): SFCOptions => ({
inlineTemplate: useProdMode.value, script: {
isProd: useProdMode.value, inlineTemplate: productionMode.value,
propsDestructure: true, isProd: productionMode.value,
}, propsDestructure: true,
style: {
isProd: useProdMode.value,
},
template: {
isProd: useProdMode.value,
compilerOptions: {
isCustomElement: (tag: string) => tag === 'mjx-container',
}, },
style: {
isProd: productionMode.value,
},
template: {
isProd: productionMode.value,
compilerOptions: {
isCustomElement: (tag: string) => tag === 'mjx-container',
},
},
}),
)
const store = useStore(
{
builtinImportMap: importMap,
vueVersion,
sfcOptions,
}, },
} hash,
)
// @ts-expect-error
globalThis.store = store
// persist state // persist state
watchEffect(() => { watchEffect(() => {
const newHash = store const newHash = store
.serialize() .serialize()
.replace(/^#/, useSSRMode.value ? `#__SSR__` : `#`) .replace(/^#/, useSSRMode.value ? `#__SSR__` : `#`)
.replace(/^#/, useProdMode.value ? `#__PROD__` : `#`) .replace(/^#/, productionMode.value ? `#__PROD__` : `#`)
history.replaceState({}, '', newHash) history.replaceState({}, '', newHash)
}) })
function toggleProdMode() { function toggleProdMode() {
const isProd = (useProdMode.value = !useProdMode.value) productionMode.value = !productionMode.value
sfcOptions.script!.inlineTemplate =
sfcOptions.script!.isProd =
sfcOptions.template!.isProd =
sfcOptions.style!.isProd =
isProd
store.toggleProduction()
store.setFiles(store.getFiles())
} }
function toggleSSR() { function toggleSSR() {
useSSRMode.value = !useSSRMode.value useSSRMode.value = !useSSRMode.value
store.setFiles(store.getFiles())
} }
function reloadPage() { function reloadPage() {
@ -111,13 +99,16 @@ function toggleTheme(isDark: boolean) {
onMounted(() => { onMounted(() => {
const cls = document.documentElement.classList const cls = document.documentElement.classList
toggleTheme(cls.contains('dark')) toggleTheme(cls.contains('dark'))
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
window.process = { env: {} }
}) })
</script> </script>
<template> <template>
<Header <Header
:store="store" :store="store"
:prod="useProdMode" :prod="productionMode"
:ssr="useSSRMode" :ssr="useSSRMode"
@toggle-theme="toggleTheme" @toggle-theme="toggleTheme"
@toggle-prod="toggleProdMode" @toggle-prod="toggleProdMode"
@ -125,17 +116,15 @@ onMounted(() => {
@reload-page="reloadPage" @reload-page="reloadPage"
/> />
<Repl <Repl
v-if="EditorComponent"
ref="replRef" ref="replRef"
:theme="theme" :theme="theme"
:editor="EditorComponent" :editor="Monaco"
@keydown.ctrl.s.prevent @keydown.ctrl.s.prevent
@keydown.meta.s.prevent @keydown.meta.s.prevent
:ssr="useSSRMode" :ssr="useSSRMode"
:store="store" :store="store"
:showCompileOutput="true" :showCompileOutput="true"
:autoResize="true" :autoResize="true"
:sfcOptions="sfcOptions"
:clearConsole="false" :clearConsole="false"
:preview-options="{ :preview-options="{
customCode: { customCode: {

View File

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import type { ReplStore } from '@vue/repl'
import { downloadProject } from './download/download' import { downloadProject } from './download/download'
import { ref } from 'vue'
import Sun from './icons/Sun.vue' import Sun from './icons/Sun.vue'
import Moon from './icons/Moon.vue' import Moon from './icons/Moon.vue'
import Share from './icons/Share.vue' import Share from './icons/Share.vue'
import Download from './icons/Download.vue' import Download from './icons/Download.vue'
import GitHub from './icons/GitHub.vue' import GitHub from './icons/GitHub.vue'
import Reload from './icons/Reload.vue' import Reload from './icons/Reload.vue'
import type { ReplStore } from '@vue/repl'
import VersionSelect from './VersionSelect.vue' import VersionSelect from './VersionSelect.vue'
const props = defineProps<{ const props = defineProps<{
@ -25,23 +25,20 @@ const emit = defineEmits([
const { store } = props const { store } = props
const currentCommit = __COMMIT__ const currentCommit = __COMMIT__
const vueVersion = ref(`@${currentCommit}`)
const vueURL = store.getImportMap().imports.vue const vueVersion = computed(() => {
if (vueURL && !vueURL.startsWith(location.origin)) { if (store.loading) {
const versionMatch = vueURL.match(/runtime-dom@([^/]+)/) return 'loading...'
if (versionMatch) vueVersion.value = versionMatch[1] }
} return store.vueVersion || `@${__COMMIT__}`
})
async function setVueVersion(v: string) { async function setVueVersion(v: string) {
vueVersion.value = `loading...` store.vueVersion = v
await store.setVueVersion(v)
vueVersion.value = v
} }
function resetVueVersion() { function resetVueVersion() {
store.resetVueVersion() store.vueVersion = null
vueVersion.value = `@${currentCommit}`
} }
async function copyLink(e: MouseEvent) { async function copyLink(e: MouseEvent) {
@ -73,7 +70,7 @@ function toggleDark() {
</h1> </h1>
<div class="links"> <div class="links">
<VersionSelect <VersionSelect
v-model="store.state.typescriptVersion" v-model="store.typescriptVersion"
pkg="typescript" pkg="typescript"
label="TypeScript Version" label="TypeScript Version"
/> />

View File

@ -11,7 +11,7 @@
"vue": "^3.4.0" "vue": "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.3", "@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.0.12" "vite": "^5.1.4"
} }
} }

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ export const E2E_TIMEOUT = 30 * 1000
const puppeteerOptions: PuppeteerLaunchOptions = { const puppeteerOptions: PuppeteerLaunchOptions = {
args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [], args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
headless: 'new', headless: true,
} }
const maxTries = 30 const maxTries = 30

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -143,6 +143,8 @@ function createConfig(format, output, plugins = []) {
} }
output.sourcemap = !!process.env.SOURCE_MAP output.sourcemap = !!process.env.SOURCE_MAP
output.externalLiveBindings = false output.externalLiveBindings = false
// https://github.com/rollup/rollup/pull/5380
output.reexportProtoFromExternal = false
if (isGlobalBuild) { if (isGlobalBuild) {
output.name = packageOptions.name output.name = packageOptions.name

View File

@ -18,7 +18,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"removeComments": false, "removeComments": false,
"jsx": "preserve", "jsx": "preserve",
"lib": ["esnext", "dom"], "lib": ["es2016", "dom"],
"types": ["vitest/globals", "puppeteer", "node"], "types": ["vitest/globals", "puppeteer", "node"],
"rootDir": ".", "rootDir": ".",
"paths": { "paths": {