diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 67e33d50..5d50c034 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,7 @@ name: "\U0001F41E Bug report" description: Report an issue labels: [pending triage] +type: Bug body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 5b87a559..84f2586a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,7 @@ name: "\U0001F680 New feature proposal" description: Propose a new feature -labels: ["enhancement: pending triage"] +labels: ["pending triage"] +type: Feature body: - type: markdown attributes: diff --git a/.github/renovate.json5 b/.github/renovate.json5 index d1fc7842..db699039 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -18,6 +18,11 @@ groupName: "prettier", matchPackageNames: ["prettier"], }, + { + "matchDepTypes": ["action"], + "excludePackagePrefixes": ["actions/", "github/"], + "pinDigests": true, + }, ], "ignoreDeps": [ // manually bumping diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 943badcd..e0a47703 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,8 @@ env: # Vitest auto retry on flaky segfault VITEST_SEGFAULT_RETRY: 3 +permissions: {} + on: push: branches: @@ -35,13 +37,13 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node_version: [18, 20] + node_version: [20, 22, 24] include: # Active LTS + other OS - os: macos-latest - node_version: 20 + node_version: 22 - os: windows-latest - node_version: 20 + node_version: 22 fail-fast: false name: "Build&Test: node-${{ matrix.node_version }}, ${{ matrix.os }}" @@ -50,7 +52,7 @@ jobs: uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v4.0.0 + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Set node version to ${{ matrix.node_version }} uses: actions/setup-node@v4 @@ -101,7 +103,7 @@ jobs: fetch-depth: 0 - name: Install pnpm - uses: pnpm/action-setup@v4.0.0 + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Set node version to LTS uses: actions/setup-node@v4 diff --git a/.github/workflows/issue-close-require.yml b/.github/workflows/issue-close-require.yml index 97b5140e..bbbc111a 100644 --- a/.github/workflows/issue-close-require.yml +++ b/.github/workflows/issue-close-require.yml @@ -8,9 +8,12 @@ jobs: close-issues: if: github.repository == 'vitejs/vite-plugin-vue' runs-on: ubuntu-latest + permissions: + issues: write # for actions-cool/issues-helper to update issues + pull-requests: write # for actions-cool/issues-helper to update PRs steps: - name: need reproduction - uses: actions-cool/issues-helper@v3 + uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3 with: actions: "close-issues" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index fe56da7a..81d95f74 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -8,10 +8,13 @@ jobs: reply-labeled: if: github.repository == 'vitejs/vite-plugin-vue' runs-on: ubuntu-latest + permissions: + issues: write # for actions-cool/issues-helper to update issues + pull-requests: write # for actions-cool/issues-helper to update PRs steps: - name: contribution welcome if: github.event.label.name == 'contribution welcome' || github.event.label.name == 'help wanted' - uses: actions-cool/issues-helper@v3 + uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3 with: actions: "create-comment, remove-labels" token: ${{ secrets.GITHUB_TOKEN }} @@ -21,26 +24,17 @@ jobs: labels: "pending triage, need reproduction" - name: remove pending - if: contains(github.event.label.description, '(priority)') && contains(github.event.issue.labels.*.name, 'pending triage') - uses: actions-cool/issues-helper@v3 + if: (github.event.label.name == 'enhancement' || contains(github.event.label.description, '(priority)')) && contains(github.event.issue.labels.*.name, 'pending triage') + uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3 with: actions: "remove-labels" token: ${{ secrets.GITHUB_TOKEN }} issue-number: ${{ github.event.issue.number }} labels: "pending triage" - - name: remove enhancement pending - if: "(github.event.label.name == 'enhancement' || contains(github.event.label.description, '(priority)')) && contains(github.event.issue.labels.*.name, 'enhancement: pending triage')" - uses: actions-cool/issues-helper@v3 - with: - actions: "remove-labels" - token: ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.issue.number }} - labels: "enhancement: pending triage" - - name: need reproduction if: github.event.label.name == 'need reproduction' - uses: actions-cool/issues-helper@v3 + uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3 with: actions: "create-comment, remove-labels" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lock-closed-issues.yml b/.github/workflows/lock-closed-issues.yml index 8810afc4..f3417208 100644 --- a/.github/workflows/lock-closed-issues.yml +++ b/.github/workflows/lock-closed-issues.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'vitejs/vite-plugin-vue' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v5 + - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} issue-inactive-days: "14" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9e62a253..ddd7907c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v4.0.0 + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Set node version to LTS uses: actions/setup-node@v4 diff --git a/.github/workflows/release-continuous.yml b/.github/workflows/release-continuous.yml index 8c61fd63..98c75092 100644 --- a/.github/workflows/release-continuous.yml +++ b/.github/workflows/release-continuous.yml @@ -1,6 +1,8 @@ name: Publish Any Commit on: [push, pull_request] +permissions: {} + jobs: build: runs-on: ubuntu-latest @@ -9,7 +11,9 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - run: corepack enable + - name: Install pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/setup-node@v4 with: node-version: lts/* diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 93ee1b64..6b99afb4 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -13,6 +13,8 @@ jobs: release: if: github.repository == 'vitejs/vite-plugin-vue' runs-on: ubuntu-latest + permissions: + contents: write # for yyx990803/release-tag to create a release tag steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index 42632757..dc5253b4 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -12,9 +12,11 @@ jobs: if: github.repository == 'vitejs/vite-plugin-vue' runs-on: ubuntu-latest name: Semantic Pull Request + permissions: + pull-requests: read steps: - name: Validate PR title - uses: amannn/action-semantic-pull-request@v5 + uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5 with: subjectPattern: ^(?![A-Z]).+$ subjectPatternError: | diff --git a/eslint.config.js b/eslint.config.js index 03c92ef4..688137a1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,7 +4,8 @@ import eslint from '@eslint/js' import tseslint from 'typescript-eslint' import nodePlugin from 'eslint-plugin-n' import * as regexpPlugin from 'eslint-plugin-regexp' -import importPlugin from 'eslint-plugin-import-x' +import importPlugin, { createNodeResolver } from 'eslint-plugin-import-x' +import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript' export default tseslint.config( eslint.configs.recommended, @@ -16,6 +17,12 @@ export default tseslint.config( plugins: { import: importPlugin, }, + settings: { + 'import-x/resolver-next': [ + createNodeResolver(), + createTypeScriptImportResolver(), + ], + }, rules: { eqeqeq: ['warn', 'always', { null: 'never' }], 'no-empty': ['warn', { allowEmptyCatch: true }], @@ -94,12 +101,6 @@ export default tseslint.config( 'no-restricted-globals': ['error', 'require', '__dirname', '__filename'], }, }, - { - files: ['*.spec.ts'], - rules: { - 'n/no-extraneous-import': 'off', - }, - }, { files: ['**/build.config.ts'], rules: { @@ -119,13 +120,13 @@ export default tseslint.config( 'n/no-unsupported-features/es-builtins': [ 'error', { - version: '^18.0.0 || >=20.0.0', + version: '^20.19.0 || >=22.12.0', }, ], 'n/no-unsupported-features/node-builtins': [ 'error', { - version: '^18.0.0 || >=20.0.0', + version: '^20.19.0 || >=22.12.0', }, ], '@typescript-eslint/explicit-module-boundary-types': 'off', @@ -141,6 +142,20 @@ export default tseslint.config( '@typescript-eslint/no-empty-function': 'off', }, }, + { + name: 'tests', + files: ['**/__tests__/**/*'], + rules: { + 'n/no-extraneous-import': 'off', + 'n/no-unsupported-features/node-builtins': [ + 'error', + { + version: '^20.19.0 || >=22.12.0', + allowExperimental: true, + }, + ], + }, + }, { files: ['*.js', '*.mjs', '*.cjs'], rules: { diff --git a/package.json b/package.json index 2592b54a..a5106ad6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "homepage": "https://github.com/vitejs/vite-plugin-vue/", "repository": { @@ -24,7 +24,7 @@ "format": "prettier --write --cache .", "lint": "eslint --cache .", "typecheck": "tsc -p scripts --noEmit && tsc -p playground --noEmit", - "test": "run-s test-serve test-build", + "test": "pnpm test-serve && pnpm test-build", "test-serve": "vitest run -c vitest.config.e2e.ts", "test-build": "VITE_TEST_BUILD=1 vitest run -c vitest.config.e2e.ts", "test-build-without-plugin-commonjs": "VITE_TEST_WITHOUT_PLUGIN_COMMONJS=1 pnpm test-build", @@ -36,34 +36,34 @@ "ci-publish": "tsx scripts/publishCI.ts" }, "devDependencies": { - "@babel/types": "^7.26.0", - "@eslint/js": "^9.15.0", + "@babel/types": "^7.27.6", + "@eslint/js": "^9.28.0", "@types/babel__core": "^7.20.5", "@types/convert-source-map": "^2.0.3", "@types/debug": "^4.1.12", "@types/fs-extra": "^11.0.4", - "@types/node": "^22.9.0", - "@vitejs/release-scripts": "^1.3.2", + "@types/node": "^22.15.31", + "@vitejs/release-scripts": "^1.5.0", "conventional-changelog-cli": "^5.0.0", - "eslint": "^9.15.0", - "eslint-plugin-import-x": "^4.4.2", - "eslint-plugin-n": "^17.13.2", - "eslint-plugin-regexp": "^2.7.0", - "execa": "^9.5.1", - "fs-extra": "^11.2.0", - "lint-staged": "^15.2.10", - "npm-run-all2": "^7.0.1", + "eslint": "^9.28.0", + "eslint-import-resolver-typescript": "^4.4.3", + "eslint-plugin-import-x": "^4.15.1", + "eslint-plugin-n": "^17.19.0", + "eslint-plugin-regexp": "^2.9.0", + "execa": "^9.6.0", + "fs-extra": "^11.3.0", + "lint-staged": "^16.1.0", "picocolors": "^1.1.1", - "playwright-chromium": "^1.49.0", - "prettier": "3.3.3", - "rollup": "^4.27.2", - "simple-git-hooks": "^2.11.1", - "tsx": "^4.19.2", - "typescript": "^5.6.3", - "typescript-eslint": "^8.15.0", - "unbuild": "2.0.0", + "playwright-chromium": "^1.53.0", + "prettier": "3.5.3", + "rollup": "^4.43.0", + "simple-git-hooks": "^2.13.0", + "tsx": "^4.20.1", + "typescript": "^5.8.3", + "typescript-eslint": "^8.34.0", + "unbuild": "3.5.0", "vite": "catalog:", - "vitest": "^2.1.5", + "vitest": "^3.2.3", "vue": "catalog:" }, "simple-git-hooks": { @@ -83,10 +83,19 @@ "eslint --cache --fix" ] }, - "packageManager": "pnpm@9.13.2", + "packageManager": "pnpm@10.12.1", "pnpm": { "overrides": { "@vitejs/plugin-vue": "workspace:*" - } + }, + "ignoredBuiltDependencies": [ + "@parcel/watcher", + "core-js", + "esbuild" + ], + "onlyBuiltDependencies": [ + "playwright-chromium", + "simple-git-hooks" + ] } } diff --git a/packages/plugin-vue-jsx/CHANGELOG.md b/packages/plugin-vue-jsx/CHANGELOG.md index 775ff736..2b7b3e68 100644 --- a/packages/plugin-vue-jsx/CHANGELOG.md +++ b/packages/plugin-vue-jsx/CHANGELOG.md @@ -1,3 +1,33 @@ +## 5.0.0-beta.0 (2025-06-06) + +* refactor!: bump required node version to 20.19+, 22.12+ and drop CJS build (#596) ([56df545](https://github.com/vitejs/vite-plugin-vue/commit/56df545)), closes [#596](https://github.com/vitejs/vite-plugin-vue/issues/596) +* feat: add Vite 7 support (#597) ([12f2881](https://github.com/vitejs/vite-plugin-vue/commit/12f2881)), closes [#597](https://github.com/vitejs/vite-plugin-vue/issues/597) + + + +## 4.2.0 (2025-05-20) + +* feat(vue-jsx): add filter (#581) ([f66a009](https://github.com/vitejs/vite-plugin-vue/commit/f66a009)), closes [#581](https://github.com/vitejs/vite-plugin-vue/issues/581) +* fix(deps): update all non-major dependencies (#527) ([8495d12](https://github.com/vitejs/vite-plugin-vue/commit/8495d12)), closes [#527](https://github.com/vitejs/vite-plugin-vue/issues/527) +* fix(deps): update all non-major dependencies (#578) ([405647f](https://github.com/vitejs/vite-plugin-vue/commit/405647f)), closes [#578](https://github.com/vitejs/vite-plugin-vue/issues/578) + + + +## 4.1.2 (2025-03-17) + +* fix: properly interpret boolean values in `define` (#545) ([46d3d65](https://github.com/vitejs/vite-plugin-vue/commit/46d3d65)), closes [#545](https://github.com/vitejs/vite-plugin-vue/issues/545) +* fix(deps): update all non-major dependencies (#482) ([cdbae68](https://github.com/vitejs/vite-plugin-vue/commit/cdbae68)), closes [#482](https://github.com/vitejs/vite-plugin-vue/issues/482) +* fix(deps): update all non-major dependencies (#502) ([5bfbbc6](https://github.com/vitejs/vite-plugin-vue/commit/5bfbbc6)), closes [#502](https://github.com/vitejs/vite-plugin-vue/issues/502) +* fix(deps): update all non-major dependencies (#510) ([28bca4b](https://github.com/vitejs/vite-plugin-vue/commit/28bca4b)), closes [#510](https://github.com/vitejs/vite-plugin-vue/issues/510) + + + +## 4.1.1 (2024-11-26) + +* chore: add vite 6 peer dep (#481) ([4288652](https://github.com/vitejs/vite-plugin-vue/commit/4288652)), closes [#481](https://github.com/vitejs/vite-plugin-vue/issues/481) + + + ## 4.1.0 (2024-11-11) * feat: support tsPluginOptions (#445) ([fdb3590](https://github.com/vitejs/vite-plugin-vue/commit/fdb3590)), closes [#445](https://github.com/vitejs/vite-plugin-vue/issues/445) diff --git a/packages/plugin-vue-jsx/build.config.ts b/packages/plugin-vue-jsx/build.config.ts index 360c2e7c..a74f5d85 100644 --- a/packages/plugin-vue-jsx/build.config.ts +++ b/packages/plugin-vue-jsx/build.config.ts @@ -4,7 +4,4 @@ export default defineBuildConfig({ entries: ['src/index'], clean: true, declaration: true, - rollup: { - emitCJS: true, - }, }) diff --git a/packages/plugin-vue-jsx/package.json b/packages/plugin-vue-jsx/package.json index 43db17a2..818c534b 100644 --- a/packages/plugin-vue-jsx/package.json +++ b/packages/plugin-vue-jsx/package.json @@ -1,29 +1,23 @@ { "name": "@vitejs/plugin-vue-jsx", - "version": "4.1.0", - "type": "commonjs", + "version": "5.0.0-beta.0", + "type": "module", "license": "MIT", "author": "Evan You", "files": [ "dist" ], - "main": "./dist/index.cjs", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", "exports": { - ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs" - } + ".": "./dist/index.mjs", + "./package.json": "./package.json" }, "scripts": { "dev": "unbuild --stub", - "build": "unbuild && pnpm run patch-cjs", - "patch-cjs": "tsx ../../scripts/patchCJS.ts", + "build": "unbuild", "prepublishOnly": "npm run build" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "repository": { "type": "git", @@ -35,15 +29,16 @@ }, "homepage": "https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue-jsx#readme", "dependencies": { - "@babel/core": "^7.26.0", - "@babel/plugin-transform-typescript": "^7.25.9", - "@vue/babel-plugin-jsx": "^1.2.5" + "@babel/core": "^7.27.4", + "@babel/plugin-transform-typescript": "^7.27.1", + "@rolldown/pluginutils": "^1.0.0-beta.15", + "@vue/babel-plugin-jsx": "^1.4.0" }, "devDependencies": { "vite": "catalog:" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vue": "^3.0.0" } } diff --git a/packages/plugin-vue-jsx/src/index.ts b/packages/plugin-vue-jsx/src/index.ts index d291472c..2bfb06fb 100644 --- a/packages/plugin-vue-jsx/src/index.ts +++ b/packages/plugin-vue-jsx/src/index.ts @@ -6,9 +6,13 @@ import jsx from '@vue/babel-plugin-jsx' import { createFilter, normalizePath } from 'vite' import type { ComponentOptions } from 'vue' import type { Plugin } from 'vite' +import { + exactRegex, + makeIdFiltersToMatchWithQuery, +} from '@rolldown/pluginutils' import type { Options } from './types' -export * from './types' +export type * from './types' const ssrRegisterHelperId = '/__vue-jsx-ssr-register-helper' const ssrRegisterHelperCode = @@ -40,19 +44,26 @@ function vueJsxPlugin(options: Options = {}): Plugin { let needSourceMap = true const { - include, + include = /\.[jt]sx$/, exclude, babelPlugins = [], defineComponentName = ['defineComponent'], tsPluginOptions = {}, ...babelPluginOptions } = options - const filter = createFilter(include || /\.[jt]sx$/, exclude) + const filter = createFilter(include, exclude) return { name: 'vite:vue-jsx', config(config) { + const parseDefine = (v: unknown) => { + try { + return typeof v === 'string' ? JSON.parse(v) : v + } catch (err) { + return v + } + } return { // only apply esbuild to ts files // since we are handling jsx and tsx now @@ -60,10 +71,14 @@ function vueJsxPlugin(options: Options = {}): Plugin { include: /\.ts$/, }, define: { - __VUE_OPTIONS_API__: config.define?.__VUE_OPTIONS_API__ ?? true, - __VUE_PROD_DEVTOOLS__: config.define?.__VUE_PROD_DEVTOOLS__ ?? false, + __VUE_OPTIONS_API__: + parseDefine(config.define?.__VUE_OPTIONS_API__) ?? true, + __VUE_PROD_DEVTOOLS__: + parseDefine(config.define?.__VUE_PROD_DEVTOOLS__) ?? false, __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: - config.define?.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ ?? false, + parseDefine( + config.define?.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__, + ) ?? false, }, } }, @@ -74,197 +89,213 @@ function vueJsxPlugin(options: Options = {}): Plugin { root = config.root }, - resolveId(id) { - if (id === ssrRegisterHelperId) { - return id - } + resolveId: { + filter: { id: exactRegex(ssrRegisterHelperId) }, + handler(id) { + if (id === ssrRegisterHelperId) { + return id + } + }, }, - load(id) { - if (id === ssrRegisterHelperId) { - return ssrRegisterHelperCode - } + load: { + filter: { id: exactRegex(ssrRegisterHelperId) }, + handler(id) { + if (id === ssrRegisterHelperId) { + return ssrRegisterHelperCode + } + }, }, - async transform(code, id, opt) { - const ssr = opt?.ssr === true - const [filepath] = id.split('?') + transform: { + filter: { + id: { + include: include ? makeIdFiltersToMatchWithQuery(include) : undefined, + exclude: exclude ? makeIdFiltersToMatchWithQuery(exclude) : undefined, + }, + }, + async handler(code, id, opt) { + const ssr = opt?.ssr === true + const [filepath] = id.split('?') - // use id for script blocks in Vue SFCs (e.g. `App.vue?vue&type=script&lang.jsx`) - // use filepath for plain jsx files (e.g. App.jsx) - if (filter(id) || filter(filepath)) { - const plugins = [[jsx, babelPluginOptions], ...babelPlugins] - if (id.endsWith('.tsx') || filepath.endsWith('.tsx')) { - plugins.push([ - // @ts-ignore missing type - await import('@babel/plugin-transform-typescript').then( - (r) => r.default, - ), - // @ts-ignore - { ...tsPluginOptions, isTSX: true, allowExtensions: true }, - ]) - } + // use id for script blocks in Vue SFCs (e.g. `App.vue?vue&type=script&lang.jsx`) + // use filepath for plain jsx files (e.g. App.jsx) + if (filter(id) || filter(filepath)) { + const plugins = [[jsx, babelPluginOptions], ...babelPlugins] + if (id.endsWith('.tsx') || filepath.endsWith('.tsx')) { + plugins.push([ + // @ts-ignore missing type + await import('@babel/plugin-transform-typescript').then( + (r) => r.default, + ), + // @ts-ignore + { ...tsPluginOptions, isTSX: true, allowExtensions: true }, + ]) + } - if (!ssr && !needHmr) { - plugins.push(() => { - return { - visitor: { - CallExpression: { - enter(_path: babel.NodePath) { - if ( - isDefineComponentCall(_path.node, defineComponentName) - ) { - const callee = _path.node.callee as types.Identifier - callee.name = `/* @__PURE__ */ ${callee.name}` - } + if (!ssr && !needHmr) { + plugins.push(() => { + return { + visitor: { + CallExpression: { + enter(_path: babel.NodePath) { + if ( + isDefineComponentCall(_path.node, defineComponentName) + ) { + const callee = _path.node.callee as types.Identifier + callee.name = `/* @__PURE__ */ ${callee.name}` + } + }, }, }, - }, - } - }) - } + } + }) + } - const result = babel.transformSync(code, { - babelrc: false, - ast: true, - plugins, - sourceMaps: needSourceMap, - sourceFileName: id, - configFile: false, - })! + const result = babel.transformSync(code, { + babelrc: false, + ast: true, + plugins, + sourceMaps: needSourceMap, + sourceFileName: id, + configFile: false, + })! - if (!ssr && !needHmr) { - if (!result.code) return - return { - code: result.code, - map: result.map, + if (!ssr && !needHmr) { + if (!result.code) return + return { + code: result.code, + map: result.map, + } } - } - interface HotComponent { - local: string - exported: string - id: string - } + interface HotComponent { + local: string + exported: string + id: string + } - // check for hmr injection - const declaredComponents: string[] = [] - const hotComponents: HotComponent[] = [] - let hasDefault = false + // check for hmr injection + const declaredComponents: string[] = [] + const hotComponents: HotComponent[] = [] + let hasDefault = false - for (const node of result.ast!.program.body) { - if (node.type === 'VariableDeclaration') { - const names = parseComponentDecls(node, defineComponentName) - if (names.length) { - declaredComponents.push(...names) + for (const node of result.ast!.program.body) { + if (node.type === 'VariableDeclaration') { + const names = parseComponentDecls(node, defineComponentName) + if (names.length) { + declaredComponents.push(...names) + } } - } - if (node.type === 'ExportNamedDeclaration') { - if ( - node.declaration && - node.declaration.type === 'VariableDeclaration' - ) { - hotComponents.push( - ...parseComponentDecls( - node.declaration, - defineComponentName, - ).map((name) => ({ - local: name, - exported: name, - id: getHash(id + name), - })), - ) - } else if (node.specifiers.length) { - for (const spec of node.specifiers) { - if ( - spec.type === 'ExportSpecifier' && - spec.exported.type === 'Identifier' - ) { - const matched = declaredComponents.find( - (name) => name === spec.local.name, - ) - if (matched) { - hotComponents.push({ - local: spec.local.name, - exported: spec.exported.name, - id: getHash(id + spec.exported.name), - }) + if (node.type === 'ExportNamedDeclaration') { + if ( + node.declaration && + node.declaration.type === 'VariableDeclaration' + ) { + hotComponents.push( + ...parseComponentDecls( + node.declaration, + defineComponentName, + ).map((name) => ({ + local: name, + exported: name, + id: getHash(id + name), + })), + ) + } else if (node.specifiers.length) { + for (const spec of node.specifiers) { + if ( + spec.type === 'ExportSpecifier' && + spec.exported.type === 'Identifier' + ) { + const matched = declaredComponents.find( + (name) => name === spec.local.name, + ) + if (matched) { + hotComponents.push({ + local: spec.local.name, + exported: spec.exported.name, + id: getHash(id + spec.exported.name), + }) + } } } } } - } - if (node.type === 'ExportDefaultDeclaration') { - if (node.declaration.type === 'Identifier') { - const _name = node.declaration.name - const matched = declaredComponents.find((name) => name === _name) - if (matched) { + if (node.type === 'ExportDefaultDeclaration') { + if (node.declaration.type === 'Identifier') { + const _name = node.declaration.name + const matched = declaredComponents.find( + (name) => name === _name, + ) + if (matched) { + hotComponents.push({ + local: _name, + exported: 'default', + id: getHash(id + 'default'), + }) + } + } else if ( + isDefineComponentCall(node.declaration, defineComponentName) + ) { + hasDefault = true hotComponents.push({ - local: _name, + local: '__default__', exported: 'default', id: getHash(id + 'default'), }) } - } else if ( - isDefineComponentCall(node.declaration, defineComponentName) - ) { - hasDefault = true - hotComponents.push({ - local: '__default__', - exported: 'default', - id: getHash(id + 'default'), - }) } } - } - - if (hotComponents.length) { - if (hasDefault && (needHmr || ssr)) { - result.code = - result.code!.replace( - /export default defineComponent/g, - `const __default__ = defineComponent`, - ) + `\nexport default __default__` - } - if (needHmr && !ssr && !/\?vue&type=script/.test(id)) { - let code = result.code - let callbackCode = `` - for (const { local, exported, id } of hotComponents) { - code += - `\n${local}.__hmrId = "${id}"` + - `\n__VUE_HMR_RUNTIME__.createRecord("${id}", ${local})` - callbackCode += `\n__VUE_HMR_RUNTIME__.reload("${id}", __${exported})` + if (hotComponents.length) { + if (hasDefault && (needHmr || ssr)) { + result.code = + result.code!.replace( + /export default defineComponent/g, + `const __default__ = defineComponent`, + ) + `\nexport default __default__` } - const newCompNames = hotComponents - .map((c) => `${c.exported}: __${c.exported}`) - .join(',') + if (needHmr && !ssr && !/\?vue&type=script/.test(id)) { + let code = result.code + let callbackCode = `` + for (const { local, exported, id } of hotComponents) { + code += + `\n${local}.__hmrId = "${id}"` + + `\n__VUE_HMR_RUNTIME__.createRecord("${id}", ${local})` + callbackCode += `\n__VUE_HMR_RUNTIME__.reload("${id}", __${exported})` + } - code += `\nimport.meta.hot.accept(({${newCompNames}}) => {${callbackCode}\n})` - result.code = code - } + const newCompNames = hotComponents + .map((c) => `${c.exported}: __${c.exported}`) + .join(',') - if (ssr) { - const normalizedId = normalizePath(path.relative(root, id)) - let ssrInjectCode = - `\nimport { ssrRegisterHelper } from "${ssrRegisterHelperId}"` + - `\nconst __moduleId = ${JSON.stringify(normalizedId)}` - for (const { local } of hotComponents) { - ssrInjectCode += `\nssrRegisterHelper(${local}, __moduleId)` + code += `\nimport.meta.hot.accept(({${newCompNames}}) => {${callbackCode}\n})` + result.code = code + } + + if (ssr) { + const normalizedId = normalizePath(path.relative(root, id)) + let ssrInjectCode = + `\nimport { ssrRegisterHelper } from "${ssrRegisterHelperId}"` + + `\nconst __moduleId = ${JSON.stringify(normalizedId)}` + for (const { local } of hotComponents) { + ssrInjectCode += `\nssrRegisterHelper(${local}, __moduleId)` + } + result.code += ssrInjectCode } - result.code += ssrInjectCode } - } - if (!result.code) return - return { - code: result.code, - map: result.map, + if (!result.code) return + return { + code: result.code, + map: result.map, + } } - } + }, }, } } @@ -297,17 +328,17 @@ function isDefineComponentCall( ) } -const hash = - // eslint-disable-next-line n/no-unsupported-features/node-builtins -- crypto.hash is supported in Node 21.7.0+, 20.12.0+ - crypto.hash ?? - (( - algorithm: string, - data: crypto.BinaryLike, - outputEncoding: crypto.BinaryToTextEncoding, - ) => crypto.createHash(algorithm).update(data).digest(outputEncoding)) - function getHash(text: string) { - return hash('sha256', text, 'hex').substring(0, 8) + return crypto.hash('sha256', text, 'hex').substring(0, 8) } export default vueJsxPlugin + +// Compat for require +function vueJsxPluginCjs(this: unknown, options: Options): Plugin { + return vueJsxPlugin.call(this, options) +} +Object.assign(vueJsxPluginCjs, { + default: vueJsxPluginCjs, +}) +export { vueJsxPluginCjs as 'module.exports' } diff --git a/packages/plugin-vue-jsx/tsconfig.json b/packages/plugin-vue-jsx/tsconfig.json index 3a3117f2..4f4926b9 100644 --- a/packages/plugin-vue-jsx/tsconfig.json +++ b/packages/plugin-vue-jsx/tsconfig.json @@ -3,9 +3,9 @@ "exclude": ["**/*.spec.ts"], "compilerOptions": { "outDir": "dist", - "target": "ES2020", - "module": "ES2020", - "moduleResolution": "Node", + "target": "es2023", + "module": "preserve", + "moduleResolution": "bundler", "strict": true, "declaration": true, "sourceMap": true, diff --git a/packages/plugin-vue/CHANGELOG.md b/packages/plugin-vue/CHANGELOG.md index fb293142..54ea8a58 100644 --- a/packages/plugin-vue/CHANGELOG.md +++ b/packages/plugin-vue/CHANGELOG.md @@ -1,3 +1,67 @@ +## 6.0.0-beta.2 (2025-06-06) + +* refactor!: bump required node version to 20.19+, 22.12+ and drop CJS build (#596) ([56df545](https://github.com/vitejs/vite-plugin-vue/commit/56df545)), closes [#596](https://github.com/vitejs/vite-plugin-vue/issues/596) +* feat: add Vite 7 support (#597) ([12f2881](https://github.com/vitejs/vite-plugin-vue/commit/12f2881)), closes [#597](https://github.com/vitejs/vite-plugin-vue/issues/597) +* fix: template src sourcemap source (#267) ([de18693](https://github.com/vitejs/vite-plugin-vue/commit/de18693)), closes [#267](https://github.com/vitejs/vite-plugin-vue/issues/267) + + + +## 6.0.0-beta.1 (2025-06-02) + +* fix(vue): import with query (#592) ([b0400f3](https://github.com/vitejs/vite-plugin-vue/commit/b0400f3)), closes [#592](https://github.com/vitejs/vite-plugin-vue/issues/592) + + + +## 6.0.0-beta.0 (2025-05-21) + +* feat(vue)!: separate include and exclude from `api.options` and add filter (#582) ([e3beac8](https://github.com/vitejs/vite-plugin-vue/commit/e3beac8)), closes [#582](https://github.com/vitejs/vite-plugin-vue/issues/582) +* fix(deps): update all non-major dependencies (#587) ([d5ea412](https://github.com/vitejs/vite-plugin-vue/commit/d5ea412)), closes [#587](https://github.com/vitejs/vite-plugin-vue/issues/587) + + + +## 5.2.4 (2025-05-09) + +* chore: fix types with Vite 6.3 (#559) ([8002511](https://github.com/vitejs/vite-plugin-vue/commit/8002511)), closes [#559](https://github.com/vitejs/vite-plugin-vue/issues/559) +* chore: use rollup types exposed from Vite (#583) ([2e1287f](https://github.com/vitejs/vite-plugin-vue/commit/2e1287f)), closes [#583](https://github.com/vitejs/vite-plugin-vue/issues/583) +* chore(deps): update upstream (#542) ([ef446fc](https://github.com/vitejs/vite-plugin-vue/commit/ef446fc)), closes [#542](https://github.com/vitejs/vite-plugin-vue/issues/542) +* chore(deps): update upstream (#569) ([98381b2](https://github.com/vitejs/vite-plugin-vue/commit/98381b2)), closes [#569](https://github.com/vitejs/vite-plugin-vue/issues/569) +* feat(plugin-vue): use `transformWithOxc` if `rolldown-vite` is detected (#584) ([6ac8e3a](https://github.com/vitejs/vite-plugin-vue/commit/6ac8e3a)), closes [#584](https://github.com/vitejs/vite-plugin-vue/issues/584) +* fix(plugin-vue): handle sourcemap with empty script code (#585) ([7f73970](https://github.com/vitejs/vite-plugin-vue/commit/7f73970)), closes [#585](https://github.com/vitejs/vite-plugin-vue/issues/585) +* fix(plugin-vue): when the resource path contains chinese characters, dev/build is inconsistent (#550 ([5f6affe](https://github.com/vitejs/vite-plugin-vue/commit/5f6affe)), closes [#550](https://github.com/vitejs/vite-plugin-vue/issues/550) + + + +## 5.2.3 (2025-03-17) + +* Revert "fix: generate unique component id" (#548) ([4bc5517](https://github.com/vitejs/vite-plugin-vue/commit/4bc5517)), closes [#548](https://github.com/vitejs/vite-plugin-vue/issues/548) + + + +## 5.2.2 (2025-03-17) + +* feat: pass descriptor vapor flag to compileTemplte ([219e007](https://github.com/vitejs/vite-plugin-vue/commit/219e007)) +* feat(css): tree shake scoped styles (#533) ([333094f](https://github.com/vitejs/vite-plugin-vue/commit/333094f)), closes [#533](https://github.com/vitejs/vite-plugin-vue/issues/533) +* fix: generate unique component id (#538) ([2704e85](https://github.com/vitejs/vite-plugin-vue/commit/2704e85)), closes [#538](https://github.com/vitejs/vite-plugin-vue/issues/538) +* fix: properly interpret boolean values in `define` (#545) ([46d3d65](https://github.com/vitejs/vite-plugin-vue/commit/46d3d65)), closes [#545](https://github.com/vitejs/vite-plugin-vue/issues/545) +* fix(deps): update all non-major dependencies (#482) ([cdbae68](https://github.com/vitejs/vite-plugin-vue/commit/cdbae68)), closes [#482](https://github.com/vitejs/vite-plugin-vue/issues/482) +* fix(deps): update all non-major dependencies (#488) ([5d39582](https://github.com/vitejs/vite-plugin-vue/commit/5d39582)), closes [#488](https://github.com/vitejs/vite-plugin-vue/issues/488) +* fix(index): move the if check earlier to avoid creating unnecessary ssr when entering return block ( ([2135c84](https://github.com/vitejs/vite-plugin-vue/commit/2135c84)), closes [#523](https://github.com/vitejs/vite-plugin-vue/issues/523) +* fix(plugin-vue): default value for compile time flags (#495) ([ae9d948](https://github.com/vitejs/vite-plugin-vue/commit/ae9d948)), closes [#495](https://github.com/vitejs/vite-plugin-vue/issues/495) +* fix(plugin-vue): ensure HMR updates styles when SFC is treated as a type dependency (#541) ([4abe3be](https://github.com/vitejs/vite-plugin-vue/commit/4abe3be)), closes [#541](https://github.com/vitejs/vite-plugin-vue/issues/541) +* fix(plugin-vue): resolve sourcemap conflicts in build watch mode with cached modules (#505) ([906cebb](https://github.com/vitejs/vite-plugin-vue/commit/906cebb)), closes [#505](https://github.com/vitejs/vite-plugin-vue/issues/505) +* fix(plugin-vue): support external import URLs for monorepos (#524) ([cdd4922](https://github.com/vitejs/vite-plugin-vue/commit/cdd4922)), closes [#524](https://github.com/vitejs/vite-plugin-vue/issues/524) +* fix(plugin-vue): support vapor template-only component (#529) ([95be153](https://github.com/vitejs/vite-plugin-vue/commit/95be153)), closes [#529](https://github.com/vitejs/vite-plugin-vue/issues/529) +* fix(plugin-vue): suppress warnings for non-recognized pseudo selectors form lightningcss (#521) ([15c0eb0](https://github.com/vitejs/vite-plugin-vue/commit/15c0eb0)), closes [#521](https://github.com/vitejs/vite-plugin-vue/issues/521) +* chore(deps): update dependency rollup to ^4.27.4 (#479) ([428320d](https://github.com/vitejs/vite-plugin-vue/commit/428320d)), closes [#479](https://github.com/vitejs/vite-plugin-vue/issues/479) +* chore(deps): update dependency rollup to ^4.28.1 (#484) ([388403f](https://github.com/vitejs/vite-plugin-vue/commit/388403f)), closes [#484](https://github.com/vitejs/vite-plugin-vue/issues/484) +* chore(deps): update dependency rollup to ^4.29.1 (#493) ([b092bc8](https://github.com/vitejs/vite-plugin-vue/commit/b092bc8)), closes [#493](https://github.com/vitejs/vite-plugin-vue/issues/493) +* chore(deps): update upstream (#503) ([8c12b9f](https://github.com/vitejs/vite-plugin-vue/commit/8c12b9f)), closes [#503](https://github.com/vitejs/vite-plugin-vue/issues/503) +* chore(deps): update upstream (#511) ([d057351](https://github.com/vitejs/vite-plugin-vue/commit/d057351)), closes [#511](https://github.com/vitejs/vite-plugin-vue/issues/511) +* chore(deps): update upstream (#526) ([59946d3](https://github.com/vitejs/vite-plugin-vue/commit/59946d3)), closes [#526](https://github.com/vitejs/vite-plugin-vue/issues/526) +* chore(plugin-vue): simplify `resolved` declaration ([7288a59](https://github.com/vitejs/vite-plugin-vue/commit/7288a59)) + + + ## 5.2.1 (2024-11-26) * chore: add vite 6 peer dep (#481) ([4288652](https://github.com/vitejs/vite-plugin-vue/commit/4288652)), closes [#481](https://github.com/vitejs/vite-plugin-vue/issues/481) diff --git a/packages/plugin-vue/build.config.ts b/packages/plugin-vue/build.config.ts index 33fef7f6..12d46d0e 100644 --- a/packages/plugin-vue/build.config.ts +++ b/packages/plugin-vue/build.config.ts @@ -6,7 +6,6 @@ export default defineBuildConfig({ clean: true, declaration: 'compatible', rollup: { - emitCJS: true, inlineDependencies: true, }, }) diff --git a/packages/plugin-vue/package.json b/packages/plugin-vue/package.json index 2264ad55..bb64b2f8 100644 --- a/packages/plugin-vue/package.json +++ b/packages/plugin-vue/package.json @@ -1,29 +1,23 @@ { "name": "@vitejs/plugin-vue", - "version": "5.2.1", - "type": "commonjs", + "version": "6.0.0-beta.2", + "type": "module", "license": "MIT", "author": "Evan You", "files": [ "dist" ], - "main": "./dist/index.cjs", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", "exports": { - ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs" - } + ".": "./dist/index.mjs", + "./package.json": "./package.json" }, "scripts": { "dev": "unbuild --stub", - "build": "unbuild && pnpm run patch-cjs", - "patch-cjs": "tsx ../../scripts/patchCJS.ts", + "build": "unbuild", "prepublishOnly": "npm run build" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "repository": { "type": "git", @@ -35,17 +29,20 @@ }, "homepage": "https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue#readme", "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vue": "^3.2.25" }, "devDependencies": { - "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/gen-mapping": "^0.3.8", "@jridgewell/trace-mapping": "^0.3.25", - "debug": "^4.3.7", - "rollup": "^4.27.2", + "debug": "^4.4.1", + "rollup": "^4.43.0", "slash": "^5.1.0", "source-map-js": "^1.2.1", "vite": "catalog:", "vue": "catalog:" + }, + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.15" } } diff --git a/packages/plugin-vue/src/handleHotUpdate.ts b/packages/plugin-vue/src/handleHotUpdate.ts index c831785f..a5781a3e 100644 --- a/packages/plugin-vue/src/handleHotUpdate.ts +++ b/packages/plugin-vue/src/handleHotUpdate.ts @@ -31,6 +31,7 @@ export async function handleHotUpdate( { file, modules, read }: HmrContext, options: ResolvedOptions, customElement: boolean, + typeDepModules?: ModuleNode[], ): Promise { const prevDescriptor = getDescriptor(file, options, false, true) if (!prevDescriptor) { @@ -172,7 +173,9 @@ export async function handleHotUpdate( } debug(`[vue:update(${updateType.join('&')})] ${file}`) } - return [...affectedModules].filter(Boolean) as ModuleNode[] + return [...affectedModules, ...(typeDepModules || [])].filter( + Boolean, + ) as ModuleNode[] } export function isEqualBlock(a: SFCBlock | null, b: SFCBlock | null): boolean { diff --git a/packages/plugin-vue/src/index.ts b/packages/plugin-vue/src/index.ts index 183d3ff6..c1af1840 100644 --- a/packages/plugin-vue/src/index.ts +++ b/packages/plugin-vue/src/index.ts @@ -1,5 +1,5 @@ import fs from 'node:fs' -import type { Plugin, ViteDevServer } from 'vite' +import type { ModuleNode, Plugin, ViteDevServer } from 'vite' import { createFilter, normalizePath } from 'vite' import type { SFCBlock, @@ -9,10 +9,15 @@ import type { } from 'vue/compiler-sfc' import type * as _compiler from 'vue/compiler-sfc' import { computed, shallowRef } from 'vue' +import { + exactRegex, + makeIdFiltersToMatchWithQuery, +} from '@rolldown/pluginutils' import { version } from '../package.json' import { resolveCompiler } from './compiler' import { parseVueRequest } from './utils/query' import { + type ExtendedSFCDescriptor, getDescriptor, getSrcDescriptor, getTempSrcDescriptor, @@ -161,7 +166,7 @@ export interface Options { customElement?: boolean | string | RegExp | (string | RegExp)[] } -export interface ResolvedOptions extends Options { +export interface ResolvedOptions extends Omit { compiler: typeof _compiler root: string sourceMap: boolean @@ -173,6 +178,14 @@ export interface ResolvedOptions extends Options { export interface Api { get options(): ResolvedOptions set options(value: ResolvedOptions) + + get include(): string | RegExp | (string | RegExp)[] | undefined + /** include cannot be updated after `options` hook is called */ + set include(value: string | RegExp | (string | RegExp)[] | undefined) + get exclude(): string | RegExp | (string | RegExp)[] | undefined + /** exclude cannot be updated after `options` hook is called */ + set exclude(value: string | RegExp | (string | RegExp)[] | undefined) + version: string } @@ -182,17 +195,19 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { const options = shallowRef({ isProduction: process.env.NODE_ENV === 'production', compiler: null as any, // to be set in buildStart - include: /\.vue$/, customElement: /\.ce\.vue$/, ...rawOptions, root: process.cwd(), sourceMap: true, cssDevSourcemap: false, }) - - const filter = computed(() => - createFilter(options.value.include, options.value.exclude), + const include = shallowRef>( + rawOptions.include ?? /\.vue$/, ) + const exclude = shallowRef(rawOptions.exclude) + let optionsHookIsCalled = false + + const filter = computed(() => createFilter(include.value, exclude.value)) const customElementFilter = computed(() => { const customElement = options.value.features?.customElement || options.value.customElement @@ -201,7 +216,9 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { : createFilter(customElement) }) - return { + let transformCachedModule = false + + const plugin: Plugin = { name: 'vite:vue', api: { @@ -211,6 +228,28 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { set options(value) { options.value = value }, + get include() { + return include.value + }, + set include(value) { + if (optionsHookIsCalled) { + throw new Error( + 'include cannot be updated after `options` hook is called', + ) + } + include.value = value + }, + get exclude() { + return exclude.value + }, + set exclude(value) { + if (optionsHookIsCalled) { + throw new Error( + 'exclude cannot be updated after `options` hook is called', + ) + } + exclude.value = value + }, version, }, @@ -224,36 +263,53 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { if (options.value.compiler.invalidateTypeCache) { options.value.compiler.invalidateTypeCache(ctx.file) } + + let typeDepModules: ModuleNode[] | undefined + const matchesFilter = filter.value(ctx.file) if (typeDepToSFCMap.has(ctx.file)) { - return handleTypeDepChange(typeDepToSFCMap.get(ctx.file)!, ctx) + typeDepModules = handleTypeDepChange( + typeDepToSFCMap.get(ctx.file)!, + ctx, + ) + if (!matchesFilter) return typeDepModules } - if (filter.value(ctx.file)) { + if (matchesFilter) { return handleHotUpdate( ctx, options.value, customElementFilter.value(ctx.file), + typeDepModules, ) } }, config(config) { + const parseDefine = (v: unknown) => { + try { + return typeof v === 'string' ? JSON.parse(v) : v + } catch (err) { + return v + } + } return { resolve: { dedupe: config.build?.ssr ? [] : ['vue'], }, define: { - __VUE_OPTIONS_API__: !!( - (options.value.features?.optionsAPI ?? true) || - config.define?.__VUE_OPTIONS_API__ - ), - __VUE_PROD_DEVTOOLS__: !!( - options.value.features?.prodDevtools || - config.define?.__VUE_PROD_DEVTOOLS__ - ), - __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: !!( - options.value.features?.prodHydrationMismatchDetails || - config.define?.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ - ), + __VUE_OPTIONS_API__: + options.value.features?.optionsAPI ?? + parseDefine(config.define?.__VUE_OPTIONS_API__) ?? + true, + __VUE_PROD_DEVTOOLS__: + (options.value.features?.prodDevtools || + parseDefine(config.define?.__VUE_PROD_DEVTOOLS__)) ?? + false, + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: + (options.value.features?.prodHydrationMismatchDetails || + parseDefine( + config.define?.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__, + )) ?? + false, }, ssr: { // @ts-ignore -- config.legacy.buildSsrCjsExternalHeuristics will be removed in Vite 5 @@ -277,6 +333,48 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { !config.isProduction ), } + // #507 suppress warnings for non-recognized pseudo selectors from lightningcss + const _warn = config.logger.warn + config.logger.warn = (...args) => { + const msg = args[0] + if ( + msg.match( + /\[lightningcss\] '(deep|slotted|global)' is not recognized as a valid pseudo-/, + ) + ) { + return + } + _warn(...args) + } + + transformCachedModule = + config.command === 'build' && + options.value.sourceMap && + config.build.watch != null + }, + + options() { + type TransformObjectHook = Extract< + typeof plugin.transform, + { filter?: unknown } + > + optionsHookIsCalled = true + ;(plugin.transform as TransformObjectHook).filter = { + id: { + include: [ + ...makeIdFiltersToMatchWithQuery(ensureArray(include.value)), + /[?&]vue\b/, + ], + exclude: exclude.value, + }, + } + }, + + shouldTransformCachedModule({ id }) { + if (transformCachedModule && parseVueRequest(id).query.vue) { + return true + } + return false }, configureServer(server) { @@ -293,105 +391,139 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { } }, - async resolveId(id) { - // component export helper - if (id === EXPORT_HELPER_ID) { - return id - } - // serve sub-part requests (*?vue) as virtual modules - if (parseVueRequest(id).query.vue) { - return id - } + resolveId: { + filter: { + id: [exactRegex(EXPORT_HELPER_ID), /[?&]vue\b/], + }, + handler(id) { + // component export helper + if (id === EXPORT_HELPER_ID) { + return id + } + // serve sub-part requests (*?vue) as virtual modules + if (parseVueRequest(id).query.vue) { + return id + } + }, }, - load(id, opt) { - const ssr = opt?.ssr === true - if (id === EXPORT_HELPER_ID) { - return helperCode - } + load: { + filter: { + id: [exactRegex(EXPORT_HELPER_ID), /[?&]vue\b/], + }, + handler(id, opt) { + if (id === EXPORT_HELPER_ID) { + return helperCode + } - const { filename, query } = parseVueRequest(id) + const ssr = opt?.ssr === true - // select corresponding block for sub-part virtual modules - if (query.vue) { - if (query.src) { - return fs.readFileSync(filename, 'utf-8') - } - const descriptor = getDescriptor(filename, options.value)! - let block: SFCBlock | null | undefined - if (query.type === 'script') { - // handle diff --git a/playground/tailwind-v3/PugTemplate.vue b/playground/tailwind-v3/PugTemplate.vue new file mode 100644 index 00000000..4169b534 --- /dev/null +++ b/playground/tailwind-v3/PugTemplate.vue @@ -0,0 +1,3 @@ + diff --git a/playground/tailwind-v3/__tests__/tailwind.spec.ts b/playground/tailwind-v3/__tests__/tailwind.spec.ts new file mode 100644 index 00000000..c372c37e --- /dev/null +++ b/playground/tailwind-v3/__tests__/tailwind.spec.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest' +import { + editFile, + getBgColor, + isServe, + page, + untilBrowserLogAfter, + untilUpdated, +} from '~utils' + +test.runIf(isServe)('regenerate CSS and HMR (pug template)', async () => { + const el = await page.$('.pug') + expect(await getBgColor(el)).toBe('rgb(248, 113, 113)') + + await untilBrowserLogAfter( + () => + editFile('PugTemplate.vue', (code) => + code.replace('bg-red-400', 'bg-red-600'), + ), + [ + '[vite] css hot updated: /index.css', + '[vite] hot updated: /PugTemplate.vue?vue&type=template&lang.js', + ], + false, + ) + await untilUpdated(() => getBgColor(el), 'rgb(220, 38, 38)') +}) diff --git a/playground/tailwind-v3/index.css b/playground/tailwind-v3/index.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/playground/tailwind-v3/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/playground/tailwind-v3/index.html b/playground/tailwind-v3/index.html new file mode 100644 index 00000000..78b06123 --- /dev/null +++ b/playground/tailwind-v3/index.html @@ -0,0 +1,9 @@ + + +
+ diff --git a/playground/tailwind-v3/package.json b/playground/tailwind-v3/package.json new file mode 100644 index 00000000..b33b3716 --- /dev/null +++ b/playground/tailwind-v3/package.json @@ -0,0 +1,22 @@ +{ + "name": "@vitejs/test-tailwind-v3", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "debug": "node --inspect-brk ../../packages/vite/bin/vite", + "preview": "vite preview" + }, + "dependencies": { + "autoprefixer": "^10.4.21", + "tailwindcss": "^3.4.17", + "vue": "catalog:" + }, + "devDependencies": { + "@types/node": "^22.15.31", + "@vitejs/plugin-vue": "workspace:*", + "ts-node": "^10.9.2" + } +} diff --git a/playground/tailwind/postcss.config.cts b/playground/tailwind-v3/postcss.config.cts similarity index 100% rename from playground/tailwind/postcss.config.cts rename to playground/tailwind-v3/postcss.config.cts diff --git a/playground/tailwind-v3/tailwind.config.js b/playground/tailwind-v3/tailwind.config.js new file mode 100644 index 00000000..b8b8bd06 --- /dev/null +++ b/playground/tailwind-v3/tailwind.config.js @@ -0,0 +1,17 @@ +import { fileURLToPath } from 'node:url' +import { dirname } from 'node:path' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +/** @type {import('tailwindcss').Config} */ +export default { + content: [__dirname + '/**/*.vue'], + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [], +} diff --git a/playground/tailwind-v3/vite.config.ts b/playground/tailwind-v3/vite.config.ts new file mode 100644 index 00000000..30207fe6 --- /dev/null +++ b/playground/tailwind-v3/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + build: { + // to make tests faster + minify: false, + }, +}) diff --git a/playground/tailwind/__tests__/tailwind.spec.ts b/playground/tailwind/__tests__/tailwind.spec.ts index c372c37e..9cf5cb8b 100644 --- a/playground/tailwind/__tests__/tailwind.spec.ts +++ b/playground/tailwind/__tests__/tailwind.spec.ts @@ -10,7 +10,7 @@ import { test.runIf(isServe)('regenerate CSS and HMR (pug template)', async () => { const el = await page.$('.pug') - expect(await getBgColor(el)).toBe('rgb(248, 113, 113)') + expect(await getBgColor(el)).toBe('oklch(0.704 0.191 22.216)') await untilBrowserLogAfter( () => @@ -23,5 +23,5 @@ test.runIf(isServe)('regenerate CSS and HMR (pug template)', async () => { ], false, ) - await untilUpdated(() => getBgColor(el), 'rgb(220, 38, 38)') + await untilUpdated(() => getBgColor(el), 'oklch(0.577 0.245 27.325)') }) diff --git a/playground/tailwind/index.css b/playground/tailwind/index.css index b5c61c95..d4b50785 100644 --- a/playground/tailwind/index.css +++ b/playground/tailwind/index.css @@ -1,3 +1 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import 'tailwindcss'; diff --git a/playground/tailwind/package.json b/playground/tailwind/package.json index ed604f64..d66832b1 100644 --- a/playground/tailwind/package.json +++ b/playground/tailwind/package.json @@ -10,14 +10,12 @@ "preview": "vite preview" }, "dependencies": { - "autoprefixer": "^10.4.20", - "tailwindcss": "^3.4.15", - "vue": "catalog:", - "vue-router": "catalog:" + "@tailwindcss/vite": "^4.1.10", + "tailwindcss": "^4.1.10", + "vue": "catalog:" }, "devDependencies": { - "@types/node": "^22.9.0", - "@vitejs/plugin-vue": "workspace:*", - "ts-node": "^10.9.2" + "@types/node": "^22.15.31", + "@vitejs/plugin-vue": "workspace:*" } } diff --git a/playground/tailwind/tailwind.config.js b/playground/tailwind/tailwind.config.js deleted file mode 100644 index 16789908..00000000 --- a/playground/tailwind/tailwind.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('tailwindcss').Config} */ - -module.exports = { - content: [__dirname + '/**/*.vue'], - theme: { - extend: {}, - }, - variants: { - extend: {}, - }, - plugins: [], -} diff --git a/playground/tailwind/vite.config.ts b/playground/tailwind/vite.config.ts index 30207fe6..24223171 100644 --- a/playground/tailwind/vite.config.ts +++ b/playground/tailwind/vite.config.ts @@ -1,8 +1,9 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import tailwind from '@tailwindcss/vite' export default defineConfig({ - plugins: [vue()], + plugins: [vue(), tailwind()], build: { // to make tests faster minify: false, diff --git a/playground/test-utils.ts b/playground/test-utils.ts index 05ec1a34..325f82b5 100644 --- a/playground/test-utils.ts +++ b/playground/test-utils.ts @@ -53,7 +53,7 @@ function componentToHex(c: number): string { return hex.length === 1 ? '0' + hex : hex } -function rgbToHex(rgb: string): string { +function rgbToHex(rgb: string): string | undefined { const match = rgb.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/) if (match) { const [_, rs, gs, bs] = match @@ -63,9 +63,8 @@ function rgbToHex(rgb: string): string { componentToHex(parseInt(gs, 10)) + componentToHex(parseInt(bs, 10)) ) - } else { - return '#000000' } + return undefined } const timeout = (n: number) => new Promise((r) => setTimeout(r, n)) diff --git a/playground/tsconfig.json b/playground/tsconfig.json index 7e125f86..7f7356c8 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -2,14 +2,14 @@ "include": ["."], "exclude": ["**/dist/**"], "compilerOptions": { - "target": "ES2020", + "target": "ES2023", "module": "ESNext", "outDir": "dist", "baseUrl": ".", "allowJs": true, "esModuleInterop": true, "resolveJsonModule": true, - "moduleResolution": "Node", + "moduleResolution": "bundler", "skipLibCheck": true, "noUnusedLocals": true, "jsx": "preserve", diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts index 94a7142d..83cef572 100644 --- a/playground/vitestSetup.ts +++ b/playground/vitestSetup.ts @@ -8,6 +8,7 @@ import type { Logger, PluginOption, ResolvedConfig, + Rollup, UserConfig, ViteDevServer, } from 'vite' @@ -19,7 +20,6 @@ import { preview, } from 'vite' import type { Browser, Page } from 'playwright-chromium' -import type { RollupError, RollupWatcher, RollupWatcherEvent } from 'rollup' import type { File } from 'vitest' import { beforeAll } from 'vitest' @@ -72,7 +72,7 @@ export let resolvedConfig: ResolvedConfig = undefined! export let page: Page = undefined! export let browser: Browser = undefined! export let viteTestUrl: string = '' -export let watcher: RollupWatcher | undefined = undefined +export let watcher: Rollup.RollupWatcher | undefined = undefined declare module 'vite' { interface InlineConfig { @@ -269,7 +269,7 @@ export async function startDefaultServe(): Promise { const isWatch = !!resolvedConfig!.build.watch // in build watch,call startStaticServer after the build is complete if (isWatch) { - watcher = rollupOutput as RollupWatcher + watcher = rollupOutput as Rollup.RollupWatcher await notifyRebuildComplete(watcher) } // @ts-ignore @@ -290,10 +290,10 @@ export async function startDefaultServe(): Promise { * Send the rebuild complete message in build watch */ export async function notifyRebuildComplete( - watcher: RollupWatcher, -): Promise { + watcher: Rollup.RollupWatcher, +): Promise { let resolveFn: undefined | (() => void) - const callback = (event: RollupWatcherEvent): void => { + const callback = (event: Rollup.RollupWatcherEvent): void => { if (event.code === 'END') { resolveFn?.() } @@ -306,7 +306,7 @@ export async function notifyRebuildComplete( } function createInMemoryLogger(logs: string[]): Logger { - const loggedErrors = new WeakSet() + const loggedErrors = new WeakSet() const warnedMessages = new Set() const logger: Logger = { diff --git a/playground/vue-external/src-import/SrcImport.vue b/playground/vue-external/src-import/SrcImport.vue new file mode 100644 index 00000000..3e753e18 --- /dev/null +++ b/playground/vue-external/src-import/SrcImport.vue @@ -0,0 +1,4 @@ + + + + diff --git a/playground/vue-external/src-import/css.module.css b/playground/vue-external/src-import/css.module.css new file mode 100644 index 00000000..09b5c09f --- /dev/null +++ b/playground/vue-external/src-import/css.module.css @@ -0,0 +1,7 @@ +.one { + background: yellow; +} + +.two { + border: solid 1px red; +} diff --git a/playground/vue-external/src-import/script.ts b/playground/vue-external/src-import/script.ts new file mode 100644 index 00000000..1a9f7ec3 --- /dev/null +++ b/playground/vue-external/src-import/script.ts @@ -0,0 +1,19 @@ +import { defineComponent } from 'vue' +import SrcImportStyle from './srcImportStyle.vue' +import SrcImportStyle2 from './srcImportStyle2.vue' +import SrcImportModuleStyle from './srcImportModuleStyle.vue' +import SrcImportModuleStyle2 from './srcImportModuleStyle2.vue' + +export default defineComponent({ + components: { + SrcImportStyle, + SrcImportStyle2, + SrcImportModuleStyle, + SrcImportModuleStyle2, + }, + setup() { + return { + msg: 'hello from script src!', + } + }, +}) diff --git a/playground/vue-external/src-import/srcImportModuleStyle.vue b/playground/vue-external/src-import/srcImportModuleStyle.vue new file mode 100644 index 00000000..15969778 --- /dev/null +++ b/playground/vue-external/src-import/srcImportModuleStyle.vue @@ -0,0 +1,4 @@ + + + + diff --git a/playground/vue-external/src-import/srcImportStyle2.vue b/playground/vue-external/src-import/srcImportStyle2.vue new file mode 100644 index 00000000..eaf25b7c --- /dev/null +++ b/playground/vue-external/src-import/srcImportStyle2.vue @@ -0,0 +1,4 @@ + + diff --git a/playground/vue-external/src-import/style.css b/playground/vue-external/src-import/style.css new file mode 100644 index 00000000..98bb04dc --- /dev/null +++ b/playground/vue-external/src-import/style.css @@ -0,0 +1,3 @@ +.external-src-imports-style { + color: tan; +} diff --git a/playground/vue-external/src-import/style2.css b/playground/vue-external/src-import/style2.css new file mode 100644 index 00000000..d929aeea --- /dev/null +++ b/playground/vue-external/src-import/style2.css @@ -0,0 +1,3 @@ +.external-src-imports-script { + color: #0088ff; +} diff --git a/playground/vue-external/src-import/template.html b/playground/vue-external/src-import/template.html new file mode 100644 index 00000000..61842d54 --- /dev/null +++ b/playground/vue-external/src-import/template.html @@ -0,0 +1,7 @@ +

External SFC Src Imports

+
{{ msg }}
+
This should be tan
+ + + + diff --git a/playground/vue-legacy/package.json b/playground/vue-legacy/package.json index 40282cf7..0b629ff4 100644 --- a/playground/vue-legacy/package.json +++ b/playground/vue-legacy/package.json @@ -14,6 +14,6 @@ }, "devDependencies": { "@vitejs/plugin-vue": "workspace:*", - "@vitejs/plugin-legacy": "^6.0.0" + "@vitejs/plugin-legacy": "^6.1.1" } } diff --git a/playground/vue-lib/__tests__/vue-lib.spec.ts b/playground/vue-lib/__tests__/vue-lib.spec.ts index 77a6ba9f..d001fe88 100644 --- a/playground/vue-lib/__tests__/vue-lib.spec.ts +++ b/playground/vue-lib/__tests__/vue-lib.spec.ts @@ -1,27 +1,34 @@ import path from 'node:path' +import type { Rollup } from 'vite' import { build } from 'vite' +import * as vite from 'vite' import { describe, expect, test } from 'vitest' -import type { OutputChunk, RollupOutput } from 'rollup' + +const isRolldownVite = 'rolldownVersion' in vite describe('vue component library', () => { - test('should output tree shakeable css module code', async () => { - // Build lib - await build({ - logLevel: 'silent', - configFile: path.resolve(__dirname, '../vite.config.lib.ts'), - }) - // Build app - const { output } = (await build({ - logLevel: 'silent', - configFile: path.resolve(__dirname, '../vite.config.consumer.ts'), - })) as RollupOutput - const { code } = output.find( - (e) => e.type === 'chunk' && e.isEntry, - ) as OutputChunk - // Unused css module should be treeshaked - expect(code).toContain('styleA') // styleA is used by CompA - expect(code).not.toContain('styleB') // styleB is not used - }) + // skip this test for now with rolldown-vite due to https://github.com/oxc-project/oxc/issues/10033 + test.skipIf(isRolldownVite)( + 'should output tree shakeable css module code', + async () => { + // Build lib + await build({ + logLevel: 'silent', + configFile: path.resolve(__dirname, '../vite.config.lib.ts'), + }) + // Build app + const { output } = (await build({ + logLevel: 'silent', + configFile: path.resolve(__dirname, '../vite.config.consumer.ts'), + })) as Rollup.RollupOutput + const { code } = output.find( + (e) => e.type === 'chunk' && e.isEntry, + ) as Rollup.OutputChunk + // Unused css module should be treeshaked + expect(code).toContain('styleA') // styleA is used by CompA + expect(code).not.toContain('styleB') // styleB is not used + }, + ) test('should inject css when cssCodeSplit = true', async () => { // Build lib diff --git a/playground/vue-sourcemap/EmptyScript.vue b/playground/vue-sourcemap/EmptyScript.vue new file mode 100644 index 00000000..325b7bab --- /dev/null +++ b/playground/vue-sourcemap/EmptyScript.vue @@ -0,0 +1,4 @@ + + diff --git a/playground/vue-sourcemap/Main.vue b/playground/vue-sourcemap/Main.vue index 8b092e88..d63d3799 100644 --- a/playground/vue-sourcemap/Main.vue +++ b/playground/vue-sourcemap/Main.vue @@ -7,6 +7,7 @@ +