diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1f12ef5dc54a5c..0014c768519180 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -19,8 +19,6 @@ "typescript", // breaking changes - "react-router", // `react-router:v6.0.0+` has breaking changes - "react-router-dom", // `react-router-dom:v6.0.0+` has breaking changes "source-map", // `source-map:v0.7.0+` needs more investigation "dotenv-expand", // `dotenv-expand:6.0.0+` has breaking changes (#6858) "kill-port", // `kill-port:^2.0.0 has perf issues (#8392) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 248bb3fbedd101..f190529faa247d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,8 @@ env: # 7 GiB by default on GitHub, setting to 6 GiB # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources NODE_OPTIONS: --max-old-space-size=6144 + # install playwright binary manually (because pnpm only runs install script once) + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" on: push: @@ -38,10 +40,6 @@ jobs: node_version: 16 fail-fast: false - env: - # Install playwright's binray under node_modules so it will be cached together - PLAYWRIGHT_BROWSERS_PATH: "0" - name: "Build&Test: node-${{ matrix.node_version }}, ${{ matrix.os }}" steps: - name: Checkout @@ -59,8 +57,25 @@ jobs: - name: Install deps run: pnpm install + # Install playwright's binary under custom directory to cache + - name: Set Playwright path (non-windows) + if: runner.os != 'Windows' + run: echo "PLAYWRIGHT_BROWSERS_PATH=$HOME/.cache/playwright-bin" >> $GITHUB_ENV + - name: Set Playwright path (windows) + if: runner.os == 'Windows' + run: echo "PLAYWRIGHT_BROWSERS_PATH=$HOME\.cache\playwright-bin" >> $env:GITHUB_ENV + + - name: Cache Playwright's binary + uses: actions/cache@v3 + with: + # Playwright removes unused browsers automatically + # So does not need to add playwright version to key + key: ${{ runner.os }}-playwright-bin-v1 + path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} + - name: Install Playwright - run: pnpm playwright install + # does not need to explictly set chromium after https://github.com/microsoft/playwright/issues/14862 is solved + run: pnpm playwright install chromium - name: Build run: pnpm run build @@ -97,8 +112,6 @@ jobs: - name: Install deps run: pnpm install - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" - name: Build run: pnpm run build diff --git a/.gitignore b/.gitignore index 9194d44b544c4e..7f1cab0e1f4356 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ node_modules playground-temp temp TODOs.md +.eslintcache diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 9c3150eb1d0c1b..55b4657af24288 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -14,9 +14,7 @@ export default defineConfig({ logo: '/logo.svg', editLink: { - repo: 'vitejs/vite', - branch: 'main', - dir: 'docs', + pattern: 'https://github.com/vitejs/vite/edit/main/docs/:path', text: 'Suggest changes to this page' }, diff --git a/docs/config/dep-optimization-options.md b/docs/config/dep-optimization-options.md index 1cc3c710c62846..413452d2a23d71 100644 --- a/docs/config/dep-optimization-options.md +++ b/docs/config/dep-optimization-options.md @@ -45,3 +45,9 @@ 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 + +- **Type:** `boolean` + +Set to `true` to force dependency pre-bundling, ignoring previously cached optimized dependencies. diff --git a/docs/config/index.md b/docs/config/index.md index 5daf5e7f714275..73a20082295304 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -41,14 +41,10 @@ const func = () => { Since Vite ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints: ```js -/** - * @type {import('vite').UserConfig} - */ -const config = { +/** @type {import('vite').UserConfig} */ +export default { // ... } - -export default config ``` Alternatively, you can use the `defineConfig` helper which should provide intellisense without the need for jsdoc annotations: diff --git a/docs/config/server-options.md b/docs/config/server-options.md index cf59ab27644656..3e09f7b744f2fc 100644 --- a/docs/config/server-options.md +++ b/docs/config/server-options.md @@ -3,7 +3,7 @@ ## server.host - **Type:** `string | boolean` -- **Default:** `'127.0.0.1'` +- **Default:** `'localhost'` Specify which IP addresses the server should listen on. Set this to `0.0.0.0` or `true` to listen on all addresses, including LAN and public addresses. @@ -107,13 +107,6 @@ Configure CORS for the dev server. This is enabled by default and allows any ori Specify server response headers. -## server.force - -- **Type:** `boolean` -- **Related:** [Dependency Pre-Bundling](/guide/dep-pre-bundling) - -Set to `true` to force dependency pre-bundling. - ## server.hmr - **Type:** `boolean | { protocol?: string, host?: string, port?: number, path?: string, timeout?: number, overlay?: boolean, clientPort?: number, server?: Server }` diff --git a/docs/config/worker-options.md b/docs/config/worker-options.md index c0c13bf4133adb..c506d249a24932 100644 --- a/docs/config/worker-options.md +++ b/docs/config/worker-options.md @@ -11,7 +11,7 @@ Output format for worker bundle. - **Type:** [`(Plugin | Plugin[])[]`](./shared-options#plugins) -Vite plugins that apply to worker bundle +Vite plugins that apply to worker bundle. Note that [config.plugins](./shared-options#plugins) does not apply to workers, it should be configured here instead. ## worker.rollupOptions diff --git a/docs/guide/features.md b/docs/guide/features.md index 57d1e5a71e4a82..65d01f9b6c7239 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -511,6 +511,12 @@ By default, the worker script will be emitted as a separate chunk in the product import MyWorker from './worker?worker&inline' ``` +If you wish to retrieve the worker as a URL, add the `url` query: + +```js +import MyWorker from './worker?worker&url' +``` + See [Worker Options](/config/#worker-options) for details on configuring the bundling of all workers. ## Build Optimizations diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 78b738afb82ddf..94ad7abce296d3 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -27,29 +27,48 @@ A small fraction of users will now require using [@vitejs/plugin-legacy](https:/ - `build.polyfillDynamicImport` (use [`@vitejs/plugin-legacy`](https://github.com/vitejs/vite/tree/main/packages/plugin-legacy) for browsers without dynamic import support) - `optimizeDeps.keepNames` (switch to [`optimizeDeps.esbuildOptions.keepNames`](../config/dep-optimization-options.md#optimizedepsesbuildoptions)) -## Dev Server Changes +## Achitecture changes and legacy Options + +This section describes the biggest architecture changes in Vite v3. To allow projects to migrate from v2 in case of a compat issue, legacy options have been added to revert to the Vite v2 strategies. + +:::warning +These options are marked as experimental and deprecated. They may be removed in a future v3 minor without respecting semver. Please pin the Vite version when using them. + +- `legacy.devDepsScanner` +- `legacy.buildRollupPluginCommonjs` +- `legacy.buildSsrCjsExternalHeuristics` + +::: + +### Dev Server Changes Vite's default dev server port is now 5173. You can use [`server.port`](../config/server-options.md#server-port) to set it to 3000. +Vite's default dev server host is now `localhost`. You can use [`server.host`](../config/server-options.md#server-host) to set it to `127.0.0.1`. + Vite optimizes dependencies with esbuild to both convert CJS-only deps to ESM and to reduce the number of modules the browser needs to request. In v3, the default strategy to discover and batch dependencies has changed. Vite no longer pre-scans user code with esbuild to get an initial list of dependencies on cold start. Instead, it delays the first dependency optimization run until every imported user module on load is processed. -To get back the v2 strategy, you can use [`optimizeDeps.devScan`](../config/dep-optimization-options.md#optimizedepsdevscan). +To get back the v2 strategy, you can use `legacy.devDepsScanner`. -## Build Changes +### Build Changes In v3, Vite uses esbuild to optimize dependencies by default. Doing so, it removes one of the most significant differences between dev and prod present in v2. Because esbuild converts CJS-only dependencies to ESM, [`@rollupjs/plugin-commonjs`](https://github.com/rollup/plugins/tree/master/packages/commonjs) is no longer used. -If you need to get back to the v2 strategy, you can use [`optimizeDeps.disabled: 'build'`](../config/dep-optimization-options.md#optimizedepsdisabled). +If you need to get back to the v2 strategy, you can use `legacy.buildRollupPluginCommonjs`. -## SSR Changes +### SSR Changes Vite v3 uses ESM for the SSR build by default. When using ESM, the [SSR externalization heuristics](https://vitejs.dev/guide/ssr.html#ssr-externals) are no longer needed. By default, all dependencies are externalized. You can use [`ssr.noExternal`](../config/ssr-options.md#ssrnoexternal) to control what dependencies to include in the SSR bundle. -If using ESM for SSR isn't possible in your project, you can set `ssr.format: 'cjs'` to generate a CJS bundle. In this case, the same externalization strategy of Vite v2 will be used. +If using ESM for SSR isn't possible in your project, you can set `legacy.buildSsrCjsExternalHeuristics` to generate a CJS bundle using the same externalization strategy of Vite v2. ## General Changes - JS file extensions in SSR and lib mode now use a valid extension (`js`, `mjs`, or `cjs`) for output JS entries and chunks based on their format and the package type. +- Terser is now an optional dependency. If you are using `build.minify: 'terser'`, you need to install it. + ```shell + npm add -D terser + ``` ### `import.meta.glob` @@ -93,6 +112,8 @@ There are some changes which only affects plugin/tool creators. - `printHttpServerUrls` is removed - `server.app`, `server.transformWithEsbuild` are removed - `import.meta.hot.acceptDeps` is removed +- [[#6901] fix: sequential injection of tags in transformIndexHtml](https://github.com/vitejs/vite/pull/6901) + - `transformIndexHtml` now gets the correct content modified by earlier plugins, so the order of the injected tags now works as expected. - [[#7995] chore: do not fixStacktrace](https://github.com/vitejs/vite/pull/7995) - `ssrLoadModule`'s `fixStacktrace` option's default is now `false` - [[#8178] feat!: migrate to ESM](https://github.com/vitejs/vite/pull/8178) @@ -105,8 +126,12 @@ Also there are other breaking changes which only affect few users. - Transpile to ES5 is now necessary even if the user code only includes ES5. - [[#7877] fix: vite client types](https://github.com/vitejs/vite/pull/7877) - `/// ` is removed from `vite/client.d.ts`. `{ "lib": ["dom"] }` or `{ "lib": ["webworker"] }` is necessary in `tsconfig.json`. +- [[#8090] feat: preserve process env vars in lib build](https://github.com/vitejs/vite/pull/8090) + - `process.env.*` is now preserved in library mode - [[#8280] feat: non-blocking esbuild optimization at build time](https://github.com/vitejs/vite/pull/8280) - - `server.force` option was removed in favor of `force` option. + - `server.force` option was removed in favor of `optimizeDeps.force` option. +- [[#8550] fix: dont handle sigterm in middleware mode](https://github.com/vitejs/vite/pull/8550) + - When running in middleware mode, Vite no longer kills process on `SIGTERM`. ## Migration from v1 diff --git a/package.json b/package.json index 44ddf5e1ac23b9..d9923fa7b80bf4 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ ], "scripts": { "preinstall": "npx only-allow pnpm", - "format": "prettier --write .", - "lint": "eslint packages/*/{src,types,__tests__}/** playground/**/__tests__/**/*.ts scripts/**", + "format": "prettier --write --cache .", + "lint": "eslint --cache packages/*/{src,types,__tests__}/** playground/**/__tests__/**/*.ts scripts/**", "typecheck": "tsc -p scripts --noEmit && tsc -p playground --noEmit", "test": "run-s test-unit test-serve test-build", "test-serve": "vitest run -c vitest.config.e2e.ts", @@ -77,7 +77,7 @@ "picocolors": "^1.0.0", "playwright-chromium": "^1.22.2", "pnpm": "^7.2.1", - "prettier": "2.6.2", + "prettier": "2.7.0", "prompts": "^2.4.2", "rimraf": "^3.0.2", "rollup": "^2.75.6", @@ -88,7 +88,7 @@ "typescript": "^4.6.4", "unbuild": "^0.7.4", "vite": "workspace:*", - "vitepress": "1.0.0-draft.8", + "vitepress": "^1.0.0-alpha.2", "vitest": "^0.14.2", "vue": "^3.2.37" }, @@ -98,16 +98,16 @@ }, "lint-staged": { "*": [ - "prettier --write --ignore-unknown" + "prettier --write --cache --ignore-unknown" ], "packages/*/{src,types}/**/*.ts": [ - "eslint --fix" + "eslint --cache --fix" ], "packages/**/*.d.ts": [ - "eslint --fix" + "eslint --cache --fix" ], "playground/**/__tests__/**/*.ts": [ - "eslint --fix" + "eslint --cache --fix" ] }, "packageManager": "pnpm@7.2.1", diff --git a/packages/create-vite/template-preact-ts/src/main.tsx b/packages/create-vite/template-preact-ts/src/main.tsx index 812f602e33bc95..e0ce3e9980eecd 100644 --- a/packages/create-vite/template-preact-ts/src/main.tsx +++ b/packages/create-vite/template-preact-ts/src/main.tsx @@ -2,4 +2,4 @@ import { render } from 'preact' import { App } from './app' import './index.css' -render(, document.getElementById('app')!) +render(, document.getElementById('app') as HTMLElement) diff --git a/packages/plugin-legacy/src/index.ts b/packages/plugin-legacy/src/index.ts index 4aaab1aab2412d..0c9fb1c8c68d33 100644 --- a/packages/plugin-legacy/src/index.ts +++ b/packages/plugin-legacy/src/index.ts @@ -134,7 +134,7 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] { config.build, 'es', opts, - options.externalSystemJS + true ) return } @@ -566,7 +566,7 @@ async function buildPolyfillChunk( buildOptions: BuildOptions, format: 'iife' | 'es', rollupOutputOptions: NormalizedOutputOptions, - externalSystemJS?: boolean + excludeSystemJS?: boolean ) { let { minify, assetsDir } = buildOptions minify = minify ? 'terser' : false @@ -575,7 +575,7 @@ async function buildPolyfillChunk( root: path.dirname(fileURLToPath(import.meta.url)), configFile: false, logLevel: 'error', - plugins: [polyfillsPlugin(imports, externalSystemJS)], + plugins: [polyfillsPlugin(imports, excludeSystemJS)], build: { write: false, target: false, @@ -614,7 +614,7 @@ const polyfillId = '\0vite/legacy-polyfills' function polyfillsPlugin( imports: Set, - externalSystemJS?: boolean + excludeSystemJS?: boolean ): Plugin { return { name: 'vite:legacy-polyfills', @@ -627,7 +627,7 @@ function polyfillsPlugin( if (id === polyfillId) { return ( [...imports].map((i) => `import "${i}";`).join('') + - (externalSystemJS ? '' : `import "systemjs/dist/s.min.js";`) + (excludeSystemJS ? '' : `import "systemjs/dist/s.min.js";`) ) } } diff --git a/packages/plugin-vue/src/main.ts b/packages/plugin-vue/src/main.ts index dda9c75d04ec78..e92c48673a680e 100644 --- a/packages/plugin-vue/src/main.ts +++ b/packages/plugin-vue/src/main.ts @@ -1,6 +1,6 @@ import path from 'path' import type { SFCBlock, SFCDescriptor } from 'vue/compiler-sfc' -import type { PluginContext, SourceMap, TransformPluginContext } from 'rollup' +import type { PluginContext, TransformPluginContext } from 'rollup' import type { RawSourceMap } from 'source-map' import type { EncodedSourceMap as TraceEncodedSourceMap } from '@jridgewell/trace-mapping' import { TraceMap, eachMapping } from '@jridgewell/trace-mapping' @@ -46,7 +46,7 @@ export async function transformMain( const hasScoped = descriptor.styles.some((s) => s.scoped) // script - const { code: scriptCode, map } = await genScriptCode( + const { code: scriptCode, map: scriptMap } = await genScriptCode( descriptor, options, pluginContext, @@ -58,7 +58,7 @@ export async function transformMain( descriptor.template && !isUseInlineTemplate(descriptor, !devServer) let templateCode = '' - let templateMap: RawSourceMap | undefined + let templateMap: RawSourceMap | undefined = undefined if (hasTemplateImport) { ;({ code: templateCode, map: templateMap } = await genTemplateCode( descriptor, @@ -156,40 +156,46 @@ export async function transformMain( ) } - // if the template is inlined into the main module (indicated by the presence - // of templateMap, we need to concatenate the two source maps. - let resolvedMap = options.sourceMap ? map : undefined - if (resolvedMap && templateMap) { - const gen = fromMap( - // version property of result.map is declared as string - // but actually it is `3` - map as Omit as TraceEncodedSourceMap - ) - const tracer = new TraceMap( - // same above - templateMap as Omit as TraceEncodedSourceMap - ) - const offset = (scriptCode.match(/\r?\n/g)?.length ?? 0) + 1 - eachMapping(tracer, (m) => { - if (m.source == null) return - addMapping(gen, { - source: m.source, - original: { line: m.originalLine, column: m.originalColumn }, - generated: { - line: m.generatedLine + offset, - column: m.generatedColumn - } + let resolvedMap: RawSourceMap | undefined = undefined + if (options.sourceMap) { + if (scriptMap && templateMap) { + // if the template is inlined into the main module (indicated by the presence + // of templateMap, we need to concatenate the two source maps. + + const gen = fromMap( + // version property of result.map is declared as string + // but actually it is `3` + scriptMap as Omit as TraceEncodedSourceMap + ) + const tracer = new TraceMap( + // same above + templateMap as Omit as TraceEncodedSourceMap + ) + const offset = (scriptCode.match(/\r?\n/g)?.length ?? 0) + 1 + eachMapping(tracer, (m) => { + if (m.source == null) return + addMapping(gen, { + source: m.source, + original: { line: m.originalLine, column: m.originalColumn }, + generated: { + line: m.generatedLine + offset, + column: m.generatedColumn + } + }) }) - }) - // same above - resolvedMap = toEncodedMap(gen) as Omit< - GenEncodedSourceMap, - 'version' - > as RawSourceMap - // if this is a template only update, we will be reusing a cached version - // of the main module compile result, which has outdated sourcesContent. - resolvedMap.sourcesContent = templateMap.sourcesContent + // same above + resolvedMap = toEncodedMap(gen) as Omit< + GenEncodedSourceMap, + 'version' + > as RawSourceMap + // if this is a template only update, we will be reusing a cached version + // of the main module compile result, which has outdated sourcesContent. + resolvedMap.sourcesContent = templateMap.sourcesContent + } else { + // if one of `scriptMap` and `templateMap` is empty, use the other one + resolvedMap = scriptMap ?? templateMap + } } if (!attachedProps.length) { @@ -287,10 +293,10 @@ async function genScriptCode( ssr: boolean ): Promise<{ code: string - map: RawSourceMap + map: RawSourceMap | undefined }> { let scriptCode = `const _sfc_main = {}` - let map: RawSourceMap | SourceMap | undefined + let map: RawSourceMap | undefined const script = resolveScript(descriptor, options, ssr) if (script) { @@ -322,7 +328,7 @@ async function genScriptCode( } return { code: scriptCode, - map: map as any + map } } diff --git a/packages/vite/CHANGELOG.md b/packages/vite/CHANGELOG.md index 92a05ffb43ea64..03b7be90f66c65 100644 --- a/packages/vite/CHANGELOG.md +++ b/packages/vite/CHANGELOG.md @@ -1,3 +1,40 @@ +## 3.0.0-alpha.12 (2022-06-16) + +* chore: correct typo in console message (#8618) ([13d05bd](https://github.com/vitejs/vite/commit/13d05bd)), closes [#8618](https://github.com/vitejs/vite/issues/8618) +* chore: enable eslint and prettier cache (#8585) ([d7beaeb](https://github.com/vitejs/vite/commit/d7beaeb)), closes [#8585](https://github.com/vitejs/vite/issues/8585) +* chore: tweak server start output (#8582) ([3439132](https://github.com/vitejs/vite/commit/3439132)), closes [#8582](https://github.com/vitejs/vite/issues/8582) +* fix: allow cache overlap in parallel builds (#8592) ([2dd0b49](https://github.com/vitejs/vite/commit/2dd0b49)), closes [#8592](https://github.com/vitejs/vite/issues/8592) +* fix: avoid replacing defines and NODE_ENV in optimized deps (fix #8593) (#8606) ([739175b](https://github.com/vitejs/vite/commit/739175b)), closes [#8593](https://github.com/vitejs/vite/issues/8593) [#8606](https://github.com/vitejs/vite/issues/8606) +* fix: sequential injection of tags in transformIndexHtml (#5851) (#6901) ([649c7f6](https://github.com/vitejs/vite/commit/649c7f6)), closes [#5851](https://github.com/vitejs/vite/issues/5851) [#6901](https://github.com/vitejs/vite/issues/6901) +* fix(asset): respect assetFileNames if rollupOptions.output is an array (#8561) ([4e6c26f](https://github.com/vitejs/vite/commit/4e6c26f)), closes [#8561](https://github.com/vitejs/vite/issues/8561) +* fix(css): escape pattern chars from base path in postcss dir-dependency messages (#7081) ([5151e74](https://github.com/vitejs/vite/commit/5151e74)), closes [#7081](https://github.com/vitejs/vite/issues/7081) +* fix(optimizer): browser mapping for yarn pnp (#6493) ([c1c7af3](https://github.com/vitejs/vite/commit/c1c7af3)), closes [#6493](https://github.com/vitejs/vite/issues/6493) +* feat: 500 response if the node proxy request fails (#7398) ([73e1775](https://github.com/vitejs/vite/commit/73e1775)), closes [#7398](https://github.com/vitejs/vite/issues/7398) +* docs: worker related notes (#8554) ([c0c5e1a](https://github.com/vitejs/vite/commit/c0c5e1a)), closes [#8554](https://github.com/vitejs/vite/issues/8554) + + + +## 3.0.0-alpha.11 (2022-06-14) + +* fix: add missed JPEG file extensions to `KNOWN_ASSET_TYPES` (#8565) ([2dfc015](https://github.com/vitejs/vite/commit/2dfc015)), closes [#8565](https://github.com/vitejs/vite/issues/8565) +* fix: default export module transformation for vitest spy (#8567) ([d357e33](https://github.com/vitejs/vite/commit/d357e33)), closes [#8567](https://github.com/vitejs/vite/issues/8567) +* fix: default host to `localhost` instead of `127.0.0.1` (#8543) ([49c0896](https://github.com/vitejs/vite/commit/49c0896)), closes [#8543](https://github.com/vitejs/vite/issues/8543) +* fix: dont handle sigterm in middleware mode (#8550) ([c6f43dd](https://github.com/vitejs/vite/commit/c6f43dd)), closes [#8550](https://github.com/vitejs/vite/issues/8550) +* fix: mime missing extensions (#8568) ([acf3024](https://github.com/vitejs/vite/commit/acf3024)), closes [#8568](https://github.com/vitejs/vite/issues/8568) +* fix: objurl for type module, and concurrent tests (#8541) ([26ecd5a](https://github.com/vitejs/vite/commit/26ecd5a)), closes [#8541](https://github.com/vitejs/vite/issues/8541) +* fix: outdated optimized dep removed from module graph (#8533) ([3f4d22d](https://github.com/vitejs/vite/commit/3f4d22d)), closes [#8533](https://github.com/vitejs/vite/issues/8533) +* fix(config): only rewrite .js loader in `loadConfigFromBundledFile` (#8556) ([2548dd3](https://github.com/vitejs/vite/commit/2548dd3)), closes [#8556](https://github.com/vitejs/vite/issues/8556) +* fix(deps): update all non-major dependencies (#8558) ([9a1fd4c](https://github.com/vitejs/vite/commit/9a1fd4c)), closes [#8558](https://github.com/vitejs/vite/issues/8558) +* fix(ssr): dont replace rollup input (#7275) ([9a88afa](https://github.com/vitejs/vite/commit/9a88afa)), closes [#7275](https://github.com/vitejs/vite/issues/7275) +* chore: include 2.9.10-2.9.12 changelog in main (#8535) ([87f58ad](https://github.com/vitejs/vite/commit/87f58ad)), closes [#8535](https://github.com/vitejs/vite/issues/8535) +* chore: refactor interop named imports (#8544) ([63b523a](https://github.com/vitejs/vite/commit/63b523a)), closes [#8544](https://github.com/vitejs/vite/issues/8544) +* chore: remove rollup `namespaceToStringTag` (#8569) ([b85802a](https://github.com/vitejs/vite/commit/b85802a)), closes [#8569](https://github.com/vitejs/vite/issues/8569) +* chore: remove unused timestamp option (#8545) ([d641860](https://github.com/vitejs/vite/commit/d641860)), closes [#8545](https://github.com/vitejs/vite/issues/8545) +* chore: update major deps (#8572) ([0e20949](https://github.com/vitejs/vite/commit/0e20949)), closes [#8572](https://github.com/vitejs/vite/issues/8572) +* feat: expose createFilter util (#8562) ([c5c424a](https://github.com/vitejs/vite/commit/c5c424a)), closes [#8562](https://github.com/vitejs/vite/issues/8562) + + + ## 3.0.0-alpha.10 (2022-06-10) * fix: deps optimizer idle logic for workers (fix #8479) (#8511) ([1e05548](https://github.com/vitejs/vite/commit/1e05548)), closes [#8479](https://github.com/vitejs/vite/issues/8479) [#8511](https://github.com/vitejs/vite/issues/8511) diff --git a/packages/vite/package.json b/packages/vite/package.json index ab4ae1235eb3ed..805d6485d53acd 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -1,6 +1,6 @@ { "name": "vite", - "version": "3.0.0-alpha.10", + "version": "3.0.0-alpha.12", "type": "module", "license": "MIT", "author": "Evan You", @@ -51,8 +51,8 @@ "patch-types": "esno scripts/patchTypes.ts", "roll-types": "api-extractor run && rimraf temp", "check-dist-types": "tsc --project tsconfig.check.json", - "lint": "eslint --ext .ts src/**", - "format": "prettier --write --parser typescript \"src/**/*.ts\"", + "lint": "eslint --cache --ext .ts src/**", + "format": "prettier --write --cache --parser typescript \"src/**/*.ts\"", "prepublishOnly": "npm run build" }, "//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!", diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 5f6a4922f76bc6..889cbf8386538b 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -50,9 +50,9 @@ describe('injectQuery', () => { }) describe('resolveHostname', () => { - test('defaults to 127.0.0.1', () => { + test('defaults to localhost', () => { expect(resolveHostname(undefined)).toEqual({ - host: '127.0.0.1', + host: 'localhost', name: 'localhost' }) }) @@ -63,6 +63,27 @@ describe('resolveHostname', () => { name: 'localhost' }) }) + + test('accepts 0.0.0.0', () => { + expect(resolveHostname('0.0.0.0')).toEqual({ + host: '0.0.0.0', + name: 'localhost' + }) + }) + + test('accepts ::', () => { + expect(resolveHostname('::')).toEqual({ + host: '::', + name: 'localhost' + }) + }) + + test('accepts 0000:0000:0000:0000:0000:0000:0000:0000', () => { + expect(resolveHostname('0000:0000:0000:0000:0000:0000:0000:0000')).toEqual({ + host: '0000:0000:0000:0000:0000:0000:0000:0000', + name: 'localhost' + }) + }) }) test('ts import of file with .js extension', () => { diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index c3d59d88a0549b..d12542d35200f1 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -306,7 +306,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): { pre: [ ...(options.watch ? [ensureWatchPlugin()] : []), watchPackageDataPlugin(config), - ...(!isDepsOptimizerEnabled(config) + ...(config.legacy?.buildRollupPluginCommonjs ? [commonjsPlugin(options.commonjsOptions)] : []), dataURIPlugin(), @@ -398,7 +398,7 @@ async function doBuild( // In CJS, we can pass the externals to rollup as is. In ESM, we need to // do it in the resolve plugin so we can add the resolved extension for // deep node_modules imports - if (ssr && config.ssr?.format === 'cjs') { + if (ssr && config.legacy?.buildSsrCjsExternalHeuristics) { external = await cjsSsrResolveExternal(config, userExternal) } @@ -733,7 +733,7 @@ async function cjsSsrResolveExternal( ): Promise { // see if we have cached deps data available let knownImports: string[] | undefined - const dataPath = path.join(getDepsCacheDir(config), '_metadata.json') + const dataPath = path.join(getDepsCacheDir(config, false), '_metadata.json') try { const data = JSON.parse( fs.readFileSync(dataPath, 'utf-8') diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 46d6afd6980ac2..4922a7ec518a18 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -87,6 +87,7 @@ cli configFile: options.config, logLevel: options.logLevel, clearScreen: options.clearScreen, + optimizeDeps: { force: options.force }, server: cleanOptions(options) }) @@ -174,7 +175,7 @@ cli configFile: options.config, logLevel: options.logLevel, clearScreen: options.clearScreen, - force: options.force, + optimizeDeps: { force: options.force }, build: buildOptions }) } catch (e) { diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 8103bf892f1b1b..e1ef47b4a9eb89 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -153,11 +153,6 @@ export interface UserConfig { * Preview specific options, e.g. host, port, https... */ preview?: PreviewOptions - /** - * Force dep pre-optimization regardless of whether deps have changed. - * @experimental - */ - force?: boolean /** * Dep optimization options */ @@ -169,11 +164,18 @@ export interface UserConfig { /** * Experimental features * - * Features under this field are addressed to be changed that might NOT follow semver. + * Features under this field could change in the future and might NOT follow semver. * Please be careful and always pin Vite's version when using them. * @experimental */ experimental?: ExperimentalOptions + /** + * Legacy options + * + * Features under this field only follow semver for patches, they could be removed in a + * future minor version. Please always pin Vite's version to a minor when using them. + */ + legacy?: LegacyOptions /** * Log level. * Default: 'info' @@ -237,6 +239,33 @@ export interface ExperimentalOptions { importGlobRestoreExtension?: boolean } +export interface LegacyOptions { + /** + * Revert vite dev to the v2.9 strategy. Enable esbuild based deps scanner. + * + * @experimental + * @deprecated + * @default false + */ + devDepsScanner?: boolean + /** + * Revert vite build to the v2.9 strategy. Disable esbuild deps optimization and adds `@rollup/plugin-commonjs` + * + * @experimental + * @deprecated + * @default false + */ + buildRollupPluginCommonjs?: boolean + /** + * Revert vite build --ssr to the v2.9 strategy. Use CJS SSR build and v2.9 externalization heuristics + * + * @experimental + * @deprecated + * @default false + */ + buildSsrCjsExternalHeuristics?: boolean +} + export interface ResolveWorkerOptions { format: 'es' | 'iife' plugins: Plugin[] @@ -260,6 +289,7 @@ export type ResolvedConfig = Readonly< command: 'build' | 'serve' mode: string isWorker: boolean + // in nested worker bundle to find the main config /** @internal */ mainConfig: ResolvedConfig | null isProduction: boolean @@ -480,7 +510,11 @@ export async function resolveConfig( : '' const server = resolveServerOptions(resolvedRoot, config.server, logger) - const ssr = resolveSSROptions(config.ssr) + let ssr = resolveSSROptions(config.ssr) + if (config.legacy?.buildSsrCjsExternalHeuristics) { + if (ssr) ssr.format = 'cjs' + else ssr = { target: 'node', format: 'cjs' } + } const optimizeDeps = config.optimizeDeps || {} @@ -530,7 +564,18 @@ export async function resolveConfig( spa: config.spa ?? true } - // flat config.worker.plugin + if (resolved.legacy?.buildRollupPluginCommonjs) { + const optimizerDisabled = resolved.optimizeDeps.disabled + if (!optimizerDisabled) { + resolved.optimizeDeps.disabled = 'build' + } else if (optimizerDisabled === 'dev') { + resolved.optimizeDeps.disabled = true // Also disabled during build + } + } + + // Some plugins that aren't intended to work in the bundling of workers (doing post-processing at build time for example). + // And Plugins may also have cached that could be corrupted by being used in these extra rollup calls. + // So we need to separate the worker plugin from the plugin that vite needs to run. const [workerPrePlugins, workerNormalPlugins, workerPostPlugins] = sortUserPlugins(config.worker?.plugins as Plugin[]) const workerResolved: ResolvedConfig = { @@ -575,6 +620,29 @@ export async function resolveConfig( ) } + // Check if all assetFileNames have the same reference. + // If not, display a warn for user. + const outputOption = config.build?.rollupOptions?.output ?? [] + // Use isArray to narrow its type to array + if (Array.isArray(outputOption)) { + const assetFileNamesList = outputOption.map( + (output) => output.assetFileNames + ) + if (assetFileNamesList.length > 1) { + const firstAssetFileNames = assetFileNamesList[0] + const hasDifferentReference = assetFileNamesList.some( + (assetFileNames) => assetFileNames !== firstAssetFileNames + ) + if (hasDifferentReference) { + resolved.logger.warn( + colors.yellow(` +assetFileNames isn't equal for every build.rollupOptions.output. A single pattern across all outputs is supported by Vite. +`) + ) + } + } + } + return resolved } diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index acd067908a598c..3b4d496e30bd3c 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -105,3 +105,15 @@ export const DEFAULT_ASSETS_RE = new RegExp( ) export const DEP_VERSION_RE = /[\?&](v=[\w\.-]+)\b/ + +export const loopbackHosts = new Set([ + 'localhost', + '127.0.0.1', + '::1', + '0000:0000:0000:0000:0000:0000:0000:0001' +]) +export const wildcardHosts = new Set([ + '0.0.0.0', + '::', + '0000:0000:0000:0000:0000:0000:0000:0000' +]) diff --git a/packages/vite/src/node/http.ts b/packages/vite/src/node/http.ts index 2a1c7c920b8ea9..8710c274fd83d1 100644 --- a/packages/vite/src/node/http.ts +++ b/packages/vite/src/node/http.ts @@ -184,9 +184,9 @@ export async function httpServerStart( logger: Logger } ): Promise { - return new Promise((resolve, reject) => { - let { port, strictPort, host, logger } = serverOptions + let { port, strictPort, host, logger } = serverOptions + return new Promise((resolve, reject) => { const onError = (e: Error & { code?: string }) => { if (e.code === 'EADDRINUSE') { if (strictPort) { diff --git a/packages/vite/src/node/logger.ts b/packages/vite/src/node/logger.ts index 1176a265a4fcbe..0518d86b271ac7 100644 --- a/packages/vite/src/node/logger.ts +++ b/packages/vite/src/node/logger.ts @@ -8,6 +8,7 @@ import type { RollupError } from 'rollup' import type { CommonServerOptions } from './http' import type { Hostname } from './utils' import { resolveHostname } from './utils' +import { loopbackHosts, wildcardHosts } from './constants' import type { ResolvedConfig } from '.' export type LogType = 'error' | 'warn' | 'info' @@ -172,19 +173,30 @@ function printServerUrls( info: Logger['info'] ): void { const urls: Array<{ label: string; url: string }> = [] + const notes: Array<{ label: string; message: string }> = [] + + if (hostname.host && loopbackHosts.has(hostname.host)) { + let hostnameName = hostname.name + if ( + hostnameName === '::1' || + hostnameName === '0000:0000:0000:0000:0000:0000:0000:0001' + ) { + hostnameName = `[${hostnameName}]` + } - if (hostname.host === '127.0.0.1') { urls.push({ label: 'Local', url: colors.cyan( - `${protocol}://${hostname.name}:${colors.bold(port)}${base}` + `${protocol}://${hostnameName}:${colors.bold(port)}${base}` ) }) - if (hostname.name !== '127.0.0.1') { - urls.push({ - label: 'Network', - url: colors.dim(`use ${colors.white(colors.bold('--host'))} to expose`) + if (hostname.name === 'localhost') { + notes.push({ + label: 'Hint', + message: colors.dim( + `Use ${colors.white(colors.bold('--host'))} to expose to network.` + ) }) } } else { @@ -208,15 +220,34 @@ function printServerUrls( }) } - const length = urls.reduce( - (length, { label }) => Math.max(length, label.length), - 0 + if (!hostname.host || wildcardHosts.has(hostname.host)) { + notes.push({ + label: 'Note', + message: colors.dim( + 'You are using a wildcard host. Ports might be overridden.' + ) + }) + } + + const length = Math.max( + ...[...urls, ...notes].map(({ label }) => label.length) ) - urls.forEach(({ label, url: text }) => { + const print = ( + iconWithColor: string, + label: string, + messageWithColor: string + ) => { info( - ` ${colors.green('➜')} ${colors.bold(label)}: ${' '.repeat( + ` ${iconWithColor} ${colors.bold(label)}: ${' '.repeat( length - label.length - )}${text}` + )}${messageWithColor}` ) + } + + urls.forEach(({ label, url: text }) => { + print(colors.green('➜'), label, text) + }) + notes.forEach(({ label, message: text }) => { + print(colors.white('❖'), label, text) }) } diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts index fbfb658e9f7505..aacabbdf9b1a3c 100644 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts @@ -80,6 +80,24 @@ export function esbuildDepPlugin( return resolver(id, _importer, undefined) } + const resolveResult = (id: string, resolved: string) => { + if (resolved.startsWith(browserExternalId)) { + return { + path: id, + namespace: 'browser-external' + } + } + if (isExternalUrl(resolved)) { + return { + path: resolved, + external: true + } + } + return { + path: path.resolve(resolved) + } + } + return { name: 'vite:dep-pre-bundle', setup(build) { @@ -156,21 +174,7 @@ export function esbuildDepPlugin( // use vite's own resolver const resolved = await resolve(id, importer, kind) if (resolved) { - if (resolved.startsWith(browserExternalId)) { - return { - path: id, - namespace: 'browser-external' - } - } - if (isExternalUrl(resolved)) { - return { - path: resolved, - external: true - } - } - return { - path: path.resolve(resolved) - } + return resolveResult(id, resolved) } } ) @@ -220,24 +224,29 @@ export function esbuildDepPlugin( build.onLoad( { filter: /.*/, namespace: 'browser-external' }, ({ path }) => { - return { - // Return in CJS to intercept named imports. Use `Object.create` to - // create the Proxy in the prototype to workaround esbuild issue. Why? - // - // In short, esbuild cjs->esm flow: - // 1. Create empty object using `Object.create(Object.getPrototypeOf(module.exports))`. - // 2. Assign props of `module.exports` to the object. - // 3. Return object for ESM use. - // - // If we do `module.exports = new Proxy({}, {})`, step 1 returns empty object, - // step 2 does nothing as there's no props for `module.exports`. The final object - // is just an empty object. - // - // Creating the Proxy in the prototype satisfies step 1 immediately, which means - // the returned object is a Proxy that we can intercept. - // - // Note: Skip keys that are accessed by esbuild and browser devtools. - contents: `\ + if (config.isProduction) { + return { + contents: 'module.exports = {}' + } + } else { + return { + // Return in CJS to intercept named imports. Use `Object.create` to + // create the Proxy in the prototype to workaround esbuild issue. Why? + // + // In short, esbuild cjs->esm flow: + // 1. Create empty object using `Object.create(Object.getPrototypeOf(module.exports))`. + // 2. Assign props of `module.exports` to the object. + // 3. Return object for ESM use. + // + // If we do `module.exports = new Proxy({}, {})`, step 1 returns empty object, + // step 2 does nothing as there's no props for `module.exports`. The final object + // is just an empty object. + // + // Creating the Proxy in the prototype satisfies step 1 immediately, which means + // the returned object is a Proxy that we can intercept. + // + // Note: Skip keys that are accessed by esbuild and browser devtools. + contents: `\ module.exports = Object.create(new Proxy({}, { get(_, key) { if ( @@ -250,6 +259,7 @@ module.exports = Object.create(new Proxy({}, { } } }))` + } } } ) @@ -258,11 +268,20 @@ module.exports = Object.create(new Proxy({}, { if (isRunningWithYarnPnp) { build.onResolve( { filter: /.*/ }, - async ({ path, importer, kind, resolveDir }) => ({ - // pass along resolveDir for entries - path: await resolve(path, importer, kind, resolveDir) - }) + async ({ path: id, importer, kind, resolveDir, namespace }) => { + const resolved = await resolve( + id, + importer, + kind, + // pass along resolveDir for entries + namespace === 'dep' ? resolveDir : undefined + ) + if (resolved) { + return resolveResult(id, resolved) + } + } ) + build.onLoad({ filter: /.*/ }, async (args) => ({ contents: await fs.readFile(args.path), loader: 'default' diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 8160ee0b0d83a0..6043a0fcd9452a 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -6,8 +6,10 @@ import colors from 'picocolors' import type { BuildOptions as EsbuildBuildOptions } from 'esbuild' import { build } from 'esbuild' import { init, parse } from 'es-module-lexer' +import { createFilter } from '@rollup/pluginutils' import type { ResolvedConfig } from '../config' import { + arraify, createDebugger, emptyDir, flattenId, @@ -43,10 +45,13 @@ export type ExportsData = { } export interface DepsOptimizer { - metadata: DepOptimizationMetadata + metadata: (options: { ssr: boolean }) => DepOptimizationMetadata scanProcessing?: Promise - - registerMissingImport: (id: string, resolved: string) => OptimizedDepInfo + registerMissingImport: ( + id: string, + resolved: string, + ssr?: boolean + ) => OptimizedDepInfo run: () => void isOptimizedDepFile: (id: string) => boolean @@ -72,13 +77,6 @@ export interface DepOptimizationOptions { * vite project root. This will overwrite default entries inference. */ entries?: string | string[] - /** - * Enable esbuild based scan phase, to get back to the optimized deps discovery - * strategy used in Vite v2 - * @default false - * @experimental - */ - devScan?: boolean /** * Force optimize listed dependencies (must be resolvable import paths, * cannot be globs). @@ -137,6 +135,11 @@ export interface DepOptimizationOptions { * @experimental */ disabled?: boolean | 'build' | 'dev' + /** + * Force dep pre-optimization regardless of whether deps have changed. + * @experimental + */ + force?: boolean } export interface DepOptimizationResult { @@ -209,7 +212,7 @@ export interface DepOptimizationMetadata { */ export async function optimizeDeps( config: ResolvedConfig, - force = config.force, + force = config.optimizeDeps.force, asCommand = false ): Promise { const log = asCommand ? config.logger.info : debug @@ -234,6 +237,53 @@ export async function optimizeDeps( return result.metadata } +export async function optimizeServerSsrDeps( + config: ResolvedConfig +): Promise { + const cachedMetadata = loadCachedDepOptimizationMetadata( + config, + config.optimizeDeps.force, + false, + true // ssr + ) + if (cachedMetadata) { + return cachedMetadata + } + + let alsoInclude: string[] | undefined + let noExternalFilter: ((id: unknown) => boolean) | undefined + + const noExternal = config.ssr?.noExternal + if (noExternal) { + alsoInclude = arraify(noExternal).filter( + (ne) => typeof ne === 'string' + ) as string[] + noExternalFilter = + noExternal === true + ? (dep: unknown) => false + : createFilter(noExternal, config.optimizeDeps?.exclude, { + resolve: false + }) + } + + const deps: Record = {} + + await addManuallyIncludedOptimizeDeps( + deps, + config, + alsoInclude, + noExternalFilter + ) + + const depsInfo = toDiscoveredDependencies(config, deps, true) + + const result = await runOptimizeDeps(config, depsInfo, true) + + await result.commit() + + return result.metadata +} + export function initDepsOptimizerMetadata( config: ResolvedConfig, timestamp?: string @@ -265,8 +315,9 @@ export function addOptimizedDepInfo( */ export function loadCachedDepOptimizationMetadata( config: ResolvedConfig, - force = config.force, - asCommand = false + force = config.optimizeDeps.force, + asCommand = false, + ssr = !!config.build.ssr ): DepOptimizationMetadata | undefined { const log = asCommand ? config.logger.info : debug @@ -276,7 +327,7 @@ export function loadCachedDepOptimizationMetadata( emptyDir(config.cacheDir) } - const depsCacheDir = getDepsCacheDir(config) + const depsCacheDir = getDepsCacheDir(config, ssr) if (!force) { let cachedMetadata: DepOptimizationMetadata | undefined @@ -343,6 +394,15 @@ export async function initialProjectDependencies( await addManuallyIncludedOptimizeDeps(deps, config) + return toDiscoveredDependencies(config, deps, !!config.build.ssr, timestamp) +} + +export function toDiscoveredDependencies( + config: ResolvedConfig, + deps: Record, + ssr: boolean, + timestamp?: string +): Record { const browserHash = getOptimizedBrowserHash( getDepHash(config), deps, @@ -353,7 +413,7 @@ export async function initialProjectDependencies( const src = deps[id] discovered[id] = { id, - file: getOptimizedDepPath(id, config), + file: getOptimizedDepPath(id, config, ssr), src, browserHash: browserHash, exportsData: extractExportsData(src, config) @@ -383,15 +443,17 @@ export function depsLogString(qualifiedIds: string[]): string { */ export async function runOptimizeDeps( resolvedConfig: ResolvedConfig, - depsInfo: Record + depsInfo: Record, + ssr: boolean = !!resolvedConfig.build.ssr ): Promise { + const isBuild = resolvedConfig.command === 'build' const config: ResolvedConfig = { ...resolvedConfig, command: 'build' } - const depsCacheDir = getDepsCacheDir(resolvedConfig) - const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig) + const depsCacheDir = getDepsCacheDir(resolvedConfig, ssr) + const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig, ssr) // Create a temporal directory so we don't need to delete optimized deps // until they have been processed. This also avoids leaving the deps cache @@ -471,20 +533,15 @@ export async function runOptimizeDeps( flatIdToExports[flatId] = exportsData } - const define: Record = { - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || config.mode) - } - for (const key in config.define) { - const value = config.define[key] - define[key] = typeof value === 'string' ? value : JSON.stringify(value) - } - const start = performance.now() const result = await build({ absWorkingDir: process.cwd(), entryPoints: Object.keys(flatIdDeps), bundle: true, + // Ensure resolution is handled by esbuildDepPlugin and + // avoid replacing `process.env.NODE_ENV` for 'browser' + platform: 'neutral', format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, @@ -492,9 +549,8 @@ export async function runOptimizeDeps( splitting: true, sourcemap: true, outdir: processingCacheDir, - ignoreAnnotations: resolvedConfig.command !== 'build', + ignoreAnnotations: !isBuild, metafile: true, - define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config) @@ -533,7 +589,7 @@ export async function runOptimizeDeps( const id = path .relative(processingCacheDirOutputPath, o) .replace(jsExtensionRE, '') - const file = getOptimizedDepPath(id, resolvedConfig) + const file = getOptimizedDepPath(id, resolvedConfig, ssr) if ( !findOptimizedDepInfoInRecord( metadata.optimized, @@ -568,16 +624,18 @@ export async function findKnownImports( async function addManuallyIncludedOptimizeDeps( deps: Record, - config: ResolvedConfig + config: ResolvedConfig, + extra?: string[], + filter?: (id: string) => boolean ): Promise { - const include = config.optimizeDeps?.include + const include = [...(config.optimizeDeps?.include ?? []), ...(extra ?? [])] if (include) { const resolve = config.createResolver({ asSrc: false, scan: true }) for (const id of include) { // normalize 'foo >bar` as 'foo > bar' to prevent same id being added // and for pretty printing const normalizedId = normalizeId(id) - if (!deps[normalizedId]) { + if (!deps[normalizedId] && filter?.(normalizedId) !== false) { const entry = await resolve(id) if (entry) { deps[normalizedId] = entry @@ -610,45 +668,55 @@ export function depsFromOptimizedDepInfo( export function getOptimizedDepPath( id: string, - config: ResolvedConfig + config: ResolvedConfig, + ssr: boolean = !!config.build.ssr ): string { return normalizePath( - path.resolve(getDepsCacheDir(config), flattenId(id) + '.js') + path.resolve(getDepsCacheDir(config, ssr), flattenId(id) + '.js') ) } -function getDepsCacheSuffix(config: ResolvedConfig): string { +function getDepsCacheSuffix(config: ResolvedConfig, ssr: boolean): string { let suffix = '' if (config.command === 'build') { - suffix += '_build' - if (config.build.ssr) { - suffix += '_ssr' - } + // Differentiate build caches depending on outDir to allow parallel builds + const { outDir } = config.build + const buildId = + outDir.length > 8 || outDir.includes('/') ? getHash(outDir) : outDir + suffix += `_build-${buildId}` + } + if (ssr) { + suffix += '_ssr' } return suffix } -export function getDepsCacheDir(config: ResolvedConfig): string { - const dirName = 'deps' + getDepsCacheSuffix(config) - return normalizePath(path.resolve(config.cacheDir, dirName)) + +export function getDepsCacheDir(config: ResolvedConfig, ssr: boolean): string { + return getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr) +} + +function getProcessingDepsCacheDir(config: ResolvedConfig, ssr: boolean) { + return ( + getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr) + '_temp' + ) } -function getProcessingDepsCacheDir(config: ResolvedConfig) { - const dirName = 'deps' + getDepsCacheSuffix(config) + '_temp' - return normalizePath(path.resolve(config.cacheDir, dirName)) +export function getDepsCacheDirPrefix(config: ResolvedConfig): string { + return normalizePath(path.resolve(config.cacheDir, 'deps')) } export function isOptimizedDepFile( id: string, config: ResolvedConfig ): boolean { - return id.startsWith(getDepsCacheDir(config)) + return id.startsWith(getDepsCacheDirPrefix(config)) } export function createIsOptimizedDepUrl( config: ResolvedConfig ): (url: string) => boolean { const { root } = config - const depsCacheDir = getDepsCacheDir(config) + const depsCacheDir = getDepsCacheDirPrefix(config) // determine the url prefix of files inside cache directory const depsCacheDirRelative = normalizePath(path.relative(root, depsCacheDir)) @@ -887,7 +955,6 @@ export function getDepHash(config: ResolvedConfig): string { { mode: process.env.NODE_ENV || config.mode, root: config.root, - define: config.define, resolve: config.resolve, buildTarget: config.build.target, assetsInclude: config.assetsInclude, diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 5dbccae6a73db3..5592c438ece552 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -18,9 +18,11 @@ import { isOptimizedDepFile, loadCachedDepOptimizationMetadata, newDepOptimizationProcessing, + optimizeServerSsrDeps, runOptimizeDeps } from '.' import type { + DepOptimizationMetadata, DepOptimizationProcessing, DepsOptimizer, OptimizedDepInfo @@ -50,7 +52,7 @@ export async function initDepsOptimizer( const { logger } = config const isBuild = config.command === 'build' - const scan = config.command !== 'build' && config.optimizeDeps.devScan + const scan = config.command !== 'build' && config.legacy?.devDepsScanner const sessionTimestamp = Date.now().toString() @@ -58,9 +60,18 @@ export async function initDepsOptimizer( let handle: NodeJS.Timeout | undefined + let ssrServerDepsMetadata: DepOptimizationMetadata + let _metadata = + cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp) + const depsOptimizer: DepsOptimizer = { - metadata: - cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp), + metadata: (options: { ssr: boolean }) => { + if (isBuild || !options.ssr) { + return _metadata + } else { + return ssrServerDepsMetadata + } + }, registerMissingImport, run: () => debouncedProcessing(0), isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config), @@ -75,6 +86,10 @@ export async function initDepsOptimizer( depsOptimizerMap.set(config, depsOptimizer) + if (!isBuild && config.ssr) { + ssrServerDepsMetadata = await optimizeServerSsrDeps(config) + } + let newDepsDiscovered = false let newDepsToLog: string[] = [] @@ -119,7 +134,7 @@ export async function initDepsOptimizer( config, sessionTimestamp ) - const { metadata } = depsOptimizer + const metadata = _metadata for (const depInfo of Object.values(discovered)) { addOptimizedDepInfo(metadata, 'discovered', { ...depInfo, @@ -137,7 +152,7 @@ export async function initDepsOptimizer( try { debug(colors.green(`scanning for dependencies...`)) - const { metadata } = depsOptimizer + const metadata = _metadata const discovered = await discoverProjectDependencies( config, @@ -183,7 +198,7 @@ export async function initDepsOptimizer( // Ensure that a rerun will not be issued for current discovered deps if (handle) clearTimeout(handle) - if (Object.keys(depsOptimizer.metadata.discovered).length === 0) { + if (Object.keys(_metadata.discovered).length === 0) { currentlyProcessing = false return } @@ -193,13 +208,13 @@ export async function initDepsOptimizer( // a succesful completion of the optimizeDeps rerun will end up // creating new bundled version of all current and discovered deps // in the cache dir and a new metadata info object assigned - // to optimizeDeps.metadata. A fullReload is only issued if - // the previous bundled dependencies have changed. + // to _metadata. A fullReload is only issued if the previous bundled + // dependencies have changed. - // if the rerun fails, optimizeDeps.metadata remains untouched, - // current discovered deps are cleaned, and a fullReload is issued + // if the rerun fails, _metadata remains untouched, current discovered + // deps are cleaned, and a fullReload is issued - let { metadata } = depsOptimizer + let metadata = _metadata // All deps, previous known and newly discovered are rebundled, // respect insertion order to keep the metadata file stable @@ -306,7 +321,7 @@ export async function initDepsOptimizer( ) } - metadata = depsOptimizer.metadata = newData + metadata = _metadata = newData resolveEnqueuedProcessingPromises() } @@ -400,7 +415,7 @@ export async function initDepsOptimizer( // debounce time to wait for new missing deps finished, issue a new // optimization of deps (both old and newly found) once the previous // optimizeDeps processing is finished - const deps = Object.keys(depsOptimizer.metadata.discovered) + const deps = Object.keys(_metadata.discovered) const depsString = depsLogString(deps) debug(colors.green(`new dependencies found: ${depsString}`)) runOptimizer() @@ -426,7 +441,12 @@ export async function initDepsOptimizer( 'Vite internal error: registering missing import before initial scanning is over' ) } - const { metadata } = depsOptimizer + if (!isBuild && ssr) { + config.logger.error( + `Error: ${id} is a missing dependency in SSR dev server, it needs to be added to optimizeDeps.include` + ) + } + const metadata = _metadata const optimized = metadata.optimized[id] if (optimized) { return optimized @@ -444,7 +464,7 @@ export async function initDepsOptimizer( newDepsDiscovered = true missing = addOptimizedDepInfo(metadata, 'discovered', { id, - file: getOptimizedDepPath(id, config), + file: getOptimizedDepPath(id, config, ssr), src: resolved, // Assing a browserHash to this missing dependency that is unique to // the current state of known + missing deps. If its optimizeDeps run diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 9e99629c8144e9..2a119808a4eb72 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -365,11 +365,22 @@ async function fileToBuiltUrl( const { search, hash } = parseUrl(id) const postfix = (search || '') + (hash || '') const output = config.build?.rollupOptions?.output - const assetFileNames = + + const defaultAssetFileNames = path.posix.join( + config.build.assetsDir, + '[name].[hash][extname]' + ) + // Steps to determine which assetFileNames will be actually used. + // First, if output is an object or string, use assetFileNames in it. + // And a default assetFileNames as fallback. + let assetFileNames: Exclude = (output && !Array.isArray(output) ? output.assetFileNames : undefined) ?? - // defaults to '/[name].[hash][extname]' - // slightly different from rollup's one ('assets/[name]-[hash][extname]') - path.posix.join(config.build.assetsDir, '[name].[hash][extname]') + defaultAssetFileNames + if (output && Array.isArray(output)) { + // Second, if output is an array, adopt assetFileNames in the first object. + assetFileNames = output[0].assetFileNames ?? assetFileNames + } + const fileName = assetFileNamesToFileName( assetFileNames, file, diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index ce068ac6104f81..45af9233bf4560 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -861,7 +861,9 @@ async function compileCSS( // https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#3-dependencies const { dir, glob: globPattern = '**' } = message const pattern = - normalizePath(path.resolve(path.dirname(id), dir)) + `/` + globPattern + glob.escapePath(normalizePath(path.resolve(path.dirname(id), dir))) + + `/` + + globPattern const files = glob.sync(pattern, { ignore: ['**/node_modules/**'] }) diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index 9d06d6e168f2e1..a66a6288f8e8c2 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -767,11 +767,6 @@ export async function applyHtmlTransforms( hooks: IndexHtmlTransformHook[], ctx: IndexHtmlTransformContext ): Promise { - const headTags: HtmlTagDescriptor[] = [] - const headPrependTags: HtmlTagDescriptor[] = [] - const bodyTags: HtmlTagDescriptor[] = [] - const bodyPrependTags: HtmlTagDescriptor[] = [] - for (const hook of hooks) { const res = await hook(html, ctx) if (!res) { @@ -787,6 +782,12 @@ export async function applyHtmlTransforms( html = res.html || html tags = res.tags } + + const headTags: HtmlTagDescriptor[] = [] + const headPrependTags: HtmlTagDescriptor[] = [] + const bodyTags: HtmlTagDescriptor[] = [] + const bodyPrependTags: HtmlTagDescriptor[] = [] + for (const tag of tags) { if (tag.injectTo === 'body') { bodyTags.push(tag) @@ -798,21 +799,12 @@ export async function applyHtmlTransforms( headPrependTags.push(tag) } } - } - } - // inject tags - if (headPrependTags.length) { - html = injectToHead(html, headPrependTags, true) - } - if (headTags.length) { - html = injectToHead(html, headTags) - } - if (bodyPrependTags.length) { - html = injectToBody(html, bodyPrependTags, true) - } - if (bodyTags.length) { - html = injectToBody(html, bodyTags) + html = injectToHead(html, headPrependTags, true) + html = injectToHead(html, headTags) + html = injectToBody(html, bodyPrependTags, true) + html = injectToBody(html, bodyTags) + } } return html @@ -859,6 +851,8 @@ function injectToHead( tags: HtmlTagDescriptor[], prepend = false ) { + if (tags.length === 0) return html + if (prepend) { // inject as the first element of head if (headPrependInjectRE.test(html)) { @@ -893,6 +887,8 @@ function injectToBody( tags: HtmlTagDescriptor[], prepend = false ) { + if (tags.length === 0) return html + if (prepend) { // inject after body open if (bodyPrependInjectRE.test(html)) { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index b4f5fbfa442407..f0afb794060ef3 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -36,6 +36,7 @@ import { normalizePath, prettifyUrl, removeImportQuery, + stripBomTag, timeFrom, transformResult, unwrapId @@ -48,7 +49,7 @@ import { } from '../ssr/ssrExternal' import { transformRequest } from '../server/transformRequest' import { - getDepsCacheDir, + getDepsCacheDirPrefix, getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer' @@ -142,10 +143,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { const start = performance.now() await init let imports: readonly ImportSpecifier[] = [] - // strip UTF-8 BOM - if (source.charCodeAt(0) === 0xfeff) { - source = source.slice(1) - } + source = stripBomTag(source) try { imports = parseImports(source)[0] } catch (e: any) { @@ -226,7 +224,8 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // the dependency needs to be resolved starting from the original source location of the optimized file // because starting from node_modules/.vite will not find the dependency if it was not hoisted // (that is, if it is under node_modules directory in the package source of the optimized file) - for (const optimizedModule of depsOptimizer.metadata.depInfoList) { + for (const optimizedModule of depsOptimizer.metadata({ ssr }) + .depInfoList) { if (!optimizedModule.src) continue // Ignore chunks if (optimizedModule.file === importerModule.file) { importerFile = optimizedModule.src @@ -260,7 +259,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // in root: infer short absolute path from root url = resolved.id.slice(root.length) } else if ( - resolved.id.startsWith(getDepsCacheDir(config)) || + resolved.id.startsWith(getDepsCacheDirPrefix(config)) || fs.existsSync(cleanUrl(resolved.id)) ) { // an optimized deps may not yet exists in the filesystem, or @@ -372,7 +371,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } // skip ssr external if (ssr) { - if (config.ssr?.format === 'cjs') { + if (config.legacy?.buildSsrCjsExternalHeuristics) { if (cjsShouldExternalizeForSSR(specifier, server._ssrExternals)) { continue } @@ -422,7 +421,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { const file = cleanUrl(resolvedId) // Remove ?v={hash} const needsInterop = await optimizedDepNeedsInterop( - depsOptimizer.metadata, + depsOptimizer.metadata({ ssr }), file, config ) @@ -623,7 +622,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // Unexpected error, log the issue but avoid an unhandled exception config.logger.error(e.message) }) - if (depsOptimizer && !config.optimizeDeps.devScan) { + if (depsOptimizer && !config.legacy?.devDepsScanner) { depsOptimizer.delayDepsOptimizerUntil(id, () => request) } }) diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index 0895bf5cb8f8a4..f56d2a4194ca84 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -172,7 +172,8 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { // the dependency needs to be resolved starting from the original source location of the optimized file // because starting from node_modules/.vite will not find the dependency if it was not hoisted // (that is, if it is under node_modules directory in the package source of the optimized file) - for (const optimizedModule of depsOptimizer.metadata.depInfoList) { + for (const optimizedModule of depsOptimizer.metadata({ ssr }) + .depInfoList) { if (!optimizedModule.src) continue // Ignore chunks if (optimizedModule.file === importer) { importerFile = optimizedModule.src @@ -263,7 +264,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { const file = cleanUrl(resolvedId) // Remove ?v={hash} const needsInterop = await optimizedDepNeedsInterop( - depsOptimizer.metadata, + depsOptimizer.metadata({ ssr }), file, config ) diff --git a/packages/vite/src/node/plugins/json.ts b/packages/vite/src/node/plugins/json.ts index 9c142501ff651e..216679a22148cd 100644 --- a/packages/vite/src/node/plugins/json.ts +++ b/packages/vite/src/node/plugins/json.ts @@ -9,6 +9,7 @@ import { dataToEsm } from '@rollup/pluginutils' import { SPECIAL_QUERY_RE } from '../constants' import type { Plugin } from '../plugin' +import { stripBomTag } from '../utils' export interface JsonOptions { /** @@ -43,6 +44,8 @@ export function jsonPlugin( if (!jsonExtRE.test(id)) return null if (SPECIAL_QUERY_RE.test(id)) return null + json = stripBomTag(json) + try { if (options.stringify) { if (isBuild) { diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index b50294f9e64b9b..3a3f541adce3bc 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -27,10 +27,11 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { // The logic to register an id to wait until it is processed // is in importAnalysis, see call to delayDepsOptimizerUntil - async load(id) { + async load(id, options) { + const ssr = options?.ssr ?? false const depsOptimizer = getDepsOptimizer(config) if (depsOptimizer?.isOptimizedDepFile(id)) { - const metadata = depsOptimizer?.metadata + const metadata = depsOptimizer?.metadata({ ssr }) if (metadata) { const file = cleanUrl(id) const versionMatch = id.match(DEP_VERSION_RE) @@ -54,7 +55,7 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { throwProcessingError(id) return } - const newMetadata = depsOptimizer.metadata + const newMetadata = depsOptimizer.metadata({ ssr }) if (metadata !== newMetadata) { const currentInfo = optimizedDepInfoFromFile(newMetadata!, file) if (info.browserHash !== currentInfo?.browserHash) { @@ -100,9 +101,10 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { }) }, - async load(id) { + async load(id, options) { + const ssr = options?.ssr ?? false const depsOptimizer = getDepsOptimizer(config) - const metadata = depsOptimizer?.metadata + const metadata = depsOptimizer?.metadata({ ssr }) if (!metadata || !depsOptimizer?.isOptimizedDepFile(id)) { return } diff --git a/packages/vite/src/node/plugins/preAlias.ts b/packages/vite/src/node/plugins/preAlias.ts index 0d6076b03a329f..46bc2436b0686a 100644 --- a/packages/vite/src/node/plugins/preAlias.ts +++ b/packages/vite/src/node/plugins/preAlias.ts @@ -11,14 +11,10 @@ export function preAliasPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:pre-alias', async resolveId(id, importer, options) { + const ssr = options?.ssr ?? false const depsOptimizer = getDepsOptimizer(config) - if ( - depsOptimizer && - !options?.ssr && - bareImportRE.test(id) && - !options?.scan - ) { - return await tryOptimizedResolve(depsOptimizer, id, importer) + if (depsOptimizer && bareImportRE.test(id) && !options?.scan) { + return await tryOptimizedResolve(depsOptimizer, ssr, id, importer) } } } diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 7ccc3a7eb5cb7a..f552b19a08a21a 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -89,7 +89,6 @@ export interface InternalResolveOptions extends ResolveOptions { export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { const { root, - isBuild, isProduction, asSrc, ssrConfig, @@ -187,7 +186,7 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { // Inject the current browserHash version if the path doesn't have one if (!normalizedFsPath.match(DEP_VERSION_RE)) { const browserHash = optimizedDepInfoFromFile( - depsOptimizer.metadata, + depsOptimizer.metadata({ ssr }), normalizedFsPath )?.browserHash if (browserHash) { @@ -266,9 +265,8 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { !external && asSrc && depsOptimizer && - (isBuild || !ssr) && !options.scan && - (res = await tryOptimizedResolve(depsOptimizer, id, importer)) + (res = await tryOptimizedResolve(depsOptimizer, ssr, id, importer)) ) { return res } @@ -542,6 +540,8 @@ export function tryNodeResolve( ): PartialResolvedId | undefined { const { root, dedupe, isBuild, preserveSymlinks, packageCache } = options + ssr ??= false + // split id by last '>' for nested selected packages, for example: // 'foo > bar > baz' => 'foo > bar' & 'baz' // 'foo' => '' & 'foo' @@ -687,7 +687,7 @@ export function tryNodeResolve( // otherwise we may introduce duplicated modules for externalized files // from pre-bundled deps. if (!isBuild) { - const versionHash = depsOptimizer.metadata.browserHash + const versionHash = depsOptimizer.metadata({ ssr }).browserHash if (versionHash && isJsType) { resolved = injectQuery(resolved, `v=${versionHash}`) } @@ -695,7 +695,7 @@ export function tryNodeResolve( } else { // this is a missing import, queue optimize-deps re-run and // get a resolved its optimized info - const optimizedInfo = depsOptimizer.registerMissingImport(id, resolved) + const optimizedInfo = depsOptimizer.registerMissingImport(id, resolved, ssr) resolved = depsOptimizer.getOptimizedDepId(optimizedInfo) } @@ -713,12 +713,18 @@ export function tryNodeResolve( export async function tryOptimizedResolve( depsOptimizer: DepsOptimizer, + ssr: boolean, id: string, importer?: string ): Promise { await depsOptimizer.scanProcessing - const depInfo = optimizedDepInfoFromId(depsOptimizer.metadata, id) + const metadata = depsOptimizer.metadata({ ssr }) + if (!metadata) { + return + } + + const depInfo = optimizedDepInfoFromId(metadata, id) if (depInfo) { return depsOptimizer.getOptimizedDepId(depInfo) } @@ -728,7 +734,7 @@ export async function tryOptimizedResolve( // further check if id is imported by nested dependency let resolvedSrc: string | undefined - for (const optimizedData of depsOptimizer.metadata.depInfoList) { + for (const optimizedData of metadata.depInfoList) { if (!optimizedData.src) continue // Ignore chunks const pkgPath = optimizedData.id diff --git a/packages/vite/src/node/plugins/wasm.ts b/packages/vite/src/node/plugins/wasm.ts index 56aa56402df8e2..19bb9a0e541892 100644 --- a/packages/vite/src/node/plugins/wasm.ts +++ b/packages/vite/src/node/plugins/wasm.ts @@ -7,11 +7,21 @@ const wasmHelperId = '/__vite-wasm-helper' const wasmHelper = async (opts = {}, url: string) => { let result if (url.startsWith('data:')) { - // @ts-ignore - const binaryString = atob(url.replace(/^data:.*?base64,/, '')) - const bytes = new Uint8Array(binaryString.length) - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i) + const urlContent = url.replace(/^data:.*?base64,/, '') + let bytes + if (typeof Buffer === 'function' && typeof Buffer.from === 'function') { + bytes = Buffer.from(urlContent, 'base64') + } else if (typeof atob === 'function') { + // @ts-ignore + const binaryString = atob(urlContent) + bytes = new Uint8Array(binaryString.length) + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i) + } + } else { + throw new Error( + 'Failed to decode base64-encoded data URL, Buffer and atob are not supported' + ) } // @ts-ignore result = await WebAssembly.instantiate(bytes, opts) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index fc66b909a33989..169600b680fd99 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -749,13 +749,15 @@ async function restartServer(server: ViteDevServer) { async function updateCjsSsrExternals(server: ViteDevServer) { if (!server._ssrExternals) { + // We use the non-ssr optimized deps to find known imports let knownImports: string[] = [] const depsOptimizer = getDepsOptimizer(server.config) if (depsOptimizer) { await depsOptimizer.scanProcessing + const metadata = depsOptimizer.metadata({ ssr: false }) knownImports = [ - ...Object.keys(depsOptimizer.metadata.optimized), - ...Object.keys(depsOptimizer.metadata.discovered) + ...Object.keys(metadata.optimized), + ...Object.keys(metadata.discovered) ] } server._ssrExternals = cjsSsrResolveExternals(server.config, knownImports) diff --git a/packages/vite/src/node/server/middlewares/proxy.ts b/packages/vite/src/node/server/middlewares/proxy.ts index 5c447435911ecd..9cd529a0dbba7b 100644 --- a/packages/vite/src/node/server/middlewares/proxy.ts +++ b/packages/vite/src/node/server/middlewares/proxy.ts @@ -43,11 +43,16 @@ export function proxyMiddleware( } const proxy = httpProxy.createProxyServer(opts) as HttpProxy.Server - proxy.on('error', (err) => { + proxy.on('error', (err, req, res) => { config.logger.error(`${colors.red(`http proxy error:`)}\n${err.stack}`, { timestamp: true, error: err }) + res + .writeHead(500, { + 'Content-Type': 'text/plain' + }) + .end() }) if (opts.configure) { diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index de0dd5bab031e4..daad169ff6fc2f 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -167,8 +167,8 @@ function createIsSsrExternal( } } -// When ssr.format is 'cjs', this function is used reverting to the Vite 2.9 -// SSR externalization heuristics +// When config.experimental.buildSsrCjsExternalHeuristics is enabled, this function +// is used reverting to the Vite 2.9 SSR externalization heuristics function cjsSsrCollectExternals( root: string, preserveSymlinks: boolean | undefined, diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 75c23f35067fbe..02611dcfffae01 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -23,7 +23,8 @@ import { DEFAULT_EXTENSIONS, ENV_PUBLIC_PATH, FS_PREFIX, - VALID_ID_PREFIX + VALID_ID_PREFIX, + wildcardHosts } from './constants' import type { ResolvedConfig } from '.' @@ -747,7 +748,7 @@ export function resolveHostname( let host: string | undefined if (optionsHost === undefined || optionsHost === false) { // Use a secure default - host = '127.0.0.1' + host = 'localhost' } else if (optionsHost === true) { // If passed --host in the CLI without arguments host = undefined // undefined typically means 0.0.0.0 or :: (listen on all IPs) @@ -755,14 +756,9 @@ export function resolveHostname( host = optionsHost } - // Set host name to localhost when possible, unless the user explicitly asked for '127.0.0.1' + // Set host name to localhost when possible const name = - (optionsHost !== '127.0.0.1' && host === '127.0.0.1') || - host === '0.0.0.0' || - host === '::' || - host === undefined - ? 'localhost' - : host + host === undefined || wildcardHosts.has(host) ? 'localhost' : host return { host, name } } @@ -1025,3 +1021,12 @@ export async function asyncFlatten(arr: T[]): Promise { } while (arr.some((v: any) => v?.then)) return arr } + +// strip UTF-8 BOM +export function stripBomTag(content: string): string { + if (content.charCodeAt(0) === 0xfeff) { + return content.slice(1) + } + + return content +} diff --git a/playground/assets/vite.config-relative-base.js b/playground/assets/vite.config-relative-base.js index 12c3132f79a9b0..ae09766c0768ac 100644 --- a/playground/assets/vite.config-relative-base.js +++ b/playground/assets/vite.config-relative-base.js @@ -22,6 +22,5 @@ module.exports = { }, testConfig: { baseRoute: '/relative-base/' - }, - cacheDir: 'node_modules/.vite/relative-base' + } } diff --git a/playground/assets/vite.config.js b/playground/assets/vite.config.js index 23bd11908130cd..c9d821ae3d73ee 100644 --- a/playground/assets/vite.config.js +++ b/playground/assets/vite.config.js @@ -17,6 +17,5 @@ module.exports = { assetsInlineLimit: 8192, // 8kb manifest: true, watch: {} - }, - cacheDir: 'node_modules/.vite/foo' + } } diff --git a/playground/backend-integration/package.json b/playground/backend-integration/package.json index c1419548cb3bd3..a9c2b4f2bca63d 100644 --- a/playground/backend-integration/package.json +++ b/playground/backend-integration/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "tailwindcss": "^2.2.19" + "tailwindcss": "^3.1.2" }, "devDependencies": { "fast-glob": "^3.2.11" diff --git a/playground/backend-integration/tailwind.config.js b/playground/backend-integration/tailwind.config.js index 6e72a7e5d9d87b..0c3ae11de7565e 100644 --- a/playground/backend-integration/tailwind.config.js +++ b/playground/backend-integration/tailwind.config.js @@ -1,7 +1,5 @@ module.exports = { - mode: 'jit', - purge: [__dirname + '/frontend/**/*.{css,html,ts,js}'], - darkMode: false, // or 'media' or 'class' + content: [__dirname + '/frontend/**/*.{css,html,ts,js}'], theme: { extend: {} }, diff --git a/playground/css/__tests__/css.spec.ts b/playground/css/__tests__/css.spec.ts index e9d1fccac61d6e..564f0665bf0cea 100644 --- a/playground/css/__tests__/css.spec.ts +++ b/playground/css/__tests__/css.spec.ts @@ -317,9 +317,11 @@ test('treeshaken async chunk', async () => { test('PostCSS dir-dependency', async () => { const el1 = await page.$('.dir-dep') const el2 = await page.$('.dir-dep-2') + const el3 = await page.$('.dir-dep-3') expect(await getColor(el1)).toBe('grey') expect(await getColor(el2)).toBe('grey') + expect(await getColor(el3)).toBe('grey') if (!isBuild) { editFile('glob-dep/foo.css', (code) => @@ -334,6 +336,13 @@ test('PostCSS dir-dependency', async () => { await untilUpdated(() => getColor(el2), 'red') expect(await getColor(el1)).toBe('blue') + editFile('glob-dep/nested (dir)/baz.css', (code) => + code.replace('color: grey', 'color: green') + ) + await untilUpdated(() => getColor(el3), 'green') + expect(await getColor(el1)).toBe('blue') + expect(await getColor(el2)).toBe('red') + // test add/remove removeFile('glob-dep/bar.css') await untilUpdated(() => getColor(el2), 'black') diff --git a/playground/css/glob-dep/nested (dir)/baz.css b/playground/css/glob-dep/nested (dir)/baz.css new file mode 100644 index 00000000000000..9a8b0f0ba47dc5 --- /dev/null +++ b/playground/css/glob-dep/nested (dir)/baz.css @@ -0,0 +1,3 @@ +.dir-dep-3 { + color: grey; +} diff --git a/playground/css/index.html b/playground/css/index.html index 15e81192cec7f1..4310967b6ca65b 100644 --- a/playground/css/index.html +++ b/playground/css/index.html @@ -113,6 +113,9 @@

CSS

PostCSS dir-dependency (file 2): this should be grey too

+

+ PostCSS dir-dependency (file 3): this should be grey too +

URL separation preservation: should have valid background-image diff --git a/playground/css/postcss-caching/css.spec.ts b/playground/css/postcss-caching/css.spec.ts index bbffdb618280e4..45e8aadaa5489c 100644 --- a/playground/css/postcss-caching/css.spec.ts +++ b/playground/css/postcss-caching/css.spec.ts @@ -36,7 +36,7 @@ test('postcss config', async () => { blueApp = null greenApp = await startServer(greenAppDir) - await page.goto(`http://localhost:${port}`) + await page.reload() // hmr reloads it automatically but reload here for consistency const greenA = await page.$('.postcss-a') expect(await getColor(greenA)).toBe('black') const greenB = await page.$('.postcss-b') diff --git a/playground/css/postcss.config.js b/playground/css/postcss.config.js index 33058023541515..b30209bff42097 100644 --- a/playground/css/postcss.config.js +++ b/playground/css/postcss.config.js @@ -16,7 +16,7 @@ function testDirDep() { AtRule(atRule, { result, Comment }) { if (atRule.name === 'test') { const pattern = normalizePath( - path.resolve(path.dirname(result.opts.from), './glob-dep/*.css') + path.resolve(path.dirname(result.opts.from), './glob-dep/**/*.css') ) const files = glob.sync(pattern) const text = files.map((f) => fs.readFileSync(f, 'utf-8')).join('\n') @@ -30,6 +30,14 @@ function testDirDep() { glob: '*.css', parent: result.opts.from }) + + result.messages.push({ + type: 'dir-dependency', + plugin: 'dir-dep', + dir: './glob-dep/nested (dir)', // includes special characters in glob + glob: '*.css', + parent: result.opts.from + }) } } } diff --git a/playground/define/__tests__/define.spec.ts b/playground/define/__tests__/define.spec.ts index 695d210a822ed6..76b1dfef5feb39 100644 --- a/playground/define/__tests__/define.spec.ts +++ b/playground/define/__tests__/define.spec.ts @@ -40,4 +40,7 @@ test('string', async () => { // html would't need to define replacement expect(await page.textContent('.exp-define')).toBe('__EXP__') expect(await page.textContent('.import-json')).toBe('__EXP__') + expect(await page.textContent('.define-in-dep')).toBe( + defines.__STRINGIFIED_OBJ__ + ) }) diff --git a/playground/define/commonjs-dep/index.js b/playground/define/commonjs-dep/index.js new file mode 100644 index 00000000000000..23e0bf1b32e32f --- /dev/null +++ b/playground/define/commonjs-dep/index.js @@ -0,0 +1 @@ +module.exports = { defined: __STRINGIFIED_OBJ__ } diff --git a/playground/define/commonjs-dep/package.json b/playground/define/commonjs-dep/package.json new file mode 100644 index 00000000000000..3047ae68c9f75a --- /dev/null +++ b/playground/define/commonjs-dep/package.json @@ -0,0 +1,6 @@ +{ + "name": "commonjs-dep", + "private": true, + "version": "1.0.0", + "type": "commonjs" +} diff --git a/playground/define/index.html b/playground/define/index.html index 1260b119149d28..c4f4c598aba563 100644 --- a/playground/define/index.html +++ b/playground/define/index.html @@ -16,6 +16,7 @@

Define

no identifier substring:

define variable in html: __EXP__

import json:

+

define in dep: