diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000000..e031870bb9dc26 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,29 @@ +This is a TypeScript project that implements a frontend build tooling called Vite. Please follow these guidelines when contributing: + +## Code Standards + +### Required Before Each Commit + +- Run `pnpm run lint` to ensure that your code adheres to the code standards. +- Run `pnpm run format` to format your code. + +### Development Flow + +- Build: `pnpm run build` +- Test: `pnpm run test` (uses Vitest and Playwright) + +## Repository Structure + +- `docs/`: Documentation. +- `packages/create-vite`: Contains the source code for the `create-vite` command. +- `packages/plugin-legacy`: Contains the source code for `@vitejs/plugin-legacy`. +- `packages/vite`: Contains the source code for the Vite core. +- `playground/`: E2E tests + +## Key Guidelines + +1. Follow TypeScript best practices. +2. Maintain existing code structure and organization. +3. Write tests for new functionality. Prefer unit tests if it can be tested without using mocks. E2E tests should be added in the `playground/` directory. +4. Never write comments that explain what the code does. Instead, write comments that explain why the code does what it does. +5. Suggest changes to the documentation if public API changes are made. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7119b954339b38..13cd6413976bd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -146,17 +146,17 @@ jobs: lint: timeout-minutes: 10 runs-on: ubuntu-latest - name: "Lint: node-20, ubuntu-latest" + name: "Lint: node-22, ubuntu-latest" steps: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - - name: Set node version to 20 + - name: Set node version to 22 uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: "pnpm" - name: Install deps diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000000000..bc8bf25ef37672 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,47 @@ +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + copilot-setup-steps: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set node version to 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: "pnpm" + + - name: Install deps + run: pnpm install + + # Install playwright's binary under custom directory to cache + - name: (non-windows) Set Playwright path and Get playwright version + if: runner.os != 'Windows' + run: | + echo "PLAYWRIGHT_BROWSERS_PATH=$HOME/.cache/playwright-bin" >> $GITHUB_ENV + PLAYWRIGHT_VERSION="$(pnpm ls --depth 0 --json -w playwright-chromium | jq --raw-output '.[0].devDependencies["playwright-chromium"].version')" + echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV + - name: (windows) Set Playwright path and Get playwright version + if: runner.os == 'Windows' + run: | + echo "PLAYWRIGHT_BROWSERS_PATH=$HOME\.cache\playwright-bin" >> $env:GITHUB_ENV + $env:PLAYWRIGHT_VERSION="$(pnpm ls --depth 0 --json -w playwright-chromium | jq --raw-output '.[0].devDependencies["playwright-chromium"].version')" + echo "PLAYWRIGHT_VERSION=$env:PLAYWRIGHT_VERSION" >> $env:GITHUB_ENV + + - name: Install Playwright + # does not need to explicitly set chromium after https://github.com/microsoft/playwright/issues/14862 is solved + run: pnpm playwright install chromium diff --git a/.github/workflows/issue-close-require.yml b/.github/workflows/issue-close-require.yml index 154b630c8187c7..d9f9e119a3dff4 100644 --- a/.github/workflows/issue-close-require.yml +++ b/.github/workflows/issue-close-require.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write # for actions-cool/issues-helper to update PRs steps: - name: needs reproduction - uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3 + uses: actions-cool/issues-helper@50068f49b7b2b3857270ead65e2d02e4459b022c # v3 with: actions: "close-issues" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index 17261b0d8fa285..076837003894b7 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -14,7 +14,7 @@ jobs: steps: - name: contribution welcome if: github.event.label.name == 'contribution welcome' || github.event.label.name == 'help wanted' - uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3 + uses: actions-cool/issues-helper@50068f49b7b2b3857270ead65e2d02e4459b022c # v3 with: actions: "remove-labels" token: ${{ secrets.GITHUB_TOKEN }} @@ -23,7 +23,7 @@ jobs: - name: remove pending 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 + uses: actions-cool/issues-helper@50068f49b7b2b3857270ead65e2d02e4459b022c # v3 with: actions: "remove-labels" token: ${{ secrets.GITHUB_TOKEN }} @@ -32,7 +32,7 @@ jobs: - name: needs reproduction if: github.event.label.name == 'needs reproduction' - uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3 + uses: actions-cool/issues-helper@50068f49b7b2b3857270ead65e2d02e4459b022c # v3 with: actions: "create-comment, remove-labels" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f492d23b13c0c7..8f893841fb7c02 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,10 +23,10 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - - name: Set node version to 20 + - name: Set node version to 22 uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ cache: "pnpm" diff --git a/.prettierignore b/.prettierignore index 859618cf7ac1b3..3db2606045deaf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,14 +1,14 @@ packages/*/CHANGELOG.md -packages/vite/src/node/ssr/runtime/__tests__/fixtures -packages/vite/src/node/ssr/__tests__/fixtures/errors +packages/vite/src/node/ssr/__tests__/fixtures/errors/syntax-error.* playground-temp/ dist/ -temp/ LICENSE.md pnpm-lock.yaml pnpm-workspace.yaml playground/tsconfig-json-load-error/has-error/tsconfig.json playground/html/invalid.html +playground/html/invalidClick.html +playground/html/invalidEscape.html playground/html/valid.html playground/external/public/slash@3.0.0.js playground/ssr-html/public/slash@3.0.0.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 718922d225f1ba..972781a3475333 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,7 +133,7 @@ You may wish to test your locally modified copy of Vite against another package ```json { "dependencies": { - "vite": "^6.0.0" + "vite": "^7.0.0" }, "pnpm": { "overrides": { diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 5d77ff488eab01..0012b7914b8407 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,4 +1,6 @@ -import type { DefaultTheme } from 'vitepress' +import path from 'node:path' +import fs from 'node:fs' +import type { DefaultTheme, HeadConfig } from 'vitepress' import { defineConfig } from 'vitepress' import { transformerTwoslash } from '@shikijs/vitepress-twoslash' import { @@ -7,6 +9,7 @@ import { } from 'vitepress-plugin-group-icons' import llmstxt from 'vitepress-plugin-llms' import type { PluginOption } from 'vite' +import { markdownItImageSize } from 'markdown-it-image-size' import { buildEnd } from './buildEnd.config' const ogDescription = 'Next Generation Frontend Tooling' @@ -77,6 +80,17 @@ const versionLinks = ((): DefaultTheme.NavItemWithLink[] => { } })() +function inlineScript(file: string): HeadConfig { + return [ + 'script', + {}, + fs.readFileSync( + path.resolve(__dirname, `./inlined-scripts/${file}`), + 'utf-8', + ), + ] +} + export default defineConfig({ title: `Vite${additionalTitle}`, description: 'Next Generation Frontend Tooling', @@ -111,6 +125,7 @@ export default defineConfig({ href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Manrope:wght@600&family=IBM+Plex+Mono:wght@400&display=swap', }, ], + inlineScript('banner.js'), ['link', { rel: 'me', href: 'https://m.webtoo.ls/@vite' }], ['meta', { property: 'og:type', content: 'website' }], ['meta', { property: 'og:title', content: ogTitle }], @@ -468,6 +483,9 @@ export default defineConfig({ codeTransformers: [transformerTwoslash()], config(md) { md.use(groupIconMdPlugin) + md.use(markdownItImageSize, { + publicDir: path.resolve(import.meta.dirname, '../public'), + }) }, }, vite: { diff --git a/docs/.vitepress/inlined-scripts/banner.js b/docs/.vitepress/inlined-scripts/banner.js new file mode 100644 index 00000000000000..dc24e5d0d018e2 --- /dev/null +++ b/docs/.vitepress/inlined-scripts/banner.js @@ -0,0 +1,11 @@ +;(() => { + const restore = (key, cls, def = false) => { + const saved = localStorage.getItem(key) + if (saved ? saved !== 'false' : def) { + document.documentElement.classList.add(cls) + } + } + + window.__VITE_BANNER_ID__ = 'viteconf2025' + restore(`vite-docs-banner-${__VITE_BANNER_ID__}`, 'banner-dismissed') +})() diff --git a/docs/.vitepress/theme/components/NonInheritBadge.vue b/docs/.vitepress/theme/components/NonInheritBadge.vue new file mode 100644 index 00000000000000..34c4c0fbf66a71 --- /dev/null +++ b/docs/.vitepress/theme/components/NonInheritBadge.vue @@ -0,0 +1,8 @@ + diff --git a/docs/.vitepress/theme/components/SponsorBanner.vue b/docs/.vitepress/theme/components/SponsorBanner.vue new file mode 100644 index 00000000000000..7c1371e78c8a5d --- /dev/null +++ b/docs/.vitepress/theme/components/SponsorBanner.vue @@ -0,0 +1,265 @@ + + + + + + + diff --git a/docs/.vitepress/theme/components/landing/3. frameworks-section/FrameworkCard.vue b/docs/.vitepress/theme/components/landing/3. frameworks-section/FrameworkCard.vue index f154f7abd1dde5..4e706eb297cb26 100644 --- a/docs/.vitepress/theme/components/landing/3. frameworks-section/FrameworkCard.vue +++ b/docs/.vitepress/theme/components/landing/3. frameworks-section/FrameworkCard.vue @@ -56,6 +56,7 @@ const props = withDefaults(defineProps(), { v-if="props.framework.logo" :src="props.framework.logo" :alt="props.framework.name" + loading="lazy" /> diff --git a/docs/.vitepress/theme/components/landing/4. community-section/CommunityCard.vue b/docs/.vitepress/theme/components/landing/4. community-section/CommunityCard.vue index 161c9738e28afa..f897b0154eeded 100644 --- a/docs/.vitepress/theme/components/landing/4. community-section/CommunityCard.vue +++ b/docs/.vitepress/theme/components/landing/4. community-section/CommunityCard.vue @@ -36,6 +36,7 @@ defineProps<{ :src="testimonial.avatar" :alt="testimonial.name" class="card__avatar" + loading="lazy" />
diff --git a/docs/.vitepress/theme/components/landing/4. community-section/CommunitySection.vue b/docs/.vitepress/theme/components/landing/4. community-section/CommunitySection.vue index 7ca709a402d557..2c9a24820ea540 100644 --- a/docs/.vitepress/theme/components/landing/4. community-section/CommunitySection.vue +++ b/docs/.vitepress/theme/components/landing/4. community-section/CommunitySection.vue @@ -53,7 +53,7 @@ SolidJS in mind, they should scale from our simplest template to opinionated sta name: 'David Cramer', handle: '@zeeg', avatar: - 'https://pbs.twimg.com/profile_images/1706891973553168384/zdAPOznc_400x400.jpg', + 'https://pbs.twimg.com/profile_images/1911613315765133312/HVkULegC_400x400.jpg', comment: ['Vite has been a game changer for the industry.'], }, { @@ -69,7 +69,7 @@ SolidJS in mind, they should scale from our simplest template to opinionated sta name: 'Christoph Nakazawa', handle: '@cpojer', avatar: - 'https://pbs.twimg.com/profile_images/1854151427595407360/4GyUCgEH_400x400.jpg', + 'https://pbs.twimg.com/profile_images/1910252462126313472/gXgT-jxL_400x400.jpg', comment: ['Vite is gonna eat the (JavaScript) world.'], }, { diff --git a/docs/.vitepress/theme/components/landing/5. sponsor-section/SponsorSection.vue b/docs/.vitepress/theme/components/landing/5. sponsor-section/SponsorSection.vue index 9e5c6586edf077..a591f3a4684372 100644 --- a/docs/.vitepress/theme/components/landing/5. sponsor-section/SponsorSection.vue +++ b/docs/.vitepress/theme/components/landing/5. sponsor-section/SponsorSection.vue @@ -45,6 +45,7 @@ const { data } = useSponsor() alt="Vite is made possible by our contributors, partner companies, and sponsors" width="58" height="55" + loading="lazy" />

Free & open source

@@ -55,7 +56,7 @@ const { data } = useSponsor()

Brought to you by

diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 1dce07332eb31e..6f81f73ebde739 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -8,18 +8,22 @@ import './styles/landing.css' import AsideSponsors from './components/AsideSponsors.vue' import SvgImage from './components/SvgImage.vue' import YouTubeVideo from './components/YouTubeVideo.vue' +import SponsorBanner from './components/SponsorBanner.vue' +import NonInheritBadge from './components/NonInheritBadge.vue' import 'virtual:group-icons.css' export default { extends: DefaultTheme, Layout() { return h(DefaultTheme.Layout, null, { + 'layout-top': () => h(SponsorBanner), 'aside-ads-before': () => h(AsideSponsors), }) }, enhanceApp({ app }) { app.component('SvgImage', SvgImage) app.component('YouTubeVideo', YouTubeVideo) + app.component('NonInheritBadge', NonInheritBadge) app.use(TwoslashFloatingVue) }, } satisfies Theme diff --git a/docs/.vitepress/theme/styles/landing.css b/docs/.vitepress/theme/styles/landing.css index 1e919af10844e4..c3d8136cfc3d36 100644 --- a/docs/.vitepress/theme/styles/landing.css +++ b/docs/.vitepress/theme/styles/landing.css @@ -4,17 +4,11 @@ html:has(.landing) { --vp-c-bg: #101010; - background-color: #101010; - - body { - background-color: #101010; - } } .landing { overflow-x: hidden; - background-color: #101010; * { -webkit-font-smoothing: antialiased !important; diff --git a/docs/changes/hotupdate-hook.md b/docs/changes/hotupdate-hook.md index 445709dbad18f1..1bdfd0b3556f57 100644 --- a/docs/changes/hotupdate-hook.md +++ b/docs/changes/hotupdate-hook.md @@ -9,12 +9,12 @@ We're planning to deprecate the `handleHotUpdate` plugin hook in favor of [`hotU Affected scope: `Vite Plugin Authors` ::: warning Future Deprecation -`hotUpdate` was first introduced in `v6.0`. The deprecation of `handleHotUpdate` is planned for a future major. We don't yet recommend moving away from `handleHotUpdate` yet. If you want to experiment and give us feedback, you can use the `future.removePluginHookHandleHotUpdate` to `"warn"` in your vite config. +`hotUpdate` was first introduced in `v6.0`. The deprecation of `handleHotUpdate` is planned for a future major. We don't recommend moving away from `handleHotUpdate` yet. If you want to experiment and give us feedback, you can use the `future.removePluginHookHandleHotUpdate` to `"warn"` in your vite config. ::: ## Motivation -The [`handleHotUpdate` hook](/guide/api-plugin.md#handlehotupdate) allows to perform custom HMR update handling. A list of modules to be updated is passed in the `HmrContext` +The [`handleHotUpdate` hook](/guide/api-plugin.md#handlehotupdate) allows to perform custom HMR update handling. A list of modules to be updated is passed in the `HmrContext`. ```ts interface HmrContext { diff --git a/docs/changes/per-environment-apis.md b/docs/changes/per-environment-apis.md index 97817e7d02d739..e0739033e326b2 100644 --- a/docs/changes/per-environment-apis.md +++ b/docs/changes/per-environment-apis.md @@ -14,7 +14,11 @@ The `Environment` instance was first introduced at `v6.0`. The deprecation of `s ```ts future: { removeServerModuleGraph: 'warn', + removeServerReloadModule: 'warn', + removeServerPluginContainer: 'warn', + removeServerHot: 'warn', removeServerTransformRequest: 'warn', + removeServerWarmupRequest: 'warn', } ``` @@ -29,5 +33,8 @@ In Vite v6, it is now possible to create any number of custom environments (`cli ## Migration Guide - `server.moduleGraph` -> [`environment.moduleGraph`](/guide/api-environment-instances#separate-module-graphs) +- `server.reloadModule(module)` -> `environment.reloadModule(module)` +- `server.pluginContainer` -> `environment.pluginContainer` - `server.transformRequest(url, ssr)` -> `environment.transformRequest(url)` - `server.warmupRequest(url, ssr)` -> `environment.warmupRequest(url)` +- `server.hot` -> `server.client.environment.hot` diff --git a/docs/changes/ssr-using-modulerunner.md b/docs/changes/ssr-using-modulerunner.md index fc0bc7d003c177..f1add2cafe01e6 100644 --- a/docs/changes/ssr-using-modulerunner.md +++ b/docs/changes/ssr-using-modulerunner.md @@ -19,3 +19,5 @@ The `server.ssrLoadModule(url)` only allows importing modules in the `ssr` e ## Migration Guide Check out the [Environment API for Frameworks Guide](../guide/api-environment-frameworks.md). + +`server.ssrFixStacktrace` and `server.ssrRewriteStacktrace` does not have to be called when using the Module Runner APIs. The stack traces will be updated unless `sourcemapInterceptor` is set to `false`. diff --git a/docs/config/dep-optimization-options.md b/docs/config/dep-optimization-options.md index 8008b3311fb755..2021d3292f8712 100644 --- a/docs/config/dep-optimization-options.md +++ b/docs/config/dep-optimization-options.md @@ -4,7 +4,7 @@ Unless noted, the options in this section are only applied to the dependency optimizer, which is only used in dev. -## optimizeDeps.entries +## optimizeDeps.entries - **Type:** `string | string[]` @@ -12,7 +12,7 @@ By default, Vite will crawl all your `.html` files to detect dependencies that n If neither of these fit your needs, you can specify custom entries using this option - the value should be a [`tinyglobby` pattern](https://github.com/SuperchupuDev/tinyglobby) or array of patterns that are relative from Vite project root. This will overwrite default entries inference. Only `node_modules` and `build.outDir` folders will be ignored by default when `optimizeDeps.entries` is explicitly defined. If other folders need to be ignored, you can use an ignore pattern as part of the entries list, marked with an initial `!`. `node_modules` will not be ignored for patterns that explicitly include the string `node_modules`. -## optimizeDeps.exclude +## optimizeDeps.exclude - **Type:** `string[]` @@ -33,7 +33,7 @@ export default defineConfig({ ::: -## optimizeDeps.include +## optimizeDeps.include - **Type:** `string[]` @@ -51,7 +51,7 @@ export default defineConfig({ }) ``` -## optimizeDeps.esbuildOptions +## optimizeDeps.esbuildOptions - **Type:** [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys)`<`[`EsbuildBuildOptions`](https://esbuild.github.io/api/#general-options)`, | 'bundle' @@ -72,20 +72,20 @@ Certain options are omitted since changing them would not be compatible with Vit - `external` is also omitted, use Vite's `optimizeDeps.exclude` option - `plugins` are merged with Vite's dep plugin -## optimizeDeps.force +## optimizeDeps.force - **Type:** `boolean` Set to `true` to force dependency pre-bundling, ignoring previously cached optimized dependencies. -## optimizeDeps.noDiscovery +## optimizeDeps.noDiscovery - **Type:** `boolean` - **Default:** `false` When set to `true`, automatic dependency discovery will be disabled and only dependencies listed in `optimizeDeps.include` will be optimized. CJS-only dependencies must be present in `optimizeDeps.include` during dev. -## optimizeDeps.holdUntilCrawlEnd +## optimizeDeps.holdUntilCrawlEnd - **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/15834) - **Type:** `boolean` @@ -93,7 +93,7 @@ When set to `true`, automatic dependency discovery will be disabled and only dep When enabled, it will hold the first optimized deps results until all static imports are crawled on cold start. This avoids the need for full-page reloads when new dependencies are discovered and they trigger the generation of new common chunks. If all dependencies are found by the scanner plus the explicitly defined ones in `include`, it is better to disable this option to let the browser process more requests in parallel. -## optimizeDeps.disabled +## optimizeDeps.disabled - **Deprecated** - **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/13839) @@ -108,7 +108,7 @@ To disable the optimizer completely, use `optimizeDeps.noDiscovery: true` to dis Optimizing dependencies during build time was an **experimental** feature. Projects trying out this strategy also removed `@rollup/plugin-commonjs` using `build.commonjsOptions: { include: [] }`. If you did so, a warning will guide you to re-enable it to support CJS only packages while bundling. ::: -## optimizeDeps.needsInterop +## optimizeDeps.needsInterop - **Experimental** - **Type:** `string[]` diff --git a/docs/config/preview-options.md b/docs/config/preview-options.md index 5f9546138d6327..acab6a3486cf8a 100644 --- a/docs/config/preview-options.md +++ b/docs/config/preview-options.md @@ -80,7 +80,7 @@ Automatically open the app in the browser on server start. When the value is a s Configure custom proxy rules for the preview server. Expects an object of `{ key: options }` pairs. If the key starts with `^`, it will be interpreted as a `RegExp`. The `configure` option can be used to access the proxy instance. -Uses [`http-proxy`](https://github.com/http-party/node-http-proxy). Full options [here](https://github.com/http-party/node-http-proxy#options). +Uses [`http-proxy-3`](https://github.com/sagemathinc/http-proxy-3). Full options [here](https://github.com/sagemathinc/http-proxy-3#options). ## preview.cors diff --git a/docs/config/server-options.md b/docs/config/server-options.md index 6ee6935f9431b9..62bdcc4ddbf5c5 100644 --- a/docs/config/server-options.md +++ b/docs/config/server-options.md @@ -122,7 +122,7 @@ Configure custom proxy rules for the dev server. Expects an object of `{ key: op Note that if you are using non-relative [`base`](/config/shared-options.md#base), you must prefix each key with that `base`. -Extends [`http-proxy`](https://github.com/http-party/node-http-proxy#options). Additional options are [here](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/proxy.ts#L13). +Extends [`http-proxy-3`](https://github.com/sagemathinc/http-proxy-3#options). Additional options are [here](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/proxy.ts#L13). In some cases, you might also want to configure the underlying dev server (e.g. to add custom middlewares to the internal [connect](https://github.com/senchalabs/connect) app). In order to do that, you need to write your own [plugin](/guide/using-plugins.html) and use [configureServer](/guide/api-plugin.html#configureserver) function. diff --git a/docs/config/shared-options.md b/docs/config/shared-options.md index eb2d2cbbfc0d66..b2da75237150e8 100644 --- a/docs/config/shared-options.md +++ b/docs/config/shared-options.md @@ -114,7 +114,7 @@ If you have duplicated copies of the same dependency in your app (likely due to For SSR builds, deduplication does not work for ESM build outputs configured from `build.rollupOptions.output`. A workaround is to use CJS build outputs until ESM has better plugin support for module loading. ::: -## resolve.conditions +## resolve.conditions - **Type:** `string[]` - **Default:** `['module', 'browser', 'development|production']` (`defaultClientConditions`) @@ -140,7 +140,7 @@ Here, `import` and `require` are "conditions". Conditions can be nested and shou Note that `import`, `require`, `default` conditions are always applied if the requirements are met. -## resolve.mainFields +## resolve.mainFields - **Type:** `string[]` - **Default:** `['browser', 'module', 'jsnext:main', 'jsnext']` (`defaultClientMainFields`) diff --git a/docs/guide/api-environment-frameworks.md b/docs/guide/api-environment-frameworks.md index 43af341ecff138..03a2badcfa2570 100644 --- a/docs/guide/api-environment-frameworks.md +++ b/docs/guide/api-environment-frameworks.md @@ -13,9 +13,13 @@ Resources: Please share your feedback with us. ::: -## Environments and Frameworks +## DevEnvironment Communication Levels -The implicit `ssr` environment and other non-client environments use a `RunnableDevEnvironment` by default during dev. While this requires the runtime to be the same with the one the Vite server is running in, this works similarly with `ssrLoadModule` and allows frameworks to migrate and enable HMR for their SSR dev story. You can guard any runnable environment with an `isRunnableDevEnvironment` function. +Since environments may run in different runtimes, communication against the environment may have constraints depending on the runtime. To allow frameworks to write runtime agnostic code easily, the Environment API provides three kinds of communication levels. + +### `RunnableDevEnvironment` + +`RunnableDevEnvironment` is an environment that can communicate arbitrary values. The implicit `ssr` environment and other non-client environments use a `RunnableDevEnvironment` by default during dev. While this requires the runtime to be the same with the one the Vite server is running in, this works similarly with `ssrLoadModule` and allows frameworks to migrate and enable HMR for their SSR dev story. You can guard any runnable environment with an `isRunnableDevEnvironment` function. ```ts export class RunnableDevEnvironment extends DevEnvironment { @@ -43,49 +47,6 @@ if (isRunnableDevEnvironment(server.environments.ssr)) { The `runner` is evaluated lazily only when it's accessed for the first time. Beware that Vite enables source map support when the `runner` is created by calling `process.setSourceMapsEnabled` or by overriding `Error.prepareStackTrace` if it's not available. ::: -Frameworks that communicate with their runtime via the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) can utilize the `FetchableDevEnvironment` that provides a standardized way of handling requests via the `handleRequest` method: - -```ts -import { - createServer, - createFetchableDevEnvironment, - isFetchableDevEnvironment, -} from 'vite' - -const server = await createServer({ - server: { middlewareMode: true }, - appType: 'custom', - environments: { - custom: { - dev: { - createEnvironment(name, config) { - return createFetchableDevEnvironment(name, config, { - handleRequest(request: Request): Promise | Response { - // handle Request and return a Response - }, - }) - }, - }, - }, - }, -}) - -// Any consumer of the environment API can now call `dispatchFetch` -if (isFetchableDevEnvironment(server.environments.custom)) { - const response: Response = await server.environments.custom.dispatchFetch( - new Request('/request-to-handle'), - ) -} -``` - -:::warning -Vite validates the input and output of the `dispatchFetch` method: the request must be an instance of the global `Request` class and the response must be the instance of the global `Response` class. Vite will throw a `TypeError` if this is not the case. - -Note that although the `FetchableDevEnvironment` is implemented as a class, it is considered an implementation detail by the Vite team and might change at any moment. -::: - -## Default `RunnableDevEnvironment` - Given a Vite server configured in middleware mode as described by the [SSR setup guide](/guide/ssr#setting-up-the-dev-server), let's implement the SSR middleware using the environment API. Remember that it doesn't have to be called `ssr`, so we'll name it `server` in this example. Error handling is omitted. ```js @@ -142,41 +103,72 @@ app.use('*', async (req, res, next) => { }) ``` -## Runtime Agnostic SSR +When using environments that support HMR (such as `RunnableDevEnvironment`), you should add `import.meta.hot.accept()` in your server entry file for optimal behavior. Without this, server file changes will invalidate the entire server module graph: + +```js +// src/entry-server.js +export function render(...) { ... } + +if (import.meta.hot) { + import.meta.hot.accept() +} +``` -Since the `RunnableDevEnvironment` can only be used to run the code in the same runtime as the Vite server, it requires a runtime that can run the Vite Server (a runtime that is compatible with Node.js). This means that you will need to use the raw `DevEnvironment` to make it runtime agnostic. +### `FetchableDevEnvironment` -:::info `FetchableDevEnvironment` proposal +:::info -The initial proposal had a `run` method on the `DevEnvironment` class that would allow consumers to invoke an import on the runner side by using the `transport` option. During our testing we found out that the API was not universal enough to start recommending it. At the moment, we are looking for feedback on [the `FetchableDevEnvironment` proposal](https://github.com/vitejs/vite/discussions/18191). +We are looking for feedback on [the `FetchableDevEnvironment` proposal](https://github.com/vitejs/vite/discussions/18191). ::: -`RunnableDevEnvironment` has a `runner.import` function that returns the value of the module. But this function is not available in the raw `DevEnvironment` and requires the code using the Vite's APIs and the user modules to be decoupled. +`FetchableDevEnvironment` is an environment that can communicate with its runtime via the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) interface. Since the `RunnableDevEnvironment` is only possible to implement in a limited set of runtimes, we recommend to use the `FetchableDevEnvironment` instead of the `RunnableDevEnvironment`. -For example, the following example uses the value of the user module from the code using the Vite's APIs: +This environment provides a standardized way of handling requests via the `handleRequest` method: ```ts -// code using the Vite's APIs -import { createServer } from 'vite' - -const server = createServer() -const ssrEnvironment = server.environment.ssr -const input = {} +import { + createServer, + createFetchableDevEnvironment, + isFetchableDevEnvironment, +} from 'vite' -const { createHandler } = await ssrEnvironment.runner.import('./entrypoint.js') -const handler = createHandler(input) -const response = handler(new Request('/')) +const server = await createServer({ + server: { middlewareMode: true }, + appType: 'custom', + environments: { + custom: { + dev: { + createEnvironment(name, config) { + return createFetchableDevEnvironment(name, config, { + handleRequest(request: Request): Promise | Response { + // handle Request and return a Response + }, + }) + }, + }, + }, + }, +}) -// ------------------------------------- -// ./entrypoint.js -export function createHandler(input) { - return function handler(req) { - return new Response('hello') - } +// Any consumer of the environment API can now call `dispatchFetch` +if (isFetchableDevEnvironment(server.environments.custom)) { + const response: Response = await server.environments.custom.dispatchFetch( + new Request('/request-to-handle'), + ) } ``` +:::warning +Vite validates the input and output of the `dispatchFetch` method: the request must be an instance of the global `Request` class and the response must be the instance of the global `Response` class. Vite will throw a `TypeError` if this is not the case. + +Note that although the `FetchableDevEnvironment` is implemented as a class, it is considered an implementation detail by the Vite team and might change at any moment. +::: + +### raw `DevEnvironment` + +If the environment does not implement the `RunnableDevEnvironment` or `FetchableDevEnvironment` interfaces, you need to set up the communication manually. + If your code can run in the same runtime as the user modules (i.e., it does not rely on Node.js-specific APIs), you can use a virtual module. This approach eliminates the need to access the value from the code using Vite's APIs. ```ts @@ -197,9 +189,7 @@ const input = {} // use exposed functions by each environment factories that runs the code // check for each environment factories what they provide -if (ssrEnvironment instanceof RunnableDevEnvironment) { - ssrEnvironment.runner.import('virtual:entrypoint') -} else if (ssrEnvironment instanceof CustomDevEnvironment) { +if (ssrEnvironment instanceof CustomDevEnvironment) { ssrEnvironment.runEntrypoint('virtual:entrypoint') } else { throw new Error(`Unsupported runtime for ${ssrEnvironment.name}`) diff --git a/docs/guide/api-environment-runtimes.md b/docs/guide/api-environment-runtimes.md index 991b9a314e27d4..9fa18ada5c589e 100644 --- a/docs/guide/api-environment-runtimes.md +++ b/docs/guide/api-environment-runtimes.md @@ -110,6 +110,8 @@ function createWorkerdDevEnvironment( } ``` +There are [multiple communication levels for the `DevEnvironment`](/guide/api-environment-frameworks#devenvironment-communication-levels). To make it easier for frameworks to write runtime agnostic code, we recommend to implement the most flexible communication level possible. + ## `ModuleRunner` A module runner is instantiated in the target runtime. All APIs in the next section are imported from `vite/module-runner` unless stated otherwise. This export entry point is kept as lightweight as possible, only exporting the minimal needed to create module runners. @@ -151,12 +153,17 @@ Module runner exposes `import` method. When Vite server triggers `full-reload` H **Example Usage:** ```js -import { ModuleRunner, ESModulesEvaluator } from 'vite/module-runner' +import { + ModuleRunner, + ESModulesEvaluator, + createNodeImportMeta, +} from 'vite/module-runner' import { transport } from './rpc-implementation.js' const moduleRunner = new ModuleRunner( { transport, + createImportMeta: createNodeImportMeta, // if the module runner runs in Node.js }, new ESModulesEvaluator(), ) @@ -276,7 +283,11 @@ You need to couple it with the `HotChannel` instance on the server like in this ```js [worker.js] import { parentPort } from 'node:worker_threads' import { fileURLToPath } from 'node:url' -import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner' +import { + ESModulesEvaluator, + ModuleRunner, + createNodeImportMeta, +} from 'vite/module-runner' /** @type {import('vite/module-runner').ModuleRunnerTransport} */ const transport = { @@ -292,6 +303,7 @@ const transport = { const runner = new ModuleRunner( { transport, + createImportMeta: createNodeImportMeta, }, new ESModulesEvaluator(), ) diff --git a/docs/guide/api-environment.md b/docs/guide/api-environment.md index c9a82fd04a762d..bfc0a7f15b7bf1 100644 --- a/docs/guide/api-environment.md +++ b/docs/guide/api-environment.md @@ -70,7 +70,7 @@ export default { } ``` -When not explicitly documented, environment inherits the configured top-level config options (for example, the new `server` and `edge` environments will inherit the `build.sourcemap: false` option). A small number of top-level options, like `optimizeDeps`, only apply to the `client` environment, as they don't work well when applied as a default to server environments. The `client` environment can also be configured explicitly through `environments.client`, but we recommend to do it with the top-level options so the client config remains unchanged when adding new environments. +When not explicitly documented, environment inherits the configured top-level config options (for example, the new `server` and `edge` environments will inherit the `build.sourcemap: false` option). A small number of top-level options, like `optimizeDeps`, only apply to the `client` environment, as they don't work well when applied as a default to server environments. Those options have badge in [the reference](/config/). The `client` environment can also be configured explicitly through `environments.client`, but we recommend to do it with the top-level options so the client config remains unchanged when adding new environments. The `EnvironmentOptions` interface exposes all the per-environment options. There are environment options that apply to both `build` and `dev`, like `resolve`. And there are `DevEnvironmentOptions` and `BuildEnvironmentOptions` for dev and build specific options (like `dev.warmup` or `build.outDir`). Some options like `optimizeDeps` only applies to dev, but is kept as top level instead of nested in `dev` for backward compatibility. diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index 6fdf173c5b7384..a91cb98ba03d6a 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -12,7 +12,7 @@ When creating a plugin, you can inline it in your `vite.config.js`. There is no ::: tip When learning, debugging, or authoring plugins, we suggest including [vite-plugin-inspect](https://github.com/antfu/vite-plugin-inspect) in your project. It allows you to inspect the intermediate state of Vite plugins. After installing, you can visit `localhost:5173/__inspect/` to inspect the modules and transformation stack of your project. Check out install instructions in the [vite-plugin-inspect docs](https://github.com/antfu/vite-plugin-inspect). -![vite-plugin-inspect](/images/vite-plugin-inspect.png) +![vite-plugin-inspect](../images/vite-plugin-inspect.png) ::: ## Conventions diff --git a/docs/guide/backend-integration.md b/docs/guide/backend-integration.md index 5838fc0335206c..12013bc3d43bb9 100644 --- a/docs/guide/backend-integration.md +++ b/docs/guide/backend-integration.md @@ -75,6 +75,10 @@ If you need a custom integration, you can follow the steps in this guide to conf "file": "assets/shared-ChJ_j-JJ.css", "src": "_shared-ChJ_j-JJ.css" }, + "logo.svg": { + "file": "assets/logo-BuPIv-2h.svg", + "src": "logo.svg" + }, "baz.js": { "file": "assets/baz-B2H3sXNv.js", "name": "baz", @@ -100,11 +104,31 @@ If you need a custom integration, you can follow the steps in this guide to conf } ``` - - The manifest has a `Record` structure - - For entry or dynamic entry chunks, the key is the relative src path from project root. - - For non entry chunks, the key is the base name of the generated file prefixed with `_`. - - For the CSS file generated when [`build.cssCodeSplit`](/config/build-options.md#build-csscodesplit) is `false`, the key is `style.css`. - - Chunks will contain information on its static and dynamic imports (both are keys that map to the corresponding chunk in the manifest), and also its corresponding CSS and asset files (if any). + The manifest has a `Record` structure where each chunk follows the `ManifestChunk` interface: + + ```ts + interface ManifestChunk { + src?: string + file: string + css?: string[] + assets?: string[] + isEntry?: boolean + name?: string + names?: string[] + isDynamicEntry?: boolean + imports?: string[] + dynamicImports?: string[] + } + ``` + + Each entry in the manifest represents one of the following: + - **Entry chunks**: Generated from files specified in [`build.rollupOptions.input`](https://rollupjs.org/configuration-options/#input). These chunks have `isEntry: true` and their key is the relative src path from project root. + - **Dynamic entry chunks**: Generated from dynamic imports. These chunks have `isDynamicEntry: true` and their key is the relative src path from project root. + - **Non-entry chunks**: Their key is the base name of the generated file prefixed with `_`. + - **Asset chunks**: Generated from imported assets like images, fonts. Their key is the relative src path from project root. + - **CSS files**: When [`build.cssCodeSplit`](/config/build-options.md#build-csscodesplit) is `false`, a single CSS file is generated with the key `style.css`. When `build.cssCodeSplit` is not `false`, the key is generated similar to JS chunks (i.e. entry chunks will not have `_` prefix and non-entry chunks will have `_` prefix). + + Chunks will contain information on their static and dynamic imports (both are keys that map to the corresponding chunk in the manifest), and also their corresponding CSS and asset files (if any). 4. You can use this file to render links or preload directives with hashed filenames. @@ -129,14 +153,13 @@ If you need a custom integration, you can follow the steps in this guide to conf ``` Specifically, a backend generating HTML should include the following tags given a manifest - file and an entry point: - - A `` tag for each file in the entry point chunk's `css` list - - Recursively follow all chunks in the entry point's `imports` list and include a - `` tag for each CSS file of each imported chunk. - - A tag for the `file` key of the entry point chunk (`
diff --git a/docs/package.json b/docs/package.json index b38ec4776f9c81..87f5c8c6289619 100644 --- a/docs/package.json +++ b/docs/package.json @@ -8,13 +8,14 @@ "docs-serve": "vitepress serve" }, "devDependencies": { - "@shikijs/vitepress-twoslash": "^3.7.0", + "@shikijs/vitepress-twoslash": "^3.9.2", "@types/express": "^5.0.3", "feed": "^5.1.0", "gsap": "^3.13.0", - "vitepress": "^2.0.0-alpha.7", + "markdown-it-image-size": "^14.7.0", + "vitepress": "^2.0.0-alpha.9", "vitepress-plugin-group-icons": "^1.6.1", - "vitepress-plugin-llms": "^1.7.0", - "vue": "^3.5.17" + "vitepress-plugin-llms": "^1.7.2", + "vue": "^3.5.18" } } diff --git a/package.json b/package.json index 2a7f7b9b0974a7..e871b6de7e34bd 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "ci-docs": "pnpm build && pnpm docs-build" }, "devDependencies": { - "@eslint/js": "^9.30.1", + "@eslint/js": "^9.32.0", "@type-challenges/utils": "^0.1.1", "@types/babel__core": "^7.20.5", "@types/babel__preset-env": "^7.10.0", @@ -50,26 +50,26 @@ "@types/estree": "^1.0.8", "@types/etag": "^1.8.4", "@types/less": "^3.0.8", - "@types/node": "^22.16.0", - "@types/picomatch": "^4.0.0", + "@types/node": "^22.17.0", + "@types/picomatch": "^4.0.2", "@types/stylus": "^0.48.43", "@types/ws": "^8.18.1", "@vitejs/release-scripts": "^1.6.0", - "eslint": "^9.30.1", + "eslint": "^9.32.0", "eslint-plugin-import-x": "^4.16.1", - "eslint-plugin-n": "^17.21.0", - "eslint-plugin-regexp": "^2.9.0", + "eslint-plugin-n": "^17.21.3", + "eslint-plugin-regexp": "^2.9.1", "execa": "^9.6.0", "globals": "^16.3.0", - "lint-staged": "^16.1.2", + "lint-staged": "^16.1.4", "picocolors": "^1.1.1", - "playwright-chromium": "^1.53.2", + "playwright-chromium": "^1.54.2", "prettier": "3.6.2", - "rollup": "^4.40.0", - "simple-git-hooks": "^2.13.0", + "rollup": "^4.43.0", + "simple-git-hooks": "^2.13.1", "tsx": "^4.20.3", "typescript": "~5.7.2", - "typescript-eslint": "^8.35.1", + "typescript-eslint": "^8.39.0", "vite": "workspace:*", "vitest": "^3.2.4" }, @@ -90,7 +90,7 @@ "eslint --cache --fix" ] }, - "packageManager": "pnpm@10.12.4", + "packageManager": "pnpm@10.14.0", "stackblitz": { "startCommand": "pnpm --filter='./packages/vite' run dev" } diff --git a/packages/create-vite/CHANGELOG.md b/packages/create-vite/CHANGELOG.md index 4ae02cfaebbd14..3be4f5d3a7cfa4 100644 --- a/packages/create-vite/CHANGELOG.md +++ b/packages/create-vite/CHANGELOG.md @@ -1,3 +1,21 @@ +## [7.1.0](https://github.com/vitejs/vite/compare/create-vite@7.0.3...create-vite@7.1.0) (2025-08-07) +### Bug Fixes + +* **deps:** update all non-major dependencies ([#20406](https://github.com/vitejs/vite/issues/20406)) ([1a1cc8a](https://github.com/vitejs/vite/commit/1a1cc8a435a21996255b3e5cc75ed4680de2a7f3)) +* **deps:** update all non-major dependencies ([#20442](https://github.com/vitejs/vite/issues/20442)) ([e49f505](https://github.com/vitejs/vite/commit/e49f50599d852eec644e79b074b4648e2dff1e5d)) +* **deps:** update all non-major dependencies ([#20489](https://github.com/vitejs/vite/issues/20489)) ([f6aa04a](https://github.com/vitejs/vite/commit/f6aa04a52d486c8881f666c450caa3dab3c6bba1)) +* **deps:** update all non-major dependencies ([#20537](https://github.com/vitejs/vite/issues/20537)) ([fc9a9d3](https://github.com/vitejs/vite/commit/fc9a9d3f1493caa3d614f64e0a61fd5684f0928b)) + +### Documentation + +* tiny typo ([#20404](https://github.com/vitejs/vite/issues/20404)) ([3123eb7](https://github.com/vitejs/vite/commit/3123eb7071b7f89c7d0043030edc8eb5b3731680)) + +### Miscellaneous Chores + +* **deps:** update dependency vue-tsc to v3 ([#20491](https://github.com/vitejs/vite/issues/20491)) ([51f512f](https://github.com/vitejs/vite/commit/51f512f12aae4b2905863d25b803c1f5d634ba03)) +* **deps:** update rolldown-related dependencies ([#20441](https://github.com/vitejs/vite/issues/20441)) ([f689d61](https://github.com/vitejs/vite/commit/f689d613429ae9452c74f8bc482d8cc2584ea6b8)) +* **deps:** update rolldown-related dependencies ([#20536](https://github.com/vitejs/vite/issues/20536)) ([8be2787](https://github.com/vitejs/vite/commit/8be278748a92b128c49a24619d8d537dd2b08ceb)) + ## [7.0.3](https://github.com/vitejs/vite/compare/create-vite@7.0.2...create-vite@7.0.3) (2025-07-11) ### Features diff --git a/packages/create-vite/package.json b/packages/create-vite/package.json index 7c1309662292a0..f17574d2a541a3 100644 --- a/packages/create-vite/package.json +++ b/packages/create-vite/package.json @@ -1,6 +1,6 @@ { "name": "create-vite", - "version": "7.0.3", + "version": "7.1.0", "type": "module", "license": "MIT", "author": "Evan You", @@ -37,6 +37,6 @@ "cross-spawn": "^7.0.6", "mri": "^1.2.0", "picocolors": "^1.1.1", - "tsdown": "^0.12.9" + "tsdown": "^0.13.3" } } diff --git a/packages/create-vite/src/index.ts b/packages/create-vite/src/index.ts index a4eafb262ab429..30fa4f5b31328a 100755 --- a/packages/create-vite/src/index.ts +++ b/packages/create-vite/src/index.ts @@ -625,7 +625,7 @@ function pkgFromUserAgent(userAgent: string | undefined): PkgInfo | undefined { function setupReactSwc(root: string, isTs: boolean) { // renovate: datasource=npm depName=@vitejs/plugin-react-swc - const reactSwcPluginVersion = '3.10.2' + const reactSwcPluginVersion = '3.11.0' editFile(path.resolve(root, 'package.json'), (content) => { return content.replace( diff --git a/packages/create-vite/template-lit-ts/package.json b/packages/create-vite/template-lit-ts/package.json index 804f1e95c7090f..9e64695fc169e9 100644 --- a/packages/create-vite/template-lit-ts/package.json +++ b/packages/create-vite/template-lit-ts/package.json @@ -9,10 +9,10 @@ "preview": "vite preview" }, "dependencies": { - "lit": "^3.3.0" + "lit": "^3.3.1" }, "devDependencies": { "typescript": "~5.8.3", - "vite": "^7.0.4" + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-lit/package.json b/packages/create-vite/template-lit/package.json index 8bddf949d38c42..06f6dc5a483c01 100644 --- a/packages/create-vite/template-lit/package.json +++ b/packages/create-vite/template-lit/package.json @@ -9,9 +9,9 @@ "preview": "vite preview" }, "dependencies": { - "lit": "^3.3.0" + "lit": "^3.3.1" }, "devDependencies": { - "vite": "^7.0.4" + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-preact-ts/package.json b/packages/create-vite/template-preact-ts/package.json index 5fae61d0d72f00..99597f6c07d46b 100644 --- a/packages/create-vite/template-preact-ts/package.json +++ b/packages/create-vite/template-preact-ts/package.json @@ -9,11 +9,11 @@ "preview": "vite preview" }, "dependencies": { - "preact": "^10.26.9" + "preact": "^10.27.0" }, "devDependencies": { "@preact/preset-vite": "^2.10.2", "typescript": "~5.8.3", - "vite": "^7.0.4" + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-preact/package.json b/packages/create-vite/template-preact/package.json index 93bf66687c3784..f805f44c289bee 100644 --- a/packages/create-vite/template-preact/package.json +++ b/packages/create-vite/template-preact/package.json @@ -9,10 +9,10 @@ "preview": "vite preview" }, "dependencies": { - "preact": "^10.26.9" + "preact": "^10.27.0" }, "devDependencies": { "@preact/preset-vite": "^2.10.2", - "vite": "^7.0.4" + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-qwik-ts/README.md b/packages/create-vite/template-qwik-ts/README.md index 179af0e662c7b4..f9f3afa460b284 100644 --- a/packages/create-vite/template-qwik-ts/README.md +++ b/packages/create-vite/template-qwik-ts/README.md @@ -2,7 +2,7 @@ ## Qwik in CSR mode -This starter is using a pure CSR (Client Side Rendering) mode. This means, that the application is fully bootstrapped in the browser. Most of Qwik innovations however take advantage of SSR (Server-Side Rendering) mode. +This starter is using a pure CSR (Client-Side Rendering) mode. This means, that the application is fully bootstrapped in the browser. Most of Qwik innovations however take advantage of SSR (Server-Side Rendering) mode. ```ts export default defineConfig({ diff --git a/packages/create-vite/template-qwik-ts/package.json b/packages/create-vite/template-qwik-ts/package.json index 57c06ff16f6243..be239acbc673cd 100644 --- a/packages/create-vite/template-qwik-ts/package.json +++ b/packages/create-vite/template-qwik-ts/package.json @@ -11,9 +11,9 @@ "devDependencies": { "serve": "^14.2.4", "typescript": "~5.8.3", - "vite": "^7.0.4" + "vite": "^7.1.0" }, "dependencies": { - "@builder.io/qwik": "^1.14.1" + "@builder.io/qwik": "^1.15.0" } } diff --git a/packages/create-vite/template-qwik/README.md b/packages/create-vite/template-qwik/README.md index 179af0e662c7b4..f9f3afa460b284 100644 --- a/packages/create-vite/template-qwik/README.md +++ b/packages/create-vite/template-qwik/README.md @@ -2,7 +2,7 @@ ## Qwik in CSR mode -This starter is using a pure CSR (Client Side Rendering) mode. This means, that the application is fully bootstrapped in the browser. Most of Qwik innovations however take advantage of SSR (Server-Side Rendering) mode. +This starter is using a pure CSR (Client-Side Rendering) mode. This means, that the application is fully bootstrapped in the browser. Most of Qwik innovations however take advantage of SSR (Server-Side Rendering) mode. ```ts export default defineConfig({ diff --git a/packages/create-vite/template-qwik/package.json b/packages/create-vite/template-qwik/package.json index ed7f588b6332d0..47fa57dc22c785 100644 --- a/packages/create-vite/template-qwik/package.json +++ b/packages/create-vite/template-qwik/package.json @@ -10,9 +10,9 @@ }, "devDependencies": { "serve": "^14.2.4", - "vite": "^7.0.4" + "vite": "^7.1.0" }, "dependencies": { - "@builder.io/qwik": "^1.14.1" + "@builder.io/qwik": "^1.15.0" } } diff --git a/packages/create-vite/template-react-ts/package.json b/packages/create-vite/template-react-ts/package.json index 1eda8233c5200b..fae6e51fdef73b 100644 --- a/packages/create-vite/template-react-ts/package.json +++ b/packages/create-vite/template-react-ts/package.json @@ -10,20 +10,20 @@ "preview": "vite preview" }, "dependencies": { - "react": "^19.1.0", - "react-dom": "^19.1.0" + "react": "^19.1.1", + "react-dom": "^19.1.1" }, "devDependencies": { - "@eslint/js": "^9.30.1", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.6.0", - "eslint": "^9.30.1", + "@eslint/js": "^9.32.0", + "@types/react": "^19.1.9", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react": "^4.7.0", + "eslint": "^9.32.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", "typescript": "~5.8.3", - "typescript-eslint": "^8.35.1", - "vite": "^7.0.4" + "typescript-eslint": "^8.39.0", + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-react/package.json b/packages/create-vite/template-react/package.json index f2270630815a6c..ab1b7f52926e3e 100644 --- a/packages/create-vite/template-react/package.json +++ b/packages/create-vite/template-react/package.json @@ -10,18 +10,18 @@ "preview": "vite preview" }, "dependencies": { - "react": "^19.1.0", - "react-dom": "^19.1.0" + "react": "^19.1.1", + "react-dom": "^19.1.1" }, "devDependencies": { - "@eslint/js": "^9.30.1", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.6.0", - "eslint": "^9.30.1", + "@eslint/js": "^9.32.0", + "@types/react": "^19.1.9", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react": "^4.7.0", + "eslint": "^9.32.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", - "vite": "^7.0.4" + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-solid-ts/package.json b/packages/create-vite/template-solid-ts/package.json index 674ca107ded46c..c8c6e229b12c79 100644 --- a/packages/create-vite/template-solid-ts/package.json +++ b/packages/create-vite/template-solid-ts/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "typescript": "~5.8.3", - "vite": "^7.0.4", - "vite-plugin-solid": "^2.11.7" + "vite": "^7.1.0", + "vite-plugin-solid": "^2.11.8" } } diff --git a/packages/create-vite/template-solid/package.json b/packages/create-vite/template-solid/package.json index 73ea957533186b..b67a758c043ea5 100644 --- a/packages/create-vite/template-solid/package.json +++ b/packages/create-vite/template-solid/package.json @@ -12,7 +12,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.4", - "vite-plugin-solid": "^2.11.7" + "vite": "^7.1.0", + "vite-plugin-solid": "^2.11.8" } } diff --git a/packages/create-vite/template-svelte-ts/package.json b/packages/create-vite/template-svelte-ts/package.json index dcd271d9abd5ae..03881116b2aedd 100644 --- a/packages/create-vite/template-svelte-ts/package.json +++ b/packages/create-vite/template-svelte-ts/package.json @@ -10,11 +10,11 @@ "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.0.0", + "@sveltejs/vite-plugin-svelte": "^6.1.0", "@tsconfig/svelte": "^5.0.4", - "svelte": "^5.35.5", - "svelte-check": "^4.2.2", + "svelte": "^5.37.3", + "svelte-check": "^4.3.1", "typescript": "~5.8.3", - "vite": "^7.0.4" + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-svelte/package.json b/packages/create-vite/template-svelte/package.json index 3679478ab8e183..15aa56606deef2 100644 --- a/packages/create-vite/template-svelte/package.json +++ b/packages/create-vite/template-svelte/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.0.0", - "svelte": "^5.35.5", - "vite": "^7.0.4" + "@sveltejs/vite-plugin-svelte": "^6.1.0", + "svelte": "^5.37.3", + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-vanilla-ts/package.json b/packages/create-vite/template-vanilla-ts/package.json index 174f32a0dab550..4337a61dd16832 100644 --- a/packages/create-vite/template-vanilla-ts/package.json +++ b/packages/create-vite/template-vanilla-ts/package.json @@ -10,6 +10,6 @@ }, "devDependencies": { "typescript": "~5.8.3", - "vite": "^7.0.4" + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-vanilla/package.json b/packages/create-vite/template-vanilla/package.json index 7cf476b6e4f28c..96c6216ee2f9ec 100644 --- a/packages/create-vite/template-vanilla/package.json +++ b/packages/create-vite/template-vanilla/package.json @@ -9,6 +9,6 @@ "preview": "vite preview" }, "devDependencies": { - "vite": "^7.0.4" + "vite": "^7.1.0" } } diff --git a/packages/create-vite/template-vue-ts/package.json b/packages/create-vite/template-vue-ts/package.json index 30ad5e5d816e26..bd25ed35209f17 100644 --- a/packages/create-vite/template-vue-ts/package.json +++ b/packages/create-vite/template-vue-ts/package.json @@ -9,13 +9,13 @@ "preview": "vite preview" }, "dependencies": { - "vue": "^3.5.17" + "vue": "^3.5.18" }, "devDependencies": { - "@vitejs/plugin-vue": "^6.0.0", + "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.7.0", "typescript": "~5.8.3", - "vite": "^7.0.4", - "vue-tsc": "^2.2.12" + "vite": "^7.1.0", + "vue-tsc": "^3.0.5" } } diff --git a/packages/create-vite/template-vue/package.json b/packages/create-vite/template-vue/package.json index 560c1d830072f6..57493475c12bb7 100644 --- a/packages/create-vite/template-vue/package.json +++ b/packages/create-vite/template-vue/package.json @@ -9,10 +9,10 @@ "preview": "vite preview" }, "dependencies": { - "vue": "^3.5.17" + "vue": "^3.5.18" }, "devDependencies": { - "@vitejs/plugin-vue": "^6.0.0", - "vite": "^7.0.4" + "@vitejs/plugin-vue": "^6.0.1", + "vite": "^7.1.0" } } diff --git a/packages/plugin-legacy/CHANGELOG.md b/packages/plugin-legacy/CHANGELOG.md index 016b9cfc15bda1..f5023ca224a403 100644 --- a/packages/plugin-legacy/CHANGELOG.md +++ b/packages/plugin-legacy/CHANGELOG.md @@ -1,3 +1,41 @@ +## [7.2.0](https://github.com/vitejs/vite/compare/plugin-legacy@7.1.0...plugin-legacy@7.2.0) (2025-08-07) +### Bug Fixes + +* **deps:** update all non-major dependencies ([#20537](https://github.com/vitejs/vite/issues/20537)) ([fc9a9d3](https://github.com/vitejs/vite/commit/fc9a9d3f1493caa3d614f64e0a61fd5684f0928b)) +* **legacy:** `modernTargets` should set `build.target` ([#20393](https://github.com/vitejs/vite/issues/20393)) ([76c5e40](https://github.com/vitejs/vite/commit/76c5e40864f42bb33ee7ea9184e32d5156fa1a4a)) + +### Miscellaneous Chores + +* **deps:** update rolldown-related dependencies ([#20441](https://github.com/vitejs/vite/issues/20441)) ([f689d61](https://github.com/vitejs/vite/commit/f689d613429ae9452c74f8bc482d8cc2584ea6b8)) +* **deps:** update rolldown-related dependencies ([#20536](https://github.com/vitejs/vite/issues/20536)) ([8be2787](https://github.com/vitejs/vite/commit/8be278748a92b128c49a24619d8d537dd2b08ceb)) + +## [7.1.0](https://github.com/vitejs/vite/compare/plugin-legacy@7.0.1...plugin-legacy@7.1.0) (2025-07-22) +### Features + +* **legacy:** add rolldown-vite support ([#20417](https://github.com/vitejs/vite/issues/20417)) ([ab62ca4](https://github.com/vitejs/vite/commit/ab62ca4897aa969fb72508656da2eae7fb3906be)) + +## [7.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@7.0.0...plugin-legacy@7.0.1) (2025-07-17) +### Bug Fixes + +* **deps:** update all non-major dependencies ([#20324](https://github.com/vitejs/vite/issues/20324)) ([3e81af3](https://github.com/vitejs/vite/commit/3e81af38a80c7617aba6bf3300d8b4267570f9cf)) +* **deps:** update all non-major dependencies ([#20366](https://github.com/vitejs/vite/issues/20366)) ([43ac73d](https://github.com/vitejs/vite/commit/43ac73da27b3907c701e95e6a7d28fde659729ec)) +* **deps:** update all non-major dependencies ([#20406](https://github.com/vitejs/vite/issues/20406)) ([1a1cc8a](https://github.com/vitejs/vite/commit/1a1cc8a435a21996255b3e5cc75ed4680de2a7f3)) +* **legacy:** don't lower CSS if legacy chunks are not generated ([#20392](https://github.com/vitejs/vite/issues/20392)) ([d2c81f7](https://github.com/vitejs/vite/commit/d2c81f7c13030c08becd8a768182074eedb87333)) + +### Performance Improvements + +* **legacy:** skip lowering when detecting polyfills ([#20387](https://github.com/vitejs/vite/issues/20387)) ([7cc0338](https://github.com/vitejs/vite/commit/7cc0338de3a67597956af58e931e46e7913c063b)) + +### Miscellaneous Chores + +* **deps:** update rolldown-related dependencies ([#20323](https://github.com/vitejs/vite/issues/20323)) ([30d2f1b](https://github.com/vitejs/vite/commit/30d2f1b38c72387ffdca3ee4746730959a020b59)) +* group commits by category in changelog ([#20310](https://github.com/vitejs/vite/issues/20310)) ([41e83f6](https://github.com/vitejs/vite/commit/41e83f62b1adb65f5af4c1ec006de1c845437edc)) + +### Code Refactoring + +* **legacy:** use Rollup type export from Vite ([#20335](https://github.com/vitejs/vite/issues/20335)) ([d62dc33](https://github.com/vitejs/vite/commit/d62dc3321db05d91e74facff51799496ce8601f3)) +* use `foo.endsWith("bar")` instead of `/bar$/.test(foo)` ([#20413](https://github.com/vitejs/vite/issues/20413)) ([862e192](https://github.com/vitejs/vite/commit/862e192d21f66039635a998724bdc6b94fd293a0)) + ## [7.0.0](https://github.com/vitejs/vite/compare/plugin-legacy@7.0.0-beta.1...plugin-legacy@7.0.0) (2025-06-24) ### Miscellaneous Chores diff --git a/packages/plugin-legacy/README.md b/packages/plugin-legacy/README.md index 8026113f8b8bc3..23128df2875d50 100644 --- a/packages/plugin-legacy/README.md +++ b/packages/plugin-legacy/README.md @@ -40,7 +40,7 @@ npm add -D terser - **Type:** `string | string[] | { [key: string]: string }` - **Default:** [`'last 2 versions and not dead, > 0.3%, Firefox ESR'`](https://browsersl.ist/#q=last+2+versions+and+not+dead%2C+%3E+0.3%25%2C+Firefox+ESR) - If explicitly set, it's passed on to [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env#targets) when rendering **legacy chunks**. + It's passed on to [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env#targets) when rendering **legacy chunks**. The query is also [Browserslist compatible](https://github.com/browserslist/browserslist). See [Browserslist Best Practices](https://github.com/browserslist/browserslist#best-practices) for more details. @@ -51,12 +51,14 @@ npm add -D terser - **Type:** `string | string[]` - **Default:** [`'edge>=79, firefox>=67, chrome>=64, safari>=12, chromeAndroid>=64, iOS>=12'`](https://browsersl.ist/#q=edge%3E%3D79%2C+firefox%3E%3D67%2C+chrome%3E%3D64%2C+safari%3E%3D12%2C+chromeAndroid%3E%3D64%2C+iOS%3E%3D12) - If explicitly set, it's passed on to [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env#targets) when rendering **modern chunks**. + It's passed on to [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env#targets) when collecting polyfills for **modern chunks**. The value set here will override the `build.target` option. The query is also [Browserslist compatible](https://github.com/browserslist/browserslist). See [Browserslist Best Practices](https://github.com/browserslist/browserslist#best-practices) for more details. If it's not set, plugin-legacy will fallback to the default value. + Note that this options should not be set unless `renderLegacyChunks` is set to `false`. + ### `polyfills` - **Type:** `boolean | string[]` diff --git a/packages/plugin-legacy/package.json b/packages/plugin-legacy/package.json index 977a40cd779408..ad9df062790fc7 100644 --- a/packages/plugin-legacy/package.json +++ b/packages/plugin-legacy/package.json @@ -1,6 +1,6 @@ { "name": "@vitejs/plugin-legacy", - "version": "7.0.0", + "version": "7.2.0", "type": "module", "license": "MIT", "author": "Evan You", @@ -32,12 +32,22 @@ }, "homepage": "https://github.com/vitejs/vite/tree/main/packages/plugin-legacy#readme", "funding": "https://github.com/vitejs/vite?sponsor=1", + "imports": { + "#legacy-for-rolldown-vite": { + "types": "./types/legacy-for-rolldown-vite.d.ts", + "default": "./dist/vendor/rolldown-vite/index.js" + } + }, "dependencies": { "@babel/core": "^7.28.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", "@babel/preset-env": "^7.28.0", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", "browserslist": "^4.25.1", "browserslist-to-esbuild": "^2.1.1", - "core-js": "^3.43.0", + "core-js": "^3.45.0", "magic-string": "^0.30.17", "regenerator-runtime": "^0.14.1", "systemjs": "^6.15.1" @@ -47,9 +57,11 @@ "vite": "^7.0.0" }, "devDependencies": { + "@vitejs/plugin-legacy-for-rolldown-vite": "https://pkg.pr.new/vitejs/rolldown-vite/@vitejs/plugin-legacy@b19b90a", "acorn": "^8.15.0", + "fdir": "^6.4.6", "picocolors": "^1.1.1", - "tsdown": "^0.12.9", + "tsdown": "^0.13.3", "vite": "workspace:*" } } diff --git a/packages/plugin-legacy/src/index.ts b/packages/plugin-legacy/src/index.ts index 4656513284c0c0..676e213372b5ca 100644 --- a/packages/plugin-legacy/src/index.ts +++ b/packages/plugin-legacy/src/index.ts @@ -3,6 +3,7 @@ import crypto from 'node:crypto' import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' import { build, normalizePath } from 'vite' +import * as vite from 'vite' import MagicString from 'magic-string' import type { BuildOptions, @@ -124,24 +125,32 @@ const _require = createRequire(import.meta.url) const nonLeadingHashInFileNameRE = /[^/]+\[hash(?::\d+)?\]/ const prefixedHashInFileNameRE = /\W?\[hash(?::\d+)?\]/ +// browsers supporting ESM + dynamic import + import.meta + async generator +const modernTargetsEsbuild = [ + 'es2020', + 'edge79', + 'firefox67', + 'chrome64', + 'safari12', +] +// same with above but by browserslist syntax +// es2020 = chrome 80+, safari 13.1+, firefox 72+, edge 80+ +// https://github.com/evanw/esbuild/issues/121#issuecomment-646956379 +const modernTargetsBabel = + 'edge>=79, firefox>=67, chrome>=64, safari>=12, chromeAndroid>=64, iOS>=12' + function viteLegacyPlugin(options: Options = {}): Plugin[] { + if ('rolldownVersion' in vite) { + const { default: viteLegacyPluginForRolldownVite } = _require( + '#legacy-for-rolldown-vite', + ) + return viteLegacyPluginForRolldownVite(options) + } + let config: ResolvedConfig let targets: Options['targets'] - let modernTargets: Options['modernTargets'] - - // browsers supporting ESM + dynamic import + import.meta + async generator - const modernTargetsEsbuild = [ - 'es2020', - 'edge79', - 'firefox67', - 'chrome64', - 'safari12', - ] - // same with above but by browserslist syntax - // es2020 = chrome 80+, safari 13.1+, firefox 72+, edge 80+ - // https://github.com/evanw/esbuild/issues/121#issuecomment-646956379 - const modernTargetsBabel = - 'edge>=79, firefox>=67, chrome>=64, safari>=12, chromeAndroid>=64, iOS>=12' + const modernTargets: Options['modernTargets'] = + options.modernTargets || modernTargetsBabel const genLegacy = options.renderLegacyChunks !== false const genModern = options.renderModernChunks !== false @@ -199,6 +208,7 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { } let overriddenBuildTarget = false + let overriddenBuildTargetOnlyModern = false let overriddenDefaultModernTargets = false const legacyConfigPlugin: Plugin = { name: 'vite:legacy-config', @@ -209,7 +219,7 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { config.build = {} } - if (!config.build.cssTarget) { + if (genLegacy && !config.build.cssTarget) { // Hint for esbuild that we are targeting legacy browsers when minifying CSS. // Full CSS compat table available at https://github.com/evanw/esbuild/blob/78e04680228cf989bdd7d471e02bbc2c8d345dc9/internal/compat/css_table.go // But note that only the `HexRGBA` feature affects the minify outcome. @@ -223,16 +233,18 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { // See https://github.com/vitejs/vite/pull/10052#issuecomment-1242076461 overriddenBuildTarget = config.build.target !== undefined overriddenDefaultModernTargets = options.modernTargets !== undefined + } else { + overriddenBuildTargetOnlyModern = config.build.target !== undefined + } - if (options.modernTargets) { - // Package is ESM only - const { default: browserslistToEsbuild } = await import( - 'browserslist-to-esbuild' - ) - config.build.target = browserslistToEsbuild(options.modernTargets) - } else { - config.build.target = modernTargetsEsbuild - } + if (options.modernTargets) { + // Package is ESM only + const { default: browserslistToEsbuild } = await import( + 'browserslist-to-esbuild' + ) + config.build.target = browserslistToEsbuild(options.modernTargets) + } else { + config.build.target = modernTargetsEsbuild } } @@ -253,6 +265,13 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { ), ) } + if (overriddenBuildTargetOnlyModern) { + config.logger.warn( + colors.yellow( + `plugin-legacy overrode 'build.target'. You should pass 'modernTargets' as an option to this plugin with the list of browsers to support instead.`, + ), + ) + } if (overriddenDefaultModernTargets) { config.logger.warn( colors.yellow( @@ -376,7 +395,6 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { } config = _config - modernTargets = options.modernTargets || modernTargetsBabel if (isDebug) { console.log(`[@vitejs/plugin-legacy] modernTargets:`, modernTargets) } @@ -554,7 +572,9 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { compact: !!config.build.minify, sourceMaps, inputSourceMap: undefined, + targets, assumptions, + browserslistConfigFile: false, presets: [ // forcing our plugin to run before preset-env by wrapping it in a // preset so we can catch the injected import statements... @@ -569,7 +589,18 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { ], [ (await import('@babel/preset-env')).default, - createBabelPresetEnvOptions(targets, { needPolyfills }), + { + bugfixes: true, + modules: false, + useBuiltIns: needPolyfills ? 'usage' : false, + corejs: needPolyfills + ? { + version: _require('core-js/package.json').version, + proposals: false, + } + : undefined, + shippedProposals: true, + }, ], ], }) @@ -721,7 +752,7 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { if (isLegacyBundle(bundle, opts) && genModern) { // avoid emitting duplicate assets for (const name in bundle) { - if (bundle[name].type === 'asset' && !/.+\.map$/.test(name)) { + if (bundle[name].type === 'asset' && !name.endsWith('.map')) { delete bundle[name] } } @@ -741,14 +772,27 @@ export async function detectPolyfills( const babel = await loadBabel() const result = babel.transform(code, { ast: true, + code: false, babelrc: false, configFile: false, compact: false, + targets, assumptions, - presets: [ + browserslistConfigFile: false, + plugins: [ + [ + (await import('babel-plugin-polyfill-corejs3')).default, + { + method: 'usage-global', + version: _require('core-js/package.json').version, + shippedProposals: true, + }, + ], [ - (await import('@babel/preset-env')).default, - createBabelPresetEnvOptions(targets, {}), + (await import('babel-plugin-polyfill-regenerator')).default, + { + method: 'usage-global', + }, ], ], }) @@ -765,27 +809,6 @@ export async function detectPolyfills( } } -function createBabelPresetEnvOptions( - targets: any, - { needPolyfills = true }: { needPolyfills?: boolean }, -) { - return { - targets, - bugfixes: true, - loose: false, - modules: false, - useBuiltIns: needPolyfills ? 'usage' : false, - corejs: needPolyfills - ? { - version: _require('core-js/package.json').version, - proposals: false, - } - : undefined, - shippedProposals: true, - ignoreBrowserslistConfig: true, - } -} - async function buildPolyfillChunk( mode: string, imports: Set, diff --git a/packages/plugin-legacy/src/shims.d.ts b/packages/plugin-legacy/src/shims.d.ts new file mode 100644 index 00000000000000..1eb577e1fc0445 --- /dev/null +++ b/packages/plugin-legacy/src/shims.d.ts @@ -0,0 +1,3 @@ +declare module 'babel-plugin-polyfill-corejs3' + +declare module 'babel-plugin-polyfill-regenerator' diff --git a/packages/plugin-legacy/tsdown.config.ts b/packages/plugin-legacy/tsdown.config.ts index ad20fd8e324229..0e8589f8ab6beb 100644 --- a/packages/plugin-legacy/tsdown.config.ts +++ b/packages/plugin-legacy/tsdown.config.ts @@ -1,8 +1,66 @@ +import path from 'node:path' +import fs from 'node:fs' import { defineConfig } from 'tsdown' +import { fdir } from 'fdir' + +const pluginLegacyForRolldownVitePackagePath = path.resolve( + import.meta.dirname, + './node_modules/@vitejs/plugin-legacy-for-rolldown-vite', +) export default defineConfig({ entry: ['src/index.ts'], target: 'node20', tsconfig: false, // disable tsconfig `paths` when bundling dts: true, + hooks: { + async 'build:done'() { + validateAllDepsForRolldownViteIsIncluded() + + const files = new fdir() + .glob('!**/*.d.ts') + .withRelativePaths() + .crawl(path.join(pluginLegacyForRolldownVitePackagePath, 'dist')) + .sync() + for (const file of files) { + const src = path.resolve( + pluginLegacyForRolldownVitePackagePath, + 'dist', + file, + ) + const dist = path.resolve( + import.meta.dirname, + 'dist/vendor/rolldown-vite', + file, + ) + fs.mkdirSync(path.dirname(dist), { recursive: true }) + fs.copyFileSync(src, dist) + } + }, + }, }) + +function validateAllDepsForRolldownViteIsIncluded() { + const pkgJsonStr = fs.readFileSync( + path.resolve(import.meta.dirname, 'package.json'), + 'utf-8', + ) + const pkgJson = JSON.parse(pkgJsonStr) + + const pkgJsonForRolldownViteStr = fs.readFileSync( + path.resolve(pluginLegacyForRolldownVitePackagePath, 'package.json'), + 'utf-8', + ) + const pkgJsonForRolldownVite = JSON.parse(pkgJsonForRolldownViteStr) + + for (const depName of Object.keys( + pkgJsonForRolldownVite.dependencies ?? {}, + )) { + if (!pkgJson.dependencies[depName]) { + throw new Error( + `All deps for rolldown-vite version of @vitejs/plugin-legacy should be ` + + `included in @vitejs/plugin-legacy, but ${depName} is not included.`, + ) + } + } +} diff --git a/packages/plugin-legacy/types/legacy-for-rolldown-vite.d.ts b/packages/plugin-legacy/types/legacy-for-rolldown-vite.d.ts new file mode 100644 index 00000000000000..abc34f7342de6a --- /dev/null +++ b/packages/plugin-legacy/types/legacy-for-rolldown-vite.d.ts @@ -0,0 +1,5 @@ +import type { Plugin } from 'vite' +import type { Options } from '../src/types' + +declare const plugin: (options?: Options) => Plugin[] +export default plugin diff --git a/packages/vite/CHANGELOG.md b/packages/vite/CHANGELOG.md index 23308df911ec21..83479210855033 100644 --- a/packages/vite/CHANGELOG.md +++ b/packages/vite/CHANGELOG.md @@ -1,3 +1,122 @@ +## [7.1.0](https://github.com/vitejs/vite/compare/v7.1.0-beta.1...v7.1.0) (2025-08-07) +### Features + +* support files with more than 1000 lines by `generateCodeFrame` ([#20508](https://github.com/vitejs/vite/issues/20508)) ([e7d0b2a](https://github.com/vitejs/vite/commit/e7d0b2afa56840dabbbad10015dc04083caaf248)) + +### Bug Fixes + +* **css:** avoid warnings for `image-set` containing `__VITE_ASSET__` ([#20520](https://github.com/vitejs/vite/issues/20520)) ([f1a2635](https://github.com/vitejs/vite/commit/f1a2635e6977a3eda681bec036f64f07686dad0d)) +* **css:** empty CSS entry points should generate CSS files, not JS files ([#20518](https://github.com/vitejs/vite/issues/20518)) ([bac9f3e](https://github.com/vitejs/vite/commit/bac9f3ecf84ae5c5add6ef224ae057508247f89e)) +* **dev:** denied request stalled when requested concurrently ([#20503](https://github.com/vitejs/vite/issues/20503)) ([64a52e7](https://github.com/vitejs/vite/commit/64a52e70d9250b16aa81ce2df27c23fe56907257)) +* **manifest:** initialize `entryCssAssetFileNames` as an empty Set ([#20542](https://github.com/vitejs/vite/issues/20542)) ([6a46cda](https://github.com/vitejs/vite/commit/6a46cdac5dece70296d1179640958deeeb2e6c19)) +* skip prepareOutDirPlugin in workers ([#20556](https://github.com/vitejs/vite/issues/20556)) ([97d5111](https://github.com/vitejs/vite/commit/97d5111645a395dae48b16b110bc76c1ee8956c8)) + +### Tests + +* detect ts support via `process.features` ([#20544](https://github.com/vitejs/vite/issues/20544)) ([856d3f0](https://github.com/vitejs/vite/commit/856d3f06e6889979f630c8453fa385f01d8adaba)) +* fix unimportant errors in test-unit ([#20545](https://github.com/vitejs/vite/issues/20545)) ([1f23554](https://github.com/vitejs/vite/commit/1f235545b14a51d41b19a49da4a7e3a8e8eb5d10)) + +## [7.1.0-beta.1](https://github.com/vitejs/vite/compare/v7.1.0-beta.0...v7.1.0-beta.1) (2025-08-05) +### Features + +* add `import.meta.main` support in config (bundle config loader) ([#20516](https://github.com/vitejs/vite/issues/20516)) ([5d3e3c2](https://github.com/vitejs/vite/commit/5d3e3c2ae5a2174941fd09fd7842794a287c3ab7)) +* **optimizer:** improve dependency optimization error messages with esbuild formatMessages ([#20525](https://github.com/vitejs/vite/issues/20525)) ([d17cfed](https://github.com/vitejs/vite/commit/d17cfeda0741e4476570700a00b7b37917c97700)) +* **ssr:** add `import.meta.main` support for Node.js module runner ([#20517](https://github.com/vitejs/vite/issues/20517)) ([794a8f2](https://github.com/vitejs/vite/commit/794a8f230218a3b1e148defc5a2d7a67409177ff)) + +### Bug Fixes + +* **asset:** only watch existing files for `new URL(, import.meta.url)` ([#20507](https://github.com/vitejs/vite/issues/20507)) ([1b211fd](https://github.com/vitejs/vite/commit/1b211fd1beccd0fc13bec700815abaa9f54147e8)) +* **client:** keep ping on WS constructor error ([#20512](https://github.com/vitejs/vite/issues/20512)) ([3676da5](https://github.com/vitejs/vite/commit/3676da5bc5b2b69b28619b8521fca94d30468fe5)) +* **deps:** update all non-major dependencies ([#20537](https://github.com/vitejs/vite/issues/20537)) ([fc9a9d3](https://github.com/vitejs/vite/commit/fc9a9d3f1493caa3d614f64e0a61fd5684f0928b)) +* don't resolve as relative for specifiers starting with a dot ([#20528](https://github.com/vitejs/vite/issues/20528)) ([c5a10ec](https://github.com/vitejs/vite/commit/c5a10ec004130bec17cf42760b76d1d404008fa3)) +* **html:** allow control character in input stream ([#20483](https://github.com/vitejs/vite/issues/20483)) ([c12a4a7](https://github.com/vitejs/vite/commit/c12a4a76a299237a0a13b885c72fdda6e4a3c9b7)) +* merge old and new `noExternal: true` correctly ([#20502](https://github.com/vitejs/vite/issues/20502)) ([9ebe4a5](https://github.com/vitejs/vite/commit/9ebe4a514a2e48e3fe194f16b0556a45ff38077a)) + +### Miscellaneous Chores + +* **deps:** update rolldown-related dependencies ([#20536](https://github.com/vitejs/vite/issues/20536)) ([8be2787](https://github.com/vitejs/vite/commit/8be278748a92b128c49a24619d8d537dd2b08ceb)) + +### Code Refactoring + +* use hook filters in the worker plugin ([#20527](https://github.com/vitejs/vite/issues/20527)) ([958cdf2](https://github.com/vitejs/vite/commit/958cdf24f882be6953ca20912dd30c84213b069b)) + +## [7.1.0-beta.0](https://github.com/vitejs/vite/compare/v7.0.6...v7.1.0-beta.0) (2025-07-30) +### Features + +* add `future: 'warn'` ([#20473](https://github.com/vitejs/vite/issues/20473)) ([e6aaf17](https://github.com/vitejs/vite/commit/e6aaf17ca21544572941957ce71bd8dbdc94e402)) +* add `removeServerPluginContainer` future deprecation ([#20437](https://github.com/vitejs/vite/issues/20437)) ([c1279e7](https://github.com/vitejs/vite/commit/c1279e75401ac6ea1d0678da88414a76ff36b6fe)) +* add `removeServerReloadModule` future deprecation ([#20436](https://github.com/vitejs/vite/issues/20436)) ([6970d17](https://github.com/vitejs/vite/commit/6970d1740cebd56af696abf60f30adb0c060f578)) +* add `server.warmupRequest` to future deprecation ([#20431](https://github.com/vitejs/vite/issues/20431)) ([8ad388a](https://github.com/vitejs/vite/commit/8ad388aeab0dc79e4bc14859b91174427805a46b)) +* add `ssrFixStacktrace` / `ssrRewriteStacktrace` to `removeSsrLoadModule` future deprecation ([#20435](https://github.com/vitejs/vite/issues/20435)) ([8c8f587](https://github.com/vitejs/vite/commit/8c8f5879ead251705c2c363f5b8b94f618fbf374)) +* **client:** ping from SharedWorker ([#19057](https://github.com/vitejs/vite/issues/19057)) ([5c97c22](https://github.com/vitejs/vite/commit/5c97c22548476e5f80856ece1d80b9234a7e6ecb)) +* **dev:** add `this.fs` support ([#20301](https://github.com/vitejs/vite/issues/20301)) ([0fe3f2f](https://github.com/vitejs/vite/commit/0fe3f2f7c325c5990f1059c28b66b24e1b8fd5d3)) +* export `defaultExternalConditions` ([#20279](https://github.com/vitejs/vite/issues/20279)) ([344d302](https://github.com/vitejs/vite/commit/344d30243b107852b133175e947a0410ea703f00)) +* implement `removePluginHookSsrArgument` future deprecation ([#20433](https://github.com/vitejs/vite/issues/20433)) ([95927d9](https://github.com/vitejs/vite/commit/95927d9c0ba1cb0b3bd8c900f039c099f8e29f90)) +* implement `removeServerHot` future deprecation ([#20434](https://github.com/vitejs/vite/issues/20434)) ([259f45d](https://github.com/vitejs/vite/commit/259f45d0698a184d6ecc352b610001fa1acdcee1)) +* resolve server URLs before calling other listeners ([#19981](https://github.com/vitejs/vite/issues/19981)) ([45f6443](https://github.com/vitejs/vite/commit/45f6443a935258d8eee62874f0695b8c1c60a481)) +* **ssr:** resolve externalized packages with `resolve.externalConditions` and add `module-sync` to default external condition ([#20409](https://github.com/vitejs/vite/issues/20409)) ([c669c52](https://github.com/vitejs/vite/commit/c669c524e6008a4902169f4b2f865e892297acf3)) +* **ssr:** support `import.meta.resolve` in module runner ([#20260](https://github.com/vitejs/vite/issues/20260)) ([62835f7](https://github.com/vitejs/vite/commit/62835f7c06d37802f0bc2abbf58bbaeaa8c73ce5)) + +### Bug Fixes + +* **deps:** update all non-major dependencies ([#20489](https://github.com/vitejs/vite/issues/20489)) ([f6aa04a](https://github.com/vitejs/vite/commit/f6aa04a52d486c8881f666c450caa3dab3c6bba1)) +* **dev:** denied requests overly ([#20410](https://github.com/vitejs/vite/issues/20410)) ([4be5270](https://github.com/vitejs/vite/commit/4be5270b27f7e6323f1771974b4b3520d86600e4)) +* **hmr:** register css deps as `type: asset` ([#20391](https://github.com/vitejs/vite/issues/20391)) ([7eac8dd](https://github.com/vitejs/vite/commit/7eac8ddb65033b8c001d6c6bc46aaeeefb79680a)) +* **optimizer:** discover correct jsx runtime during scan ([#20495](https://github.com/vitejs/vite/issues/20495)) ([10d48bb](https://github.com/vitejs/vite/commit/10d48bb2e30824d217e415a58cea9e69c2820c2a)) +* **preview:** set correct host for `resolvedUrls` ([#20496](https://github.com/vitejs/vite/issues/20496)) ([62b3e0d](https://github.com/vitejs/vite/commit/62b3e0d95c143e2f8b4e88d99c381d23663025ee)) +* **worker:** resolve WebKit compat with inline workers by deferring blob URL revocation ([#20460](https://github.com/vitejs/vite/issues/20460)) ([8033e5b](https://github.com/vitejs/vite/commit/8033e5bf8d3ff43995d0620490ed8739c59171dd)) + +### Performance Improvements + +* **client:** reduce reload debounce ([#20429](https://github.com/vitejs/vite/issues/20429)) ([22ad43b](https://github.com/vitejs/vite/commit/22ad43b4bf2435efe78a65b84e8469b23521900a)) + +### Miscellaneous Chores + +* **deps:** update dependency parse5 to v8 ([#20490](https://github.com/vitejs/vite/issues/20490)) ([744582d](https://github.com/vitejs/vite/commit/744582d0187c50045fb6cf229e3fab13093af08e)) +* format ([f20addc](https://github.com/vitejs/vite/commit/f20addc5363058f5fd797e5bc71fab3877ed0a76)) +* stablize `cssScopeTo` ([#19592](https://github.com/vitejs/vite/issues/19592)) ([ced1343](https://github.com/vitejs/vite/commit/ced13433fb71e2101850a4da1b0ef70cbc38b804)) + +### Code Refactoring + +* extract prepareOutDir as a plugin ([#20373](https://github.com/vitejs/vite/issues/20373)) ([2c4af1f](https://github.com/vitejs/vite/commit/2c4af1f90b3ac98df6f4585a329528e6bd850462)) +* extract resolve rollup options ([#20375](https://github.com/vitejs/vite/issues/20375)) ([61a9778](https://github.com/vitejs/vite/commit/61a97780e6c54adb87345cb8c1f5f0d8e9ca5c05)) +* rewrite openchrome.applescript to JXA ([#20424](https://github.com/vitejs/vite/issues/20424)) ([7979f9d](https://github.com/vitejs/vite/commit/7979f9da555aa16bd221b32ea78ce8cb5292fac4)) +* use `http-proxy-3` ([#20402](https://github.com/vitejs/vite/issues/20402)) ([26d9872](https://github.com/vitejs/vite/commit/26d987232aad389733a7635b92122bb1d78dfcad)) +* use hook filters in internal plugins ([#20358](https://github.com/vitejs/vite/issues/20358)) ([f19c4d7](https://github.com/vitejs/vite/commit/f19c4d72de142814994e30120aa4ad57552cb874)) +* use hook filters in internal resolve plugin ([#20480](https://github.com/vitejs/vite/issues/20480)) ([acd2a13](https://github.com/vitejs/vite/commit/acd2a13c2d80e8c5c721bcf9738dfc03346cbfe1)) + +## [7.0.6](https://github.com/vitejs/vite/compare/v7.0.5...v7.0.6) (2025-07-24) +### Bug Fixes + +* **deps:** update all non-major dependencies ([#20442](https://github.com/vitejs/vite/issues/20442)) ([e49f505](https://github.com/vitejs/vite/commit/e49f50599d852eec644e79b074b4648e2dff1e5d)) +* **dev:** incorrect sourcemap when optimized CJS is imported ([#20458](https://github.com/vitejs/vite/issues/20458)) ([ead2dec](https://github.com/vitejs/vite/commit/ead2dec74170ad26db8a18bbd68f075efaceb0e3)) +* **module-runner:** normalize file:// on windows ([#20449](https://github.com/vitejs/vite/issues/20449)) ([1c9cb49](https://github.com/vitejs/vite/commit/1c9cb493f0467c463113d301b00ce07cbe4b6f58)) +* respond with correct headers and status code for HEAD requests ([#20421](https://github.com/vitejs/vite/issues/20421)) ([23d04fc](https://github.com/vitejs/vite/commit/23d04fc2d8a4fcf7c2011418693d6000748aa655)) + +### Miscellaneous Chores + +* **deps:** update rolldown-related dependencies ([#20441](https://github.com/vitejs/vite/issues/20441)) ([f689d61](https://github.com/vitejs/vite/commit/f689d613429ae9452c74f8bc482d8cc2584ea6b8)) +* remove some files from prettier ignore ([#20459](https://github.com/vitejs/vite/issues/20459)) ([8403f69](https://github.com/vitejs/vite/commit/8403f69551131b5c39bfaf242ffac2e5efcd1dd6)) + +### Code Refactoring + +* use environment transform request ([#20430](https://github.com/vitejs/vite/issues/20430)) ([24e6a0c](https://github.com/vitejs/vite/commit/24e6a0c3165557396db6ab59d3001e037c76ce32)) + +## [7.0.5](https://github.com/vitejs/vite/compare/v7.0.4...v7.0.5) (2025-07-17) +### Bug Fixes + +* **deps:** update all non-major dependencies ([#20406](https://github.com/vitejs/vite/issues/20406)) ([1a1cc8a](https://github.com/vitejs/vite/commit/1a1cc8a435a21996255b3e5cc75ed4680de2a7f3)) +* remove special handling for `Accept: text/html` ([#20376](https://github.com/vitejs/vite/issues/20376)) ([c9614b9](https://github.com/vitejs/vite/commit/c9614b9c378be4a32e84f37be71a8becce52af7b)) +* watch assets referenced by `new URL(, import.meta.url)` ([#20382](https://github.com/vitejs/vite/issues/20382)) ([6bc8bf6](https://github.com/vitejs/vite/commit/6bc8bf634d4a2c9915da9813963dd80a4186daeb)) + +### Miscellaneous Chores + +* **deps:** update dependency rolldown to ^1.0.0-beta.27 ([#20405](https://github.com/vitejs/vite/issues/20405)) ([1165667](https://github.com/vitejs/vite/commit/1165667b271fb1fb76584278e72a85d564c9bb09)) + +### Code Refactoring + +* use `foo.endsWith("bar")` instead of `/bar$/.test(foo)` ([#20413](https://github.com/vitejs/vite/issues/20413)) ([862e192](https://github.com/vitejs/vite/commit/862e192d21f66039635a998724bdc6b94fd293a0)) + ## [7.0.4](https://github.com/vitejs/vite/compare/v7.0.3...v7.0.4) (2025-07-10) ### Bug Fixes diff --git a/packages/vite/LICENSE.md b/packages/vite/LICENSE.md index 58c8e2e440c88d..7a1d8bd1ab5155 100644 --- a/packages/vite/LICENSE.md +++ b/packages/vite/LICENSE.md @@ -360,6 +360,38 @@ Repository: lukeed/polka --------------------------------------- +## @rolldown/pluginutils +License: MIT +Repository: git+https://github.com/rolldown/rolldown.git + +> MIT License +> +> Copyright (c) 2024-present VoidZero Inc. & Contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. +> +> end of terms and conditions +> +> The licenses of externally maintained libraries from which parts of the Software is derived are listed [here](https://github.com/rolldown/rolldown/blob/main/THIRD-PARTY-LICENSE). + +--------------------------------------- + ## @rollup/plugin-alias, @rollup/plugin-commonjs, @rollup/plugin-dynamic-import-vars, @rollup/pluginutils License: MIT By: Johannes Stein @@ -509,10 +541,10 @@ Repository: jonschlinkert/is-number --------------------------------------- -## bundle-name, default-browser, default-browser-id, define-lazy-prop, is-docker, is-inside-container, is-wsl, open, run-applescript +## bundle-name, default-browser, default-browser-id, define-lazy-prop, is-docker, is-inside-container, is-wsl, open, run-applescript, wsl-utils License: MIT By: Sindre Sorhus -Repositories: sindresorhus/bundle-name, sindresorhus/default-browser, sindresorhus/default-browser-id, sindresorhus/define-lazy-prop, sindresorhus/is-docker, sindresorhus/is-inside-container, sindresorhus/is-wsl, sindresorhus/open, sindresorhus/run-applescript +Repositories: sindresorhus/bundle-name, sindresorhus/default-browser, sindresorhus/default-browser-id, sindresorhus/define-lazy-prop, sindresorhus/is-docker, sindresorhus/is-inside-container, sindresorhus/is-wsl, sindresorhus/open, sindresorhus/run-applescript, sindresorhus/wsl-utils > MIT License > @@ -1027,35 +1059,6 @@ Repository: jshttp/etag --------------------------------------- -## eventemitter3 -License: MIT -By: Arnout Kazemier -Repository: git://github.com/primus/eventemitter3.git - -> The MIT License (MIT) -> -> Copyright (c) 2014 Arnout Kazemier -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - ## finalhandler License: MIT By: Douglas Christopher Wilson @@ -1193,33 +1196,33 @@ Repository: git+https://github.com/sapphi-red/host-validation-middleware.git --------------------------------------- -## http-proxy +## http-proxy-3 License: MIT -By: Charlie Robbins, jcrugzz -Repository: https://github.com/http-party/node-http-proxy.git +By: William Stein, Charlie Robbins, Jimb Esser, jcrugzz +Repository: https://github.com/sagemathinc/http-proxy-3.git -> node-http-proxy +> node-http-3 > -> Copyright (c) 2010-2016 Charlie Robbins, Jarrett Cruger & the Contributors. +> Copyright (c) 2010-2025 William Stein, Charlie Robbins, Jarrett Cruger & the Contributors. > -> Permission is hereby granted, free of charge, to any person obtaining -> a copy of this software and associated documentation files (the -> "Software"), to deal in the Software without restriction, including -> without limitation the rights to use, copy, modify, merge, publish, -> distribute, sublicense, and/or sell copies of the Software, and to -> permit persons to whom the Software is furnished to do so, subject to -> the following conditions: +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: > -> The above copyright notice and this permission notice shall be -> included in all copies or substantial portions of the Software. +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. > -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------- @@ -2057,35 +2060,6 @@ Repository: git://github.com/paulmillr/readdirp.git --------------------------------------- -## requires-port -License: MIT -By: Arnout Kazemier -Repository: https://github.com/unshiftio/requires-port - -> The MIT License (MIT) -> -> Copyright (c) 2015 Unshift.io, Arnout Kazemier, the Contributors. -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - ## resolve.exports, totalist License: MIT By: Luke Edwards diff --git a/packages/vite/bin/openChrome.applescript b/packages/vite/bin/openChrome.applescript deleted file mode 100644 index 9ce2293231987a..00000000000000 --- a/packages/vite/bin/openChrome.applescript +++ /dev/null @@ -1,95 +0,0 @@ -(* -Copyright (c) 2015-present, Facebook, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file at -https://github.com/facebookincubator/create-react-app/blob/master/LICENSE -*) - -property targetTab: null -property targetTabIndex: -1 -property targetWindow: null -property theProgram: "Google Chrome" - -on run argv - set theURL to item 1 of argv - - -- Allow requested program to be optional, - -- default to Google Chrome - if (count of argv) > 1 then - set theProgram to item 2 of argv - end if - - using terms from application "Google Chrome" - tell application theProgram - - if (count every window) = 0 then - make new window - end if - - -- 1: Looking for tab running debugger - -- then, Reload debugging tab if found - -- then return - set found to my lookupTabWithUrl(theURL) - if found then - set targetWindow's active tab index to targetTabIndex - tell targetTab to reload - tell targetWindow to activate - set index of targetWindow to 1 - return - end if - - -- 2: Looking for Empty tab - -- In case debugging tab was not found - -- We try to find an empty tab instead - set found to my lookupTabWithUrl("chrome://newtab/") - if found then - set targetWindow's active tab index to targetTabIndex - set URL of targetTab to theURL - tell targetWindow to activate - return - end if - - -- 3: Create new tab - -- both debugging and empty tab were not found - -- make a new tab with url - tell window 1 - activate - make new tab with properties {URL:theURL} - end tell - end tell - end using terms from -end run - --- Function: --- Lookup tab with given url --- if found, store tab, index, and window in properties --- (properties were declared on top of file) -on lookupTabWithUrl(lookupUrl) - using terms from application "Google Chrome" - tell application theProgram - -- Find a tab with the given url - set found to false - set theTabIndex to -1 - repeat with theWindow in every window - set theTabIndex to 0 - repeat with theTab in every tab of theWindow - set theTabIndex to theTabIndex + 1 - if (theTab's URL as string) contains lookupUrl then - -- assign tab, tab index, and window to properties - set targetTab to theTab - set targetTabIndex to theTabIndex - set targetWindow to theWindow - set found to true - exit repeat - end if - end repeat - - if found then - exit repeat - end if - end repeat - end tell - end using terms from - return found -end lookupTabWithUrl diff --git a/packages/vite/bin/openChrome.js b/packages/vite/bin/openChrome.js new file mode 100644 index 00000000000000..f051e2951c9aea --- /dev/null +++ b/packages/vite/bin/openChrome.js @@ -0,0 +1,68 @@ +/* +Copyright (c) 2015-present, Facebook, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file at +https://github.com/facebookincubator/create-react-app/blob/master/LICENSE +*/ + +/* global Application */ + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function run(argv) { + const urlToOpen = argv[0] + // Allow requested program to be optional, default to Google Chrome + const programName = argv[1] ?? 'Google Chrome' + + const app = Application(programName) + + if (app.windows.length === 0) { + app.Window().make() + } + + // 1: Looking for tab running debugger then, + // Reload debugging tab if found, then return + const found = lookupTabWithUrl(urlToOpen, app) + if (found) { + found.targetWindow.activeTabIndex = found.targetTabIndex + found.targetTab.reload() + found.targetWindow.index = 1 + app.activate() + return + } + + // 2: Looking for Empty tab + // In case debugging tab was not found + // We try to find an empty tab instead + const emptyTabFound = lookupTabWithUrl('chrome://newtab/', app) + if (emptyTabFound) { + emptyTabFound.targetWindow.activeTabIndex = emptyTabFound.targetTabIndex + emptyTabFound.targetTab.url = urlToOpen + app.activate() + return + } + + // 3: Create new tab + // both debugging and empty tab were not found make a new tab with url + const firstWindow = app.windows[0] + firstWindow.tabs.push(app.Tab({ url: urlToOpen })) + app.activate() +} + +/** + * Lookup tab with given url + */ +function lookupTabWithUrl(lookupUrl, app) { + const windows = app.windows() + for (const window of windows) { + for (const [tabIndex, tab] of window.tabs().entries()) { + if (tab.url().includes(lookupUrl)) { + return { + targetTab: tab, + targetTabIndex: tabIndex + 1, + targetWindow: window, + } + } + } + } +} diff --git a/packages/vite/package.json b/packages/vite/package.json index 279fe35f448a12..f98a7567242a62 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -1,6 +1,6 @@ { "name": "vite", - "version": "7.0.4", + "version": "7.1.0", "type": "module", "license": "MIT", "author": "Evan You", @@ -83,9 +83,9 @@ "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", - "picomatch": "^4.0.2", + "picomatch": "^4.0.3", "postcss": "^8.5.6", - "rollup": "^4.40.0", + "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { @@ -95,8 +95,9 @@ "@ampproject/remapping": "^2.3.0", "@babel/parser": "^7.28.0", "@jridgewell/trace-mapping": "^0.3.29", - "@oxc-project/types": "0.75.1", + "@oxc-project/types": "0.77.0", "@polka/compression": "^1.0.0-next.25", + "@rolldown/pluginutils": "^1.0.0-beta.31", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-dynamic-import-vars": "2.1.4", @@ -104,7 +105,7 @@ "@types/escape-html": "^1.0.4", "@types/pnpapi": "^0.0.5", "artichokie": "^0.3.2", - "baseline-browser-mapping": "^2.4.4", + "baseline-browser-mapping": "^2.5.7", "cac": "^6.7.14", "chokidar": "^3.6.0", "connect": "^3.7.0", @@ -113,22 +114,22 @@ "cross-spawn": "^7.0.6", "debug": "^4.4.1", "dep-types": "link:./src/types", - "dotenv": "^17.0.1", + "dotenv": "^17.2.1", "dotenv-expand": "^12.0.2", "es-module-lexer": "^1.7.0", "escape-html": "^1.0.3", "estree-walker": "^3.0.3", "etag": "^1.8.1", "host-validation-middleware": "^0.1.1", - "http-proxy": "^1.18.1", - "launch-editor-middleware": "^2.10.0", + "http-proxy-3": "^1.20.10", + "launch-editor-middleware": "^2.11.0", "lightningcss": "^1.30.1", "magic-string": "^0.30.17", "mlly": "^1.7.4", "mrmime": "^2.0.1", "nanoid": "^5.1.5", - "open": "^10.1.2", - "parse5": "^7.3.0", + "open": "^10.2.0", + "parse5": "^8.0.0", "pathe": "^2.0.3", "periscopic": "^4.0.2", "picocolors": "^1.1.1", @@ -137,8 +138,8 @@ "postcss-modules": "^6.0.1", "premove": "^4.0.0", "resolve.exports": "^2.0.3", - "rolldown": "^1.0.0-beta.24", - "rolldown-plugin-dts": "^0.13.13", + "rolldown": "^1.0.0-beta.31", + "rolldown-plugin-dts": "^0.15.3", "rollup-plugin-license": "^3.6.0", "sass": "^1.89.2", "sass-embedded": "^1.89.2", diff --git a/packages/vite/rolldown.dts.config.ts b/packages/vite/rolldown.dts.config.ts index 4262989f719469..7f6c7f05442ac6 100644 --- a/packages/vite/rolldown.dts.config.ts +++ b/packages/vite/rolldown.dts.config.ts @@ -1,5 +1,6 @@ import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' +import { builtinModules } from 'node:module' import { defineConfig } from 'rolldown' import type { OutputChunk, @@ -30,7 +31,6 @@ const external = [ 'rollup/parseAst', ...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDependencies), - ...Object.keys(pkg.devDependencies), ] export default defineConfig({ @@ -48,7 +48,12 @@ export default defineConfig({ external, plugins: [ patchTypes(), - dts({ tsconfig: './src/node/tsconfig.build.json', emitDtsOnly: true }), + addNodePrefix(), + dts({ + tsconfig: './src/node/tsconfig.build.json', + emitDtsOnly: true, + resolve: true, + }), ], }) @@ -80,6 +85,9 @@ const identifierReplacements: Record> = { Server$2: 'HttpsServer', ServerOptions$2: 'HttpsServerOptions', }, + 'node:url': { + URL$1: 'url_URL', + }, 'vite/module-runner': { FetchResult$1: 'moduleRunner_FetchResult', }, @@ -100,6 +108,7 @@ const ignoreConfusingTypeNames = [ 'Plugin$1', 'MinimalPluginContext$1', 'ServerOptions$1', + 'ServerOptions$3', ] /** @@ -412,3 +421,18 @@ function escapeRegex(str: string): string { function unique(arr: T[]): T[] { return Array.from(new Set(arr)) } + +function addNodePrefix(): Plugin { + return { + name: 'add-node-prefix', + resolveId: { + order: 'pre', + filter: { + id: new RegExp(`^(?:${builtinModules.join('|')})$`), + }, + handler(id) { + return { id: `node:${id}`, external: true } + }, + }, + } +} diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index f17c43592c4d5d..0905bb32bd3d10 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -132,7 +132,7 @@ const debounceReload = (time: number) => { }, time) } } -const pageReload = debounceReload(50) +const pageReload = debounceReload(20) const hmrClient = new HMRClient( { @@ -323,25 +323,157 @@ function hasErrorOverlay() { return document.querySelectorAll(overlayId).length } -async function waitForSuccessfulPing(socketUrl: string, ms = 1000) { - async function ping() { - const socket = new WebSocket(socketUrl, 'vite-ping') - return new Promise((resolve) => { - function onOpen() { - resolve(true) - close() +function waitForSuccessfulPing(socketUrl: string) { + if (typeof SharedWorker === 'undefined') { + const visibilityManager: VisibilityManager = { + currentState: document.visibilityState, + listeners: new Set(), + } + const onVisibilityChange = () => { + visibilityManager.currentState = document.visibilityState + for (const listener of visibilityManager.listeners) { + listener(visibilityManager.currentState) } - function onError() { - resolve(false) - close() + } + document.addEventListener('visibilitychange', onVisibilityChange) + return waitForSuccessfulPingInternal(socketUrl, visibilityManager) + } + + // needs to be inlined to + // - load the worker after the server is closed + // - make it work with backend integrations + const blob = new Blob( + [ + '"use strict";', + `const waitForSuccessfulPingInternal = ${waitForSuccessfulPingInternal.toString()};`, + `const fn = ${pingWorkerContentMain.toString()};`, + `fn(${JSON.stringify(socketUrl)})`, + ], + { type: 'application/javascript' }, + ) + const objURL = URL.createObjectURL(blob) + const sharedWorker = new SharedWorker(objURL) + return new Promise((resolve, reject) => { + const onVisibilityChange = () => { + sharedWorker.port.postMessage({ visibility: document.visibilityState }) + } + document.addEventListener('visibilitychange', onVisibilityChange) + + sharedWorker.port.addEventListener('message', (event) => { + document.removeEventListener('visibilitychange', onVisibilityChange) + sharedWorker.port.close() + + const data: { type: 'success' } | { type: 'error'; error: unknown } = + event.data + if (data.type === 'error') { + reject(data.error) + return } - function close() { - socket.removeEventListener('open', onOpen) - socket.removeEventListener('error', onError) - socket.close() + resolve() + }) + + onVisibilityChange() + sharedWorker.port.start() + }) +} + +type VisibilityManager = { + currentState: DocumentVisibilityState + listeners: Set<(newVisibility: DocumentVisibilityState) => void> +} + +function pingWorkerContentMain(socketUrl: string) { + self.addEventListener('connect', (_event) => { + const event = _event as MessageEvent + const port = event.ports[0] + + if (!socketUrl) { + port.postMessage({ + type: 'error', + error: new Error('socketUrl not found'), + }) + return + } + + const visibilityManager: VisibilityManager = { + currentState: 'visible', + listeners: new Set(), + } + port.addEventListener('message', (event) => { + const { visibility } = event.data + visibilityManager.currentState = visibility + console.debug('new window visibility', visibility) + for (const listener of visibilityManager.listeners) { + listener(visibility) } - socket.addEventListener('open', onOpen) - socket.addEventListener('error', onError) + }) + port.start() + + console.debug('connected from window') + waitForSuccessfulPingInternal(socketUrl, visibilityManager).then( + () => { + console.debug('ping successful') + try { + port.postMessage({ type: 'success' }) + } catch (error) { + port.postMessage({ type: 'error', error }) + } + }, + (error) => { + console.debug('error happened', error) + try { + port.postMessage({ type: 'error', error }) + } catch (error) { + port.postMessage({ type: 'error', error }) + } + }, + ) + }) +} + +async function waitForSuccessfulPingInternal( + socketUrl: string, + visibilityManager: VisibilityManager, + ms = 1000, +) { + function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) + } + + async function ping() { + try { + const socket = new WebSocket(socketUrl, 'vite-ping') + return new Promise((resolve) => { + function onOpen() { + resolve(true) + close() + } + function onError() { + resolve(false) + close() + } + function close() { + socket.removeEventListener('open', onOpen) + socket.removeEventListener('error', onError) + socket.close() + } + socket.addEventListener('open', onOpen) + socket.addEventListener('error', onError) + }) + } catch { + return false + } + } + + function waitForWindowShow(visibilityManager: VisibilityManager) { + return new Promise((resolve) => { + const onChange = (newVisibility: DocumentVisibilityState) => { + if (newVisibility === 'visible') { + resolve() + visibilityManager.listeners.delete(onChange) + } + } + visibilityManager.listeners.add(onChange) }) } @@ -351,33 +483,17 @@ async function waitForSuccessfulPing(socketUrl: string, ms = 1000) { await wait(ms) while (true) { - if (document.visibilityState === 'visible') { + if (visibilityManager.currentState === 'visible') { if (await ping()) { break } await wait(ms) } else { - await waitForWindowShow() + await waitForWindowShow(visibilityManager) } } } -function wait(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)) -} - -function waitForWindowShow() { - return new Promise((resolve) => { - const onChange = async () => { - if (document.visibilityState === 'visible') { - resolve() - document.removeEventListener('visibilitychange', onChange) - } - } - document.addEventListener('visibilitychange', onChange) - }) -} - const sheetsMap = new Map() // collect existing style elements that may have been inserted during SSR diff --git a/packages/vite/src/module-runner/createImportMeta.ts b/packages/vite/src/module-runner/createImportMeta.ts new file mode 100644 index 00000000000000..bbf2822e723ae1 --- /dev/null +++ b/packages/vite/src/module-runner/createImportMeta.ts @@ -0,0 +1,63 @@ +import { isWindows } from '../shared/utils' +import { + type ImportMetaResolver, + createImportMetaResolver, +} from './importMetaResolver' +import type { ModuleRunnerImportMeta } from './types' +import { posixDirname, posixPathToFileHref, toWindowsPath } from './utils' + +const envProxy = new Proxy({} as any, { + get(_, p) { + throw new Error( + `[module runner] Dynamic access of "import.meta.env" is not supported. Please, use "import.meta.env.${String(p)}" instead.`, + ) + }, +}) + +export function createDefaultImportMeta( + modulePath: string, +): ModuleRunnerImportMeta { + const href = posixPathToFileHref(modulePath) + const filename = modulePath + const dirname = posixDirname(modulePath) + return { + filename: isWindows ? toWindowsPath(filename) : filename, + dirname: isWindows ? toWindowsPath(dirname) : dirname, + url: href, + env: envProxy, + resolve(_id: string, _parent?: string) { + throw new Error('[module runner] "import.meta.resolve" is not supported.') + }, + // should be replaced during transformation + glob() { + throw new Error( + `[module runner] "import.meta.glob" is statically replaced during ` + + `file transformation. Make sure to reference it by the full name.`, + ) + }, + } +} + +let importMetaResolverCache: Promise | undefined + +/** + * Create import.meta object for Node.js. + */ +export async function createNodeImportMeta( + modulePath: string, +): Promise { + const defaultMeta = createDefaultImportMeta(modulePath) + const href = defaultMeta.url + + importMetaResolverCache ??= createImportMetaResolver() + const importMetaResolver = await importMetaResolverCache + + return { + ...defaultMeta, + main: false, + resolve(id: string, parent?: string) { + const resolver = importMetaResolver ?? defaultMeta.resolve + return resolver(id, parent ?? href) + }, + } +} diff --git a/packages/vite/src/module-runner/evaluatedModules.ts b/packages/vite/src/module-runner/evaluatedModules.ts index b19d2421417b2e..4063e1dfd9f051 100644 --- a/packages/vite/src/module-runner/evaluatedModules.ts +++ b/packages/vite/src/module-runner/evaluatedModules.ts @@ -135,10 +135,9 @@ const prefixedBuiltins = new Set([ // transform file url to id // virtual:custom -> virtual:custom // \0custom -> \0custom -// /root/id -> /id -// /root/id.js -> /id.js -// C:/root/id.js -> /id.js -// C:\root\id.js -> /id.js +// node:fs -> fs +// /@fs/C:/root/id.js => C:/root/id.js +// file:///C:/root/id.js -> C:/root/id.js export function normalizeModuleId(file: string): string { if (prefixedBuiltins.has(file)) return file @@ -149,5 +148,5 @@ export function normalizeModuleId(file: string): string { .replace(/^\/+/, '/') // if it's not in the root, keep it as a path, not a URL - return unixFile.replace(/^file:\//, '/') + return unixFile.replace(/^file:\/+/, isWindows ? '' : '/') } diff --git a/packages/vite/src/module-runner/importMetaResolver.ts b/packages/vite/src/module-runner/importMetaResolver.ts new file mode 100644 index 00000000000000..25ccd8f0b54975 --- /dev/null +++ b/packages/vite/src/module-runner/importMetaResolver.ts @@ -0,0 +1,48 @@ +export type ImportMetaResolver = (specifier: string, importer: string) => string + +const customizationHookNamespace = 'vite-module-runner:import-meta-resolve/v1/' +const customizationHooksModule = /* js */ ` + +export async function resolve(specifier, context, nextResolve) { + if (specifier.startsWith(${JSON.stringify(customizationHookNamespace)})) { + const data = specifier.slice(${JSON.stringify(customizationHookNamespace)}.length) + const [parsedSpecifier, parsedImporter] = JSON.parse(data) + specifier = parsedSpecifier + context.parentURL = parsedImporter + } + + return nextResolve(specifier, context) +} + +` + +export async function createImportMetaResolver(): Promise< + ImportMetaResolver | undefined +> { + let module: typeof import('node:module') + try { + module = (await import('node:module')).Module + } catch { + return + } + // `module.Module` may be `undefined` when `node:module` is mocked + if (!module?.register) { + return + } + + try { + const hookModuleContent = `data:text/javascript,${encodeURI(customizationHooksModule)}` + module.register(hookModuleContent) + } catch (e) { + // For `--experimental-network-imports` flag that exists in Node before v22 + if ('code' in e && e.code === 'ERR_NETWORK_IMPORT_DISALLOWED') { + return + } + throw e + } + + return (specifier: string, importer: string) => + import.meta.resolve( + `${customizationHookNamespace}${JSON.stringify([specifier, importer])}`, + ) +} diff --git a/packages/vite/src/module-runner/index.ts b/packages/vite/src/module-runner/index.ts index 65bb11a2cb9aba..e23db77dde3a86 100644 --- a/packages/vite/src/module-runner/index.ts +++ b/packages/vite/src/module-runner/index.ts @@ -7,6 +7,10 @@ export { } from './evaluatedModules' export { ModuleRunner } from './runner' export { ESModulesEvaluator } from './esmEvaluator' +export { + createDefaultImportMeta, + createNodeImportMeta, +} from './createImportMeta' export { createWebSocketModuleRunnerTransport } from '../shared/moduleRunnerTransport' diff --git a/packages/vite/src/module-runner/runner.ts b/packages/vite/src/module-runner/runner.ts index 451aa6cb2d9ad1..6230d33f16ab5a 100644 --- a/packages/vite/src/module-runner/runner.ts +++ b/packages/vite/src/module-runner/runner.ts @@ -1,6 +1,6 @@ import type { ViteHotContext } from 'types/hot' import { HMRClient, HMRContext, type HMRLogger } from '../shared/hmr' -import { cleanUrl, isPrimitive, isWindows } from '../shared/utils' +import { cleanUrl, isPrimitive } from '../shared/utils' import { analyzeImportedModDifference } from '../shared/ssrTransform' import { type NormalizedModuleRunnerTransport, @@ -11,17 +11,11 @@ import { EvaluatedModules } from './evaluatedModules' import type { ModuleEvaluator, ModuleRunnerContext, - ModuleRunnerImportMeta, ModuleRunnerOptions, ResolvedResult, SSRImportMetadata, } from './types' -import { - posixDirname, - posixPathToFileHref, - posixResolve, - toWindowsPath, -} from './utils' +import { posixDirname, posixPathToFileHref, posixResolve } from './utils' import { ssrDynamicImportKey, ssrExportAllKey, @@ -34,6 +28,7 @@ import { hmrLogger, silentConsole } from './hmrLogger' import { createHMRHandlerForRunner } from './hmrHandler' import { enableSourceMapSupport } from './sourcemap/index' import { ESModulesEvaluator } from './esmEvaluator' +import { createDefaultImportMeta } from './createImportMeta' interface ModuleRunnerDebugger { (formatter: unknown, ...args: unknown[]): void @@ -43,13 +38,6 @@ export class ModuleRunner { public evaluatedModules: EvaluatedModules public hmrClient?: HMRClient - private readonly envProxy = new Proxy({} as any, { - get(_, p) { - throw new Error( - `[module runner] Dynamic access of "import.meta.env" is not supported. Please, use "import.meta.env.${String(p)}" instead.`, - ) - }, - }) private readonly transport: NormalizedModuleRunnerTransport private readonly resetSourceMapSupport?: () => void private readonly concurrentModuleNodePromises = new Map< @@ -351,29 +339,13 @@ export class ModuleRunner { ) } + const createImportMeta = + this.options.createImportMeta ?? createDefaultImportMeta + const modulePath = cleanUrl(file || moduleId) // disambiguate the `:/` on windows: see nodejs/node#31710 const href = posixPathToFileHref(modulePath) - const filename = modulePath - const dirname = posixDirname(modulePath) - const meta: ModuleRunnerImportMeta = { - filename: isWindows ? toWindowsPath(filename) : filename, - dirname: isWindows ? toWindowsPath(dirname) : dirname, - url: href, - env: this.envProxy, - resolve(_id, _parent?) { - throw new Error( - '[module runner] "import.meta.resolve" is not supported.', - ) - }, - // should be replaced during transformation - glob() { - throw new Error( - `[module runner] "import.meta.glob" is statically replaced during ` + - `file transformation. Make sure to reference it by the full name.`, - ) - }, - } + const meta = await createImportMeta(modulePath) const exports = Object.create(null) Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module', diff --git a/packages/vite/src/module-runner/types.ts b/packages/vite/src/module-runner/types.ts index e86062d31a6a25..f4ff25b47aab74 100644 --- a/packages/vite/src/module-runner/types.ts +++ b/packages/vite/src/module-runner/types.ts @@ -105,6 +105,14 @@ export interface ModuleRunnerOptions { * @default true */ hmr?: boolean | ModuleRunnerHmr + /** + * Create import.meta object for the module. + * + * @default createDefaultImportMeta + */ + createImportMeta?: ( + modulePath: string, + ) => ModuleRunnerImportMeta | Promise /** * Custom module cache. If not provided, creates a separate module cache for each ModuleRunner instance. */ diff --git a/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap b/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap index 7692ad1d7b223f..806e9eba2be8ce 100644 --- a/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap +++ b/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap @@ -139,6 +139,17 @@ exports[`generateCodeFrames > start with position 3`] = ` " `; +exports[`generateCodeFrames > supports more than 1000 lines 1`] = ` +" +1198 | // 1197 +1199 | // 1198 +1200 | // 1199 + | ^ +1201 | // 1200 +1202 | // 1201 +" +`; + exports[`generateCodeFrames > works with CRLF 1`] = ` " 1 | import foo from './foo' diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts index a6b466e8853b47..fddf467e130fda 100644 --- a/packages/vite/src/node/__tests__/config.spec.ts +++ b/packages/vite/src/node/__tests__/config.spec.ts @@ -225,6 +225,42 @@ describe('mergeConfig', () => { expect(mergeConfig(newConfig, baseConfig)).toEqual(mergedConfig) }) + test('merge ssr.noExternal and environments.ssr.resolve.noExternal', async () => { + const oldTrue = await resolveConfig( + { + ssr: { + noExternal: true, + }, + environments: { + ssr: { + resolve: { + noExternal: ['dep'], + }, + }, + }, + }, + 'serve', + ) + expect(oldTrue.environments.ssr.resolve.noExternal).toEqual(true) + + const newTrue = await resolveConfig( + { + ssr: { + noExternal: ['dep'], + }, + environments: { + ssr: { + resolve: { + noExternal: true, + }, + }, + }, + }, + 'serve', + ) + expect(newTrue.environments.ssr.resolve.noExternal).toEqual(true) + }) + test('handles server.hmr.server', () => { const httpServer = http.createServer() @@ -803,6 +839,20 @@ describe('loadConfigFromFile', () => { `) }) + test('import.meta.main is correctly set', async () => { + const { config } = (await loadConfigFromFile( + {} as any, + path.resolve(fixtures, './import-meta/vite.config.ts'), + path.resolve(fixtures, './import-meta'), + ))! + + const c = config as any + expect(c.isMain).toBe(false) + expect(c.url).toContain('file://') + expect(c.dirname).toContain('import-meta') + expect(c.filename).toContain('vite.config.ts') + }) + describe('loadConfigFromFile with configLoader: native', () => { const fixtureRoot = path.resolve(fixtures, './native-import') diff --git a/packages/vite/src/node/__tests__/dev.spec.ts b/packages/vite/src/node/__tests__/dev.spec.ts index 346bebd2aac42e..f3822ede88413b 100644 --- a/packages/vite/src/node/__tests__/dev.spec.ts +++ b/packages/vite/src/node/__tests__/dev.spec.ts @@ -1,5 +1,8 @@ -import { describe, expect, test } from 'vitest' -import { resolveConfig } from '..' +import { afterEach, describe, expect, test } from 'vitest' +import type { ResolvedServerUrls } from 'vite' +import { createServer, resolveConfig } from '..' +import type { ViteDevServer } from '..' +import { promiseWithResolvers } from '../../shared/utils' describe('resolveBuildEnvironmentOptions in dev', () => { test('build.rollupOptions should not have input in lib', async () => { @@ -17,3 +20,49 @@ describe('resolveBuildEnvironmentOptions in dev', () => { expect(config.build.rollupOptions).not.toHaveProperty('input') }) }) + +describe('the dev server', () => { + let server: ViteDevServer + + afterEach(() => { + server?.close() + }) + + test('resolves the server URLs before the httpServer listening events are called', async () => { + expect.assertions(1) + + const options = { + port: 5013, // make sure the port is unique + } + + const { promise, resolve } = + promiseWithResolvers() + server = await createServer({ + root: __dirname, + logLevel: 'error', + server: { + strictPort: true, + ws: false, + ...options, + }, + plugins: [ + { + name: 'test', + configureServer(server) { + server.httpServer?.on('listening', () => { + resolve(server.resolvedUrls) + }) + }, + }, + ], + }) + + await server.listen() + const urls = await promise + + expect(urls).toStrictEqual({ + local: ['http://localhost:5013/'], + network: [], + }) + }) +}) diff --git a/packages/vite/src/node/__tests__/environment.spec.ts b/packages/vite/src/node/__tests__/environment.spec.ts index 75b12136c5cd2d..08bc215ce11409 100644 --- a/packages/vite/src/node/__tests__/environment.spec.ts +++ b/packages/vite/src/node/__tests__/environment.spec.ts @@ -20,6 +20,8 @@ describe('custom environment conditions', () => { middlewareMode: true, ws: false, }, + // disable scanner for client env to suppress scanner warnings + optimizeDeps: { entries: [] }, environments: { // default ssr: { diff --git a/packages/vite/src/node/__tests__/fixtures/config/import-meta/vite.config.ts b/packages/vite/src/node/__tests__/fixtures/config/import-meta/vite.config.ts new file mode 100644 index 00000000000000..910f527ce12026 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/config/import-meta/vite.config.ts @@ -0,0 +1,6 @@ +export default { + isMain: import.meta.main, + url: import.meta.url, + dirname: import.meta.dirname, + filename: import.meta.filename, +} diff --git a/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/entry-jsx.tsx b/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/entry-jsx.tsx new file mode 100644 index 00000000000000..87d97daf0d3369 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/entry-jsx.tsx @@ -0,0 +1,4 @@ +;(globalThis as any).__test_scan_jsx_runtime ??= 0 +;(globalThis as any).__test_scan_jsx_runtime++ + +export default
diff --git a/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/entry-no-jsx.js b/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/entry-no-jsx.js new file mode 100644 index 00000000000000..e9b42610e57f78 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/entry-no-jsx.js @@ -0,0 +1,3 @@ +import * as vue from 'vue' + +export default vue diff --git a/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/tsconfig.json b/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/tsconfig.json new file mode 100644 index 00000000000000..77bd00c24e571b --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/scan-jsx-runtime/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "vue", + "strict": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "moduleResolution": "Bundler", + "module": "ESNext", + "target": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"] + } +} diff --git a/packages/vite/src/node/__tests__/package.json b/packages/vite/src/node/__tests__/package.json index 4f6f029f385542..65debd1f7ed9cb 100644 --- a/packages/vite/src/node/__tests__/package.json +++ b/packages/vite/src/node/__tests__/package.json @@ -5,6 +5,7 @@ "dependencies": { "@vitejs/parent": "link:./packages/parent", "@vitejs/cjs-ssr-dep": "link:./fixtures/cjs-ssr-dep", - "@vitejs/test-dep-conditions": "file:./fixtures/test-dep-conditions" + "@vitejs/test-dep-conditions": "file:./fixtures/test-dep-conditions", + "vue": "^3.5.18" } } diff --git a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts index 66c97ab176a8c7..6cca2639cb2a05 100644 --- a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts @@ -41,6 +41,7 @@ const createServerWithPlugin = async (plugin: Plugin) => { logLevel: 'error', server: { middlewareMode: true, + ws: false, }, }) onTestFinished(() => server.close()) @@ -330,4 +331,18 @@ describe('supports plugin context', () => { }, }) }) + + test('this.fs is supported in dev', async () => { + expect.hasAssertions() + + const server = await createServerWithPlugin({ + name: 'test', + resolveId(id) { + if (id !== ENTRY_ID) return + expect(this.fs.readFile).toBeTypeOf('function') + }, + }) + await server.transformRequest(ENTRY_ID) + await server.close() + }) }) diff --git a/packages/vite/src/node/__tests__/plugins/import.spec.ts b/packages/vite/src/node/__tests__/plugins/import.spec.ts index 89fbd80d8ecdc1..156a61ab283539 100644 --- a/packages/vite/src/node/__tests__/plugins/import.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/import.spec.ts @@ -1,9 +1,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest' import { transformCjsImport } from '../../plugins/importAnalysis' -describe('transformCjsImport', () => { - const url = './node_modules/.vite/deps/react.js' - const rawUrl = 'react' +describe('runTransform', () => { const config: any = { command: 'serve', logger: { @@ -11,19 +9,31 @@ describe('transformCjsImport', () => { }, } + function runTransformCjsImport(importExp: string) { + const result = transformCjsImport( + importExp, + './node_modules/.vite/deps/react.js', + 'react', + 0, + 'modA', + config, + ) + if (result !== undefined) { + expect(result.split('\n').length, 'result line count').toBe( + importExp.split('\n').length, + ) + } + return result + } + beforeEach(() => { config.logger.warn.mockClear() }) test('import specifier', () => { expect( - transformCjsImport( + runTransformCjsImport( 'import { useState, Component, "👋" as fake } from "react"', - url, - rawUrl, - 0, - '', - config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + @@ -34,29 +44,13 @@ describe('transformCjsImport', () => { }) test('import default specifier', () => { - expect( - transformCjsImport( - 'import React from "react"', - url, - rawUrl, - 0, - '', - config, - ), - ).toBe( + expect(runTransformCjsImport('import React from "react"')).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react', ) expect( - transformCjsImport( - 'import { default as React } from "react"', - url, - rawUrl, - 0, - '', - config, - ), + runTransformCjsImport('import { default as React } from "react"'), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react', @@ -64,60 +58,30 @@ describe('transformCjsImport', () => { }) test('import all specifier', () => { - expect( - transformCjsImport( - 'import * as react from "react"', - url, - rawUrl, - 0, - '', - config, - ), - ).toBe( + expect(runTransformCjsImport('import * as react from "react"')).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + - `const react = ((m) => m?.__esModule ? m : { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m })(__vite__cjsImport0_react)`, + `const react = ((m) => m?.__esModule ? m : { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m})(__vite__cjsImport0_react)`, ) }) test('export all specifier', () => { - expect( - transformCjsImport( - 'export * from "react"', - url, - rawUrl, - 0, - 'modA', - config, - ), - ).toBe(undefined) + expect(runTransformCjsImport('export * from "react"')).toBe(undefined) expect(config.logger.warn).toBeCalledWith( expect.stringContaining(`export * from "react"\` in modA`), ) - expect( - transformCjsImport( - 'export * as react from "react"', - url, - rawUrl, - 0, - '', - config, - ), - ).toBe(undefined) + expect(runTransformCjsImport('export * as react from "react"')).toBe( + undefined, + ) expect(config.logger.warn).toBeCalledTimes(1) }) test('export name specifier', () => { expect( - transformCjsImport( + runTransformCjsImport( 'export { useState, Component, "👋" } from "react"', - url, - rawUrl, - 0, - '', - config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + @@ -128,13 +92,8 @@ describe('transformCjsImport', () => { ) expect( - transformCjsImport( + runTransformCjsImport( 'export { useState as useStateAlias, Component as ComponentAlias, "👋" as "👍" } from "react"', - url, - rawUrl, - 0, - '', - config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + @@ -146,30 +105,14 @@ describe('transformCjsImport', () => { }) test('export default specifier', () => { - expect( - transformCjsImport( - 'export { default } from "react"', - url, - rawUrl, - 0, - '', - config, - ), - ).toBe( + expect(runTransformCjsImport('export { default } from "react"')).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const __vite__cjsExportDefault_0 = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react; ' + 'export default __vite__cjsExportDefault_0', ) expect( - transformCjsImport( - 'export { default as React} from "react"', - url, - rawUrl, - 0, - '', - config, - ), + runTransformCjsImport('export { default as React} from "react"'), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const __vite__cjsExportI_React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react; ' + @@ -177,14 +120,7 @@ describe('transformCjsImport', () => { ) expect( - transformCjsImport( - 'export { Component as default } from "react"', - url, - rawUrl, - 0, - '', - config, - ), + runTransformCjsImport('export { Component as default } from "react"'), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const __vite__cjsExportDefault_0 = __vite__cjsImport0_react["Component"]; ' + diff --git a/packages/vite/src/node/__tests__/resolve.spec.ts b/packages/vite/src/node/__tests__/resolve.spec.ts index f089a1518a1f6e..4c5465b00833ef 100644 --- a/packages/vite/src/node/__tests__/resolve.spec.ts +++ b/packages/vite/src/node/__tests__/resolve.spec.ts @@ -13,6 +13,7 @@ describe('import and resolveId', () => { logLevel: 'error', server: { middlewareMode: true, + ws: false, }, }) onTestFinished(() => server.close()) diff --git a/packages/vite/src/node/__tests__/runnerImport.spec.ts b/packages/vite/src/node/__tests__/runnerImport.spec.ts index 9f3530559806c1..4b646d8edf3acf 100644 --- a/packages/vite/src/node/__tests__/runnerImport.spec.ts +++ b/packages/vite/src/node/__tests__/runnerImport.spec.ts @@ -4,9 +4,8 @@ import { loadConfigFromFile } from 'vite' import { runnerImport } from '../ssr/runnerImport' import { slash } from '../../shared/utils' -const [nvMajor, nvMinor] = process.versions.node.split('.').map(Number) -const isTypeStrippingSupported = - (nvMajor === 23 && nvMinor >= 6) || nvMajor >= 24 +// eslint-disable-next-line n/no-unsupported-features/node-builtins +const isTypeStrippingSupported = !!process.features.typescript describe('importing files using inlined environment', () => { const fixture = (name: string) => diff --git a/packages/vite/src/node/__tests__/scan.spec.ts b/packages/vite/src/node/__tests__/scan.spec.ts index 6fa9f76d1ceac4..d09b68d3ead76a 100644 --- a/packages/vite/src/node/__tests__/scan.spec.ts +++ b/packages/vite/src/node/__tests__/scan.spec.ts @@ -1,6 +1,8 @@ +import path from 'node:path' import { describe, expect, test } from 'vitest' import { commentRE, importsRE, scriptRE } from '../optimizer/scan' import { multilineCommentsRE, singlelineCommentsRE } from '../utils' +import { createServer, createServerModuleRunner } from '..' describe('optimizer-scan:script-test', () => { const scriptContent = `import { defineComponent } from 'vue' @@ -123,3 +125,46 @@ describe('optimizer-scan:script-test', () => { expect(ret).not.toContain('export default') }) }) + +test('scan jsx-runtime', async (ctx) => { + const server = await createServer({ + configFile: false, + logLevel: 'error', + root: path.join(import.meta.dirname, 'fixtures', 'scan-jsx-runtime'), + environments: { + client: { + // silence client optimizer + optimizeDeps: { + noDiscovery: true, + }, + }, + ssr: { + resolve: { + noExternal: true, + }, + optimizeDeps: { + force: true, + noDiscovery: false, + entries: ['./entry-jsx.tsx', './entry-no-jsx.js'], + }, + }, + }, + }) + + // start server to ensure optimizer run + await server.listen() + ctx.onTestFinished(() => server.close()) + + const runner = createServerModuleRunner(server.environments.ssr, { + hmr: { logger: false }, + }) + + // flush initial optimizer by importing any file + await runner.import('./entry-no-jsx.js') + + // verify jsx won't trigger optimizer re-run + const mod1 = await runner.import('./entry-jsx.js') + const mod2 = await runner.import('./entry-jsx.js') + expect((globalThis as any).__test_scan_jsx_runtime).toBe(1) + expect(mod1).toBe(mod2) +}) diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 0996b9e712d8b9..9dbf6784994f07 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -288,6 +288,9 @@ foo() // 2 // 3 `.trim() + const veryLongSource = Array.from({ length: 2000 }, (_, i) => `// ${i}`).join( + '\n', + ) const expectSnapshot = (value: string) => { try { @@ -340,6 +343,10 @@ foo() test('invalid start > end', () => { expectSnapshot(generateCodeFrame(source, 2, 0)) }) + + test('supports more than 1000 lines', () => { + expectSnapshot(generateCodeFrame(veryLongSource, { line: 1200, column: 0 })) + }) }) describe('getHash', () => { diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 233b0bb5cd5cc3..a4c009eab32c49 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs' import path from 'node:path' import colors from 'picocolors' import type { @@ -24,7 +23,6 @@ import commonjsPlugin from '@rollup/plugin-commonjs' import type { RollupCommonJSOptions } from 'dep-types/commonjs' import type { RollupDynamicImportVarsOptions } from 'dep-types/dynamicImportVars' import type { TransformOptions } from 'esbuild' -import { withTrailingSlash } from '../shared/utils' import { DEFAULT_ASSETS_INLINE_LIMIT, ESBUILD_BASELINE_WIDELY_AVAILABLE_TARGET, @@ -45,15 +43,12 @@ import { type TerserOptions, terserPlugin } from './plugins/terser' import { arraify, asyncFlatten, - copyDir, createDebugger, displayTime, - emptyDir, getPkgName, joinUrlSegments, mergeConfig, mergeWithDefaults, - normalizePath, partialEncodeURIPath, } from './utils' import { perEnvironmentPlugin } from './plugin' @@ -80,6 +75,12 @@ import { BasicMinimalPluginContext, basePluginContextMeta, } from './server/pluginContainer' +import { + isFutureDeprecationEnabled, + warnFutureDeprecation, +} from './deprecations' +import { prepareOutDirPlugin } from './plugins/prepareOutDir' +import type { Environment } from './environment' export interface BuildEnvironmentOptions { /** @@ -466,6 +467,7 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{ return { pre: [ completeSystemWrapPlugin(), + ...(!config.isWorker ? [prepareOutDirPlugin()] : []), perEnvironmentPlugin('commonjs', (environment) => { const { commonjsOptions } = environment.config.build const usePluginCommonjs = @@ -526,25 +528,12 @@ function resolveConfigToBuild( ) } -/** - * Build an App environment, or a App library (if libraryOptions is provided) - **/ -async function buildEnvironment( - environment: BuildEnvironment, -): Promise { +function resolveRollupOptions(environment: Environment) { const { root, packageCache, build: options } = environment.config const libOptions = options.lib const { logger } = environment const ssr = environment.config.consumer === 'server' - logger.info( - colors.cyan( - `vite v${VERSION} ${colors.green( - `building ${ssr ? `SSR bundle ` : ``}for ${environment.config.mode}...`, - )}`, - ), - ) - const resolve = (p: string) => path.resolve(root, p) const input = libOptions ? options.rollupOptions.input || @@ -606,182 +595,136 @@ async function buildEnvironment( }, } - /** - * The stack string usually contains a copy of the message at the start of the stack. - * If the stack starts with the message, we remove it and just return the stack trace - * portion. Otherwise the original stack trace is used. - */ - function extractStack(e: RollupError) { - const { stack, name = 'Error', message } = e - - // If we don't have a stack, not much we can do. - if (!stack) { - return stack - } + const isSsrTargetWebworkerEnvironment = + environment.name === 'ssr' && + environment.getTopLevelConfig().ssr?.target === 'webworker' - const expectedPrefix = `${name}: ${message}\n` - if (stack.startsWith(expectedPrefix)) { - return stack.slice(expectedPrefix.length) + const buildOutputOptions = (output: OutputOptions = {}): OutputOptions => { + // @ts-expect-error See https://github.com/vitejs/vite/issues/5812#issuecomment-984345618 + if (output.output) { + logger.warn( + `You've set "rollupOptions.output.output" in your config. ` + + `This is deprecated and will override all Vite.js default output options. ` + + `Please use "rollupOptions.output" instead.`, + ) } - - return stack - } - - /** - * Esbuild code frames have newlines at the start and end of the frame, rollup doesn't - * This function normalizes the frame to match the esbuild format which has more pleasing padding - */ - const normalizeCodeFrame = (frame: string) => { - const trimmedPadding = frame.replace(/^\n|\n$/g, '') - return `\n${trimmedPadding}\n` - } - - const enhanceRollupError = (e: RollupError) => { - const stackOnly = extractStack(e) - - let msg = colors.red((e.plugin ? `[${e.plugin}] ` : '') + e.message) - if (e.loc && e.loc.file && e.loc.file !== e.id) { - msg += `\nfile: ${colors.cyan( - `${e.loc.file}:${e.loc.line}:${e.loc.column}` + - (e.id ? ` (${e.id})` : ''), - )}` - } else if (e.id) { - msg += `\nfile: ${colors.cyan( - e.id + (e.loc ? `:${e.loc.line}:${e.loc.column}` : ''), - )}` + if (output.file) { + throw new Error( + `Vite does not support "rollupOptions.output.file". ` + + `Please use "rollupOptions.output.dir" and "rollupOptions.output.entryFileNames" instead.`, + ) } - if (e.frame) { - msg += `\n` + colors.yellow(normalizeCodeFrame(e.frame)) + if (output.sourcemap) { + logger.warnOnce( + colors.yellow( + `Vite does not support "rollupOptions.output.sourcemap". ` + + `Please use "build.sourcemap" instead.`, + ), + ) } - e.message = msg - - // We are rebuilding the stack trace to include the more detailed message at the top. - // Previously this code was relying on mutating e.message changing the generated stack - // when it was accessed, but we don't have any guarantees that the error we are working - // with hasn't already had its stack accessed before we get here. - if (stackOnly !== undefined) { - e.stack = `${e.message}\n${stackOnly}` + const format = output.format || 'es' + const jsExt = + (ssr && !isSsrTargetWebworkerEnvironment) || libOptions + ? resolveOutputJsExtension( + format, + findNearestPackageData(root, packageCache)?.data.type, + ) + : 'js' + return { + dir: outDir, + // Default format is 'es' for regular and for SSR builds + format, + exports: 'auto', + sourcemap: options.sourcemap, + name: libOptions ? libOptions.name : undefined, + hoistTransitiveImports: libOptions ? false : undefined, + // es2015 enables `generatedCode.symbols` + // - #764 add `Symbol.toStringTag` when build es module into cjs chunk + // - #1048 add `Symbol.toStringTag` for module default export + generatedCode: 'es2015', + entryFileNames: ssr + ? `[name].${jsExt}` + : libOptions + ? ({ name }) => + resolveLibFilename( + libOptions, + format, + name, + root, + jsExt, + packageCache, + ) + : path.posix.join(options.assetsDir, `[name]-[hash].${jsExt}`), + chunkFileNames: libOptions + ? `[name]-[hash].${jsExt}` + : path.posix.join(options.assetsDir, `[name]-[hash].${jsExt}`), + assetFileNames: libOptions + ? `[name].[ext]` + : path.posix.join(options.assetsDir, `[name]-[hash].[ext]`), + inlineDynamicImports: + output.format === 'umd' || + output.format === 'iife' || + (isSsrTargetWebworkerEnvironment && + (typeof input === 'string' || Object.keys(input).length === 1)), + ...output, } } - const outputBuildError = (e: RollupError) => { - enhanceRollupError(e) - clearLine() - logger.error(e.message, { error: e }) + // resolve lib mode outputs + const outputs = resolveBuildOutputs( + options.rollupOptions.output, + libOptions, + logger, + ) + + if (Array.isArray(outputs)) { + rollupOptions.output = outputs.map(buildOutputOptions) + } else { + rollupOptions.output = buildOutputOptions(outputs) } - const isSsrTargetWebworkerEnvironment = - environment.name === 'ssr' && - environment.getTopLevelConfig().ssr?.target === 'webworker' + return rollupOptions +} + +/** + * Build an App environment, or a App library (if libraryOptions is provided) + **/ +async function buildEnvironment( + environment: BuildEnvironment, +): Promise { + const { logger, config } = environment + const { root, build: options } = config + const ssr = config.consumer === 'server' + + logger.info( + colors.cyan( + `vite v${VERSION} ${colors.green( + `building ${ssr ? `SSR bundle ` : ``}for ${environment.config.mode}...`, + )}`, + ), + ) let bundle: RollupBuild | undefined let startTime: number | undefined try { - const buildOutputOptions = (output: OutputOptions = {}): OutputOptions => { - // @ts-expect-error See https://github.com/vitejs/vite/issues/5812#issuecomment-984345618 - if (output.output) { - logger.warn( - `You've set "rollupOptions.output.output" in your config. ` + - `This is deprecated and will override all Vite.js default output options. ` + - `Please use "rollupOptions.output" instead.`, - ) - } - if (output.file) { - throw new Error( - `Vite does not support "rollupOptions.output.file". ` + - `Please use "rollupOptions.output.dir" and "rollupOptions.output.entryFileNames" instead.`, - ) - } - if (output.sourcemap) { - logger.warnOnce( - colors.yellow( - `Vite does not support "rollupOptions.output.sourcemap". ` + - `Please use "build.sourcemap" instead.`, - ), - ) - } - - const format = output.format || 'es' - const jsExt = - (ssr && !isSsrTargetWebworkerEnvironment) || libOptions - ? resolveOutputJsExtension( - format, - findNearestPackageData(root, packageCache)?.data.type, - ) - : 'js' - return { - dir: outDir, - // Default format is 'es' for regular and for SSR builds - format, - exports: 'auto', - sourcemap: options.sourcemap, - name: libOptions ? libOptions.name : undefined, - hoistTransitiveImports: libOptions ? false : undefined, - // es2015 enables `generatedCode.symbols` - // - #764 add `Symbol.toStringTag` when build es module into cjs chunk - // - #1048 add `Symbol.toStringTag` for module default export - generatedCode: 'es2015', - entryFileNames: ssr - ? `[name].${jsExt}` - : libOptions - ? ({ name }) => - resolveLibFilename( - libOptions, - format, - name, - root, - jsExt, - packageCache, - ) - : path.posix.join(options.assetsDir, `[name]-[hash].${jsExt}`), - chunkFileNames: libOptions - ? `[name]-[hash].${jsExt}` - : path.posix.join(options.assetsDir, `[name]-[hash].${jsExt}`), - assetFileNames: libOptions - ? `[name].[ext]` - : path.posix.join(options.assetsDir, `[name]-[hash].[ext]`), - inlineDynamicImports: - output.format === 'umd' || - output.format === 'iife' || - (isSsrTargetWebworkerEnvironment && - (typeof input === 'string' || Object.keys(input).length === 1)), - ...output, - } - } - - // resolve lib mode outputs - const outputs = resolveBuildOutputs( - options.rollupOptions.output, - libOptions, - logger, - ) - const normalizedOutputs: OutputOptions[] = [] - - if (Array.isArray(outputs)) { - for (const resolvedOutput of outputs) { - normalizedOutputs.push(buildOutputOptions(resolvedOutput)) - } - } else { - normalizedOutputs.push(buildOutputOptions(outputs)) - } - - const resolvedOutDirs = getResolvedOutDirs( - root, - options.outDir, - options.rollupOptions.output, - ) - const emptyOutDir = resolveEmptyOutDir( - options.emptyOutDir, - root, - resolvedOutDirs, - logger, - ) + const rollupOptions = resolveRollupOptions(environment) // watch file changes with rollup if (options.watch) { logger.info(colors.cyan(`\nwatching for file changes...`)) + const resolvedOutDirs = getResolvedOutDirs( + root, + options.outDir, + options.rollupOptions.output, + ) + const emptyOutDir = resolveEmptyOutDir( + options.emptyOutDir, + root, + resolvedOutDirs, + logger, + ) const resolvedChokidarOptions = resolveChokidarOptions( options.watch.chokidar, resolvedOutDirs, @@ -792,7 +735,6 @@ async function buildEnvironment( const { watch } = await import('rollup') const watcher = watch({ ...rollupOptions, - output: normalizedOutputs, watch: { ...options.watch, chokidar: resolvedChokidarOptions, @@ -802,14 +744,14 @@ async function buildEnvironment( watcher.on('event', (event) => { if (event.code === 'BUNDLE_START') { logger.info(colors.cyan(`\nbuild started...`)) - if (options.write) { - prepareOutDir(resolvedOutDirs, emptyOutDir, environment) - } } else if (event.code === 'BUNDLE_END') { event.result.close() logger.info(colors.cyan(`built in ${event.duration}ms.`)) } else if (event.code === 'ERROR') { - outputBuildError(event.error) + const e = event.error + enhanceRollupError(e) + clearLine() + logger.error(e.message, { error: e }) } }) @@ -821,18 +763,14 @@ async function buildEnvironment( startTime = Date.now() bundle = await rollup(rollupOptions) - if (options.write) { - prepareOutDir(resolvedOutDirs, emptyOutDir, environment) - } - const res: RollupOutput[] = [] - for (const output of normalizedOutputs) { + for (const output of arraify(rollupOptions.output!)) { res.push(await bundle[options.write ? 'write' : 'generate'](output)) } logger.info( `${colors.green(`✓ built in ${displayTime(Date.now() - startTime)}`)}`, ) - return Array.isArray(outputs) ? res : res[0] + return Array.isArray(rollupOptions.output) ? res : res[0] } catch (e) { enhanceRollupError(e) clearLine() @@ -848,54 +786,65 @@ async function buildEnvironment( } } -function prepareOutDir( - outDirs: Set, - emptyOutDir: boolean | null, - environment: BuildEnvironment, -) { - const { publicDir } = environment.config - const outDirsArray = [...outDirs] - for (const outDir of outDirs) { - if (emptyOutDir !== false && fs.existsSync(outDir)) { - // skip those other outDirs which are nested in current outDir - const skipDirs = outDirsArray - .map((dir) => { - const relative = path.relative(outDir, dir) - if ( - relative && - !relative.startsWith('..') && - !path.isAbsolute(relative) - ) { - return relative - } - return '' - }) - .filter(Boolean) - emptyDir(outDir, [...skipDirs, '.git']) - } - if ( - environment.config.build.copyPublicDir && - publicDir && - fs.existsSync(publicDir) - ) { - if (!areSeparateFolders(outDir, publicDir)) { - environment.logger.warn( - colors.yellow( - `\n${colors.bold( - `(!)`, - )} The public directory feature may not work correctly. outDir ${colors.white( - colors.dim(outDir), - )} and publicDir ${colors.white( - colors.dim(publicDir), - )} are not separate folders.\n`, - ), - ) - } - copyDir(publicDir, outDir) - } +function enhanceRollupError(e: RollupError) { + const stackOnly = extractStack(e) + + let msg = colors.red((e.plugin ? `[${e.plugin}] ` : '') + e.message) + if (e.loc && e.loc.file && e.loc.file !== e.id) { + msg += `\nfile: ${colors.cyan( + `${e.loc.file}:${e.loc.line}:${e.loc.column}` + + (e.id ? ` (${e.id})` : ''), + )}` + } else if (e.id) { + msg += `\nfile: ${colors.cyan( + e.id + (e.loc ? `:${e.loc.line}:${e.loc.column}` : ''), + )}` + } + if (e.frame) { + msg += `\n` + colors.yellow(normalizeCodeFrame(e.frame)) + } + + e.message = msg + + // We are rebuilding the stack trace to include the more detailed message at the top. + // Previously this code was relying on mutating e.message changing the generated stack + // when it was accessed, but we don't have any guarantees that the error we are working + // with hasn't already had its stack accessed before we get here. + if (stackOnly !== undefined) { + e.stack = `${e.message}\n${stackOnly}` } } +/** + * The stack string usually contains a copy of the message at the start of the stack. + * If the stack starts with the message, we remove it and just return the stack trace + * portion. Otherwise the original stack trace is used. + */ +function extractStack(e: RollupError) { + const { stack, name = 'Error', message } = e + + // If we don't have a stack, not much we can do. + if (!stack) { + return stack + } + + const expectedPrefix = `${name}: ${message}\n` + if (stack.startsWith(expectedPrefix)) { + return stack.slice(expectedPrefix.length) + } + + return stack +} + +/** + * Esbuild code frames have newlines at the start and end of the frame, rollup doesn't + * This function normalizes the frame to match the esbuild format which has more pleasing padding + */ +function normalizeCodeFrame(frame: string) { + const trimmedPadding = frame.replace(/^\n|\n$/g, '') + return `\n${trimmedPadding}\n` +} + type JsExt = 'js' | 'cjs' | 'mjs' function resolveOutputJsExtension( @@ -1014,7 +963,7 @@ function clearLine() { export function onRollupLog( level: LogLevel, log: RollupLog, - environment: BuildEnvironment, + environment: Environment, ): void { const debugLogger = createDebugger('vite:build') const viteLog: LogOrStringHandler = (logLeveling, rawLogging) => { @@ -1131,7 +1080,7 @@ function isExternal(id: string, test: string | RegExp) { } export function injectEnvironmentToHooks( - environment: BuildEnvironment, + environment: Environment, plugin: Plugin, ): Plugin { const { resolveId, load, transform } = plugin @@ -1141,13 +1090,21 @@ export function injectEnvironmentToHooks( for (const hook of Object.keys(clone) as RollupPluginHooks[]) { switch (hook) { case 'resolveId': - clone[hook] = wrapEnvironmentResolveId(environment, resolveId) + clone[hook] = wrapEnvironmentResolveId( + environment, + resolveId, + plugin.name, + ) break case 'load': - clone[hook] = wrapEnvironmentLoad(environment, load) + clone[hook] = wrapEnvironmentLoad(environment, load, plugin.name) break case 'transform': - clone[hook] = wrapEnvironmentTransform(environment, transform) + clone[hook] = wrapEnvironmentTransform( + environment, + transform, + plugin.name, + ) break default: if (ROLLUP_HOOKS.includes(hook)) { @@ -1161,8 +1118,9 @@ export function injectEnvironmentToHooks( } function wrapEnvironmentResolveId( - environment: BuildEnvironment, - hook?: Plugin['resolveId'], + environment: Environment, + hook: Plugin['resolveId'] | undefined, + pluginName: string, ): Plugin['resolveId'] { if (!hook) return @@ -1172,7 +1130,7 @@ function wrapEnvironmentResolveId( injectEnvironmentInContext(this, environment), id, importer, - injectSsrFlag(options, environment), + injectSsrFlag(options, environment, pluginName), ) } @@ -1187,8 +1145,9 @@ function wrapEnvironmentResolveId( } function wrapEnvironmentLoad( - environment: BuildEnvironment, - hook?: Plugin['load'], + environment: Environment, + hook: Plugin['load'] | undefined, + pluginName: string, ): Plugin['load'] { if (!hook) return @@ -1197,7 +1156,7 @@ function wrapEnvironmentLoad( return fn.call( injectEnvironmentInContext(this, environment), id, - injectSsrFlag(args[0], environment), + injectSsrFlag(args[0], environment, pluginName), ) } @@ -1212,8 +1171,9 @@ function wrapEnvironmentLoad( } function wrapEnvironmentTransform( - environment: BuildEnvironment, - hook?: Plugin['transform'], + environment: Environment, + hook: Plugin['transform'] | undefined, + pluginName: string, ): Plugin['transform'] { if (!hook) return @@ -1223,7 +1183,7 @@ function wrapEnvironmentTransform( injectEnvironmentInContext(this, environment), code, importer, - injectSsrFlag(args[0], environment), + injectSsrFlag(args[0], environment, pluginName), ) } @@ -1238,7 +1198,7 @@ function wrapEnvironmentTransform( } function wrapEnvironmentHook( - environment: BuildEnvironment, + environment: Environment, hook?: Plugin[HookName], ): Plugin[HookName] { if (!hook) return @@ -1265,7 +1225,7 @@ function wrapEnvironmentHook( function injectEnvironmentInContext( context: Context, - environment: BuildEnvironment, + environment: Environment, ) { context.meta.viteVersion ??= VERSION context.environment ??= environment @@ -1273,13 +1233,37 @@ function injectEnvironmentInContext( } function injectSsrFlag>( - options?: T, - environment?: BuildEnvironment, + options: T | undefined, + environment: Environment, + pluginName: string, ): T & { ssr?: boolean } { - const ssr = environment ? environment.config.consumer === 'server' : true - return { ...(options ?? {}), ssr } as T & { + let ssr = environment.config.consumer === 'server' + const newOptions = { ...(options ?? {}), ssr } as T & { ssr?: boolean } + + if ( + isFutureDeprecationEnabled( + environment?.getTopLevelConfig(), + 'removePluginHookSsrArgument', + ) + ) { + Object.defineProperty(newOptions, 'ssr', { + get() { + warnFutureDeprecation( + environment?.getTopLevelConfig(), + 'removePluginHookSsrArgument', + `Used in plugin "${pluginName}".`, + ) + return ssr + }, + set(v) { + ssr = v + }, + }) + } + + return newOptions } /* @@ -1458,16 +1442,6 @@ export function toOutputFilePathWithoutRuntime( export const toOutputFilePathInCss = toOutputFilePathWithoutRuntime export const toOutputFilePathInHtml = toOutputFilePathWithoutRuntime -function areSeparateFolders(a: string, b: string) { - const na = normalizePath(a) - const nb = normalizePath(b) - return ( - na !== nb && - !na.startsWith(withTrailingSlash(nb)) && - !nb.startsWith(withTrailingSlash(na)) - ) -} - export class BuildEnvironment extends BaseEnvironment { mode = 'build' as const diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 4865b30aed697c..fa235befcb4572 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -19,6 +19,7 @@ import { DEFAULT_CLIENT_CONDITIONS, DEFAULT_CLIENT_MAIN_FIELDS, DEFAULT_CONFIG_FILES, + DEFAULT_EXTERNAL_CONDITIONS, DEFAULT_PREVIEW_PORT, DEFAULT_SERVER_CONDITIONS, DEFAULT_SERVER_MAIN_FIELDS, @@ -390,7 +391,7 @@ export interface UserConfig extends DefaultEnvironmentOptions { /** * Options to opt-in to future behavior */ - future?: FutureOptions + future?: FutureOptions | 'warn' /** * Legacy options * @@ -482,8 +483,11 @@ export interface FutureOptions { removePluginHookSsrArgument?: 'warn' removeServerModuleGraph?: 'warn' + removeServerReloadModule?: 'warn' + removeServerPluginContainer?: 'warn' removeServerHot?: 'warn' removeServerTransformRequest?: 'warn' + removeServerWarmupRequest?: 'warn' removeSsrLoadModule?: 'warn' } @@ -555,6 +559,7 @@ export interface ResolvedConfig | 'dev' | 'environments' | 'experimental' + | 'future' | 'server' | 'preview' > & { @@ -613,6 +618,7 @@ export interface ResolvedConfig worker: ResolvedWorkerOptions appType: AppType experimental: RequiredExceptFor + future: FutureOptions | undefined environments: Record /** * The token to connect to the WebSocket server from browsers. @@ -651,7 +657,7 @@ export const configDefaults = Object.freeze({ resolve: { // mainFields // conditions - externalConditions: ['node'], + externalConditions: [...DEFAULT_EXTERNAL_CONDITIONS], extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'], dedupe: [], /** @experimental */ @@ -702,6 +708,7 @@ export const configDefaults = Object.freeze({ removeServerModuleGraph: undefined, removeServerHot: undefined, removeServerTransformRequest: undefined, + removeServerWarmupRequest: undefined, removeSsrLoadModule: undefined, }, legacy: { @@ -1168,16 +1175,21 @@ export async function resolveConfig( configEnvironmentsSsr.optimizeDeps ?? {}, ) + // merge with `resolve` as the root to merge `noExternal` correctly configEnvironmentsSsr.resolve = mergeConfig( { - conditions: config.ssr?.resolve?.conditions, - externalConditions: config.ssr?.resolve?.externalConditions, - mainFields: config.ssr?.resolve?.mainFields, - external: config.ssr?.external, - noExternal: config.ssr?.noExternal, - } satisfies EnvironmentResolveOptions, - configEnvironmentsSsr.resolve ?? {}, - ) + resolve: { + conditions: config.ssr?.resolve?.conditions, + externalConditions: config.ssr?.resolve?.externalConditions, + mainFields: config.ssr?.resolve?.mainFields, + external: config.ssr?.external, + noExternal: config.ssr?.noExternal, + }, + } satisfies EnvironmentOptions, + { + resolve: configEnvironmentsSsr.resolve ?? {}, + }, + ).resolve } if (config.build?.ssrEmitAssets !== undefined) { @@ -1529,7 +1541,20 @@ export async function resolveConfig( configDefaults.experimental, config.experimental ?? {}, ), - future: config.future, + future: + config.future === 'warn' + ? ({ + removePluginHookHandleHotUpdate: 'warn', + removePluginHookSsrArgument: 'warn', + removeServerModuleGraph: 'warn', + removeServerReloadModule: 'warn', + removeServerPluginContainer: 'warn', + removeServerHot: 'warn', + removeServerTransformRequest: 'warn', + removeServerWarmupRequest: 'warn', + removeSsrLoadModule: 'warn', + } satisfies Required) + : config.future, ssr, @@ -1921,6 +1946,7 @@ async function bundleConfigFile( 'import.meta.url': importMetaUrlVarName, 'import.meta.dirname': dirnameVarName, 'import.meta.filename': filenameVarName, + 'import.meta.main': 'false', }, plugins: [ { diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index ce204b1c04e30b..f55d3675fddd10 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -64,6 +64,11 @@ export const DEFAULT_SERVER_CONDITIONS = Object.freeze( DEFAULT_CONDITIONS.filter((c) => c !== 'browser'), ) +export const DEFAULT_EXTERNAL_CONDITIONS = Object.freeze([ + 'node', + 'module-sync', +]) + /** * The browser versions that are included in the Baseline Widely Available on 2025-05-01. * diff --git a/packages/vite/src/node/deprecations.ts b/packages/vite/src/node/deprecations.ts index aab1e8045aa0fc..be0efc1cef366a 100644 --- a/packages/vite/src/node/deprecations.ts +++ b/packages/vite/src/node/deprecations.ts @@ -8,8 +8,11 @@ const deprecationCode = { removePluginHookHandleHotUpdate: 'changes/hotupdate-hook', removeServerModuleGraph: 'changes/per-environment-apis', + removeServerReloadModule: 'changes/per-environment-apis', + removeServerPluginContainer: 'changes/per-environment-apis', removeServerHot: 'changes/per-environment-apis', removeServerTransformRequest: 'changes/per-environment-apis', + removeServerWarmupRequest: 'changes/per-environment-apis', removeSsrLoadModule: 'changes/ssr-using-modulerunner', } satisfies Record @@ -22,9 +25,15 @@ const deprecationMessages = { removeServerModuleGraph: 'The `server.moduleGraph` is replaced with `this.environment.moduleGraph`.', + removeServerReloadModule: + 'The `server.reloadModule` is replaced with `environment.reloadModule`.', + removeServerPluginContainer: + 'The `server.pluginContainer` is replaced with `this.environment.pluginContainer`.', removeServerHot: 'The `server.hot` is replaced with `this.environment.hot`.', removeServerTransformRequest: 'The `server.transformRequest` is replaced with `this.environment.transformRequest`.', + removeServerWarmupRequest: + 'The `server.warmupRequest` is replaced with `this.environment.warmupRequest`.', removeSsrLoadModule: 'The `server.ssrLoadModule` is replaced with Environment Runner.', @@ -32,6 +41,13 @@ const deprecationMessages = { let _ignoreDeprecationWarnings = false +export function isFutureDeprecationEnabled( + config: ResolvedConfig, + type: keyof FutureOptions, +): boolean { + return !!config.future?.[type] +} + // Later we could have a `warnDeprecation` utils when the deprecation is landed /** * Warn about future deprecations. diff --git a/packages/vite/src/node/http.ts b/packages/vite/src/node/http.ts index 32c47461f7fd56..3d0a09d0b5a18c 100644 --- a/packages/vite/src/node/http.ts +++ b/packages/vite/src/node/http.ts @@ -48,8 +48,8 @@ export interface CommonServerOptions { /** * Configure custom proxy rules for the dev server. Expects an object * of `{ key: options }` pairs. - * Uses [`http-proxy`](https://github.com/http-party/node-http-proxy). - * Full options [here](https://github.com/http-party/node-http-proxy#options). + * Uses [`http-proxy-3`](https://github.com/sagemathinc/http-proxy-3). + * Full options [here](https://github.com/sagemathinc/http-proxy-3#options). * * Example `vite.config.js`: * ``` js diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index ff5ce7efba6e77..de73b7814f663e 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -53,6 +53,7 @@ export { VERSION as version, DEFAULT_CLIENT_CONDITIONS as defaultClientConditions, DEFAULT_CLIENT_MAIN_FIELDS as defaultClientMainFields, + DEFAULT_EXTERNAL_CONDITIONS as defaultExternalConditions, DEFAULT_SERVER_CONDITIONS as defaultServerConditions, DEFAULT_SERVER_MAIN_FIELDS as defaultServerMainFields, defaultAllowedOrigins, @@ -250,7 +251,7 @@ export type { } from 'dep-types/alias' export type { Connect } from 'dep-types/connect' export type { WebSocket, WebSocketAlias } from 'dep-types/ws' -export type { HttpProxy } from 'dep-types/http-proxy' +export type * as HttpProxy from 'http-proxy-3' export type { FSWatcher, WatchOptions } from 'dep-types/chokidar' export type { Terser } from 'types/internal/terserOptions' export type { RollupCommonJSOptions } from 'dep-types/commonjs' diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 8c9ba6d29e1b83..db698b8c3b722c 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -5,7 +5,7 @@ import { promisify } from 'node:util' import { performance } from 'node:perf_hooks' import colors from 'picocolors' import type { BuildContext, BuildOptions as EsbuildBuildOptions } from 'esbuild' -import esbuild, { build } from 'esbuild' +import esbuild, { build, formatMessages } from 'esbuild' import { init, parse } from 'es-module-lexer' import { isDynamicPattern } from 'tinyglobby' import type { ResolvedConfig } from '../config' @@ -724,12 +724,24 @@ export function runOptimizeDeps( return successfulResult }) - .catch((e) => { + .catch(async (e) => { if (e.errors && e.message.includes('The build was canceled')) { // esbuild logs an error when cancelling, but this is expected so // return an empty result instead return cancelledResult } + const prependMessage = colors.red( + 'Error during dependency optimization:\n\n', + ) + if (e.errors) { + const msgs = await formatMessages(e.errors, { + kind: 'error', + color: true, + }) + e.message = prependMessage + msgs.join('\n') + } else { + e.message = prependMessage + e.message + } throw e }) .finally(() => { diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 6cd237350a7c13..569e7bf2b493e2 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -294,8 +294,31 @@ async function prepareEsbuildScanner( const { tsconfig } = await loadTsconfigJsonForFile( path.join(environment.config.root, '_dummy.js'), ) - if (tsconfig.compilerOptions?.experimentalDecorators) { - tsconfigRaw = { compilerOptions: { experimentalDecorators: true } } + if ( + tsconfig.compilerOptions?.experimentalDecorators || + tsconfig.compilerOptions?.jsx || + tsconfig.compilerOptions?.jsxFactory || + tsconfig.compilerOptions?.jsxFragmentFactory || + tsconfig.compilerOptions?.jsxImportSource + ) { + tsconfigRaw = { + compilerOptions: { + experimentalDecorators: + tsconfig.compilerOptions?.experimentalDecorators, + // esbuild uses tsconfig fields when both the normal options and tsconfig was set + // but we want to prioritize the normal options + jsx: esbuildOptions.jsx ? undefined : tsconfig.compilerOptions?.jsx, + jsxFactory: esbuildOptions.jsxFactory + ? undefined + : tsconfig.compilerOptions?.jsxFactory, + jsxFragmentFactory: esbuildOptions.jsxFragment + ? undefined + : tsconfig.compilerOptions?.jsxFragmentFactory, + jsxImportSource: esbuildOptions.jsxImportSource + ? undefined + : tsconfig.compilerOptions?.jsxImportSource, + }, + } } } @@ -310,6 +333,7 @@ async function prepareEsbuildScanner( format: 'esm', logLevel: 'silent', plugins: [...plugins, plugin], + jsxDev: !environment.config.isProduction, ...esbuildOptions, tsconfigRaw, }) diff --git a/packages/vite/src/node/plugin.ts b/packages/vite/src/node/plugin.ts index 1dad6098a4fac9..08940187b54456 100644 --- a/packages/vite/src/node/plugin.ts +++ b/packages/vite/src/node/plugin.ts @@ -144,10 +144,6 @@ export interface Plugin extends RollupPlugin { id: string, options?: { ssr?: boolean - /** - * @internal - */ - html?: boolean }, ) => Promise | LoadResult, { filter?: { id?: StringFilter } } diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index a0054bf4459a16..74f0220a4e9c19 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -42,7 +42,6 @@ const jsSourceMapRE = /\.[cm]?js\.map$/ export const noInlineRE = /[?&]no-inline\b/ export const inlineRE = /[?&]inline\b/ -const svgExtRE = /\.svg(?:$|\?)/ const assetCache = new WeakMap>() @@ -167,13 +166,14 @@ export function assetPlugin(config: ResolvedConfig): Plugin { }, load: { - async handler(id) { - if (id[0] === '\0') { + filter: { + id: { // Rollup convention, this id should be handled by the // plugin that marked it with \0 - return - } - + exclude: /^\0/, + }, + }, + async handler(id) { // raw requests, read from disk if (rawRE.test(id)) { const file = checkPublicFile(id, config) || cleanUrl(id) @@ -308,7 +308,7 @@ export async function fileToDevUrl( // If is svg and it's inlined in build, also inline it in dev to match // the behaviour in build due to quote handling differences. const cleanedId = cleanUrl(id) - if (svgExtRE.test(cleanedId)) { + if (cleanedId.endsWith('.svg')) { const file = publicFile || cleanedId const content = await fsp.readFile(file) if (shouldInline(environment, file, id, content, undefined, undefined)) { diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index 50a13e1b85aa78..74af98cc62a6f0 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -1,6 +1,7 @@ import path from 'node:path' import MagicString from 'magic-string' import { stripLiteral } from 'strip-literal' +import { exactRegex } from '@rolldown/pluginutils' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '../config' import { @@ -8,6 +9,7 @@ import { isDataUrl, isParentDirectory, transformStableResult, + tryStatSync, } from '../utils' import { CLIENT_ENTRY } from '../constants' import { slash } from '../../shared/utils' @@ -50,122 +52,124 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { }, transform: { + filter: { + id: { + exclude: [exactRegex(preloadHelperId), exactRegex(CLIENT_ENTRY)], + }, + code: /new\s+URL.+import\.meta\.url/, + }, async handler(code, id) { - if ( - id !== preloadHelperId && - id !== CLIENT_ENTRY && - code.includes('new URL') && - code.includes(`import.meta.url`) - ) { - let s: MagicString | undefined - const assetImportMetaUrlRE = - /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg - const cleanString = stripLiteral(code) - - let match: RegExpExecArray | null - while ((match = assetImportMetaUrlRE.exec(cleanString))) { - const [[startIndex, endIndex], [urlStart, urlEnd]] = match.indices! - if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continue + let s: MagicString | undefined + const assetImportMetaUrlRE = + /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg + const cleanString = stripLiteral(code) - const rawUrl = code.slice(urlStart, urlEnd) + let match: RegExpExecArray | null + while ((match = assetImportMetaUrlRE.exec(cleanString))) { + const [[startIndex, endIndex], [urlStart, urlEnd]] = match.indices! + if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continue - if (!s) s = new MagicString(code) + const rawUrl = code.slice(urlStart, urlEnd) - // potential dynamic template string - if (rawUrl[0] === '`' && rawUrl.includes('${')) { - const queryDelimiterIndex = getQueryDelimiterIndex(rawUrl) - const hasQueryDelimiter = queryDelimiterIndex !== -1 - const pureUrl = hasQueryDelimiter - ? rawUrl.slice(0, queryDelimiterIndex) + '`' - : rawUrl - const queryString = hasQueryDelimiter - ? rawUrl.slice(queryDelimiterIndex, -1) - : '' - const ast = this.parse(pureUrl) - const templateLiteral = (ast as any).body[0].expression - if (templateLiteral.expressions.length) { - const pattern = buildGlobPattern(templateLiteral) - if (pattern.startsWith('*')) { - // don't transform for patterns like this - // because users won't intend to do that in most cases - continue - } + if (!s) s = new MagicString(code) - const globOptions = { - eager: true, - import: 'default', - // A hack to allow 'as' & 'query' exist at the same time - query: injectQuery(queryString, 'url'), - } - s.update( - startIndex, - endIndex, - `new URL((import.meta.glob(${JSON.stringify( - pattern, - )}, ${JSON.stringify( - globOptions, - )}))[${pureUrl}], import.meta.url)`, - ) + // potential dynamic template string + if (rawUrl[0] === '`' && rawUrl.includes('${')) { + const queryDelimiterIndex = getQueryDelimiterIndex(rawUrl) + const hasQueryDelimiter = queryDelimiterIndex !== -1 + const pureUrl = hasQueryDelimiter + ? rawUrl.slice(0, queryDelimiterIndex) + '`' + : rawUrl + const queryString = hasQueryDelimiter + ? rawUrl.slice(queryDelimiterIndex, -1) + : '' + const ast = this.parse(pureUrl) + const templateLiteral = (ast as any).body[0].expression + if (templateLiteral.expressions.length) { + const pattern = buildGlobPattern(templateLiteral) + if (pattern.startsWith('*')) { + // don't transform for patterns like this + // because users won't intend to do that in most cases continue } - } - const url = rawUrl.slice(1, -1) - if (isDataUrl(url)) { + const globOptions = { + eager: true, + import: 'default', + // A hack to allow 'as' & 'query' exist at the same time + query: injectQuery(queryString, 'url'), + } + s.update( + startIndex, + endIndex, + `new URL((import.meta.glob(${JSON.stringify( + pattern, + )}, ${JSON.stringify( + globOptions, + )}))[${pureUrl}], import.meta.url)`, + ) continue } - let file: string | undefined - if (url[0] === '.') { - file = slash(path.resolve(path.dirname(id), url)) - file = tryFsResolve(file, fsResolveOptions) ?? file - } else { - assetResolver ??= createBackCompatIdResolver(config, { - extensions: [], - mainFields: [], - tryIndex: false, - preferRelative: true, - }) - file = await assetResolver(this.environment, url, id) - file ??= - url[0] === '/' - ? slash(path.join(publicDir, url)) - : slash(path.resolve(path.dirname(id), url)) - } + } - // Get final asset URL. If the file does not exist, - // we fall back to the initial URL and let it resolve in runtime - let builtUrl: string | undefined - if (file) { - try { - if (publicDir && isParentDirectory(publicDir, file)) { - const publicPath = '/' + path.posix.relative(publicDir, file) - builtUrl = await fileToUrl(this, publicPath) - } else { - builtUrl = await fileToUrl(this, file) + const url = rawUrl.slice(1, -1) + if (isDataUrl(url)) { + continue + } + let file: string | undefined + if (url[0] === '.') { + file = slash(path.resolve(path.dirname(id), url)) + file = tryFsResolve(file, fsResolveOptions) ?? file + } else { + assetResolver ??= createBackCompatIdResolver(config, { + extensions: [], + mainFields: [], + tryIndex: false, + preferRelative: true, + }) + file = await assetResolver(this.environment, url, id) + file ??= + url[0] === '/' + ? slash(path.join(publicDir, url)) + : slash(path.resolve(path.dirname(id), url)) + } + + // Get final asset URL. If the file does not exist, + // we fall back to the initial URL and let it resolve in runtime + let builtUrl: string | undefined + if (file) { + try { + if (publicDir && isParentDirectory(publicDir, file)) { + const publicPath = '/' + path.posix.relative(publicDir, file) + builtUrl = await fileToUrl(this, publicPath) + } else { + builtUrl = await fileToUrl(this, file) + // during dev, builtUrl may point to a directory or a non-existing file + if (tryStatSync(file)?.isFile()) { + this.addWatchFile(file) } - } catch { - // do nothing, we'll log a warning after this } + } catch { + // do nothing, we'll log a warning after this } - if (!builtUrl) { - const rawExp = code.slice(startIndex, endIndex) - config.logger.warnOnce( - `\n${rawExp} doesn't exist at build time, it will remain unchanged to be resolved at runtime. ` + - `If this is intended, you can use the /* @vite-ignore */ comment to suppress this warning.`, - ) - builtUrl = url - } - s.update( - startIndex, - endIndex, - `new URL(${JSON.stringify(builtUrl)}, import.meta.url)`, - ) } - if (s) { - return transformStableResult(s, id, config) + if (!builtUrl) { + const rawExp = code.slice(startIndex, endIndex) + config.logger.warnOnce( + `\n${rawExp} doesn't exist at build time, it will remain unchanged to be resolved at runtime. ` + + `If this is intended, you can use the /* @vite-ignore */ comment to suppress this warning.`, + ) + builtUrl = url } + s.update( + startIndex, + endIndex, + `new URL(${JSON.stringify(builtUrl)}, import.meta.url)`, + ) + } + if (s) { + return transformStableResult(s, id, config) } - return null }, }, } diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index ffe4c4bf41b2bc..bc6abca56b2366 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -98,7 +98,6 @@ import { addToHTMLProxyTransformResult } from './html' import { assetUrlRE, cssEntriesMap, - fileToDevUrl, fileToUrl, publicAssetUrlCache, publicAssetUrlRE, @@ -334,9 +333,10 @@ export function cssPlugin(config: ResolvedConfig): Plugin { }, load: { + filter: { + id: CSS_LANGS_RE, + }, async handler(id) { - if (!isCSSRequest(id)) return - if (urlRE.test(id)) { if (isModuleCSSRequest(id)) { throw new Error( @@ -361,15 +361,13 @@ export function cssPlugin(config: ResolvedConfig): Plugin { }, }, transform: { + filter: { + id: { + include: CSS_LANGS_RE, + exclude: [commonjsProxyRE, SPECIAL_QUERY_RE], + }, + }, async handler(raw, id) { - if ( - !isCSSRequest(id) || - commonjsProxyRE.test(id) || - SPECIAL_QUERY_RE.test(id) - ) { - return - } - const { environment } = this const resolveUrl = (url: string, importer?: string) => idResolver(environment, url, importer) @@ -509,15 +507,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { }, transform: { + filter: { + id: { + include: CSS_LANGS_RE, + exclude: [commonjsProxyRE, SPECIAL_QUERY_RE], + }, + }, async handler(css, id) { - if ( - !isCSSRequest(id) || - commonjsProxyRE.test(id) || - SPECIAL_QUERY_RE.test(id) - ) { - return - } - css = stripBomTag(css) // cache css compile result to map @@ -624,7 +620,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { }, async renderChunk(code, chunk, opts, meta) { - let chunkCSS = '' + let chunkCSS: string | undefined const renderedModules = new Proxy( {} as Record, { @@ -665,7 +661,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { isPureCssChunk = false } - chunkCSS += styles.get(id) + chunkCSS = (chunkCSS || '') + styles.get(id) } else if (!isJsChunkEmpty) { // if the module does not have a style, then it's not a pure css chunk. // this is true because in the `transform` hook above, only modules @@ -828,7 +824,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { } } - if (chunkCSS) { + if (chunkCSS !== undefined) { if (isPureCssChunk && (opts.format === 'es' || opts.format === 'cjs')) { // this is a shared CSS-only chunk that is empty. pureCssChunks.add(chunk) @@ -856,7 +852,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { // wait for previous tasks as well chunkCSS = await codeSplitEmitQueue.run(async () => { - return finalizeCss(chunkCSS, true, config) + return finalizeCss(chunkCSS!, true, config) }) // emit corresponding css file @@ -1073,15 +1069,13 @@ export function cssAnalysisPlugin(config: ResolvedConfig): Plugin { name: 'vite:css-analysis', transform: { + filter: { + id: { + include: CSS_LANGS_RE, + exclude: [commonjsProxyRE, SPECIAL_QUERY_RE], + }, + }, async handler(_, id) { - if ( - !isCSSRequest(id) || - commonjsProxyRE.test(id) || - SPECIAL_QUERY_RE.test(id) - ) { - return - } - const { moduleGraph } = this.environment as DevEnvironment const thisModule = moduleGraph.getModuleById(id) @@ -1101,20 +1095,7 @@ export function cssAnalysisPlugin(config: ResolvedConfig): Plugin { // main import to hot update const depModules = new Set() for (const file of pluginImports) { - if (isCSSRequest(file)) { - depModules.add(moduleGraph.createFileOnlyEntry(file)) - } else { - const url = await fileToDevUrl( - this.environment, - file, - /* skipBase */ true, - ) - if (url.startsWith('data:')) { - depModules.add(moduleGraph.createFileOnlyEntry(file)) - } else { - depModules.add(await moduleGraph.ensureEntryFromUrl(url)) - } - } + depModules.add(moduleGraph.createFileOnlyEntry(file)) } moduleGraph.updateModuleInfo( thisModule, @@ -2040,7 +2021,10 @@ function skipUrlReplacer(unquotedUrl: string) { isExternalUrl(unquotedUrl) || isDataUrl(unquotedUrl) || unquotedUrl[0] === '#' || - functionCallRE.test(unquotedUrl) + functionCallRE.test(unquotedUrl) || + // skip if it is already a placeholder + unquotedUrl.startsWith('__VITE_ASSET__') || + unquotedUrl.startsWith('__VITE_PUBLIC_ASSET__') ) } async function doUrlReplace( diff --git a/packages/vite/src/node/plugins/dynamicImportVars.ts b/packages/vite/src/node/plugins/dynamicImportVars.ts index 1c16e7c7697218..e993bd2aa33bec 100644 --- a/packages/vite/src/node/plugins/dynamicImportVars.ts +++ b/packages/vite/src/node/plugins/dynamicImportVars.ts @@ -4,6 +4,7 @@ import { init, parse as parseImports } from 'es-module-lexer' import type { ImportSpecifier } from 'es-module-lexer' import { parseAst } from 'rollup/parseAst' import { dynamicImportToGlob } from '@rollup/plugin-dynamic-import-vars' +import { exactRegex } from '@rolldown/pluginutils' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '../config' import { CLIENT_ENTRY } from '../constants' @@ -181,29 +182,27 @@ export function dynamicImportVarsPlugin(config: ResolvedConfig): Plugin { name: 'vite:dynamic-import-vars', resolveId: { + filter: { id: exactRegex(dynamicImportHelperId) }, handler(id) { - if (id === dynamicImportHelperId) { - return id - } + return id }, }, load: { - handler(id) { - if (id === dynamicImportHelperId) { - return `export default ${dynamicImportHelper.toString()}` - } + filter: { id: exactRegex(dynamicImportHelperId) }, + handler(_id) { + return `export default ${dynamicImportHelper.toString()}` }, }, transform: { + filter: { + id: { exclude: exactRegex(CLIENT_ENTRY) }, + code: hasDynamicImportRE, + }, async handler(source, importer) { const { environment } = this - if ( - !getFilter(this)(importer) || - importer === CLIENT_ENTRY || - !hasDynamicImportRE.test(source) - ) { + if (!getFilter(this)(importer)) { return } diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index 56e328974cd708..4bdc175c72e5dc 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -9,7 +9,12 @@ import type { } from 'rollup' import MagicString from 'magic-string' import colors from 'picocolors' -import type { DefaultTreeAdapterMap, ParserError, Token } from 'parse5' +import type { + DefaultTreeAdapterMap, + ErrorCodes, + ParserError, + Token, +} from 'parse5' import { stripLiteral } from 'strip-literal' import escapeHtml from 'escape-html' import type { MinimalPluginContextWithoutEnvironment, Plugin } from '../plugin' @@ -34,6 +39,7 @@ import { resolveEnvPrefix } from '../env' import { cleanUrl } from '../../shared/utils' import { perEnvironmentState } from '../environment' import { getNodeAssetAttributes } from '../assetSource' +import type { Logger } from '../logger' import { assetUrlRE, getPublicAssetFilename, @@ -96,14 +102,14 @@ export function htmlInlineProxyPlugin(config: ResolvedConfig): Plugin { name: 'vite:html-inline-proxy', resolveId: { + filter: { id: isHtmlProxyRE }, handler(id) { - if (isHTMLProxy(id)) { - return id - } + return id }, }, load: { + filter: { id: isHtmlProxyRE }, handler(id) { const proxyMatch = htmlProxyRE.exec(id) if (proxyMatch) { @@ -184,21 +190,29 @@ function traverseNodes( } } +type ParseWarnings = Partial> + export async function traverseHtml( html: string, filePath: string, + warn: Logger['warn'], visitor: (node: DefaultTreeAdapterMap['node']) => void, ): Promise { // lazy load compiler const { parse } = await import('parse5') + const warnings: ParseWarnings = {} const ast = parse(html, { scriptingEnabled: false, // parse inside