diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd3e7b7240c426..5e1509a5c73328 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -198,9 +198,3 @@ jobs: - name: Test docs run: pnpm run test-docs - - # From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions - - name: Check workflow files - run: | - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) - ./actionlint -color -shellcheck="" diff --git a/.github/workflows/issue-close-require.yml b/.github/workflows/issue-close-require.yml index 2c1bed4167f8b3..26b25f5678cca2 100644 --- a/.github/workflows/issue-close-require.yml +++ b/.github/workflows/issue-close-require.yml @@ -13,12 +13,8 @@ jobs: issues: write # for actions/issues-helper to update issues pull-requests: write # for actions/issues-helper to update PRs steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - name: needs reproduction - uses: ./.github/actions/issues-helper + uses: $/.github/actions/issues-helper with: actions: "close-issues" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index d68cbd74adc0cb..8d41a338810f90 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -9,17 +9,12 @@ jobs: if: github.repository == 'vitejs/vite' runs-on: ubuntu-slim permissions: - contents: read # to check out the repo for local actions issues: write # for actions/issues-helper to update issues pull-requests: write # for actions/issues-helper to update PRs steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - name: contribution welcome if: github.event.label.name == 'contribution welcome' || github.event.label.name == 'help wanted' - uses: ./.github/actions/issues-helper + uses: $/.github/actions/issues-helper with: actions: "remove-labels" token: ${{ secrets.GITHUB_TOKEN }} @@ -28,7 +23,7 @@ jobs: - name: remove pending if: (github.event.label.name == 'enhancement' || contains(github.event.label.description, '(priority)') || github.event.label.name == 'needs reproduction') && contains(github.event.issue.labels.*.name, 'pending triage') - uses: ./.github/actions/issues-helper + uses: $/.github/actions/issues-helper with: actions: "remove-labels" token: ${{ secrets.GITHUB_TOKEN }} @@ -37,7 +32,7 @@ jobs: - name: needs reproduction if: github.event.label.name == 'needs reproduction' - uses: ./.github/actions/issues-helper + uses: $/.github/actions/issues-helper with: actions: "create-comment" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lock-closed-issues.yml b/.github/workflows/lock-closed-issues.yml index a67244440f6c81..835b145ae6876d 100644 --- a/.github/workflows/lock-closed-issues.yml +++ b/.github/workflows/lock-closed-issues.yml @@ -5,7 +5,6 @@ on: - cron: "0 0 * * *" permissions: - contents: read # to check out the repo for local actions issues: write # for actions/issues-helper to update issues jobs: @@ -13,11 +12,7 @@ jobs: if: github.repository == 'vitejs/vite' runs-on: ubuntu-slim steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - - uses: ./.github/actions/issues-helper + - uses: $/.github/actions/issues-helper with: actions: "lock-closed-issues" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/config/server-options.md b/docs/config/server-options.md index 8c09c5395670ff..e960658162b48c 100644 --- a/docs/config/server-options.md +++ b/docs/config/server-options.md @@ -160,6 +160,12 @@ export default defineConfig({ }) ``` +::: warning Origin check for WebSockets + +Vite does not check the origin of WebSocket requests before proxying. The proxy target is expected to check the `Origin` header or other checks. Note that the `rewriteWsOrigin` option will rewrite the origin to the target origin and will cause the origin check to be bypassed. + +::: + ## server.cors - **Type:** `boolean | CorsOptions` diff --git a/docs/guide/api-environment-plugins.md b/docs/guide/api-environment-plugins.md index 959cf5af6e1271..cc0b7994bc2b6e 100644 --- a/docs/guide/api-environment-plugins.md +++ b/docs/guide/api-environment-plugins.md @@ -103,7 +103,7 @@ interface HotUpdateOptions { - `modules` is an array of modules in this environment that are affected by the changed file. It's an array because a single file may map to multiple served modules (e.g. Vue SFCs). -- `read` is an async read function that returns the content of the file. This is provided because, on some systems, the file change callback may fire too fast before the editor finishes updating the file, and direct `fs.readFile` will return empty content. The read function passed in normalizes this behavior. +- `read` is an async read function that returns the content of the file. This is provided because on some systems, the file change callback may fire too fast before the editor finishes updating the file, and direct `fs.readFile` will return empty content. The read function passed in normalizes this behavior. The hook can choose to: diff --git a/docs/guide/build.md b/docs/guide/build.md index 14534530d20f07..0bf99492ffd73c 100644 --- a/docs/guide/build.md +++ b/docs/guide/build.md @@ -80,7 +80,7 @@ window.addEventListener('vite:preloadError', (event) => { }) ``` -When a new deployment occurs, the hosting service may delete the assets from previous deployments. As a result, a user who visited your site before the new deployment might encounter an import error. This error happens because the assets running on that user's device are outdated and it tries to import the corresponding old chunk, which is deleted. This event is useful for addressing this situation. In this case, make sure to set `Cache-Control: no-cache` on the HTML file, otherwise the old assets will be still referenced. +When a new deployment occurs, the hosting service may delete the assets from previous deployments. As a result, a user who visited your site before the new deployment might encounter an import error. This error happens because the assets running on that user's device are outdated and the code tries to import the corresponding old chunk, which is deleted. This event is useful for addressing this situation. In this case, make sure to set `Cache-Control: no-cache` on the HTML file, otherwise the old assets will be still referenced. ## Rebuild on Files Changes diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 39d212a1567c42..6c477b0ea43c18 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -64,7 +64,7 @@ vite build [root] | `-w, --watch` | Rebuilds when modules have changed on disk (`boolean`) | | `-c, --config ` | Use specified config file (`string`) | | `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | +| `-l, --logLevel ` | info \| warn \| error \| silent (`string`) | | `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | | `--configLoader ` | Use `bundle` to bundle the config with Rolldown, or `runner` (experimental) to process it on the fly, or `native` (experimental) to load using the native runtime (default: `bundle`) | | `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](/guide/troubleshooting#performance-bottlenecks)) | @@ -95,7 +95,7 @@ vite optimize [root] | `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | | `-c, --config ` | Use specified config file (`string`) | | `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | +| `-l, --logLevel ` | info \| warn \| error \| silent (`string`) | | `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | | `--configLoader ` | Use `bundle` to bundle the config with Rolldown, or `runner` (experimental) to process it on the fly, or `native` (experimental) to load using the native runtime (default: `bundle`) | | `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | @@ -126,7 +126,7 @@ vite preview [root] | `--outDir ` | Output directory (default: `dist`) (`string`) | | `-c, --config ` | Use specified config file (`string`) | | `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | +| `-l, --logLevel ` | info \| warn \| error \| silent (`string`) | | `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | | `--configLoader ` | Use `bundle` to bundle the config with Rolldown, or `runner` (experimental) to process it on the fly, or `native` (experimental) to load using the native runtime (default: `bundle`) | | `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | diff --git a/packages/vite/package.json b/packages/vite/package.json index d9d7ab7f8b2d4e..e9d41ae722139d 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -135,7 +135,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts index 5cf98adaf2d835..cf3927586ba5c5 100644 --- a/packages/vite/src/node/__tests__/config.spec.ts +++ b/packages/vite/src/node/__tests__/config.spec.ts @@ -6,7 +6,12 @@ import { stripVTControlCharacters } from 'node:util' import { afterEach, assert, describe, expect, test, vi } from 'vitest' import type { InlineConfig, PluginOption } from '..' import type { UserConfig, UserConfigExport } from '../config' -import { defineConfig, loadConfigFromFile, resolveConfig } from '../config' +import { + bundleConfigFile, + defineConfig, + loadConfigFromFile, + resolveConfig, +} from '../config' import { resolveServerOptions } from '../server' import { resolveEnvPrefix } from '../env' import { @@ -1959,6 +1964,21 @@ describe('loadConfigFromFile', () => { expect(c.dirname).toContain('shebang-crlf') }) + test('sourcemap of a nested config file points to itself', async () => { + const configPath = path.resolve( + fixtures, + './nested/.nested/vite.config.mts', + ) + const { code } = await bundleConfigFile(configPath, true) + const [, base64Map] = code.match( + /\/\/# sourceMappingURL=data:application\/json;charset=utf-8;base64,(.+)/, + )! + const map = JSON.parse(Buffer.from(base64Map, 'base64').toString()) + expect(map.sources.map(normalizePath)).toStrictEqual([ + normalizePath(configPath), + ]) + }) + describe('loadConfigFromFile with configLoader: native', () => { const fixtureRoot = path.resolve(fixtures, './native-import') @@ -2017,6 +2037,28 @@ describe('loadConfigFromFile', () => { }) }) +describe('root resolution', () => { + const fixtureRoot = path.resolve( + import.meta.dirname, + './fixtures/config/root-resolution', + ) + const realDir = path.join(fixtureRoot, 'real') + const linkDir = path.join(fixtureRoot, 'link') + + test('resolves a symlinked root to its real path', async () => { + const config = await resolveConfig({ root: linkDir }, 'serve') + expect(config.root).toBe(normalizePath(fs.realpathSync.native(realDir))) + }) + + test('keeps a symlinked root when resolve.preserveSymlinks is true', async () => { + const config = await resolveConfig( + { root: linkDir, resolve: { preserveSymlinks: true } }, + 'serve', + ) + expect(config.root).toBe(normalizePath(linkDir)) + }) +}) + describe('resolveServerOptions', () => { const warnFn = vi.fn() const logger = { warn: warnFn } as unknown as Logger diff --git a/packages/vite/src/node/__tests__/fixtures/config/nested/.nested/vite.config.mts b/packages/vite/src/node/__tests__/fixtures/config/nested/.nested/vite.config.mts new file mode 100644 index 00000000000000..b1c6ea436a5400 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/config/nested/.nested/vite.config.mts @@ -0,0 +1 @@ +export default {} diff --git a/packages/vite/src/node/__tests__/fixtures/config/root-resolution/link b/packages/vite/src/node/__tests__/fixtures/config/root-resolution/link new file mode 120000 index 00000000000000..ac558a3e1bf444 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/config/root-resolution/link @@ -0,0 +1 @@ +real \ No newline at end of file diff --git a/packages/vite/src/node/__tests__/fixtures/config/root-resolution/real/.gitkeep b/packages/vite/src/node/__tests__/fixtures/config/root-resolution/real/.gitkeep new file mode 100644 index 00000000000000..8b137891791fe9 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/config/root-resolution/real/.gitkeep @@ -0,0 +1 @@ + diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 4589a0e16b9e07..b9f516d2a9829e 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1600,9 +1600,11 @@ export async function resolveConfig( let nonNormalizedResolvedRoot = config.root ? path.resolve(config.root) : process.cwd() - try { - nonNormalizedResolvedRoot = safeRealpathSync(nonNormalizedResolvedRoot) - } catch {} + if (!config.resolve?.preserveSymlinks) { + try { + nonNormalizedResolvedRoot = safeRealpathSync(nonNormalizedResolvedRoot) + } catch {} + } const resolvedRoot = normalizePath(nonNormalizedResolvedRoot) checkBadCharactersInPath( @@ -2504,7 +2506,7 @@ async function bundleAndLoadConfigFile( } } -async function bundleConfigFile( +export async function bundleConfigFile( fileName: string, isESM: boolean, ): Promise<{ @@ -2653,8 +2655,8 @@ async function bundleConfigFile( const result = await bundle.generate({ format: isESM ? 'esm' : 'cjs', sourcemap: 'inline', - sourcemapPathTransform(relative) { - return path.resolve(fileName, relative) + sourcemapPathTransform(relative, sourcemapPath) { + return path.resolve(path.dirname(sourcemapPath), relative) }, // we want to generate a single chunk like esbuild does with `splitting: false` codeSplitting: false, diff --git a/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts b/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts index 88c1648eac18a9..4568f45b56b293 100644 --- a/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts +++ b/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts @@ -23,6 +23,7 @@ import { createInMemoryLogger, editFile, isBuild, + isBundledDev, promiseWithResolvers, readFile, removeFile, @@ -1186,6 +1187,9 @@ async function setupModuleRunner( noDiscovery: true, include: [], }, + experimental: { + bundledDev: isBundledDev, + }, ...serverOptions, }) diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index 48f126c16dc2c3..aaa03ec30857ac 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -19,6 +19,7 @@ const bundledDevExclude = [ './playground/fs-serve/__tests__/base/fs-serve-base.spec.ts', './playground/fs-serve/__tests__/deny/fs-serve-deny.spec.ts', './playground/fs-serve/__tests__/fs-serve.spec.ts', + './playground/hmr-ssr/__tests__/hmr-ssr.spec.ts', './playground/legacy/__tests__/chunk-importmap/legacy-chunk-importmap.spec.ts', './playground/object-hooks/__tests__/object-hooks.spec.ts', './playground/optimize-deps/__tests__/optimize-deps.spec.ts',