diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..4d4f41e30 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +node_modules +dist +temp +coverage diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4296d0f39..b8afcf984 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,14 +1,22 @@ -/* eslint-disable no-restricted-globals */ - +const { builtinModules } = require('node:module') const DOMGlobals = ['window', 'document'] const NodeGlobals = ['module', 'require'] +const banConstEnum = { + selector: 'TSEnumDeclaration[const=true]', + message: + 'Please use non-const enums. This project automatically inlines enums.', +} + +/** + * @type {import('eslint-define-config').ESLintConfig} + */ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - sourceType: 'module' + sourceType: 'module', }, - plugins: ['jest'], + plugins: ['jest', 'import', '@typescript-eslint'], rules: { 'no-debugger': 'error', // most of the codebase are expected to be env agnostic @@ -16,14 +24,34 @@ module.exports = { 'no-restricted-syntax': [ 'error', + banConstEnum, // since we target ES2015 for baseline support, we need to forbid object // rest spread usage in destructure as it compiles into a verbose helper. 'ObjectPattern > RestElement', // tsc compiles assignment spread into Object.assign() calls, but esbuild // still generates verbose helpers, so spread assignment is also prohiboted 'ObjectExpression > SpreadElement', - 'AwaitExpression' - ] + 'AwaitExpression', + ], + 'sort-imports': ['error', { ignoreDeclarationSort: true }], + + 'import/no-nodejs-modules': [ + 'error', + { allow: builtinModules.map(mod => `node:${mod}`) }, + ], + // 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', + // Enforce the use of 'import type' for importing types + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + fixStyle: 'inline-type-imports', + disallowTypeAnnotations: false, + }, + ], + // Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers + '@typescript-eslint/no-import-type-side-effects': 'error', }, overrides: [ // tests, no restrictions (runs in Node / jest with jsdom) @@ -33,56 +61,66 @@ module.exports = { 'no-restricted-globals': 'off', 'no-restricted-syntax': 'off', 'jest/no-disabled-tests': 'error', - 'jest/no-focused-tests': 'error' - } + 'jest/no-focused-tests': 'error', + }, }, // shared, may be used in any env { - files: ['packages/shared/**'], + files: ['packages/shared/**', '.eslintrc.cjs'], rules: { - 'no-restricted-globals': 'off' - } + 'no-restricted-globals': 'off', + }, }, // Packages targeting DOM { files: ['packages/{vue,vue-compat,runtime-dom}/**'], rules: { - 'no-restricted-globals': ['error', ...NodeGlobals] - } + 'no-restricted-globals': ['error', ...NodeGlobals], + }, }, // Packages targeting Node { - files: [ - 'packages/{compiler-sfc,compiler-ssr,server-renderer,reactivity-transform}/**' - ], + files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'], rules: { 'no-restricted-globals': ['error', ...DOMGlobals], - 'no-restricted-syntax': 'off' - } + 'no-restricted-syntax': ['error', banConstEnum], + }, }, // Private package, browser only + no syntax restrictions { files: ['packages/template-explorer/**', 'packages/sfc-playground/**'], rules: { 'no-restricted-globals': ['error', ...NodeGlobals], - 'no-restricted-syntax': 'off' - } + 'no-restricted-syntax': ['error', banConstEnum], + }, }, // JavaScript files { files: ['*.js', '*.cjs'], rules: { // We only do `no-unused-vars` checks for js files, TS files are checked by TypeScript itself. - 'no-unused-vars': ['error', { vars: 'all', args: 'none' }] - } + 'no-unused-vars': ['error', { vars: 'all', args: 'none' }], + }, }, // Node scripts { - files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'], + files: [ + 'scripts/**', + './*.{js,ts}', + 'packages/*/*.js', + 'packages/vue/*/*.js', + ], rules: { 'no-restricted-globals': 'off', - 'no-restricted-syntax': 'off' - } - } - ] + 'no-restricted-syntax': ['error', banConstEnum], + }, + }, + // Import nodejs modules in compiler-sfc + { + files: ['packages/compiler-sfc/src/**'], + rules: { + 'import/no-nodejs-modules': ['error', { allow: builtinModules }], + }, + }, + ], } diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..d06b03a3f --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# update prettier & eslint config (#9162) +bfe6b459d3a0ce6168611ee1ac7e6e789709df9d diff --git a/.github/contributing.md b/.github/contributing.md index afdae6711..da1bd5ec4 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -63,7 +63,7 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before You will need [Node.js](https://nodejs.org) **version 18.12+**, and [PNPM](https://pnpm.io) **version 8+**. -We also recommend installing [ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier. +We also recommend installing [@antfu/ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier. After cloning the repo, run: @@ -86,11 +86,11 @@ The project uses [simple-git-hooks](https://github.com/toplenboren/simple-git-ho - Type check the entire project - Automatically format changed files using Prettier -- Verify commit message format (logic in `scripts/verifyCommit.js`) +- Verify commit message format (logic in `scripts/verify-commit.js`) ## Scripts -**The examples below will be using the `nr` command from the [ni](https://github.com/antfu/ni) package.** You can also use plain `npm run`, but you will need to pass all additional arguments after the command after an extra `--`. For example, `nr build runtime --all` is equivalent to `npm run build -- runtime --all`. +**The examples below will be using the `nr` command from the [@antfu/ni](https://github.com/antfu/ni) package.** You can also use plain `npm run`, but you will need to pass all additional arguments after the command after an extra `--`. For example, `nr build runtime --all` is equivalent to `npm run build -- runtime --all`. The `run-s` and `run-p` commands found in some scripts are from [npm-run-all](https://github.com/mysticatea/npm-run-all) for orchestrating multiple scripts. `run-s` means "run in sequence" while `run-p` means "run in parallel". diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 088913317..a43f4ae30 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -7,35 +7,35 @@ packageRules: [ { depTypeList: ['peerDependencies'], - enabled: false + enabled: false, }, { groupName: 'test', matchPackageNames: ['vitest', 'jsdom', 'puppeteer'], - matchPackagePrefixes: ['@vitest'] + matchPackagePrefixes: ['@vitest'], }, { groupName: 'playground', matchFileNames: [ 'packages/sfc-playground/package.json', - 'packages/template-explorer/package.json' - ] + 'packages/template-explorer/package.json', + ], }, { groupName: 'compiler', matchPackageNames: ['magic-string'], - matchPackagePrefixes: ['@babel', 'postcss'] + matchPackagePrefixes: ['@babel', 'postcss'], }, { groupName: 'build', matchPackageNames: ['vite', 'terser'], - matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'] + matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'], }, { groupName: 'lint', matchPackageNames: ['simple-git-hooks', 'lint-staged'], - matchPackagePrefixes: ['@typescript-eslint', 'eslint', 'prettier'] - } + matchPackagePrefixes: ['@typescript-eslint', 'eslint', 'prettier'], + }, ], ignoreDeps: [ 'vue', @@ -45,6 +45,6 @@ 'typescript', // ESM only - 'estree-walker' - ] + 'estree-walker', + ], } diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 5e7bb63c2..d7f11b0d2 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -30,4 +30,4 @@ jobs: - name: Run prettier run: pnpm run format - - uses: autofix-ci/action@bee19d72e71787c12ca0f29de72f2833e437e4c9 + - uses: autofix-ci/action@ea32e3a12414e6d3183163c3424a7d7a8631ad84 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9e8078dd..e458fbd57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,31 +58,6 @@ 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 @@ -110,6 +85,9 @@ jobs: - name: Run e2e tests run: pnpm run test-e2e + - name: verify treeshaking + run: node scripts/verify-treeshaking.js + lint-and-test-dts: runs-on: ubuntu-latest if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository @@ -137,3 +115,28 @@ jobs: - name: Run type declaration tests run: pnpm run test-dts + + # 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 }} diff --git a/.prettierignore b/.prettierignore index 1521c8b76..fbd3dca8c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,4 @@ dist +*.md +*.html +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc index ef93d9482..759232e7c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ -semi: false -singleQuote: true -printWidth: 80 -trailingComma: 'none' -arrowParens: 'avoid' +{ + "semi": false, + "singleQuote": true, + "arrowParens": "avoid" +} diff --git a/.vscode/launch.json b/.vscode/launch.json index b63ffc79b..b616400b4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,7 +21,7 @@ "console": "integratedTerminal", "sourceMaps": true, "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "program": "${workspaceFolder}/node_modules/jest/bin/jest" } } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5528537..44f187edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,276 @@ +## [3.4.3](https://github.com/vuejs/core/compare/v3.4.2...v3.4.3) (2023-12-30) + + +### Bug Fixes + +* **compiler-sfc:** respect sfc parse options in cache key ([b8d58ec](https://github.com/vuejs/core/commit/b8d58ec4f42cbeb9443bf06138add46158db9af0)) + + + +## [3.4.2](https://github.com/vuejs/core/compare/v3.4.1...v3.4.2) (2023-12-30) + + +### Bug Fixes + +* **compiler-sfc:** fix dev regression for dot / namespace component usage ([dce99c1](https://github.com/vuejs/core/commit/dce99c12df981ca45a4d848c37ba8b16496025f0)), closes [#9947](https://github.com/vuejs/core/issues/9947) +* **runtime-core:** support deep: false when watch reactive ([#9928](https://github.com/vuejs/core/issues/9928)) ([4f703d1](https://github.com/vuejs/core/commit/4f703d120d76d711084346f73ea295c73e6ef6b6)), closes [#9916](https://github.com/vuejs/core/issues/9916) +* **ssr:** fix hydration error for slot outlet inside transition-group ([#9937](https://github.com/vuejs/core/issues/9937)) ([6cb00ed](https://github.com/vuejs/core/commit/6cb00ed0f9b64428ec18fada0f68467d6a813fde)), closes [#9933](https://github.com/vuejs/core/issues/9933) + + + +## [3.4.1](https://github.com/vuejs/core/compare/v3.4.0...v3.4.1) (2023-12-30) + + +### Bug Fixes + +* **compat:** correct enum value for COMPILER_FILTERS feature ([#9875](https://github.com/vuejs/core/issues/9875)) ([77d33e2](https://github.com/vuejs/core/commit/77d33e263cf19983caf4e5c53a0eb0bee374843c)) +* **defineModel:** always default modifiers to empty object ([9bc3c7e](https://github.com/vuejs/core/commit/9bc3c7e29cf15f5ca96703542d10cfd786a3fc55)), closes [#9945](https://github.com/vuejs/core/issues/9945) +* **defineModel:** support local mutation when only prop but no listener is passed ([97ce041](https://github.com/vuejs/core/commit/97ce041910b6ca4bef10f939493d6b5a06ea5b07)) +* **types:** fix defineModel watch type error ([#9942](https://github.com/vuejs/core/issues/9942)) ([4af8583](https://github.com/vuejs/core/commit/4af85835f7e593a7dffa7dc7e99f14877eb70fd1)), closes [#9939](https://github.com/vuejs/core/issues/9939) + + +### Features + +* **compiler-sfc:** support passing template parsing options when parsing sfc ([6fab855](https://github.com/vuejs/core/commit/6fab8551e4aeef4610987640de8b435b1ae321bb)) (necessary to fix https://github.com/vitejs/vite-plugin-vue/issues/322) + + + +# [3.4.0 Slam Dunk](https://github.com/vuejs/core/compare/v3.4.0-rc.3...v3.4.0) (2023-12-29) + +> Read [this blog post](https://blog.vuejs.org/posts/vue-3-4) for an overview of the release highlights. + +### Potential Actions Needed + +1. To fully leverage new features in 3.4, it is recommended to also update the following dependencies when upgrading to 3.4: + + - Volar / vue-tsc@^1.8.27 (**required**) + - @vitejs/plugin-vue@^5.0.0 (if using Vite) + - nuxt@^3.9.0 (if using Nuxt) + - vue-loader@^17.4.0 (if using webpack or vue-cli) + +2. If using TSX with Vue, check actions needed in [Removed: Global JSX Namespace](https://blog.vuejs.org/posts/vue-3-4#global-jsx-namespace). + +3. Make sure you are no longer using any deprecated features (if you are, you should have warnings in the console telling you so). They may have been [removed in 3.4](https://blog.vuejs.org/posts/vue-3-4#other-removed-features). + +### Features + +* **general:** MathML support ([#7836](https://github.com/vuejs/core/issues/7836)) ([d42b6ba](https://github.com/vuejs/core/commit/d42b6ba3f530746eb1221eb7a4be0f44eb56f7d3)), closes [#7820](https://github.com/vuejs/core/issues/7820) +* **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) +* **reactivity:** expose last result for computed getter ([#9497](https://github.com/vuejs/core/issues/9497)) ([48b47a1](https://github.com/vuejs/core/commit/48b47a1ab63577e2dbd91947eea544e3ef185b85)) +* **runtime-core / dx:** link errors to docs in prod build ([#9165](https://github.com/vuejs/core/issues/9165)) ([9f8ba98](https://github.com/vuejs/core/commit/9f8ba9821fe166f77e63fa940e9e7e13ec3344fa)) +* **runtime-core:** add `once` option to watch ([#9034](https://github.com/vuejs/core/issues/9034)) ([a645e7a](https://github.com/vuejs/core/commit/a645e7aa51006516ba668b3a4365d296eb92ee7d)) +* **runtime-core:** provide full props to props validator functions ([#3258](https://github.com/vuejs/core/issues/3258)) ([8e27692](https://github.com/vuejs/core/commit/8e27692029a4645cd54287f776c0420f2b82740b)) +* **compiler-core:** export error message ([#8729](https://github.com/vuejs/core/issues/8729)) ([f7e80ee](https://github.com/vuejs/core/commit/f7e80ee4a065a9eaba98720abf415d9e87756cbd)) +* **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-core:** improve parsing tolerance for language-tools ([41ff68e](https://github.com/vuejs/core/commit/41ff68ea579d933333392146625560359acb728a)) +* **compiler-core:** support accessing Error as global in template expressions ([#7018](https://github.com/vuejs/core/issues/7018)) ([bcca475](https://github.com/vuejs/core/commit/bcca475dbc58d76434cd8120b94929758cee2825)) +* **compiler-core:** lift vnode hooks deprecation warning to error ([8abc754](https://github.com/vuejs/core/commit/8abc754d5d86d9dfd5a7927b846f1a743f352364)) +* **compiler-core:** export runtime error strings ([#9301](https://github.com/vuejs/core/issues/9301)) ([feb2f2e](https://github.com/vuejs/core/commit/feb2f2edce2d91218a5e9a52c81e322e4033296b)) +* **compiler-core:** add current filename to TransformContext ([#8950](https://github.com/vuejs/core/issues/8950)) ([638f1ab](https://github.com/vuejs/core/commit/638f1abbb632000553e2b7d75e87c95d8ca192d6)) +* **compiler-sfc:** analyze import usage in template via AST ([#9729](https://github.com/vuejs/core/issues/9729)) ([e8bbc94](https://github.com/vuejs/core/commit/e8bbc946cba6bf74c9da56f938b67d2a04c340ba)), closes [#8897](https://github.com/vuejs/core/issues/8897) [nuxt/nuxt#22416](https://github.com/nuxt/nuxt/issues/22416) +* **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)) +* **compiler-sfc:** bump postcss-modules to v6 ([2a507e3](https://github.com/vuejs/core/commit/2a507e32f0e2ef73813705a568b8633f68bda7a9)) +* **compiler-sfc:** promote defineModel stable ([#9598](https://github.com/vuejs/core/issues/9598)) ([ef688ba](https://github.com/vuejs/core/commit/ef688ba92bfccbc8b7ea3997eb297665d13e5249)) +* **compiler-sfc:** support import attributes and `using` syntax ([#8786](https://github.com/vuejs/core/issues/8786)) ([5b2bd1d](https://github.com/vuejs/core/commit/5b2bd1df78e8ff524c3a184adaa284681aba6574)) +* **compiler-sfc:** `defineModel` support local mutation by default, remove local option ([f74785b](https://github.com/vuejs/core/commit/f74785bc4ad351102dde17fdfd2c7276b823111f)), closes [/github.com/vuejs/rfcs/discussions/503#discussioncomment-7566278](https://github.com//github.com/vuejs/rfcs/discussions/503/issues/discussioncomment-7566278) +* **ssr:** add `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__` feature flag ([#9550](https://github.com/vuejs/core/issues/9550)) ([bc7698d](https://github.com/vuejs/core/commit/bc7698dbfed9b5327a93565f9df336ae5a94d605)) +* **ssr:** improve ssr hydration mismatch checks ([#5953](https://github.com/vuejs/core/issues/5953)) ([2ffc1e8](https://github.com/vuejs/core/commit/2ffc1e8cfdc6ec9c45c4a4dd8e3081b2aa138f1e)), closes [#5063](https://github.com/vuejs/core/issues/5063) +* **types:** use enum to replace const enum ([#9261](https://github.com/vuejs/core/issues/9261)) ([fff7b86](https://github.com/vuejs/core/commit/fff7b864f4292d0430ba2bda7098ad43876b0210)), closes [#1228](https://github.com/vuejs/core/issues/1228) +* **types:** add emits and slots type to `FunctionalComponent` ([#8644](https://github.com/vuejs/core/issues/8644)) ([927ab17](https://github.com/vuejs/core/commit/927ab17cfc645e82d061fdf227c34689491268e1)) +* **types:** export `AriaAttributes` type ([#8909](https://github.com/vuejs/core/issues/8909)) ([fd0b6ba](https://github.com/vuejs/core/commit/fd0b6ba01660499fa07b0cf360eefaac8cca8287)) +* **types:** export `ObjectPlugin` and `FunctionPlugin` types ([#8946](https://github.com/vuejs/core/issues/8946)) ([fa4969e](https://github.com/vuejs/core/commit/fa4969e7a3aefa6863203f9294fc5e769ddf6d8f)), closes [#8577](https://github.com/vuejs/core/issues/8577) +* **types:** expose `DefineProps` type ([096ba81](https://github.com/vuejs/core/commit/096ba81817b7da15f61bc55fc1a93f72ac9586e0)) +* **types:** expose `PublicProps` type ([#2403](https://github.com/vuejs/core/issues/2403)) ([44135dc](https://github.com/vuejs/core/commit/44135dc95fb8fea26b84d1433839d28b8c21f708)) +* **types:** improve event type inference when using `h` with native elements ([#9756](https://github.com/vuejs/core/issues/9756)) ([a625376](https://github.com/vuejs/core/commit/a625376ac8901eea81bf3c66cb531f2157f073ef)) +* **types:** provide `ComponentInstance` type ([#5408](https://github.com/vuejs/core/issues/5408)) ([bfb8565](https://github.com/vuejs/core/commit/bfb856565d3105db4b18991ae9e404e7cc989b25)) +* **types:** support passing generics when registering global directives ([#9660](https://github.com/vuejs/core/issues/9660)) ([a41409e](https://github.com/vuejs/core/commit/a41409ed02a8c7220e637f56caf6813edeb077f8)) + + +### Performance Improvements + +* **compiler-sfc:** avoid sfc source map unnecessary serialization and parsing ([f15d2f6](https://github.com/vuejs/core/commit/f15d2f6cf69c0c39f8dfb5c33122790c68bf92e2)) +* **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)) +* **compiler-core:** optimize away isBuiltInType ([66c0ed0](https://github.com/vuejs/core/commit/66c0ed0a3c1c6f37dafc6b1c52b75c6bf60e3136)) +* **compiler-core:** optimize position cloning ([2073236](https://github.com/vuejs/core/commit/20732366b9b3530d33b842cf1fc985919afb9317)) +* **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)) +* **shared:** optimize makeMap ([ae6fba9](https://github.com/vuejs/core/commit/ae6fba94954bac6430902f77b0d1113a98a75b18)) + + +### BREAKING CHANGES + +#### Global JSX Registration Removed + +Starting in 3.4, Vue no longer registers the global `JSX` namespace by default. This is necessary to avoid global namespace collision with React so that TSX of both libs can co-exist in the same project. This should not affect SFC-only users with latest version of Volar. + +If you are using TSX, there are two options: + +1. Explicitly set [jsxImportSource](https://www.typescriptlang.org/tsconfig#jsxImportSource) to `'vue'` in `tsconfig.json` before upgrading to 3.4. You can also opt-in per file by adding a `/* @jsxImportSource vue */` comment at the top of the file. + +2. If you have code that depends on the presence of the global `JSX` namespace, e.g. usage of types like `JSX.Element` etc., you can retain the exact pre-3.4 global behavior by explicitly referencing `vue/jsx`, which registers the global `JSX` namespace. + +Note that this is a type-only breaking change in a minor release, which adheres to our [release policy](https://vuejs.org/about/releases.html#semantic-versioning-edge-cases). + +#### Deprecated Features Removed + +- [Reactivity Transform](https://vuejs.org/guide/extras/reactivity-transform.html) 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 [Vue Macros plugin](https://vue-macros.dev/features/reactivity-transform.html). +- `app.config.unwrapInjectedRef` has been removed. It was deprecated and enabled by default in 3.3. In 3.4 it is no longer possible to disable this behavior. +- `@vnodeXXX` event listeners in templates are now a compiler error instead of a deprecation warning. Use `@vue:XXX` listeners instead. +- `v-is` directive has been removed. It was deprecated in 3.3. Use the [`is` attribute with `vue:` prefix](https://vuejs.org/api/built-in-special-attributes.html#is) instead. + +# [3.4.0-rc.3](https://github.com/vuejs/core/compare/v3.4.0-rc.2...v3.4.0-rc.3) (2023-12-27) + + +### Bug Fixes + +* also export runtime error strings in all cjs builds ([38706e4](https://github.com/vuejs/core/commit/38706e4a1e5e5380e7df910b2a784d0a9bc9db29)) + + +### Features + +* **defineModel:** support modifiers and transformers ([a772031](https://github.com/vuejs/core/commit/a772031ea8431bd732ffeaeaac09bd76a0daec9b)) + + + +# [3.4.0-rc.2](https://github.com/vuejs/core/compare/v3.4.0-rc.1...v3.4.0-rc.2) (2023-12-26) + + +### Bug Fixes + +* **deps:** update dependency @vue/repl to ^3.1.0 ([#9911](https://github.com/vuejs/core/issues/9911)) ([f96c413](https://github.com/vuejs/core/commit/f96c413e8ef2f24cacda5bb499492922f62c6e8b)) +* **types:** fix distribution of union types when unwrapping setup bindings ([#9909](https://github.com/vuejs/core/issues/9909)) ([0695c69](https://github.com/vuejs/core/commit/0695c69e0dfaf99882a623fe75b433c9618ea648)), closes [#9903](https://github.com/vuejs/core/issues/9903) +* **warning:** ensure prod hydration warnings actually work ([b4ebe7a](https://github.com/vuejs/core/commit/b4ebe7ae8b904f28cdda33caf87bc05718d3a08a)) + + +### Features + +* **compiler-sfc:** export aggregated error messages for compiler-core and compiler-dom ([25c726e](https://github.com/vuejs/core/commit/25c726eca81fc384b41fafbeba5e8dfcda1f030f)) + + + +# [3.4.0-rc.1](https://github.com/vuejs/core/compare/v3.4.0-beta.4...v3.4.0-rc.1) (2023-12-25) + + +### Bug Fixes + +* **compiler-core:** fix parsing `', + { parseMode: 'sfc' }, + ) + const element = ast.children[0] as ElementNode + expect(element).toMatchObject({ + type: NodeTypes.ELEMENT, + ns: Namespaces.HTML, + tag: 'script', + tagType: ElementTypes.ELEMENT, + codegenNode: undefined, + children: [], + innerLoc: { + start: { column: 67, line: 1, offset: 66 }, + end: { column: 67, line: 1, offset: 66 }, + }, + props: [ + { + loc: { + source: 'setup', + end: { column: 14, line: 1, offset: 13 }, + start: { column: 9, line: 1, offset: 8 }, + }, + name: 'setup', + nameLoc: { + source: 'setup', + end: { column: 14, line: 1, offset: 13 }, + start: { column: 9, line: 1, offset: 8 }, + }, + type: NodeTypes.ATTRIBUTE, + value: undefined, + }, + { + loc: { + source: 'lang="ts"', + end: { column: 24, line: 1, offset: 23 }, + start: { column: 15, line: 1, offset: 14 }, + }, + name: 'lang', + nameLoc: { + source: 'lang', + end: { column: 19, line: 1, offset: 18 }, + start: { column: 15, line: 1, offset: 14 }, + }, + type: NodeTypes.ATTRIBUTE, + value: { + content: 'ts', + loc: { + source: '"ts"', + end: { column: 24, line: 1, offset: 23 }, + start: { column: 20, line: 1, offset: 19 }, + }, + type: NodeTypes.TEXT, + }, + }, + { + loc: { + source: 'generic="T extends Record"', + end: { column: 66, line: 1, offset: 65 }, + start: { column: 25, line: 1, offset: 24 }, + }, + name: 'generic', + nameLoc: { + source: 'generic', + end: { column: 32, line: 1, offset: 31 }, + start: { column: 25, line: 1, offset: 24 }, + }, + type: NodeTypes.ATTRIBUTE, + value: { + content: 'T extends Record', + loc: { + source: '"T extends Record"', + end: { column: 66, line: 1, offset: 65 }, + start: { column: 33, line: 1, offset: 32 }, + }, + type: NodeTypes.TEXT, + }, + }, + ], }) }) @@ -943,76 +1081,95 @@ describe('compiler: parse', () => { { type: NodeTypes.ATTRIBUTE, name: 'id', + nameLoc: { + start: { offset: 5, line: 1, column: 6 }, + end: { offset: 7, line: 1, column: 8 }, + source: 'id', + }, value: { type: NodeTypes.TEXT, content: 'a', loc: { start: { offset: 8, line: 1, column: 9 }, end: { offset: 9, line: 1, column: 10 }, - source: 'a' - } + source: 'a', + }, }, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 9, line: 1, column: 10 }, - source: 'id=a' - } + source: 'id=a', + }, }, { type: NodeTypes.ATTRIBUTE, name: 'class', + nameLoc: { + start: { offset: 10, line: 1, column: 11 }, + end: { offset: 15, line: 1, column: 16 }, + source: 'class', + }, value: { type: NodeTypes.TEXT, content: 'c', loc: { start: { offset: 16, line: 1, column: 17 }, end: { offset: 19, line: 1, column: 20 }, - source: '"c"' - } + source: '"c"', + }, }, loc: { start: { offset: 10, line: 1, column: 11 }, end: { offset: 19, line: 1, column: 20 }, - source: 'class="c"' - } + source: 'class="c"', + }, }, { type: NodeTypes.ATTRIBUTE, name: 'inert', + nameLoc: { + start: { offset: 20, line: 1, column: 21 }, + end: { offset: 25, line: 1, column: 26 }, + source: 'inert', + }, value: undefined, loc: { start: { offset: 20, line: 1, column: 21 }, end: { offset: 25, line: 1, column: 26 }, - source: 'inert' - } + source: 'inert', + }, }, { type: NodeTypes.ATTRIBUTE, name: 'style', + nameLoc: { + start: { offset: 26, line: 1, column: 27 }, + end: { offset: 31, line: 1, column: 32 }, + source: 'style', + }, value: { type: NodeTypes.TEXT, content: '', loc: { start: { offset: 32, line: 1, column: 33 }, end: { offset: 34, line: 1, column: 35 }, - source: "''" - } + source: "''", + }, }, loc: { start: { offset: 26, line: 1, column: 27 }, end: { offset: 34, line: 1, column: 35 }, - source: "style=''" - } - } + source: "style=''", + }, + }, ], - isSelfClosing: false, children: [], loc: { start: { offset: 0, line: 1, column: 1 }, end: { offset: 41, line: 1, column: 42 }, - source: '
' - } + source: '
', + }, }) }) @@ -1024,60 +1181,40 @@ describe('compiler: parse', () => { expect(element).toStrictEqual({ children: [], codegenNode: undefined, - isSelfClosing: false, loc: { - end: { - column: 10, - line: 3, - offset: 29 - }, + start: { column: 1, line: 1, offset: 0 }, + end: { column: 10, line: 3, offset: 29 }, source: '
', - start: { - column: 1, - line: 1, - offset: 0 - } }, ns: Namespaces.HTML, props: [ { - loc: { - end: { - column: 3, - line: 3, - offset: 22 - }, - source: 'class=" \n\t c \t\n "', - start: { - column: 6, - line: 1, - offset: 5 - } - }, name: 'class', + nameLoc: { + start: { column: 6, line: 1, offset: 5 }, + end: { column: 11, line: 1, offset: 10 }, + source: 'class', + }, type: NodeTypes.ATTRIBUTE, value: { content: 'c', loc: { - end: { - column: 3, - line: 3, - offset: 22 - }, + start: { column: 12, line: 1, offset: 11 }, + end: { column: 3, line: 3, offset: 22 }, source: '" \n\t c \t\n "', - start: { - column: 12, - line: 1, - offset: 11 - } }, - type: NodeTypes.TEXT - } - } + type: NodeTypes.TEXT, + }, + loc: { + start: { column: 6, line: 1, offset: 5 }, + end: { column: 3, line: 3, offset: 22 }, + source: 'class=" \n\t c \t\n "', + }, + }, ], tag: 'div', tagType: ElementTypes.ELEMENT, - type: NodeTypes.ELEMENT + type: NodeTypes.ELEMENT, }) }) @@ -1088,14 +1225,15 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'if', + rawName: 'v-if', arg: undefined, modifiers: [], exp: undefined, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 9, line: 1, column: 10 }, - source: 'v-if' - } + source: 'v-if', + }, }) }) @@ -1106,6 +1244,7 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'if', + rawName: 'v-if', arg: undefined, modifiers: [], exp: { @@ -1116,14 +1255,14 @@ describe('compiler: parse', () => { loc: { start: { offset: 11, line: 1, column: 12 }, end: { offset: 12, line: 1, column: 13 }, - source: 'a' - } + source: 'a', + }, }, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 13, line: 1, column: 14 }, - source: 'v-if="a"' - } + source: 'v-if="a"', + }, }) }) @@ -1134,33 +1273,25 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'on', + rawName: 'v-on:click', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, - loc: { + start: { column: 11, line: 1, offset: 10 }, + end: { column: 16, line: 1, offset: 15 }, source: 'click', - start: { - column: 11, - line: 1, - offset: 10 - }, - end: { - column: 16, - line: 1, - offset: 15 - } - } + }, }, modifiers: [], exp: undefined, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 15, line: 1, column: 16 }, - source: 'v-on:click' - } + source: 'v-on:click', + }, }) }) @@ -1173,8 +1304,7 @@ describe('compiler: parse', () => { loc: { start: { offset: 12, line: 1, column: 13 }, end: { offset: 16, line: 1, column: 17 }, - source: 'slot' - } + }, }) }) @@ -1184,11 +1314,11 @@ describe('compiler: parse', () => { const directive = (ast.children[0] as ElementNode) .props[0] as DirectiveNode expect(directive.arg).toMatchObject({ + content: 'item.item', loc: { start: { offset: 6, line: 1, column: 7 }, end: { offset: 15, line: 1, column: 16 }, - source: 'item.item' - } + }, }) }) @@ -1199,33 +1329,25 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'on', + rawName: 'v-on:[event]', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'event', isStatic: false, constType: ConstantTypes.NOT_CONSTANT, - loc: { + start: { column: 11, line: 1, offset: 10 }, + end: { column: 18, line: 1, offset: 17 }, source: '[event]', - start: { - column: 11, - line: 1, - offset: 10 - }, - end: { - column: 18, - line: 1, - offset: 17 - } - } + }, }, modifiers: [], exp: undefined, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 17, line: 1, column: 18 }, - source: 'v-on:[event]' - } + source: 'v-on:[event]', + }, }) }) @@ -1236,14 +1358,15 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'on', + rawName: 'v-on.enter', arg: undefined, modifiers: ['enter'], exp: undefined, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 15, line: 1, column: 16 }, - source: 'v-on.enter' - } + source: 'v-on.enter', + }, }) }) @@ -1254,14 +1377,15 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'on', + rawName: 'v-on.enter.exact', arg: undefined, modifiers: ['enter', 'exact'], exp: undefined, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 21, line: 1, column: 22 }, - source: 'v-on.enter.exact' - } + source: 'v-on.enter.exact', + }, }) }) @@ -1272,33 +1396,25 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'on', + rawName: 'v-on:click.enter.exact', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, - loc: { + start: { column: 11, line: 1, offset: 10 }, + end: { column: 16, line: 1, offset: 15 }, source: 'click', - start: { - column: 11, - line: 1, - offset: 10 - }, - end: { - column: 16, - line: 1, - offset: 15 - } - } + }, }, modifiers: ['enter', 'exact'], exp: undefined, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 27, line: 1, column: 28 }, - source: 'v-on:click.enter.exact' - } + source: 'v-on:click.enter.exact', + }, }) }) @@ -1309,41 +1425,34 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'on', + rawName: 'v-on:[a.b].camel', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a.b', isStatic: false, constType: ConstantTypes.NOT_CONSTANT, - loc: { + start: { column: 11, line: 1, offset: 10 }, + end: { column: 16, line: 1, offset: 15 }, source: '[a.b]', - start: { - column: 11, - line: 1, - offset: 10 - }, - end: { - column: 16, - line: 1, - offset: 15 - } - } + }, }, modifiers: ['camel'], exp: undefined, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 21, line: 1, column: 22 }, - source: 'v-on:[a.b].camel' - } + source: 'v-on:[a.b].camel', + }, }) }) + test('directive with no name', () => { let errorCode = -1 const ast = baseParse('
', { onError: err => { errorCode = err.code as number - } + }, }) const directive = (ast.children[0] as ElementNode).props[0] @@ -1355,8 +1464,13 @@ describe('compiler: parse', () => { loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 7, line: 1, column: 8 }, - source: 'v-' - } + source: 'v-', + }, + nameLoc: { + start: { offset: 5, line: 1, column: 6 }, + end: { offset: 7, line: 1, column: 8 }, + source: 'v-', + }, }) }) @@ -1367,25 +1481,17 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'bind', + rawName: ':a', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, - loc: { + start: { column: 7, line: 1, offset: 6 }, + end: { column: 8, line: 1, offset: 7 }, source: 'a', - start: { - column: 7, - line: 1, - offset: 6 - }, - end: { - column: 8, - line: 1, - offset: 7 - } - } + }, }, modifiers: [], exp: { @@ -1393,18 +1499,17 @@ describe('compiler: parse', () => { content: 'b', isStatic: false, constType: ConstantTypes.NOT_CONSTANT, - loc: { start: { offset: 8, line: 1, column: 9 }, end: { offset: 9, line: 1, column: 10 }, - source: 'b' - } + source: 'b', + }, }, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 9, line: 1, column: 10 }, - source: ':a=b' - } + source: ':a=b', + }, }) }) @@ -1415,25 +1520,17 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'bind', + rawName: '.a', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, - loc: { + start: { column: 7, line: 1, offset: 6 }, + end: { column: 8, line: 1, offset: 7 }, source: 'a', - start: { - column: 7, - line: 1, - offset: 6 - }, - end: { - column: 8, - line: 1, - offset: 7 - } - } + }, }, modifiers: ['prop'], exp: { @@ -1441,18 +1538,17 @@ describe('compiler: parse', () => { content: 'b', isStatic: false, constType: ConstantTypes.NOT_CONSTANT, - loc: { start: { offset: 8, line: 1, column: 9 }, end: { offset: 9, line: 1, column: 10 }, - source: 'b' - } + source: 'b', + }, }, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 9, line: 1, column: 10 }, - source: '.a=b' - } + source: '.a=b', + }, }) }) @@ -1463,25 +1559,17 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'bind', + rawName: ':a.sync', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, - loc: { + start: { column: 7, line: 1, offset: 6 }, + end: { column: 8, line: 1, offset: 7 }, source: 'a', - start: { - column: 7, - line: 1, - offset: 6 - }, - end: { - column: 8, - line: 1, - offset: 7 - } - } + }, }, modifiers: ['sync'], exp: { @@ -1493,14 +1581,14 @@ describe('compiler: parse', () => { loc: { start: { offset: 13, line: 1, column: 14 }, end: { offset: 14, line: 1, column: 15 }, - source: 'b' - } + source: 'b', + }, }, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 14, line: 1, column: 15 }, - source: ':a.sync=b' - } + source: ':a.sync=b', + }, }) }) @@ -1511,25 +1599,17 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'on', + rawName: '@a', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, - loc: { + start: { column: 7, line: 1, offset: 6 }, + end: { column: 8, line: 1, offset: 7 }, source: 'a', - start: { - column: 7, - line: 1, - offset: 6 - }, - end: { - column: 8, - line: 1, - offset: 7 - } - } + }, }, modifiers: [], exp: { @@ -1541,14 +1621,14 @@ describe('compiler: parse', () => { loc: { start: { offset: 8, line: 1, column: 9 }, end: { offset: 9, line: 1, column: 10 }, - source: 'b' - } + source: 'b', + }, }, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 9, line: 1, column: 10 }, - source: '@a=b' - } + source: '@a=b', + }, }) }) @@ -1559,25 +1639,17 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'on', + rawName: '@a.enter', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, - loc: { + start: { column: 7, line: 1, offset: 6 }, + end: { column: 8, line: 1, offset: 7 }, source: 'a', - start: { - column: 7, - line: 1, - offset: 6 - }, - end: { - column: 8, - line: 1, - offset: 7 - } - } + }, }, modifiers: ['enter'], exp: { @@ -1589,14 +1661,14 @@ describe('compiler: parse', () => { loc: { start: { offset: 14, line: 1, column: 15 }, end: { offset: 15, line: 1, column: 16 }, - source: 'b' - } + source: 'b', + }, }, loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 15, line: 1, column: 16 }, - source: '@a.enter=b' - } + source: '@a.enter=b', + }, }) }) @@ -1607,24 +1679,17 @@ describe('compiler: parse', () => { expect(directive).toStrictEqual({ type: NodeTypes.DIRECTIVE, name: 'slot', + rawName: '#a', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, loc: { + start: { column: 8, line: 1, offset: 7 }, + end: { column: 9, line: 1, offset: 8 }, source: 'a', - start: { - column: 8, - line: 1, - offset: 7 - }, - end: { - column: 9, - line: 1, - offset: 8 - } - } + }, }, modifiers: [], exp: { @@ -1636,14 +1701,14 @@ describe('compiler: parse', () => { loc: { start: { offset: 10, line: 1, column: 11 }, end: { offset: 15, line: 1, column: 16 }, - source: '{ b }' - } + source: '{ b }', + }, }, loc: { start: { offset: 6, line: 1, column: 7 }, end: { offset: 16, line: 1, column: 17 }, - source: '#a="{ b }"' - } + source: '#a="{ b }"', + }, }) }) @@ -1655,32 +1720,32 @@ describe('compiler: parse', () => { expect(directive).toMatchObject({ type: NodeTypes.DIRECTIVE, name: 'slot', + rawName: 'v-slot:foo.bar', arg: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'foo.bar', isStatic: true, constType: ConstantTypes.CAN_STRINGIFY, loc: { - source: 'foo.bar', start: { column: 14, line: 1, - offset: 13 + offset: 13, }, end: { column: 21, line: 1, - offset: 20 - } - } - } + offset: 20, + }, + }, + }, }) }) test('v-pre', () => { const ast = baseParse( `
{{ bar }}
\n` + - `
{{ bar }}
` + `
{{ bar }}
`, ) const divWithPre = ast.children[0] as ElementNode @@ -1690,29 +1755,22 @@ describe('compiler: parse', () => { name: `:id`, value: { type: NodeTypes.TEXT, - content: `foo` + content: `foo`, }, loc: { - source: `:id="foo"`, - start: { - line: 1, - column: 12 - }, - end: { - line: 1, - column: 21 - } - } - } + start: { line: 1, column: 12 }, + end: { line: 1, column: 21 }, + }, + }, ]) expect(divWithPre.children[0]).toMatchObject({ type: NodeTypes.ELEMENT, tagType: ElementTypes.ELEMENT, - tag: `Comp` + tag: `Comp`, }) expect(divWithPre.children[1]).toMatchObject({ type: NodeTypes.TEXT, - content: `{{ bar }}` + content: `{{ bar }}`, }) // should not affect siblings after it @@ -1724,44 +1782,72 @@ describe('compiler: parse', () => { arg: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, - content: `id` + content: `id`, }, exp: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, - content: `foo` + content: `foo`, }, loc: { - source: `:id="foo"`, start: { line: 2, - column: 6 + column: 6, }, end: { line: 2, - column: 15 - } - } - } + column: 15, + }, + }, + }, ]) expect(divWithoutPre.children[0]).toMatchObject({ type: NodeTypes.ELEMENT, tagType: ElementTypes.COMPONENT, - tag: `Comp` + tag: `Comp`, }) expect(divWithoutPre.children[1]).toMatchObject({ type: NodeTypes.INTERPOLATION, content: { type: NodeTypes.SIMPLE_EXPRESSION, content: `bar`, - isStatic: false - } + isStatic: false, + }, }) }) + // https://github.com/vuejs/docs/issues/2586 + test('v-pre with half-open interpolation', () => { + const ast = baseParse( + `
+ {{ number + }} +
+ `, + ) + expect((ast.children[0] as ElementNode).children).toMatchObject([ + { + type: NodeTypes.ELEMENT, + children: [{ type: NodeTypes.TEXT, content: `{{ number ` }], + }, + { + type: NodeTypes.ELEMENT, + children: [{ type: NodeTypes.TEXT, content: `}}` }], + }, + ]) + + const ast2 = baseParse(`
{{ number
`) + expect((ast2.children[0] as ElementNode).children).toMatchObject([ + { + type: NodeTypes.ELEMENT, + children: [{ type: NodeTypes.TEXT, content: `{{ number ` }], + }, + ]) + }) + test('self-closing v-pre', () => { const ast = baseParse( - `
\n
{{ bar }}
` + `
\n
{{ bar }}
`, ) // should not affect siblings after it const divWithoutPre = ast.children[1] as ElementNode @@ -1772,38 +1858,37 @@ describe('compiler: parse', () => { arg: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, - content: `id` + content: `id`, }, exp: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, - content: `foo` + content: `foo`, }, loc: { - source: `:id="foo"`, start: { line: 2, - column: 6 + column: 6, }, end: { line: 2, - column: 15 - } - } - } + column: 15, + }, + }, + }, ]) expect(divWithoutPre.children[0]).toMatchObject({ type: NodeTypes.ELEMENT, tagType: ElementTypes.COMPONENT, - tag: `Comp` + tag: `Comp`, }) expect(divWithoutPre.children[1]).toMatchObject({ type: NodeTypes.INTERPOLATION, content: { type: NodeTypes.SIMPLE_EXPRESSION, content: `bar`, - isStatic: false - } + isStatic: false, + }, }) }) @@ -1818,133 +1903,177 @@ describe('compiler: parse', () => { loc: { start: { offset: 5, line: 1, column: 6 }, end: { offset: 10, line: 1, column: 11 }, - source: 'hello' - } + source: 'hello', + }, }) }) }) - test('self closing single tag', () => { - const ast = baseParse('
') + describe('Edge Cases', () => { + test('self closing single tag', () => { + const ast = baseParse('
') - expect(ast.children).toHaveLength(1) - expect(ast.children[0]).toMatchObject({ tag: 'div' }) - }) - - test('self closing multiple tag', () => { - const ast = baseParse( - `
\n` + - `

` - ) - - expect(ast).toMatchSnapshot() - - expect(ast.children).toHaveLength(2) - expect(ast.children[0]).toMatchObject({ tag: 'div' }) - expect(ast.children[1]).toMatchObject({ tag: 'p' }) - }) - - test('valid html', () => { - const ast = baseParse( - `

\n` + - `

\n` + - ` \n` + - `

` - ) - - expect(ast).toMatchSnapshot() - - expect(ast.children).toHaveLength(1) - const el = ast.children[0] as any - expect(el).toMatchObject({ - tag: 'div' - }) - expect(el.children).toHaveLength(2) - expect(el.children[0]).toMatchObject({ - tag: 'p' - }) - expect(el.children[1]).toMatchObject({ - type: NodeTypes.COMMENT - }) - }) - - test('invalid html', () => { - expect(() => { - baseParse(`
\n\n
\n`) - }).toThrow('Element is missing end tag.') - - const spy = vi.fn() - const ast = baseParse(`
\n\n
\n`, { - onError: spy + expect(ast.children).toHaveLength(1) + expect(ast.children[0]).toMatchObject({ tag: 'div' }) }) - expect(spy.mock.calls).toMatchObject([ - [ + test('self closing multiple tag', () => { + const ast = baseParse( + `
\n` + + `

`, + ) + + expect(ast).toMatchSnapshot() + + expect(ast.children).toHaveLength(2) + expect(ast.children[0]).toMatchObject({ tag: 'div' }) + expect(ast.children[1]).toMatchObject({ tag: 'p' }) + }) + + test('valid html', () => { + const ast = baseParse( + `

\n` + + `

\n` + + ` \n` + + `

`, + ) + + expect(ast).toMatchSnapshot() + + expect(ast.children).toHaveLength(1) + const el = ast.children[0] as any + expect(el).toMatchObject({ + tag: 'div', + }) + expect(el.children).toHaveLength(2) + expect(el.children[0]).toMatchObject({ + tag: 'p', + }) + expect(el.children[1]).toMatchObject({ + type: NodeTypes.COMMENT, + }) + }) + + test('invalid html', () => { + expect(() => { + baseParse(`
\n\n
\n`) + }).toThrow('Element is missing end tag.') + + const spy = vi.fn() + const ast = baseParse(`
\n\n
\n`, { + onError: spy, + }) + + expect(spy.mock.calls).toMatchObject([ + [ + { + code: ErrorCodes.X_MISSING_END_TAG, + loc: { + start: { + offset: 6, + line: 2, + column: 1, + }, + }, + }, + ], + [ + { + code: ErrorCodes.X_INVALID_END_TAG, + loc: { + start: { + offset: 20, + line: 4, + column: 1, + }, + }, + }, + ], + ]) + + expect(ast).toMatchSnapshot() + }) + + test('parse with correct location info', () => { + const fooSrc = `foo\n is ` + const barSrc = `{{ bar }}` + const butSrc = ` but ` + const bazSrc = `{{ baz }}` + const [foo, bar, but, baz] = baseParse( + fooSrc + barSrc + butSrc + bazSrc, + ).children + + let offset = 0 + expect(foo.loc.start).toEqual({ line: 1, column: 1, offset }) + offset += fooSrc.length + expect(foo.loc.end).toEqual({ line: 2, column: 5, offset }) + + expect(bar.loc.start).toEqual({ line: 2, column: 5, offset }) + const barInner = (bar as InterpolationNode).content + offset += 3 + expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset }) + offset += 3 + expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset }) + offset += 3 + expect(bar.loc.end).toEqual({ line: 2, column: 14, offset }) + + expect(but.loc.start).toEqual({ line: 2, column: 14, offset }) + offset += butSrc.length + expect(but.loc.end).toEqual({ line: 2, column: 19, offset }) + + expect(baz.loc.start).toEqual({ line: 2, column: 19, offset }) + const bazInner = (baz as InterpolationNode).content + offset += 3 + expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset }) + offset += 3 + expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset }) + offset += 3 + expect(baz.loc.end).toEqual({ line: 2, column: 28, offset }) + }) + + // With standard HTML parsing, the following input would ignore the slash + // and treat "<" and "template" as attributes on the open tag of "Hello", + // causing `