diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 86f39f816d1..27ae80c5577 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -14,10 +14,10 @@ jobs: - uses: actions/checkout@v5 - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae787e6b6ab..027695f398f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: uses: pnpm/action-setup@v4 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 520cc248b3a..645495ab598 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: uses: pnpm/action-setup@v4 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/size-data.yml b/.github/workflows/size-data.yml index 7d2078e17a2..55357b965af 100644 --- a/.github/workflows/size-data.yml +++ b/.github/workflows/size-data.yml @@ -25,10 +25,10 @@ jobs: - uses: actions/checkout@v5 - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' cache: pnpm @@ -45,7 +45,7 @@ jobs: echo ${{ github.base_ref }} > ./temp/size/base.txt - name: Upload Size Data - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: size-data path: temp/size diff --git a/.github/workflows/size-report.yml b/.github/workflows/size-report.yml index 9ef9010b410..af54b7afd82 100644 --- a/.github/workflows/size-report.yml +++ b/.github/workflows/size-report.yml @@ -25,10 +25,10 @@ jobs: - uses: actions/checkout@v5 - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' cache: pnpm diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5bcf6e0ba5b..2fa2b538259 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,10 +14,10 @@ jobs: - uses: actions/checkout@v5 - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' cache: 'pnpm' @@ -35,10 +35,10 @@ jobs: - uses: actions/checkout@v5 - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' cache: 'pnpm' @@ -63,10 +63,10 @@ jobs: key: chromium-${{ hashFiles('pnpm-lock.yaml') }} - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' cache: 'pnpm' @@ -88,10 +88,10 @@ jobs: - uses: actions/checkout@v5 - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 + uses: pnpm/action-setup@v4.2.0 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.vscode/settings.json b/.vscode/settings.json index 302428290b9..fa3fa65efaf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,7 @@ "cSpell.enabledLanguageIds": ["markdown", "plaintext", "text", "yml"], - // Use prettier to format typescript, javascript and JSON files + // Use prettier to format TypeScript, JavaScript and JSON files "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 813a2ae1b0c..3a071763ba7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## [3.5.24](https://github.com/vuejs/core/compare/v3.5.23...v3.5.24) (2025-11-07) + + +### Reverts + +* Revert "fix(compiler-core): correctly handle ts type assertions in expression…" (#14062) ([11ec51a](https://github.com/vuejs/core/commit/11ec51aa5a7914745fee10ed2b9f9464fab4d02c)), closes [#14062](https://github.com/vuejs/core/issues/14062) [#14060](https://github.com/vuejs/core/issues/14060) + + + +## [3.5.23](https://github.com/vuejs/core/compare/v3.5.22...v3.5.23) (2025-11-06) + + +### Bug Fixes + +* **compiler-core:** correctly handle ts type assertions in expressions ([#13397](https://github.com/vuejs/core/issues/13397)) ([e6544ac](https://github.com/vuejs/core/commit/e6544ac292b5b473274f87cdb83ebeac3e7e61a4)), closes [#13395](https://github.com/vuejs/core/issues/13395) +* **compiler-core:** fix v-bind shorthand handling for in-DOM templates ([#13933](https://github.com/vuejs/core/issues/13933)) ([b3cca26](https://github.com/vuejs/core/commit/b3cca2611c656b85f0c4e737b9ec248d2627dded)), closes [#13930](https://github.com/vuejs/core/issues/13930) +* **compiler-sfc:** resolve numeric literals and template literals without expressions as static property key ([#13998](https://github.com/vuejs/core/issues/13998)) ([75d44c7](https://github.com/vuejs/core/commit/75d44c718981f91843e197265cc68e82fe2532dd)) +* **compiler-ssr:** textarea with v-text directive SSR ([#13975](https://github.com/vuejs/core/issues/13975)) ([006a0c1](https://github.com/vuejs/core/commit/006a0c1011a224bcbf21195c6df76812c3a7e757)) +* **compiler:** using guard instead of non-nullish assertion ([#13982](https://github.com/vuejs/core/issues/13982)) ([dcc6f36](https://github.com/vuejs/core/commit/dcc6f362577ed86ccad31c2623c6cf75137dd27a)) +* **custom-element:** batch custom element prop patching ([#13478](https://github.com/vuejs/core/issues/13478)) ([c13e674](https://github.com/vuejs/core/commit/c13e674fb9f92ab9339d28a862d18de460faf56e)), closes [#12619](https://github.com/vuejs/core/issues/12619) +* **custom-element:** optimize slot retrieval to avoid duplicates ([#13961](https://github.com/vuejs/core/issues/13961)) ([84ca349](https://github.com/vuejs/core/commit/84ca349fef73f6f55fc98299fcfa5c1eeef721db)), closes [#13955](https://github.com/vuejs/core/issues/13955) +* **hydration:** avoid mismatch during hydrate text with newlines in interpolation ([#9232](https://github.com/vuejs/core/issues/9232)) ([6cbdf78](https://github.com/vuejs/core/commit/6cbdf7823b0c961190bee5b7c117b7f2bbeb832f)), closes [#9229](https://github.com/vuejs/core/issues/9229) +* **runtime-core:** pass props and children to loadingComponent ([#13997](https://github.com/vuejs/core/issues/13997)) ([40c4b2a](https://github.com/vuejs/core/commit/40c4b2a876ce606973521dfc3024e26bfc10953a)) +* **runtime-dom:** ensure iframe sandbox is handled as an attribute to prevent unintended behavior ([#13950](https://github.com/vuejs/core/issues/13950)) ([5689884](https://github.com/vuejs/core/commit/5689884c8e32cda6a802ac36b4d23218f67b38ed)), closes [#13946](https://github.com/vuejs/core/issues/13946) +* **suspense:** clear placeholder and fallback el after resolve to enable GC ([#13928](https://github.com/vuejs/core/issues/13928)) ([f411c66](https://github.com/vuejs/core/commit/f411c6604c12c531883aa0d30b81a7f69092f8a6)) +* **transition-group:** use offsetLeft and offsetTop instead of getBoundingClientRect to avoid transform scale affect animation ([#6108](https://github.com/vuejs/core/issues/6108)) ([dc4dd59](https://github.com/vuejs/core/commit/dc4dd594fbecce6ed7f44ffa69dc8b5d022287b6)), closes [#6105](https://github.com/vuejs/core/issues/6105) +* **v-model:** handle number modifier on change ([#13959](https://github.com/vuejs/core/issues/13959)) ([8fbe48f](https://github.com/vuejs/core/commit/8fbe48fe396d830999afd07f9413d899157d5f5e)), closes [#13958](https://github.com/vuejs/core/issues/13958) + + + ## [3.5.22](https://github.com/vuejs/core/compare/v3.5.21...v3.5.22) (2025-09-25) diff --git a/eslint.config.js b/eslint.config.js index b752b2e19f1..2b472e14d1e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,6 @@ import importX from 'eslint-plugin-import-x' import tseslint from 'typescript-eslint' +import { defineConfig } from 'eslint/config' import vitest from '@vitest/eslint-plugin' import { builtinModules } from 'node:module' @@ -12,7 +13,7 @@ const banConstEnum = { 'Please use non-const enums. This project automatically inlines enums.', } -export default tseslint.config( +export default defineConfig( { files: ['**/*.js', '**/*.ts', '**/*.tsx'], extends: [tseslint.configs.base], @@ -60,7 +61,10 @@ export default tseslint.config( ], // This rule enforces the preference for using '@ts-expect-error' comments in TypeScript // code to indicate intentional type errors, improving code clarity and maintainability. - '@typescript-eslint/prefer-ts-expect-error': 'error', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { minimumDescriptionLength: 0 }, + ], // Enforce the use of 'import type' for importing types '@typescript-eslint/consistent-type-imports': [ 'error', diff --git a/package.json b/package.json index be996cb828d..2ba9d2bc609 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, - "version": "3.5.22", - "packageManager": "pnpm@10.17.1", + "version": "3.5.24", + "packageManager": "pnpm@10.20.0", "type": "module", "scripts": { "dev": "node scripts/dev.js", @@ -65,43 +65,43 @@ "@babel/parser": "catalog:", "@babel/types": "catalog:", "@rollup/plugin-alias": "^5.1.1", - "@rollup/plugin-commonjs": "^28.0.6", + "@rollup/plugin-commonjs": "^28.0.9", "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-replace": "5.0.4", - "@swc/core": "^1.13.5", + "@swc/core": "^1.14.0", "@types/hash-sum": "^1.0.2", - "@types/node": "^22.18.6", + "@types/node": "^22.19.0", "@types/semver": "^7.7.1", "@types/serve-handler": "^6.1.4", "@vitest/coverage-v8": "^3.2.4", - "@vitest/eslint-plugin": "^1.3.12", + "@vitest/eslint-plugin": "^1.4.0", "@vue/consolidate": "1.0.0", "conventional-changelog-cli": "^5.0.0", "enquirer": "^2.4.1", - "esbuild": "^0.25.10", + "esbuild": "^0.25.12", "esbuild-plugin-polyfill-node": "^0.3.0", "eslint": "^9.27.0", "eslint-plugin-import-x": "^4.13.1", "estree-walker": "catalog:", - "jsdom": "^27.0.0", + "jsdom": "^27.1.0", "lint-staged": "^16.0.0", "lodash": "^4.17.21", - "magic-string": "^0.30.19", + "magic-string": "^0.30.21", "markdown-table": "^3.0.4", "marked": "13.0.3", "npm-run-all2": "^8.0.4", "picocolors": "^1.1.1", "prettier": "^3.5.3", - "pretty-bytes": "^6.1.1", + "pretty-bytes": "^7.1.0", "pug": "^3.0.3", - "puppeteer": "~24.22.2", - "rimraf": "^6.0.1", - "rollup": "^4.52.2", + "puppeteer": "~24.28.0", + "rimraf": "^6.1.0", + "rollup": "^4.52.5", "rollup-plugin-dts": "^6.2.3", "rollup-plugin-esbuild": "^6.2.1", "rollup-plugin-polyfill-node": "^0.13.0", - "semver": "^7.7.2", + "semver": "^7.7.3", "serve": "^14.2.5", "serve-handler": "^6.1.6", "simple-git-hooks": "^2.13.0", diff --git a/packages-private/sfc-playground/src/download/template/package.json b/packages-private/sfc-playground/src/download/template/package.json index 7104182ff02..9c715b0c8cc 100644 --- a/packages-private/sfc-playground/src/download/template/package.json +++ b/packages-private/sfc-playground/src/download/template/package.json @@ -12,6 +12,6 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.1", - "vite": "^7.1.7" + "vite": "^7.1.12" } } diff --git a/packages-private/template-explorer/package.json b/packages-private/template-explorer/package.json index 96038782717..eace79a9ef4 100644 --- a/packages-private/template-explorer/package.json +++ b/packages-private/template-explorer/package.json @@ -11,7 +11,7 @@ "enableNonBrowserBranches": true }, "dependencies": { - "monaco-editor": "^0.53.0", + "monaco-editor": "^0.54.0", "source-map-js": "^1.2.1" } } diff --git a/packages/compiler-core/__tests__/transforms/vBind.spec.ts b/packages/compiler-core/__tests__/transforms/vBind.spec.ts index 2221d926e52..ff21351d05d 100644 --- a/packages/compiler-core/__tests__/transforms/vBind.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vBind.spec.ts @@ -112,6 +112,27 @@ describe('compiler: transform v-bind', () => { }) }) + test('no expression (shorthand) in-DOM templates', () => { + try { + __BROWSER__ = true + // :id in in-DOM templates will be parsed into :id="" by browser + const node = parseWithVBind(`
`) + const props = (node.codegenNode as VNodeCall).props as ObjectExpression + expect(props.properties[0]).toMatchObject({ + key: { + content: `id`, + isStatic: true, + }, + value: { + content: `id`, + isStatic: false, + }, + }) + } finally { + __BROWSER__ = false + } + }) + test('dynamic arg', () => { const node = parseWithVBind(``) const props = (node.codegenNode as VNodeCall).props as CallExpression diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 50af1523ed5..2ff4004996a 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.5.22", + "version": "3.5.24", "description": "@vue/compiler-core", "main": "index.js", "module": "dist/compiler-core.esm-bundler.js", diff --git a/packages/compiler-core/src/parser.ts b/packages/compiler-core/src/parser.ts index a6e25681d75..2d85289fc68 100644 --- a/packages/compiler-core/src/parser.ts +++ b/packages/compiler-core/src/parser.ts @@ -1054,7 +1054,7 @@ export function baseParse(input: string, options?: ParserOptions): RootNode { `[@vue/compiler-core] decodeEntities option is passed but will be ` + `ignored in non-browser builds.`, ) - } else if (__BROWSER__ && !currentOptions.decodeEntities) { + } else if (__BROWSER__ && !__TEST__ && !currentOptions.decodeEntities) { throw new Error( `[@vue/compiler-core] decodeEntities option is required in browser builds.`, ) diff --git a/packages/compiler-core/src/transforms/transformVBindShorthand.ts b/packages/compiler-core/src/transforms/transformVBindShorthand.ts index ff5b51047a3..210180822b6 100644 --- a/packages/compiler-core/src/transforms/transformVBindShorthand.ts +++ b/packages/compiler-core/src/transforms/transformVBindShorthand.ts @@ -15,9 +15,14 @@ export const transformVBindShorthand: NodeTransform = (node, context) => { if ( prop.type === NodeTypes.DIRECTIVE && prop.name === 'bind' && - !prop.exp + (!prop.exp || + // #13930 :foo in in-DOM templates will be parsed into :foo="" by browser + (__BROWSER__ && + prop.exp.type === NodeTypes.SIMPLE_EXPRESSION && + !prop.exp.content.trim())) && + prop.arg ) { - const arg = prop.arg! + const arg = prop.arg if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) { // only simple expression is allowed for same-name shorthand context.onError( diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index f22e8a441c7..8dec2451f7b 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.5.22", + "version": "3.5.24", "description": "@vue/compiler-dom", "main": "index.js", "module": "dist/compiler-dom.esm-bundler.js", diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index eff2f01b981..557f6ec5d93 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -20,6 +20,8 @@ describe('resolveType', () => { foo: number // property bar(): void // method 'baz': string // string literal key + [\`qux\`]: boolean // template literal key + 123: symbol // numeric literal key (e: 'foo'): void // call signature (e: 'bar'): void }>()`) @@ -27,6 +29,8 @@ describe('resolveType', () => { foo: ['Number'], bar: ['Function'], baz: ['String'], + qux: ['Boolean'], + 123: ['Symbol'], }) expect(calls?.length).toBe(2) }) @@ -195,7 +199,7 @@ describe('resolveType', () => { type T = 'foo' | 'bar' type S = 'x' | 'y' defineProps<{ - [\`_\${T}_\${S}_\`]: string + [K in \`_\${T}_\${S}_\`]: string }>() `).props, ).toStrictEqual({ diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index 6b5380ce76c..e822d6e8ff9 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.5.22", + "version": "3.5.24", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", "module": "dist/compiler-sfc.esm-browser.js", @@ -58,10 +58,10 @@ "hash-sum": "^2.0.0", "lru-cache": "10.1.0", "merge-source-map": "^1.1.0", - "minimatch": "~10.0.3", + "minimatch": "~10.1.1", "postcss-modules": "^6.0.1", "postcss-selector-parser": "^7.1.0", "pug": "^3.0.3", - "sass": "^1.93.2" + "sass": "^1.93.3" } } diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 54b93b3fe7a..9cb4ce8e7c2 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -29,6 +29,7 @@ import { createGetCanonicalFileName, getId, getImportedName, + getStringLiteralKey, joinPaths, normalizePath, } from './utils' @@ -336,13 +337,9 @@ function typeElementsToMap( Object.assign(scope.types, typeParameters) } ;(e as MaybeWithScope)._ownerScope = scope - const name = getId(e.key) - if (name && !e.computed) { + const name = getStringLiteralKey(e) + if (name !== null) { res.props[name] = e as ResolvedElements['props'][string] - } else if (e.key.type === 'TemplateLiteral') { - for (const key of resolveTemplateKeys(ctx, e.key, scope)) { - res.props[key] = e as ResolvedElements['props'][string] - } } else { ctx.error( `Unsupported computed key in type referenced by a macro`, @@ -853,7 +850,7 @@ export function registerTS(_loadTS: () => typeof TS): void { ) { throw new Error( 'Failed to load TypeScript, which is required for resolving imported types. ' + - 'Please make sure "typescript" is installed as a project dependency.', + 'Please make sure "TypeScript" is installed as a project dependency.', ) } else { throw new Error( @@ -951,7 +948,7 @@ function importSourceToScope( if (!ts) { return ctx.error( `Failed to resolve import source ${JSON.stringify(source)}. ` + - `typescript is required as a peer dep for vue in order ` + + `TypeScript is required as a peer dep for vue in order ` + `to support resolving types from module imports.`, node, scope, @@ -2029,8 +2026,7 @@ function findStaticPropertyType(node: TSTypeLiteral, key: string) { const prop = node.members.find( m => m.type === 'TSPropertySignature' && - !m.computed && - getId(m.key) === key && + getStringLiteralKey(m) === key && m.typeAnnotation, ) return prop && prop.typeAnnotation!.typeAnnotation diff --git a/packages/compiler-sfc/src/script/utils.ts b/packages/compiler-sfc/src/script/utils.ts index 1b7da0e3497..bda64dc2e41 100644 --- a/packages/compiler-sfc/src/script/utils.ts +++ b/packages/compiler-sfc/src/script/utils.ts @@ -7,6 +7,8 @@ import type { ImportSpecifier, Node, StringLiteral, + TSMethodSignature, + TSPropertySignature, } from '@babel/types' import path from 'path' @@ -79,6 +81,22 @@ export function getId(node: Expression) { : null } +export function getStringLiteralKey( + node: TSPropertySignature | TSMethodSignature, +): string | null { + return node.computed + ? node.key.type === 'TemplateLiteral' && !node.key.expressions.length + ? node.key.quasis.map(q => q.value.cooked).join('') + : null + : node.key.type === 'Identifier' + ? node.key.name + : node.key.type === 'StringLiteral' + ? node.key.value + : node.key.type === 'NumericLiteral' + ? String(node.key.value) + : null +} + const identity = (str: string) => str const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g const toLowerCase = (str: string) => str.toLowerCase() diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index c7815eab2bb..6d5d3f46826 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-ssr", - "version": "3.5.22", + "version": "3.5.24", "description": "@vue/compiler-ssr", "main": "dist/compiler-ssr.cjs.js", "types": "dist/compiler-ssr.d.ts", diff --git a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts index ee46894f9fc..3fbedc1ae57 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts @@ -122,8 +122,13 @@ export const ssrTransformElement: NodeTransform = (node, context) => { | InterpolationNode | undefined // If interpolation, this is dynamic