diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 17898db2741284..05c1c02ab4cb12 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -16,7 +16,6 @@ "esbuild", "rollup", "node", - "ts-node", "typescript", // breaking changes diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 880df9e8d5aa92..013794932f334c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,6 +74,9 @@ jobs: - name: Test build run: pnpm run test-build + - name: Test docs + run: pnpm run test-docs + lint: timeout-minutes: 10 runs-on: ubuntu-latest diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 652772a8d5d12a..cc91df0ed32f7c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,8 +19,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v2 - with: - version: 6 - name: Set node version to 16.x uses: actions/setup-node@v3 diff --git a/.npmrc b/.npmrc index 525ca23d077413..6d64ed708da7d4 100644 --- a/.npmrc +++ b/.npmrc @@ -4,4 +4,5 @@ hoist-pattern[]=@emotion/* hoist-pattern[]=postcss hoist-pattern[]=pug hoist-pattern[]=source-map-support +hoist-pattern[]=ts-node strict-peer-dependencies=false diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 2e6d1ae6848838..8f0bdb8ef613f1 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -176,6 +176,45 @@ export default defineConfig({ } ] } + ], + '/config/': [ + { + text: 'Config', + items: [ + { + text: 'Configuring Vite', + link: '/config/' + }, + { + text: 'Shared Options', + link: '/config/shared-options' + }, + { + text: 'Server Options', + link: '/config/server-options' + }, + { + text: 'Build Options', + link: '/config/build-options' + }, + { + text: 'Preview Options', + link: '/config/preview-options' + }, + { + text: 'Dep Optimization Options', + link: '/config/dep-optimization-options' + }, + { + text: 'SSR Options', + link: '/config/ssr-options' + }, + { + text: 'Worker Options', + link: '/config/worker-options' + } + ] + } ] } } diff --git a/docs/.vitepress/theme/styles/vars.css b/docs/.vitepress/theme/styles/vars.css index 11e06d0c2ae061..938bf984656ec3 100644 --- a/docs/.vitepress/theme/styles/vars.css +++ b/docs/.vitepress/theme/styles/vars.css @@ -8,6 +8,7 @@ --vp-c-brand-lighter: #9499ff; --vp-c-brand-dark: #535bf2; --vp-c-brand-darker: #454ce1; + --vp-c-brand-dimm: rgba(100, 108, 255, 0.08); } /** @@ -39,6 +40,22 @@ ); } +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: var(--vp-c-brand); + --vp-custom-block-tip-text: var(--vp-c-brand-darker); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); +} + +.dark { + --vp-custom-block-tip-border: var(--vp-c-brand); + --vp-custom-block-tip-text: var(--vp-c-brand-lighter); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); +} + /** * Component: Algolia * -------------------------------------------------------------------------- */ diff --git a/docs/config/build-options.md b/docs/config/build-options.md new file mode 100644 index 00000000000000..11b4685aa72555 --- /dev/null +++ b/docs/config/build-options.md @@ -0,0 +1,189 @@ +# Build Options + +## build.target + +- **Type:** `string | string[]` +- **Default:** `'modules'` +- **Related:** [Browser Compatibility](/guide/build#browser-compatibility) + +Browser compatibility target for the final bundle. The default value is a Vite special value, `'modules'`, which targets browsers with [native ES Modules](https://caniuse.com/es6-module) and [native ESM dynamic import](https://caniuse.com/es6-module-dynamic-import) support. + +Another special value is `'esnext'` - which assumes native dynamic imports support and will transpile as little as possible: + +- If the [`build.minify`](#build-minify) option is `'terser'`, `'esnext'` will be forced down to `'es2021'`. +- In other cases, it will perform no transpilation at all. + +The transform is performed with esbuild and the value should be a valid [esbuild target option](https://esbuild.github.io/api/#target). Custom targets can either be a ES version (e.g. `es2015`), a browser with version (e.g. `chrome58`), or an array of multiple target strings. + +Note the build will fail if the code contains features that cannot be safely transpiled by esbuild. See [esbuild docs](https://esbuild.github.io/content-types/#javascript) for more details. + +## build.polyfillModulePreload + +- **Type:** `boolean` +- **Default:** `true` + +Whether to automatically inject [module preload polyfill](https://guybedford.com/es-module-preloading-integrity#modulepreload-polyfill). + +If set to `true`, the polyfill is auto injected into the proxy module of each `index.html` entry. If the build is configured to use a non-html custom entry via `build.rollupOptions.input`, then it is necessary to manually import the polyfill in your custom entry: + +```js +import 'vite/modulepreload-polyfill' +``` + +Note: the polyfill does **not** apply to [Library Mode](/guide/build#library-mode). If you need to support browsers without native dynamic import, you should probably avoid using it in your library. + +## build.outDir + +- **Type:** `string` +- **Default:** `dist` + +Specify the output directory (relative to [project root](/guide/#index-html-and-project-root)). + +## build.assetsDir + +- **Type:** `string` +- **Default:** `assets` + +Specify the directory to nest generated assets under (relative to `build.outDir`). + +## build.assetsInlineLimit + +- **Type:** `number` +- **Default:** `4096` (4kb) + +Imported or referenced assets that are smaller than this threshold will be inlined as base64 URLs to avoid extra http requests. Set to `0` to disable inlining altogether. + +::: tip Note +If you specify `build.lib`, `build.assetsInlineLimit` will be ignored and assets will always be inlined, regardless of file size. +::: + +## build.cssCodeSplit + +- **Type:** `boolean` +- **Default:** `true` + +Enable/disable CSS code splitting. When enabled, CSS imported in async chunks will be inlined into the async chunk itself and inserted when the chunk is loaded. + +If disabled, all CSS in the entire project will be extracted into a single CSS file. + +::: tip Note +If you specify `build.lib`, `build.cssCodeSplit` will be `false` as default. +::: + +## build.cssTarget + +- **Type:** `string | string[]` +- **Default:** the same as [`build.target`](/config/#build-target) + +This options allows users to set a different browser target for CSS minification from the one used for JavaScript transpilation. + +It should only be used when you are targeting a non-mainstream browser. +One example is Android WeChat WebView, which supports most modern JavaScript features but not the [`#RGBA` hexadecimal color notation in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#rgb_colors). +In this case, you need to set `build.cssTarget` to `chrome61` to prevent vite from transform `rgba()` colors into `#RGBA` hexadecimal notations. + +## build.sourcemap + +- **Type:** `boolean | 'inline' | 'hidden'` +- **Default:** `false` + +Generate production source maps. If `true`, a separate sourcemap file will be created. If `'inline'`, the sourcemap will be appended to the resulting output file as a data URI. `'hidden'` works like `true` except that the corresponding sourcemap comments in the bundled files are suppressed. + +## build.rollupOptions + +- **Type:** [`RollupOptions`](https://rollupjs.org/guide/en/#big-list-of-options) + +Directly customize the underlying Rollup bundle. This is the same as options that can be exported from a Rollup config file and will be merged with Vite's internal Rollup options. See [Rollup options docs](https://rollupjs.org/guide/en/#big-list-of-options) for more details. + +## build.commonjsOptions + +- **Type:** [`RollupCommonJSOptions`](https://github.com/rollup/plugins/tree/master/packages/commonjs#options) + +Options to pass on to [@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/master/packages/commonjs). + +## build.dynamicImportVarsOptions + +- **Type:** [`RollupDynamicImportVarsOptions`](https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#options) +- **Related:** [Dynamic Import](/guide/features#dynamic-import) + +Options to pass on to [@rollup/plugin-dynamic-import-vars](https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars). + +## build.lib + +- **Type:** `{ entry: string, name?: string, formats?: ('es' | 'cjs' | 'umd' | 'iife')[], fileName?: string | ((format: ModuleFormat) => string) }` +- **Related:** [Library Mode](/guide/build#library-mode) + +Build as a library. `entry` is required since the library cannot use HTML as entry. `name` is the exposed global variable and is required when `formats` includes `'umd'` or `'iife'`. Default `formats` are `['es', 'umd']`. `fileName` is the name of the package file output, default `fileName` is the name option of package.json, it can also be defined as function taking the `format` as an argument. + +## build.manifest + +- **Type:** `boolean | string` +- **Default:** `false` +- **Related:** [Backend Integration](/guide/backend-integration) + +When set to `true`, the build will also generate a `manifest.json` file that contains a mapping of non-hashed asset filenames to their hashed versions, which can then be used by a server framework to render the correct asset links. When the value is a string, it will be used as the manifest file name. + +## build.ssrManifest + +- **Type:** `boolean | string` +- **Default:** `false` +- **Related:** [Server-Side Rendering](/guide/ssr) + +When set to `true`, the build will also generate a SSR manifest for determining style links and asset preload directives in production. When the value is a string, it will be used as the manifest file name. + +## build.ssr + +- **Type:** `boolean | string` +- **Default:** `undefined` +- **Related:** [Server-Side Rendering](/guide/ssr) + +Produce SSR-oriented build. The value can be a string to directly specify the SSR entry, or `true`, which requires specifying the SSR entry via `rollupOptions.input`. + +## build.minify + +- **Type:** `boolean | 'terser' | 'esbuild'` +- **Default:** `'esbuild'` + +Set to `false` to disable minification, or specify the minifier to use. The default is [esbuild](https://github.com/evanw/esbuild) which is 20 ~ 40x faster than terser and only 1 ~ 2% worse compression. [Benchmarks](https://github.com/privatenumber/minification-benchmarks) + +Note the `build.minify` option is not available when using the `'es'` format in lib mode. + +## build.terserOptions + +- **Type:** `TerserOptions` + +Additional [minify options](https://terser.org/docs/api-reference#minify-options) to pass on to Terser. + +## build.write + +- **Type:** `boolean` +- **Default:** `true` + +Set to `false` to disable writing the bundle to disk. This is mostly used in [programmatic `build()` calls](/guide/api-javascript#build) where further post processing of the bundle is needed before writing to disk. + +## build.emptyOutDir + +- **Type:** `boolean` +- **Default:** `true` if `outDir` is inside `root` + +By default, Vite will empty the `outDir` on build if it is inside project root. It will emit a warning if `outDir` is outside of root to avoid accidentally removing important files. You can explicitly set this option to suppress the warning. This is also available via command line as `--emptyOutDir`. + +## build.reportCompressedSize + +- **Type:** `boolean` +- **Default:** `true` + +Enable/disable gzip-compressed size reporting. Compressing large output files can be slow, so disabling this may increase build performance for large projects. + +### build.chunkSizeWarningLimit + +- **Type:** `number` +- **Default:** `500` + + Limit for chunk size warnings (in kbs). + +## build.watch + +- **Type:** [`WatcherOptions`](https://rollupjs.org/guide/en/#watch-options)`| null` +- **Default:** `null` + +Set to `{}` to enable rollup watcher. This is mostly used in cases that involve build-only plugins or integrations processes. diff --git a/docs/config/dep-optimization-options.md b/docs/config/dep-optimization-options.md new file mode 100644 index 00000000000000..1cc3c710c62846 --- /dev/null +++ b/docs/config/dep-optimization-options.md @@ -0,0 +1,47 @@ +# Dep Optimization Options + +- **Related:** [Dependency Pre-Bundling](/guide/dep-pre-bundling) + +## optimizeDeps.entries + +- **Type:** `string | string[]` + +By default, Vite will crawl all your `.html` files to detect dependencies that need to be pre-bundled (ignoring `node_modules`, `build.outDir`, `__tests__` and `coverage`). If `build.rollupOptions.input` is specified, Vite will crawl those entry points instead. + +If neither of these fit your needs, you can specify custom entries using this option - the value should be a [fast-glob pattern](https://github.com/mrmlnc/fast-glob#basic-syntax) or array of patterns that are relative from Vite project root. This will overwrite default entries inference. Only `node_modules` and `build.outDir` folders will be ignored by default when `optimizeDeps.entries` is explicitly defined. If other folders needs to be ignored, you can use an ignore pattern as part of the entries list, marked with an initial `!`. + +## optimizeDeps.exclude + +- **Type:** `string[]` + +Dependencies to exclude from pre-bundling. + +:::warning CommonJS +CommonJS dependencies should not be excluded from optimization. If an ESM dependency is excluded from optimization, but has a nested CommonJS dependency, the CommonJS dependency should be added to `optimizeDeps.include`. Example: + +```js +export default defineConfig({ + optimizeDeps: { + include: ['esm-dep > cjs-dep'] + } +}) +``` + +::: + +## optimizeDeps.include + +- **Type:** `string[]` + +By default, linked packages not inside `node_modules` are not pre-bundled. Use this option to force a linked package to be pre-bundled. + +## optimizeDeps.esbuildOptions + +- **Type:** [`EsbuildBuildOptions`](https://esbuild.github.io/api/#simple-options) + +Options to pass to esbuild during the dep scanning and optimization. + +Certain options are omitted since changing them would not be compatible with Vite's dep optimization. + +- `external` is also omitted, use Vite's `optimizeDeps.exclude` option +- `plugins` are merged with Vite's dep plugin diff --git a/docs/config/index.md b/docs/config/index.md index 49983a551c7453..31e341a399756e 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -4,10 +4,6 @@ title: Configuring Vite # Configuring Vite -## Config File - -### Config File Resolving - When running `vite` from the command line, Vite will automatically try to resolve a config file named `vite.config.js` inside [project root](/guide/#index-html-and-project-root). The most basic config file looks like this: @@ -40,7 +36,7 @@ console.log("import.meta.url") // break error on build ::: -### Config Intellisense +## Config Intellisense Since Vite ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints: @@ -67,7 +63,7 @@ export default defineConfig({ Vite also directly supports TS config files. You can use `vite.config.ts` with the `defineConfig` helper as well. -### Conditional Config +## Conditional Config If the config needs to conditional determine options based on the command (`dev`/`serve` or `build`) or the [mode](/guide/env-and-mode) being used, it can export a function instead: @@ -88,7 +84,7 @@ export default defineConfig(({ command, mode }) => { It is important to note that in Vite's API the `command` value is `serve` during dev (in the cli `vite`, `vite dev`, and `vite serve` are aliases), and `build` when building for production (`vite build`). -### Async Config +## Async Config If the config needs to call async function, it can export a async function instead: @@ -101,7 +97,7 @@ export default defineConfig(async ({ command, mode }) => { }) ``` -### Environment Variables +## Environment Variables Environmental Variables can be obtained from `process.env` as usual. @@ -122,969 +118,3 @@ export default defineConfig(({ command, mode }) => { } }) ``` - -## Shared Options - -### root - -- **Type:** `string` -- **Default:** `process.cwd()` - - Project root directory (where `index.html` is located). Can be an absolute path, or a path relative to the location of the config file itself. - - See [Project Root](/guide/#index-html-and-project-root) for more details. - -### base - -- **Type:** `string` -- **Default:** `/` - - Base public path when served in development or production. Valid values include: - - - Absolute URL pathname, e.g. `/foo/` - - Full URL, e.g. `https://foo.com/` - - Empty string or `./` (for embedded deployment) - - See [Public Base Path](/guide/build#public-base-path) for more details. - -### mode - -- **Type:** `string` -- **Default:** `'development'` for serve, `'production'` for build - - Specifying this in config will override the default mode for **both serve and build**. This value can also be overridden via the command line `--mode` option. - - See [Env Variables and Modes](/guide/env-and-mode) for more details. - -### define - -- **Type:** `Record` - - Define global constant replacements. Entries will be defined as globals during dev and statically replaced during build. - - - Starting from `2.0.0-beta.70`, string values will be used as raw expressions, so if defining a string constant, it needs to be explicitly quoted (e.g. with `JSON.stringify`). - - - To be consistent with [esbuild behavior](https://esbuild.github.io/api/#define), expressions must either be a JSON object (null, boolean, number, string, array, or object) or a single identifier. - - - Replacements are performed only when the match isn't surrounded by other letters, numbers, `_` or `$`. - - ::: warning - Because it's implemented as straightforward text replacements without any syntax analysis, we recommend using `define` for CONSTANTS only. - - For example, `process.env.FOO` and `__APP_VERSION__` are good fits. But `process` or `global` should not be put into this option. Variables can be shimmed or polyfilled instead. - ::: - - ::: tip NOTE - For TypeScript users, make sure to add the type declarations in the `env.d.ts` or `vite-env.d.ts` file to get type checks and Intellisense. - - Example: - - ```ts - // vite-env.d.ts - declare const __APP_VERSION__: string - ``` - - ::: - - ::: tip NOTE - Since dev and build implement `define` differently, we should avoid some use cases to avoid inconsistency. - - Example: - - ```js - const obj = { - __NAME__, // Don't define object shorthand property names - __KEY__: value // Don't define object key - } - ``` - - ::: - -### plugins - -- **Type:** `(Plugin | Plugin[])[]` - - Array of plugins to use. Falsy plugins are ignored and arrays of plugins are flattened. See [Plugin API](/guide/api-plugin) for more details on Vite plugins. - -### publicDir - -- **Type:** `string | false` -- **Default:** `"public"` - - Directory to serve as plain static assets. Files in this directory are served at `/` during dev and copied to the root of `outDir` during build, and are always served or copied as-is without transform. The value can be either an absolute file system path or a path relative to project root. - - Defining `publicDir` as `false` disables this feature. - - See [The `public` Directory](/guide/assets#the-public-directory) for more details. - -### cacheDir - -- **Type:** `string` -- **Default:** `"node_modules/.vite"` - - Directory to save cache files. Files in this directory are pre-bundled deps or some other cache files generated by vite, which can improve the performance. You can use `--force` flag or manually delete the directory to regenerate the cache files. The value can be either an absolute file system path or a path relative to project root. Default to `.vite` when no package.json is detected. - -### resolve.alias - -- **Type:** - `Record | Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>` - - Will be passed to `@rollup/plugin-alias` as its [entries option](https://github.com/rollup/plugins/tree/master/packages/alias#entries). Can either be an object, or an array of `{ find, replacement, customResolver }` pairs. - - When aliasing to file system paths, always use absolute paths. Relative alias values will be used as-is and will not be resolved into file system paths. - - More advanced custom resolution can be achieved through [plugins](/guide/api-plugin). - -### resolve.dedupe - -- **Type:** `string[]` - - If you have duplicated copies of the same dependency in your app (likely due to hoisting or linked packages in monorepos), use this option to force Vite to always resolve listed dependencies to the same copy (from project root). - - :::warning SSR + ESM - For SSR builds, deduplication does not work for ESM build outputs configured from `build.rollupOptions.output`. A workaround is to use CJS build outputs until ESM has better plugin support for module loading. - ::: - -### resolve.conditions - -- **Type:** `string[]` - - Additional allowed conditions when resolving [Conditional Exports](https://nodejs.org/api/packages.html#packages_conditional_exports) from a package. - - A package with conditional exports may have the following `exports` field in its `package.json`: - - ```json - { - "exports": { - ".": { - "import": "./index.mjs", - "require": "./index.js" - } - } - } - ``` - - Here, `import` and `require` are "conditions". Conditions can be nested and should be specified from most specific to least specific. - - Vite has a list of "allowed conditions" and will match the first condition that is in the allowed list. The default allowed conditions are: `import`, `module`, `browser`, `default`, and `production/development` based on current mode. The `resolve.conditions` config option allows specifying additional allowed conditions. - - :::warning Resolving subpath exports - Export keys ending with "/" is deprecated by Node and may not work well. Please contact the package author to use [`*` subpath patterns](https://nodejs.org/api/packages.html#package-entry-points) instead. - ::: - -### resolve.mainFields - -- **Type:** `string[]` -- **Default:** `['module', 'jsnext:main', 'jsnext']` - - List of fields in `package.json` to try when resolving a package's entry point. Note this takes lower precedence than conditional exports resolved from the `exports` field: if an entry point is successfully resolved from `exports`, the main field will be ignored. - -### resolve.extensions - -- **Type:** `string[]` -- **Default:** `['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']` - - List of file extensions to try for imports that omit extensions. Note it is **NOT** recommended to omit extensions for custom import types (e.g. `.vue`) since it can interfere with IDE and type support. - -### resolve.preserveSymlinks - -- **Type:** `boolean` -- **Default:** `false` - - Enabling this setting causes vite to determine file identity by the original file path (i.e. the path without following symlinks) instead of the real file path (i.e. the path after following symlinks). - -- **Related:** [esbuild#preserve-symlinks](https://esbuild.github.io/api/#preserve-symlinks), [webpack#resolve.symlinks - ](https://webpack.js.org/configuration/resolve/#resolvesymlinks) - -### css.modules - -- **Type:** - - ```ts - interface CSSModulesOptions { - scopeBehaviour?: 'global' | 'local' - globalModulePaths?: RegExp[] - generateScopedName?: - | string - | ((name: string, filename: string, css: string) => string) - hashPrefix?: string - /** - * default: null - */ - localsConvention?: - | 'camelCase' - | 'camelCaseOnly' - | 'dashes' - | 'dashesOnly' - | null - } - ``` - - Configure CSS modules behavior. The options are passed on to [postcss-modules](https://github.com/css-modules/postcss-modules). - -### css.postcss - -- **Type:** `string | (postcss.ProcessOptions & { plugins?: postcss.Plugin[] })` - - Inline PostCSS config or a custom directory to search PostCSS config from (default is project root). - - For inline PostCSS config, it expects the same format as `postcss.config.js`. But for `plugins` property, only [array format](https://github.com/postcss/postcss-load-config/blob/main/README.md#array) can be used. - - The search is done using [postcss-load-config](https://github.com/postcss/postcss-load-config) and only the supported config file names are loaded. - - Note if an inline config is provided, Vite will not search for other PostCSS config sources. - -### css.preprocessorOptions - -- **Type:** `Record` - - Specify options to pass to CSS pre-processors. The file extensions are used as keys for the options. Example: - - ```js - export default defineConfig({ - css: { - preprocessorOptions: { - scss: { - additionalData: `$injectedColor: orange;` - }, - styl: { - additionalData: `$injectedColor ?= orange` - } - } - } - }) - ``` - -### css.devSourcemap - -- **Experimental** -- **Type:** `boolean` -- **Default:** `false` - - Whether to enable sourcemaps during dev. - -### json.namedExports - -- **Type:** `boolean` -- **Default:** `true` - - Whether to support named imports from `.json` files. - -### json.stringify - -- **Type:** `boolean` -- **Default:** `false` - - If set to `true`, imported JSON will be transformed into `export default JSON.parse("...")` which is significantly more performant than Object literals, especially when the JSON file is large. - - Enabling this disables named imports. - -### esbuild - -- **Type:** `ESBuildOptions | false` - - `ESBuildOptions` extends [esbuild's own transform options](https://esbuild.github.io/api/#transform-api). The most common use case is customizing JSX: - - ```js - export default defineConfig({ - esbuild: { - jsxFactory: 'h', - jsxFragment: 'Fragment' - } - }) - ``` - - By default, esbuild is applied to `ts`, `jsx` and `tsx` files. You can customize this with `esbuild.include` and `esbuild.exclude`, which can be a regex, a [picomatch](https://github.com/micromatch/picomatch#globbing-features) pattern, or an array of either. - - In addition, you can also use `esbuild.jsxInject` to automatically inject JSX helper imports for every file transformed by esbuild: - - ```js - export default defineConfig({ - esbuild: { - jsxInject: `import React from 'react'` - } - }) - ``` - - Set to `false` to disable esbuild transforms. - -### assetsInclude - -- **Type:** `string | RegExp | (string | RegExp)[]` -- **Related:** [Static Asset Handling](/guide/assets) - - Specify additional [picomatch patterns](https://github.com/micromatch/picomatch#globbing-features) to be treated as static assets so that: - - - They will be excluded from the plugin transform pipeline when referenced from HTML or directly requested over `fetch` or XHR. - - - Importing them from JS will return their resolved URL string (this can be overwritten if you have a `enforce: 'pre'` plugin to handle the asset type differently). - - The built-in asset type list can be found [here](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/constants.ts). - - **Example:** - - ```js - export default defineConfig({ - assetsInclude: ['**/*.gltf'] - }) - ``` - -### logLevel - -- **Type:** `'info' | 'warn' | 'error' | 'silent'` - - Adjust console output verbosity. Default is `'info'`. - -### clearScreen - -- **Type:** `boolean` -- **Default:** `true` - - Set to `false` to prevent Vite from clearing the terminal screen when logging certain messages. Via command line, use `--clearScreen false`. - -### envDir - -- **Type:** `string` -- **Default:** `root` - - The directory from which `.env` files are loaded. Can be an absolute path, or a path relative to the project root. - - See [here](/guide/env-and-mode#env-files) for more about environment files. - -### envPrefix - -- **Type:** `string | string[]` -- **Default:** `VITE_` - - Env variables starts with `envPrefix` will be exposed to your client source code via import.meta.env. - - :::warning SECURITY NOTES - `envPrefix` should not be set as `''`, which will expose all your env variables and cause unexpected leaking of of sensitive information. Vite will throw error when detecting `''`. - ::: - -## Server Options - -### server.host - -- **Type:** `string | boolean` -- **Default:** `'127.0.0.1'` - - 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. - - This can be set via the CLI using `--host 0.0.0.0` or `--host`. - -### server.port - -- **Type:** `number` -- **Default:** `5173` - - Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on. - -### server.strictPort - -- **Type:** `boolean` - - Set to `true` to exit if port is already in use, instead of automatically try the next available port. - -### server.https - -- **Type:** `boolean | https.ServerOptions` - - Enable TLS + HTTP/2. Note this downgrades to TLS only when the [`server.proxy` option](#server-proxy) is also used. - - The value can also be an [options object](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) passed to `https.createServer()`. - -### server.open - -- **Type:** `boolean | string` - - Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname. If you want to open the server in a specific browser you like, you can set the env `process.env.BROWSER` (e.g. `firefox`). See [the `open` package](https://github.com/sindresorhus/open#app) for more details. - - **Example:** - - ```js - export default defineConfig({ - server: { - open: '/docs/index.html' - } - }) - ``` - -### server.proxy - -- **Type:** `Record` - - Configure custom proxy rules for the dev server. Expects an object of `{ key: options }` pairs. If the key starts with `^`, it will be interpreted as a `RegExp`. The `configure` option can be used to access the proxy instance. - - Uses [`http-proxy`](https://github.com/http-party/node-http-proxy). Full options [here](https://github.com/http-party/node-http-proxy#options). - - In some cases, you might also want to configure the underlying dev server (e.g. to add custom middlewares to the internal [connect](https://github.com/senchalabs/connect) app). In order to do that, you need to write your own [plugin](/guide/using-plugins.html) and use [configureServer](/guide/api-plugin.html#configureserver) function. - - **Example:** - - ```js - export default defineConfig({ - server: { - proxy: { - // string shorthand - '/foo': 'http://localhost:4567', - // with options - '/api': { - target: 'http://jsonplaceholder.typicode.com', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, '') - }, - // with RegEx - '^/fallback/.*': { - target: 'http://jsonplaceholder.typicode.com', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/fallback/, '') - }, - // Using the proxy instance - '/api': { - target: 'http://jsonplaceholder.typicode.com', - changeOrigin: true, - configure: (proxy, options) => { - // proxy will be an instance of 'http-proxy' - } - }, - // Proxying websockets or socket.io - '/socket.io': { - target: 'ws://localhost:5173', - ws: true - } - } - } - }) - ``` - -### server.cors - -- **Type:** `boolean | CorsOptions` - - Configure CORS for the dev server. This is enabled by default and allows any origin. Pass an [options object](https://github.com/expressjs/cors) to fine tune the behavior or `false` to disable. - -### server.headers - -- **Type:** `OutgoingHttpHeaders` - - 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 }` - - Disable or configure HMR connection (in cases where the HMR websocket must use a different address from the http server). - - Set `server.hmr.overlay` to `false` to disable the server error overlay. - - `clientPort` is an advanced option that overrides the port only on the client side, allowing you to serve the websocket on a different port than the client code looks for it on. Useful if you're using an SSL proxy in front of your dev server. - - If specifying `server.hmr.server`, Vite will process HMR connection requests through the provided server. If not in middleware mode, Vite will attempt to process HMR connection requests through the existing server. This can be helpful when using self-signed certificates or when you want to expose Vite over a network on a single port. - -### server.watch - -- **Type:** `object` - - File system watcher options to pass on to [chokidar](https://github.com/paulmillr/chokidar#api). - - When running Vite on Windows Subsystem for Linux (WSL) 2, if the project folder resides in a Windows filesystem, you'll need to set this option to `{ usePolling: true }`. This is due to [a WSL2 limitation](https://github.com/microsoft/WSL/issues/4739) with the Windows filesystem. - - The Vite server watcher skips `.git/` and `node_modules/` directories by default. If you want to watch a package inside `node_modules/`, you can pass a negated glob pattern to `server.watch.ignored`. That is: - - ```js - export default defineConfig({ - server: { - watch: { - ignored: ['!**/node_modules/your-package-name/**'] - } - }, - // The watched package must be excluded from optimization, - // so that it can appear in the dependency graph and trigger hot reload. - optimizeDeps: { - exclude: ['your-package-name'] - } - }) - ``` - -### server.middlewareMode - -- **Type:** `'ssr' | 'html'` - - Create Vite server in middleware mode. (without a HTTP server) - - - `'ssr'` will disable Vite's own HTML serving logic so that you should serve `index.html` manually. - - `'html'` will enable Vite's own HTML serving logic. - -- **Related:** [SSR - Setting Up the Dev Server](/guide/ssr#setting-up-the-dev-server) - -- **Example:** - -```js -const express = require('express') -const { createServer: createViteServer } = require('vite') - -async function createServer() { - const app = express() - - // Create Vite server in middleware mode. - const vite = await createViteServer({ - server: { middlewareMode: 'ssr' } - }) - // Use vite's connect instance as middleware - app.use(vite.middlewares) - - app.use('*', async (req, res) => { - // If `middlewareMode` is `'ssr'`, should serve `index.html` here. - // If `middlewareMode` is `'html'`, there is no need to serve `index.html` - // because Vite will do that. - }) -} - -createServer() -``` - -### server.base - -- **Type:** `string | undefined` - - Prepend this folder to http requests, for use when proxying vite as a subfolder. Should start and end with the `/` character. - -### server.fs.strict - -- **Type:** `boolean` -- **Default:** `true` (enabled by default since Vite 2.7) - - Restrict serving files outside of workspace root. - -### server.fs.allow - -- **Type:** `string[]` - - Restrict files that could be served via `/@fs/`. When `server.fs.strict` is set to `true`, accessing files outside this directory list that aren't imported from an allowed file will result in a 403. - - Vite will search for the root of the potential workspace and use it as default. A valid workspace met the following conditions, otherwise will fallback to the [project root](/guide/#index-html-and-project-root). - - - contains `workspaces` field in `package.json` - - contains one of the following file - - `lerna.json` - - `pnpm-workspace.yaml` - - Accepts a path to specify the custom workspace root. Could be a absolute path or a path relative to [project root](/guide/#index-html-and-project-root). For example: - - ```js - export default defineConfig({ - server: { - fs: { - // Allow serving files from one level up to the project root - allow: ['..'] - } - } - }) - ``` - - When `server.fs.allow` is specified, the auto workspace root detection will be disabled. To extend the original behavior, a utility `searchForWorkspaceRoot` is exposed: - - ```js - import { defineConfig, searchForWorkspaceRoot } from 'vite' - - export default defineConfig({ - server: { - fs: { - allow: [ - // search up for workspace root - searchForWorkspaceRoot(process.cwd()), - // your custom rules - '/path/to/custom/allow' - ] - } - } - }) - ``` - -### server.fs.deny - -- **Type:** `string[]` - - Blocklist for sensitive files being restricted to be served by Vite dev server. - - Default to `['.env', '.env.*', '*.{pem,crt}']`. - -### server.origin - -- **Type:** `string` - -Defines the origin of the generated asset URLs during development. - -```js -export default defineConfig({ - server: { - origin: 'http://127.0.0.1:8080' - } -}) -``` - -## Build Options - -### build.target - -- **Type:** `string | string[]` -- **Default:** `'modules'` -- **Related:** [Browser Compatibility](/guide/build#browser-compatibility) - - Browser compatibility target for the final bundle. The default value is a Vite special value, `'modules'`, which targets browsers with [native ES Modules](https://caniuse.com/es6-module) and [native ESM dynamic import](https://caniuse.com/es6-module-dynamic-import) support. - - Another special value is `'esnext'` - which assumes native dynamic imports support and will transpile as little as possible: - - - If the [`build.minify`](#build-minify) option is `'terser'`, `'esnext'` will be forced down to `'es2021'`. - - In other cases, it will perform no transpilation at all. - - The transform is performed with esbuild and the value should be a valid [esbuild target option](https://esbuild.github.io/api/#target). Custom targets can either be a ES version (e.g. `es2015`), a browser with version (e.g. `chrome58`), or an array of multiple target strings. - - Note the build will fail if the code contains features that cannot be safely transpiled by esbuild. See [esbuild docs](https://esbuild.github.io/content-types/#javascript) for more details. - -### build.polyfillModulePreload - -- **Type:** `boolean` -- **Default:** `true` - - Whether to automatically inject [module preload polyfill](https://guybedford.com/es-module-preloading-integrity#modulepreload-polyfill). - - If set to `true`, the polyfill is auto injected into the proxy module of each `index.html` entry. If the build is configured to use a non-html custom entry via `build.rollupOptions.input`, then it is necessary to manually import the polyfill in your custom entry: - - ```js - import 'vite/modulepreload-polyfill' - ``` - - Note: the polyfill does **not** apply to [Library Mode](/guide/build#library-mode). If you need to support browsers without native dynamic import, you should probably avoid using it in your library. - -### build.outDir - -- **Type:** `string` -- **Default:** `dist` - - Specify the output directory (relative to [project root](/guide/#index-html-and-project-root)). - -### build.assetsDir - -- **Type:** `string` -- **Default:** `assets` - - Specify the directory to nest generated assets under (relative to `build.outDir`). - -### build.assetsInlineLimit - -- **Type:** `number` -- **Default:** `4096` (4kb) - - Imported or referenced assets that are smaller than this threshold will be inlined as base64 URLs to avoid extra http requests. Set to `0` to disable inlining altogether. - - ::: tip Note - If you specify `build.lib`, `build.assetsInlineLimit` will be ignored and assets will always be inlined, regardless of file size. - ::: - -### build.cssCodeSplit - -- **Type:** `boolean` -- **Default:** `true` - - Enable/disable CSS code splitting. When enabled, CSS imported in async chunks will be inlined into the async chunk itself and inserted when the chunk is loaded. - - If disabled, all CSS in the entire project will be extracted into a single CSS file. - - ::: tip Note - If you specify `build.lib`, `build.cssCodeSplit` will be `false` as default. - ::: - -### build.cssTarget - -- **Type:** `string | string[]` -- **Default:** the same as [`build.target`](/config/#build-target) - - This options allows users to set a different browser target for CSS minification from the one used for JavaScript transpilation. - - It should only be used when you are targeting a non-mainstream browser. - One example is Android WeChat WebView, which supports most modern JavaScript features but not the [`#RGBA` hexadecimal color notation in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#rgb_colors). - In this case, you need to set `build.cssTarget` to `chrome61` to prevent vite from transform `rgba()` colors into `#RGBA` hexadecimal notations. - -### build.sourcemap - -- **Type:** `boolean | 'inline' | 'hidden'` -- **Default:** `false` - - Generate production source maps. If `true`, a separate sourcemap file will be created. If `'inline'`, the sourcemap will be appended to the resulting output file as a data URI. `'hidden'` works like `true` except that the corresponding sourcemap comments in the bundled files are suppressed. - -### build.rollupOptions - -- **Type:** [`RollupOptions`](https://rollupjs.org/guide/en/#big-list-of-options) - - Directly customize the underlying Rollup bundle. This is the same as options that can be exported from a Rollup config file and will be merged with Vite's internal Rollup options. See [Rollup options docs](https://rollupjs.org/guide/en/#big-list-of-options) for more details. - -### build.commonjsOptions - -- **Type:** [`RollupCommonJSOptions`](https://github.com/rollup/plugins/tree/master/packages/commonjs#options) - - Options to pass on to [@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/master/packages/commonjs). - -### build.dynamicImportVarsOptions - -- **Type:** [`RollupDynamicImportVarsOptions`](https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#options) -- **Related:** [Dynamic Import](/guide/features#dynamic-import) - - Options to pass on to [@rollup/plugin-dynamic-import-vars](https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars). - -### build.lib - -- **Type:** `{ entry: string, name?: string, formats?: ('es' | 'cjs' | 'umd' | 'iife')[], fileName?: string | ((format: ModuleFormat) => string) }` -- **Related:** [Library Mode](/guide/build#library-mode) - - Build as a library. `entry` is required since the library cannot use HTML as entry. `name` is the exposed global variable and is required when `formats` includes `'umd'` or `'iife'`. Default `formats` are `['es', 'umd']`. `fileName` is the name of the package file output, default `fileName` is the name option of package.json, it can also be defined as function taking the `format` as an argument. - -### build.manifest - -- **Type:** `boolean | string` -- **Default:** `false` -- **Related:** [Backend Integration](/guide/backend-integration) - - When set to `true`, the build will also generate a `manifest.json` file that contains a mapping of non-hashed asset filenames to their hashed versions, which can then be used by a server framework to render the correct asset links. When the value is a string, it will be used as the manifest file name. - -### build.ssrManifest - -- **Type:** `boolean | string` -- **Default:** `false` -- **Related:** [Server-Side Rendering](/guide/ssr) - - When set to `true`, the build will also generate a SSR manifest for determining style links and asset preload directives in production. When the value is a string, it will be used as the manifest file name. - -### build.ssr - -- **Type:** `boolean | string` -- **Default:** `undefined` -- **Related:** [Server-Side Rendering](/guide/ssr) - - Produce SSR-oriented build. The value can be a string to directly specify the SSR entry, or `true`, which requires specifying the SSR entry via `rollupOptions.input`. - -### build.minify - -- **Type:** `boolean | 'terser' | 'esbuild'` -- **Default:** `'esbuild'` - - Set to `false` to disable minification, or specify the minifier to use. The default is [esbuild](https://github.com/evanw/esbuild) which is 20 ~ 40x faster than terser and only 1 ~ 2% worse compression. [Benchmarks](https://github.com/privatenumber/minification-benchmarks) - - Note the `build.minify` option is not available when using the `'es'` format in lib mode. - -### build.terserOptions - -- **Type:** `TerserOptions` - - Additional [minify options](https://terser.org/docs/api-reference#minify-options) to pass on to Terser. - -### build.write - -- **Type:** `boolean` -- **Default:** `true` - - Set to `false` to disable writing the bundle to disk. This is mostly used in [programmatic `build()` calls](/guide/api-javascript#build) where further post processing of the bundle is needed before writing to disk. - -### build.emptyOutDir - -- **Type:** `boolean` -- **Default:** `true` if `outDir` is inside `root` - - By default, Vite will empty the `outDir` on build if it is inside project root. It will emit a warning if `outDir` is outside of root to avoid accidentally removing important files. You can explicitly set this option to suppress the warning. This is also available via command line as `--emptyOutDir`. - -### build.reportCompressedSize - -- **Type:** `boolean` -- **Default:** `true` - - Enable/disable gzip-compressed size reporting. Compressing large output files can be slow, so disabling this may increase build performance for large projects. - -### build.chunkSizeWarningLimit - -- **Type:** `number` -- **Default:** `500` - - Limit for chunk size warnings (in kbs). - -### build.watch - -- **Type:** [`WatcherOptions`](https://rollupjs.org/guide/en/#watch-options)`| null` -- **Default:** `null` - - Set to `{}` to enable rollup watcher. This is mostly used in cases that involve build-only plugins or integrations processes. - -## Preview Options - -### preview.host - -- **Type:** `string | boolean` -- **Default:** [`server.host`](#server_host) - - 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. - - This can be set via the CLI using `--host 0.0.0.0` or `--host`. - -### preview.port - -- **Type:** `number` -- **Default:** `4173` - - Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on. - -**Example:** - -```js -export default defineConfig({ - server: { - port: 3030 - }, - preview: { - port: 8080 - } -}) -``` - -### preview.strictPort - -- **Type:** `boolean` -- **Default:** [`server.strictPort`](#server_strictport) - - Set to `true` to exit if port is already in use, instead of automatically try the next available port. - -### preview.https - -- **Type:** `boolean | https.ServerOptions` -- **Default:** [`server.https`](#server_https) - - Enable TLS + HTTP/2. Note this downgrades to TLS only when the [`server.proxy` option](#server-proxy) is also used. - - The value can also be an [options object](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) passed to `https.createServer()`. - -### preview.open - -- **Type:** `boolean | string` -- **Default:** [`server.open`](#server_open) - - Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname. If you want to open the server in a specific browser you like, you can set the env `process.env.BROWSER` (e.g. `firefox`). See [the `open` package](https://github.com/sindresorhus/open#app) for more details. - -### preview.proxy - -- **Type:** `Record` -- **Default:** [`server.proxy`](#server_proxy) - - Configure custom proxy rules for the dev server. Expects an object of `{ key: options }` pairs. If the key starts with `^`, it will be interpreted as a `RegExp`. The `configure` option can be used to access the proxy instance. - - Uses [`http-proxy`](https://github.com/http-party/node-http-proxy). Full options [here](https://github.com/http-party/node-http-proxy#options). - -### preview.cors - -- **Type:** `boolean | CorsOptions` -- **Default:** [`server.cors`](#server_proxy) - - Configure CORS for the dev server. This is enabled by default and allows any origin. Pass an [options object](https://github.com/expressjs/cors) to fine tune the behavior or `false` to disable. - -## Dep Optimization Options - -- **Related:** [Dependency Pre-Bundling](/guide/dep-pre-bundling) - -### optimizeDeps.entries - -- **Type:** `string | string[]` - - By default, Vite will crawl all your `.html` files to detect dependencies that need to be pre-bundled (ignoring `node_modules`, `build.outDir`, `__tests__` and `coverage`). If `build.rollupOptions.input` is specified, Vite will crawl those entry points instead. - - If neither of these fit your needs, you can specify custom entries using this option - the value should be a [fast-glob pattern](https://github.com/mrmlnc/fast-glob#basic-syntax) or array of patterns that are relative from Vite project root. This will overwrite default entries inference. Only `node_modules` and `build.outDir` folders will be ignored by default when `optimizeDeps.entries` is explicitily defined. If other folders needs to be ignored, you can use an ignore pattern as part of the entries list, marked with an initial `!`. - -### optimizeDeps.exclude - -- **Type:** `string[]` - - Dependencies to exclude from pre-bundling. - - :::warning CommonJS - CommonJS dependencies should not be excluded from optimization. If an ESM dependency is excluded from optimization, but has a nested CommonJS dependency, the CommonJS dependency should be added to `optimizeDeps.include`. Example: - - ```js - export default defineConfig({ - optimizeDeps: { - include: ['esm-dep > cjs-dep'] - } - }) - ``` - - ::: - -### optimizeDeps.include - -- **Type:** `string[]` - - By default, linked packages not inside `node_modules` are not pre-bundled. Use this option to force a linked package to be pre-bundled. - -### optimizeDeps.esbuildOptions - -- **Type:** [`EsbuildBuildOptions`](https://esbuild.github.io/api/#simple-options) - - Options to pass to esbuild during the dep scanning and optimization. - - Certain options are omitted since changing them would not be compatible with Vite's dep optimization. - - - `external` is also omitted, use Vite's `optimizeDeps.exclude` option - - `plugins` are merged with Vite's dep plugin - -## SSR Options - -:::warning Experimental -SSR options may be adjusted in minor releases. -::: - -- **Related:** [SSR Externals](/guide/ssr#ssr-externals) - -### ssr.external - -- **Type:** `string[]` - - Force externalize dependencies for SSR. - -### ssr.noExternal - -- **Type:** `string | RegExp | (string | RegExp)[] | true` - - Prevent listed dependencies from being externalized for SSR. If `true`, no dependencies are externalized. - -### ssr.target - -- **Type:** `'node' | 'webworker'` -- **Default:** `node` - - Build target for the SSR server. - -## Worker Options - -### worker.format - -- **Type:** `'es' | 'iife'` -- **Default:** `iife` - - Output format for worker bundle. - -### worker.plugins - -- **Type:** [`(Plugin | Plugin[])[]`](#plugins) - - Vite plugins that apply to worker bundle - -### worker.rollupOptions - -- **Type:** [`RollupOptions`](https://rollupjs.org/guide/en/#big-list-of-options) - - Rollup options to build worker bundle. diff --git a/docs/config/preview-options.md b/docs/config/preview-options.md new file mode 100644 index 00000000000000..cf068bf74327ce --- /dev/null +++ b/docs/config/preview-options.md @@ -0,0 +1,70 @@ +# Preview Options + +## preview.host + +- **Type:** `string | boolean` +- **Default:** [`server.host`](./server-options#server-host) + +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. + +This can be set via the CLI using `--host 0.0.0.0` or `--host`. + +## preview.port + +- **Type:** `number` +- **Default:** `4173` + +Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on. + +**Example:** + +```js +export default defineConfig({ + server: { + port: 3030 + }, + preview: { + port: 8080 + } +}) +``` + +## preview.strictPort + +- **Type:** `boolean` +- **Default:** [`server.strictPort`](./server-options#server-strictport) + +Set to `true` to exit if port is already in use, instead of automatically try the next available port. + +## preview.https + +- **Type:** `boolean | https.ServerOptions` +- **Default:** [`server.https`](./server-options#server-https) + +Enable TLS + HTTP/2. Note this downgrades to TLS only when the [`server.proxy` option](./server-options#server-proxy) is also used. + +The value can also be an [options object](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) passed to `https.createServer()`. + +## preview.open + +- **Type:** `boolean | string` +- **Default:** [`server.open`](./server-options#server_open) + +Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname. If you want to open the server in a specific browser you like, you can set the env `process.env.BROWSER` (e.g. `firefox`). See [the `open` package](https://github.com/sindresorhus/open#app) for more details. + +## preview.proxy + +- **Type:** `Record` +- **Default:** [`server.proxy`](./server-options#server_proxy) + +Configure custom proxy rules for the dev server. Expects an object of `{ key: options }` pairs. If the key starts with `^`, it will be interpreted as a `RegExp`. The `configure` option can be used to access the proxy instance. + +Uses [`http-proxy`](https://github.com/http-party/node-http-proxy). Full options [here](https://github.com/http-party/node-http-proxy#options). + +## preview.cors + +- **Type:** `boolean | CorsOptions` +- **Default:** [`server.cors`](./server-options#server_proxy) + +Configure CORS for the dev server. This is enabled by default and allows any origin. Pass an [options object](https://github.com/expressjs/cors) to fine tune the behavior or `false` to disable. diff --git a/docs/config/server-options.md b/docs/config/server-options.md new file mode 100644 index 00000000000000..cf59ab27644656 --- /dev/null +++ b/docs/config/server-options.md @@ -0,0 +1,269 @@ +# Server Options + +## server.host + +- **Type:** `string | boolean` +- **Default:** `'127.0.0.1'` + +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. + +This can be set via the CLI using `--host 0.0.0.0` or `--host`. + +## server.port + +- **Type:** `number` +- **Default:** `5173` + +Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on. + +## server.strictPort + +- **Type:** `boolean` + +Set to `true` to exit if port is already in use, instead of automatically try the next available port. + +## server.https + +- **Type:** `boolean | https.ServerOptions` + +Enable TLS + HTTP/2. Note this downgrades to TLS only when the [`server.proxy` option](#server-proxy) is also used. + +The value can also be an [options object](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) passed to `https.createServer()`. + +## server.open + +- **Type:** `boolean | string` + +Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname. If you want to open the server in a specific browser you like, you can set the env `process.env.BROWSER` (e.g. `firefox`). See [the `open` package](https://github.com/sindresorhus/open#app) for more details. + +**Example:** + +```js +export default defineConfig({ + server: { + open: '/docs/index.html' + } +}) +``` + +## server.proxy + +- **Type:** `Record` + +Configure custom proxy rules for the dev server. Expects an object of `{ key: options }` pairs. If the key starts with `^`, it will be interpreted as a `RegExp`. The `configure` option can be used to access the proxy instance. + +Uses [`http-proxy`](https://github.com/http-party/node-http-proxy). Full options [here](https://github.com/http-party/node-http-proxy#options). + +In some cases, you might also want to configure the underlying dev server (e.g. to add custom middlewares to the internal [connect](https://github.com/senchalabs/connect) app). In order to do that, you need to write your own [plugin](/guide/using-plugins.html) and use [configureServer](/guide/api-plugin.html#configureserver) function. + +**Example:** + +```js +export default defineConfig({ + server: { + proxy: { + // string shorthand + '/foo': 'http://localhost:4567', + // with options + '/api': { + target: 'http://jsonplaceholder.typicode.com', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + }, + // with RegEx + '^/fallback/.*': { + target: 'http://jsonplaceholder.typicode.com', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/fallback/, '') + }, + // Using the proxy instance + '/api': { + target: 'http://jsonplaceholder.typicode.com', + changeOrigin: true, + configure: (proxy, options) => { + // proxy will be an instance of 'http-proxy' + } + }, + // Proxying websockets or socket.io + '/socket.io': { + target: 'ws://localhost:5173', + ws: true + } + } + } +}) +``` + +## server.cors + +- **Type:** `boolean | CorsOptions` + +Configure CORS for the dev server. This is enabled by default and allows any origin. Pass an [options object](https://github.com/expressjs/cors) to fine tune the behavior or `false` to disable. + +## server.headers + +- **Type:** `OutgoingHttpHeaders` + +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 }` + +Disable or configure HMR connection (in cases where the HMR websocket must use a different address from the http server). + +Set `server.hmr.overlay` to `false` to disable the server error overlay. + +`clientPort` is an advanced option that overrides the port only on the client side, allowing you to serve the websocket on a different port than the client code looks for it on. Useful if you're using an SSL proxy in front of your dev server. + +If specifying `server.hmr.server`, Vite will process HMR connection requests through the provided server. If not in middleware mode, Vite will attempt to process HMR connection requests through the existing server. This can be helpful when using self-signed certificates or when you want to expose Vite over a network on a single port. + +## server.watch + +- **Type:** `object` + +File system watcher options to pass on to [chokidar](https://github.com/paulmillr/chokidar#api). + +When running Vite on Windows Subsystem for Linux (WSL) 2, if the project folder resides in a Windows filesystem, you'll need to set this option to `{ usePolling: true }`. This is due to [a WSL2 limitation](https://github.com/microsoft/WSL/issues/4739) with the Windows filesystem. + +The Vite server watcher skips `.git/` and `node_modules/` directories by default. If you want to watch a package inside `node_modules/`, you can pass a negated glob pattern to `server.watch.ignored`. That is: + +```js +export default defineConfig({ + server: { + watch: { + ignored: ['!**/node_modules/your-package-name/**'] + } + }, + // The watched package must be excluded from optimization, + // so that it can appear in the dependency graph and trigger hot reload. + optimizeDeps: { + exclude: ['your-package-name'] + } +}) +``` + +## server.middlewareMode + +- **Type:** `'ssr' | 'html'` + +Create Vite server in middleware mode. (without a HTTP server) + +- `'ssr'` will disable Vite's own HTML serving logic so that you should serve `index.html` manually. +- `'html'` will enable Vite's own HTML serving logic. + +- **Related:** [SSR - Setting Up the Dev Server](/guide/ssr#setting-up-the-dev-server) + +- **Example:** + +```js +const express = require('express') +const { createServer: createViteServer } = require('vite') + +async function createServer() { + const app = express() + + // Create Vite server in middleware mode. + const vite = await createViteServer({ + server: { middlewareMode: 'ssr' } + }) + // Use vite's connect instance as middleware + app.use(vite.middlewares) + + app.use('*', async (req, res) => { + // If `middlewareMode` is `'ssr'`, should serve `index.html` here. + // If `middlewareMode` is `'html'`, there is no need to serve `index.html` + // because Vite will do that. + }) +} + +createServer() +``` + +## server.base + +- **Type:** `string | undefined` + +Prepend this folder to http requests, for use when proxying vite as a subfolder. Should start and end with the `/` character. + +## server.fs.strict + +- **Type:** `boolean` +- **Default:** `true` (enabled by default since Vite 2.7) + +Restrict serving files outside of workspace root. + +## server.fs.allow + +- **Type:** `string[]` + +Restrict files that could be served via `/@fs/`. When `server.fs.strict` is set to `true`, accessing files outside this directory list that aren't imported from an allowed file will result in a 403. + +Vite will search for the root of the potential workspace and use it as default. A valid workspace met the following conditions, otherwise will fallback to the [project root](/guide/#index-html-and-project-root). + +- contains `workspaces` field in `package.json` +- contains one of the following file + - `lerna.json` + - `pnpm-workspace.yaml` + +Accepts a path to specify the custom workspace root. Could be a absolute path or a path relative to [project root](/guide/#index-html-and-project-root). For example: + +```js +export default defineConfig({ + server: { + fs: { + // Allow serving files from one level up to the project root + allow: ['..'] + } + } +}) +``` + +When `server.fs.allow` is specified, the auto workspace root detection will be disabled. To extend the original behavior, a utility `searchForWorkspaceRoot` is exposed: + +```js +import { defineConfig, searchForWorkspaceRoot } from 'vite' + +export default defineConfig({ + server: { + fs: { + allow: [ + // search up for workspace root + searchForWorkspaceRoot(process.cwd()), + // your custom rules + '/path/to/custom/allow' + ] + } + } +}) +``` + +## server.fs.deny + +- **Type:** `string[]` + +Blocklist for sensitive files being restricted to be served by Vite dev server. + +Default to `['.env', '.env.*', '*.{pem,crt}']`. + +## server.origin + +- **Type:** `string` + +Defines the origin of the generated asset URLs during development. + +```js +export default defineConfig({ + server: { + origin: 'http://127.0.0.1:8080' + } +}) +``` diff --git a/docs/config/shared-options.md b/docs/config/shared-options.md new file mode 100644 index 00000000000000..f44e02d88737dd --- /dev/null +++ b/docs/config/shared-options.md @@ -0,0 +1,344 @@ +# Shared Options + +## root + +- **Type:** `string` +- **Default:** `process.cwd()` + +Project root directory (where `index.html` is located). Can be an absolute path, or a path relative to the location of the config file itself. + +See [Project Root](/guide/#index-html-and-project-root) for more details. + +## base + +- **Type:** `string` +- **Default:** `/` + +Base public path when served in development or production. Valid values include: + +- Absolute URL pathname, e.g. `/foo/` +- Full URL, e.g. `https://foo.com/` +- Empty string or `./` (for embedded deployment) + +See [Public Base Path](/guide/build#public-base-path) for more details. + +## mode + +- **Type:** `string` +- **Default:** `'development'` for serve, `'production'` for build + +Specifying this in config will override the default mode for **both serve and build**. This value can also be overridden via the command line `--mode` option. + +See [Env Variables and Modes](/guide/env-and-mode) for more details. + +## define + +- **Type:** `Record` + +Define global constant replacements. Entries will be defined as globals during dev and statically replaced during build. + +- Starting from `2.0.0-beta.70`, string values will be used as raw expressions, so if defining a string constant, it needs to be explicitly quoted (e.g. with `JSON.stringify`). + +- To be consistent with [esbuild behavior](https://esbuild.github.io/api/#define), expressions must either be a JSON object (null, boolean, number, string, array, or object) or a single identifier. + +- Replacements are performed only when the match isn't surrounded by other letters, numbers, `_` or `$`. + +::: warning +Because it's implemented as straightforward text replacements without any syntax analysis, we recommend using `define` for CONSTANTS only. + +For example, `process.env.FOO` and `__APP_VERSION__` are good fits. But `process` or `global` should not be put into this option. Variables can be shimmed or polyfilled instead. +::: + +::: tip NOTE +For TypeScript users, make sure to add the type declarations in the `env.d.ts` or `vite-env.d.ts` file to get type checks and Intellisense. + +Example: + +```ts +// vite-env.d.ts +declare const __APP_VERSION__: string +``` + +::: + +::: tip NOTE +Since dev and build implement `define` differently, we should avoid some use cases to avoid inconsistency. + +Example: + +```js +const obj = { + __NAME__, // Don't define object shorthand property names + __KEY__: value // Don't define object key +} +``` + +::: + +## plugins + +- **Type:** `(Plugin | Plugin[])[]` + +Array of plugins to use. Falsy plugins are ignored and arrays of plugins are flattened. See [Plugin API](/guide/api-plugin) for more details on Vite plugins. + +## publicDir + +- **Type:** `string | false` +- **Default:** `"public"` + +Directory to serve as plain static assets. Files in this directory are served at `/` during dev and copied to the root of `outDir` during build, and are always served or copied as-is without transform. The value can be either an absolute file system path or a path relative to project root. + +Defining `publicDir` as `false` disables this feature. + +See [The `public` Directory](/guide/assets#the-public-directory) for more details. + +## cacheDir + +- **Type:** `string` +- **Default:** `"node_modules/.vite"` + +Directory to save cache files. Files in this directory are pre-bundled deps or some other cache files generated by vite, which can improve the performance. You can use `--force` flag or manually delete the directory to regenerate the cache files. The value can be either an absolute file system path or a path relative to project root. Default to `.vite` when no package.json is detected. + +## resolve.alias + +- **Type:** + `Record | Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>` + +Will be passed to `@rollup/plugin-alias` as its [entries option](https://github.com/rollup/plugins/tree/master/packages/alias#entries). Can either be an object, or an array of `{ find, replacement, customResolver }` pairs. + +When aliasing to file system paths, always use absolute paths. Relative alias values will be used as-is and will not be resolved into file system paths. + +More advanced custom resolution can be achieved through [plugins](/guide/api-plugin). + +## resolve.dedupe + +- **Type:** `string[]` + +If you have duplicated copies of the same dependency in your app (likely due to hoisting or linked packages in monorepos), use this option to force Vite to always resolve listed dependencies to the same copy (from project root). + +:::warning SSR + ESM +For SSR builds, deduplication does not work for ESM build outputs configured from `build.rollupOptions.output`. A workaround is to use CJS build outputs until ESM has better plugin support for module loading. +::: + +## resolve.conditions + +- **Type:** `string[]` + +Additional allowed conditions when resolving [Conditional Exports](https://nodejs.org/api/packages.html#packages_conditional_exports) from a package. + +A package with conditional exports may have the following `exports` field in its `package.json`: + +```json +{ + "exports": { + ".": { + "import": "./index.mjs", + "require": "./index.js" + } + } +} +``` + +Here, `import` and `require` are "conditions". Conditions can be nested and should be specified from most specific to least specific. + +Vite has a list of "allowed conditions" and will match the first condition that is in the allowed list. The default allowed conditions are: `import`, `module`, `browser`, `default`, and `production/development` based on current mode. The `resolve.conditions` config option allows specifying additional allowed conditions. + +:::warning Resolving subpath exports +Export keys ending with "/" is deprecated by Node and may not work well. Please contact the package author to use [`*` subpath patterns](https://nodejs.org/api/packages.html#package-entry-points) instead. +::: + +## resolve.mainFields + +- **Type:** `string[]` +- **Default:** `['module', 'jsnext:main', 'jsnext']` + +List of fields in `package.json` to try when resolving a package's entry point. Note this takes lower precedence than conditional exports resolved from the `exports` field: if an entry point is successfully resolved from `exports`, the main field will be ignored. + +## resolve.extensions + +- **Type:** `string[]` +- **Default:** `['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']` + +List of file extensions to try for imports that omit extensions. Note it is **NOT** recommended to omit extensions for custom import types (e.g. `.vue`) since it can interfere with IDE and type support. + +## resolve.preserveSymlinks + +- **Type:** `boolean` +- **Default:** `false` + +Enabling this setting causes vite to determine file identity by the original file path (i.e. the path without following symlinks) instead of the real file path (i.e. the path after following symlinks). + +- **Related:** [esbuild#preserve-symlinks](https://esbuild.github.io/api/#preserve-symlinks), [webpack#resolve.symlinks + ](https://webpack.js.org/configuration/resolve/#resolvesymlinks) + +## css.modules + +- **Type:** + ```ts + interface CSSModulesOptions { + scopeBehaviour?: 'global' | 'local' + globalModulePaths?: RegExp[] + generateScopedName?: + | string + | ((name: string, filename: string, css: string) => string) + hashPrefix?: string + /** + * default: null + */ + localsConvention?: + | 'camelCase' + | 'camelCaseOnly' + | 'dashes' + | 'dashesOnly' + | null + } + ``` + +Configure CSS modules behavior. The options are passed on to [postcss-modules](https://github.com/css-modules/postcss-modules). + +## css.postcss + +- **Type:** `string | (postcss.ProcessOptions & { plugins?: postcss.Plugin[] })` + +Inline PostCSS config or a custom directory to search PostCSS config from (default is project root). + +For inline PostCSS config, it expects the same format as `postcss.config.js`. But for `plugins` property, only [array format](https://github.com/postcss/postcss-load-config/blob/main/README.md#array) can be used. + +The search is done using [postcss-load-config](https://github.com/postcss/postcss-load-config) and only the supported config file names are loaded. + +Note if an inline config is provided, Vite will not search for other PostCSS config sources. + +## css.preprocessorOptions + +- **Type:** `Record` + +Specify options to pass to CSS pre-processors. The file extensions are used as keys for the options. Example: + +```js +export default defineConfig({ + css: { + preprocessorOptions: { + scss: { + additionalData: `$injectedColor: orange;` + }, + styl: { + additionalData: `$injectedColor ?= orange` + } + } + } +}) +``` + +## css.devSourcemap + +- **Experimental** +- **Type:** `boolean` +- **Default:** `false` + +Whether to enable sourcemaps during dev. + +## json.namedExports + +- **Type:** `boolean` +- **Default:** `true` + +Whether to support named imports from `.json` files. + +## json.stringify + +- **Type:** `boolean` +- **Default:** `false` + +If set to `true`, imported JSON will be transformed into `export default JSON.parse("...")` which is significantly more performant than Object literals, especially when the JSON file is large. + +Enabling this disables named imports. + +## esbuild + +- **Type:** `ESBuildOptions | false` + +`ESBuildOptions` extends [esbuild's own transform options](https://esbuild.github.io/api/#transform-api). The most common use case is customizing JSX: + +```js +export default defineConfig({ + esbuild: { + jsxFactory: 'h', + jsxFragment: 'Fragment' + } +}) +``` + +By default, esbuild is applied to `ts`, `jsx` and `tsx` files. You can customize this with `esbuild.include` and `esbuild.exclude`, which can be a regex, a [picomatch](https://github.com/micromatch/picomatch#globbing-features) pattern, or an array of either. + +In addition, you can also use `esbuild.jsxInject` to automatically inject JSX helper imports for every file transformed by esbuild: + +```js +export default defineConfig({ + esbuild: { + jsxInject: `import React from 'react'` + } +}) +``` + +Set to `false` to disable esbuild transforms. + +## assetsInclude + +- **Type:** `string | RegExp | (string | RegExp)[]` +- **Related:** [Static Asset Handling](/guide/assets) + +Specify additional [picomatch patterns](https://github.com/micromatch/picomatch#globbing-features) to be treated as static assets so that: + +- They will be excluded from the plugin transform pipeline when referenced from HTML or directly requested over `fetch` or XHR. + +- Importing them from JS will return their resolved URL string (this can be overwritten if you have a `enforce: 'pre'` plugin to handle the asset type differently). + +The built-in asset type list can be found [here](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/constants.ts). + +**Example:** + +```js +export default defineConfig({ + assetsInclude: ['**/*.gltf'] +}) +``` + +## logLevel + +- **Type:** `'info' | 'warn' | 'error' | 'silent'` + +Adjust console output verbosity. Default is `'info'`. + +## clearScreen + +- **Type:** `boolean` +- **Default:** `true` + +Set to `false` to prevent Vite from clearing the terminal screen when logging certain messages. Via command line, use `--clearScreen false`. + +## envDir + +- **Type:** `string` +- **Default:** `root` + +The directory from which `.env` files are loaded. Can be an absolute path, or a path relative to the project root. + +See [here](/guide/env-and-mode#env-files) for more about environment files. + +## envPrefix + +- **Type:** `string | string[]` +- **Default:** `VITE_` + +Env variables starts with `envPrefix` will be exposed to your client source code via import.meta.env. + +:::warning SECURITY NOTES +`envPrefix` should not be set as `''`, which will expose all your env variables and cause unexpected leaking of of sensitive information. Vite will throw error when detecting `''`. +::: + +## spa + +- **Type:** `boolean` +- **Default:** `true` + +Whether your application is a Single Page Application (SPA). Set to `false` for other kinds of apps like MPAs. Learn more in Vite's [SSR guide](/guide/ssr#vite-cli). diff --git a/docs/config/ssr-options.md b/docs/config/ssr-options.md new file mode 100644 index 00000000000000..7d828c8c989679 --- /dev/null +++ b/docs/config/ssr-options.md @@ -0,0 +1,26 @@ +# SSR Options + +- **Related:** [SSR Externals](/guide/ssr#ssr-externals) + +:::warning Experimental +SSR options may be adjusted in minor releases. +::: + +## ssr.external + +- **Type:** `string[]` + +Force externalize dependencies for SSR. + +## ssr.noExternal + +- **Type:** `string | RegExp | (string | RegExp)[] | true` + +Prevent listed dependencies from being externalized for SSR. If `true`, no dependencies are externalized. + +## ssr.target + +- **Type:** `'node' | 'webworker'` +- **Default:** `node` + +Build target for the SSR server. diff --git a/docs/config/worker-options.md b/docs/config/worker-options.md new file mode 100644 index 00000000000000..c0c13bf4133adb --- /dev/null +++ b/docs/config/worker-options.md @@ -0,0 +1,20 @@ +# Worker Options + +## worker.format + +- **Type:** `'es' | 'iife'` +- **Default:** `iife` + +Output format for worker bundle. + +## worker.plugins + +- **Type:** [`(Plugin | Plugin[])[]`](./shared-options#plugins) + +Vite plugins that apply to worker bundle + +## worker.rollupOptions + +- **Type:** [`RollupOptions`](https://rollupjs.org/guide/en/#big-list-of-options) + +Rollup options to build worker bundle. diff --git a/docs/guide/features.md b/docs/guide/features.md index ef0610111b9709..2006b5850fd4a2 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -14,7 +14,7 @@ The above will throw an error in the browser. Vite will detect such bare module 1. [Pre-bundle](./dep-pre-bundling) them to improve page loading speed and convert CommonJS / UMD modules to ESM. The pre-bundling step is performed with [esbuild](http://esbuild.github.io/) and makes Vite's cold start time significantly faster than any JavaScript-based bundler. -2. Rewrite the imports to valid URLs like `/node_modules/.vite/my-dep.js?v=f3sf2ebd` so that the browser can import them properly. +2. Rewrite the imports to valid URLs like `/node_modules/.vite/deps/my-dep.js?v=f3sf2ebd` so that the browser can import them properly. **Dependencies are Strongly Cached** diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index ee078e82eb60a1..4e75453bbac86a 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -264,3 +264,14 @@ In some cases like `webworker` runtimes, you might want to bundle your SSR build - Treat all dependencies as `noExternal` - Throw an error if any Node.js built-ins are imported + +## Vite CLI + +The CLI commands `$ vite dev` and `$ vite preview` can also be used for SSR apps: + +1. Add your SSR middleware to the development server with [`configureServer`](/guide/api-plugin#configureserver) and to the preview server with [`configurePreviewServer`](/guide/api-plugin#configurepreviewserver). + :::tip Note + Use a post hook so that your SSR middleware runs _after_ Vite's middlewares. + ::: + +2. Set `config.spa` to `false`. This switches the development and preview server from SPA mode to SSR/MPA mode. diff --git a/package.json b/package.json index 5b00aa8da96c97..6dde8708780745 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "test-serve": "vitest run -c vitest.config.e2e.ts", "test-build": "cross-env VITE_TEST_BUILD=1 vitest run -c vitest.config.e2e.ts", "test-unit": "vitest run", + "test-docs": "pnpm run docs-build", "debug-serve": "cross-env VITE_DEBUG_SERVE=1 vitest run -c vitest.config.e2e.ts", "debug-build": "cross-env VITE_TEST_BUILD=1 VITE_PRESERVE_BUILD_ARTIFACTS=1 vitest run -c vitest.config.e2e.ts", "docs": "vitepress dev docs", @@ -28,12 +29,14 @@ "docs-serve": "vitepress serve docs", "build": "pnpm -r --filter=./packages/* run build", "dev": "pnpm -r --parallel --filter=./packages/* run dev", - "release": "ts-node scripts/release.ts", - "ci-publish": "ts-node scripts/publishCI.ts", + "release": "esno scripts/release.ts", + "ci-publish": "esno scripts/publishCI.ts", "ci-docs": "run-s build docs-build" }, "devDependencies": { + "@babel/types": "^7.17.10", "@microsoft/api-extractor": "^7.24.1", + "@rollup/plugin-typescript": "^8.3.2", "@types/babel__core": "^7.1.19", "@types/babel__standalone": "^7.1.4", "@types/convert-source-map": "^1.5.2", @@ -63,6 +66,7 @@ "eslint-define-config": "^1.4.1", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", + "esno": "^0.16.3", "execa": "^5.1.1", "fs-extra": "^10.1.0", "kill-port": "^1.6.1", @@ -77,11 +81,10 @@ "prompts": "^2.4.2", "rimraf": "^3.0.2", "rollup": "^2.72.1", - "rollup-plugin-esbuild": "^4.9.1", "semver": "^7.3.7", "simple-git-hooks": "^2.7.0", "sirv": "^2.0.2", - "ts-node": "^10.7.0", + "tslib": "^2.4.0", "typescript": "^4.6.4", "unbuild": "^0.7.4", "vite": "workspace:*", @@ -91,7 +94,7 @@ }, "simple-git-hooks": { "pre-commit": "pnpm exec lint-staged --concurrent false", - "commit-msg": "pnpm exec ts-node scripts/verifyCommit.ts $1" + "commit-msg": "pnpm exec esno scripts/verifyCommit.ts $1" }, "lint-staged": { "*": [ diff --git a/packages/create-vite/index.js b/packages/create-vite/index.js index 0efdbe6154824b..93ddb3c3f027a1 100755 --- a/packages/create-vite/index.js +++ b/packages/create-vite/index.js @@ -348,14 +348,7 @@ function emptyDir(dir) { return } for (const file of fs.readdirSync(dir)) { - const abs = path.resolve(dir, file) - // baseline is Node 12 so can't use rmSync :( - if (fs.lstatSync(abs).isDirectory()) { - emptyDir(abs) - fs.rmdirSync(abs) - } else { - fs.unlinkSync(abs) - } + fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }) } } diff --git a/packages/create-vite/template-lit-ts/package.json b/packages/create-vite/template-lit-ts/package.json index 8987f1701715fc..b8376ef424953f 100644 --- a/packages/create-vite/template-lit-ts/package.json +++ b/packages/create-vite/template-lit-ts/package.json @@ -2,6 +2,7 @@ "name": "vite-lit-ts-starter", "private": true, "version": "0.0.0", + "type": "module", "main": "dist/my-element.es.mjs", "exports": { ".": "./dist/my-element.es.mjs" diff --git a/packages/create-vite/template-lit/package.json b/packages/create-vite/template-lit/package.json index 3c7183e1da471d..cda355c551588b 100644 --- a/packages/create-vite/template-lit/package.json +++ b/packages/create-vite/template-lit/package.json @@ -2,6 +2,7 @@ "name": "vite-lit-starter", "private": true, "version": "0.0.0", + "type": "module", "main": "dist/my-element.es.mjs", "exports": { ".": "./dist/my-element.es.mjs" diff --git a/packages/create-vite/template-preact-ts/package.json b/packages/create-vite/template-preact-ts/package.json index 10116a4f458984..daa47a76c434bc 100644 --- a/packages/create-vite/template-preact-ts/package.json +++ b/packages/create-vite/template-preact-ts/package.json @@ -2,6 +2,7 @@ "name": "vite-preact-ts-starter", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/packages/create-vite/template-preact/package.json b/packages/create-vite/template-preact/package.json index f858cd48124e3b..438307a4c75b61 100644 --- a/packages/create-vite/template-preact/package.json +++ b/packages/create-vite/template-preact/package.json @@ -2,6 +2,7 @@ "name": "vite-preact-starter", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/packages/create-vite/template-react-ts/package.json b/packages/create-vite/template-react-ts/package.json index a06ad23ad8fdc1..345ba8ed8197e8 100644 --- a/packages/create-vite/template-react-ts/package.json +++ b/packages/create-vite/template-react-ts/package.json @@ -2,6 +2,7 @@ "name": "vite-react-typescript-starter", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/packages/create-vite/template-react/package.json b/packages/create-vite/template-react/package.json index b1e856d7d0918e..4cdfad55c5aa45 100644 --- a/packages/create-vite/template-react/package.json +++ b/packages/create-vite/template-react/package.json @@ -2,6 +2,7 @@ "name": "vite-react-starter", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/packages/create-vite/template-svelte-ts/package.json b/packages/create-vite/template-svelte-ts/package.json index 921d32f6bab6bb..6efa5660e804cc 100644 --- a/packages/create-vite/template-svelte-ts/package.json +++ b/packages/create-vite/template-svelte-ts/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.0-next.44", - "@tsconfig/svelte": "^2.0.1", + "@tsconfig/svelte": "^3.0.0", "svelte": "^3.48.0", "svelte-check": "^2.7.1", "svelte-preprocess": "^4.10.6", diff --git a/packages/create-vite/template-vanilla-ts/package.json b/packages/create-vite/template-vanilla-ts/package.json index 7e8c89b4c5ff83..ff74d1a70ac3aa 100644 --- a/packages/create-vite/template-vanilla-ts/package.json +++ b/packages/create-vite/template-vanilla-ts/package.json @@ -2,6 +2,7 @@ "name": "vite-typescript-starter", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/packages/create-vite/template-vanilla/package.json b/packages/create-vite/template-vanilla/package.json index df52274a9b36cd..470ae1e4770149 100644 --- a/packages/create-vite/template-vanilla/package.json +++ b/packages/create-vite/template-vanilla/package.json @@ -2,6 +2,7 @@ "name": "vite-starter", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/packages/create-vite/template-vue-ts/package.json b/packages/create-vite/template-vue-ts/package.json index f8169a36603a30..c2861bf4020a84 100644 --- a/packages/create-vite/template-vue-ts/package.json +++ b/packages/create-vite/template-vue-ts/package.json @@ -2,6 +2,7 @@ "name": "vite-vue-typescript-starter", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "vite", "build": "vue-tsc --noEmit && vite build", diff --git a/packages/create-vite/template-vue/package.json b/packages/create-vite/template-vue/package.json index 93364a56773cf9..47293319f25fe0 100644 --- a/packages/create-vite/template-vue/package.json +++ b/packages/create-vite/template-vue/package.json @@ -2,6 +2,7 @@ "name": "vite-vue-starter", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/packages/plugin-legacy/README.md b/packages/plugin-legacy/README.md index bd581052e88801..f7cd62b4dbe15c 100644 --- a/packages/plugin-legacy/README.md +++ b/packages/plugin-legacy/README.md @@ -147,8 +147,12 @@ The legacy plugin requires inline scripts for [Safari 10.1 `nomodule` fix](https - `sha256-MS6/3FCg4WjP9gwgaBGwLpRCY6fZBgwmhVCdrPrNf3E=` - `sha256-tQjf8gvb2ROOMapIxFvFAYBeUJ0v1HCbOcSmDNXGtDo=` -- `sha256-4m6wOIrq/wFDmi9Xh3mFM2mwI4ik9n3TMgHk6xDtLxk=` -- `sha256-uS7/g9fhQwNZS1f/MqYqqKv8y9hCu36IfX9XZB5L7YY=` +- `sha256-BoFUHKsYhJ9tbsHugtNQCmnkBbZ11pcW6kZguu+T+EU=` +- `sha256-A18HC3jLpyEc9B8oyxq/NBFCyFBJFSsRLt0gmT9kft8=` + + These values (without the `sha256-` prefix) can also be retrieved via diff --git a/packages/plugin-legacy/package.json b/packages/plugin-legacy/package.json index b04c5b1f5350f5..f7a696eb9b2893 100644 --- a/packages/plugin-legacy/package.json +++ b/packages/plugin-legacy/package.json @@ -19,7 +19,7 @@ "scripts": { "dev": "unbuild --stub", "build": "unbuild && pnpm run patch-cjs", - "patch-cjs": "ts-node ../../scripts/patchCJS.ts", + "patch-cjs": "esno ../../scripts/patchCJS.ts", "prepublishOnly": "npm run build" }, "engines": { diff --git a/packages/plugin-legacy/src/index.ts b/packages/plugin-legacy/src/index.ts index 8e0cbf54a34333..ed2c4a24820a61 100644 --- a/packages/plugin-legacy/src/index.ts +++ b/packages/plugin-legacy/src/index.ts @@ -40,7 +40,7 @@ const legacyEntryId = 'vite-legacy-entry' const systemJSInlineCode = `System.import(document.getElementById('${legacyEntryId}').getAttribute('data-src'))` const detectModernBrowserVarName = '__vite_is_modern_browser' -const detectModernBrowserCode = `try{import(new URL(import.meta.url).href).catch(()=>1);}catch(e){}window.${detectModernBrowserVarName}=true;` +const detectModernBrowserCode = `try{import.meta.url;import("_").catch(()=>1);}catch(e){}window.${detectModernBrowserVarName}=true;` const dynamicFallbackInlineCode = `!function(){if(window.${detectModernBrowserVarName})return;console.warn("vite: loading legacy build because dynamic import or import.meta.url is unsupported, syntax error above should be ignored");var e=document.getElementById("${legacyPolyfillId}"),n=document.createElement("script");n.src=e.src,n.onload=function(){${systemJSInlineCode}},document.body.appendChild(n)}();` const forceDynamicImportUsage = `export function __vite_legacy_guard(){import('data:text/javascript,')};` diff --git a/packages/plugin-react/package.json b/packages/plugin-react/package.json index 61b4fc138910c9..ce13afd2af6972 100644 --- a/packages/plugin-react/package.json +++ b/packages/plugin-react/package.json @@ -23,7 +23,7 @@ "scripts": { "dev": "unbuild --stub", "build": "unbuild && pnpm run patch-cjs", - "patch-cjs": "ts-node ../../scripts/patchCJS.ts", + "patch-cjs": "esno ../../scripts/patchCJS.ts", "prepublishOnly": "npm run build" }, "engines": { diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 63c950ebf46d8c..e52ad1ba0ba05a 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -1,7 +1,9 @@ +import path from 'path' import type { ParserOptions, TransformOptions, types as t } from '@babel/core' import * as babel from '@babel/core' import { createFilter } from '@rollup/pluginutils' import resolve from 'resolve' +import { normalizePath } from 'vite' import type { Plugin, PluginOption, ResolvedConfig } from 'vite' import { addRefreshWrapper, @@ -91,6 +93,7 @@ declare module 'vite' { export default function viteReact(opts: Options = {}): PluginOption[] { // Provide default values for Rollup compat. let base = '/' + let resolvedCacheDir: string let filter = createFilter(opts.include, opts.exclude) let isProduction = true let projectRoot = process.cwd() @@ -119,6 +122,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] { configResolved(config) { base = config.base projectRoot = config.root + resolvedCacheDir = normalizePath(path.resolve(config.cacheDir)) filter = createFilter(opts.include, opts.exclude, { resolve: projectRoot }) @@ -209,8 +213,12 @@ export default function viteReact(opts: Options = {}): PluginOption[] { // By reverse-compiling "React.createElement" calls into JSX, // React elements provided by dependencies will also use the // automatic runtime! + // Avoid parsing the optimized react-dom since it will never + // contain compiled JSX and it's a pretty big file (800kb). + const isOptimizedReactDom = + id.startsWith(resolvedCacheDir) && id.includes('/react-dom.js') const [restoredAst, isCommonJS] = - !isProjectFile && !isJSX + !isProjectFile && !isJSX && !isOptimizedReactDom ? await restoreJSX(babel, code, id) : [null, false] diff --git a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts index 28825918895dde..4acb6bb64f5e1b 100644 --- a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts +++ b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts @@ -27,12 +27,6 @@ export async function restoreJSX( code: string, filename: string ): Promise { - // Avoid parsing the optimized react-dom since it will never - // contain compiled JSX and it's a pretty big file (800kb). - if (filename.includes('/.vite/react-dom.js')) { - return jsxNotFound - } - const [reactAlias, isCommonJS] = parseReactAlias(code) if (!reactAlias) { diff --git a/packages/plugin-vue-jsx/package.json b/packages/plugin-vue-jsx/package.json index 78e46dfa94ad65..69f27be9f8e898 100644 --- a/packages/plugin-vue-jsx/package.json +++ b/packages/plugin-vue-jsx/package.json @@ -19,7 +19,7 @@ "scripts": { "dev": "unbuild --stub", "build": "unbuild && pnpm run patch-cjs", - "patch-cjs": "ts-node ../../scripts/patchCJS.ts", + "patch-cjs": "esno ../../scripts/patchCJS.ts", "prepublishOnly": "npm run build" }, "engines": { diff --git a/packages/plugin-vue/package.json b/packages/plugin-vue/package.json index e270cd6e3d234c..6725ec2eb6152d 100644 --- a/packages/plugin-vue/package.json +++ b/packages/plugin-vue/package.json @@ -19,7 +19,7 @@ "scripts": { "dev": "unbuild --stub", "build": "unbuild && pnpm run patch-cjs", - "patch-cjs": "ts-node ../../scripts/patchCJS.ts", + "patch-cjs": "esno ../../scripts/patchCJS.ts", "prepublishOnly": "npm run build" }, "engines": { diff --git a/packages/vite/CHANGELOG.md b/packages/vite/CHANGELOG.md index 36471ddeeab3df..aeaf52c3168728 100644 --- a/packages/vite/CHANGELOG.md +++ b/packages/vite/CHANGELOG.md @@ -1,3 +1,48 @@ +## 3.0.0-alpha.4 (2022-05-25) + +* feat: non-blocking needs interop (#7568) ([531cd7b](https://github.com/vitejs/vite/commit/531cd7b)), closes [#7568](https://github.com/vitejs/vite/issues/7568) +* fix: expose client dist in `exports` (#8324) ([689adc0](https://github.com/vitejs/vite/commit/689adc0)), closes [#8324](https://github.com/vitejs/vite/issues/8324) + + + +## 3.0.0-alpha.3 (2022-05-25) + +* fix(cjs): build cjs for `loadEnv` (#8305) ([80dd2df](https://github.com/vitejs/vite/commit/80dd2df)), closes [#8305](https://github.com/vitejs/vite/issues/8305) +* refactor(cli): improve output aesthetics (#6997) ([809ab47](https://github.com/vitejs/vite/commit/809ab47)), closes [#6997](https://github.com/vitejs/vite/issues/6997) +* dx: sourcemap combine debug utils (#8307) ([45dba50](https://github.com/vitejs/vite/commit/45dba50)), closes [#8307](https://github.com/vitejs/vite/issues/8307) +* chore: cleanup now that we've dropped Node 12 (#8239) ([29659a0](https://github.com/vitejs/vite/commit/29659a0)), closes [#8239](https://github.com/vitejs/vite/issues/8239) +* chore: use `esno` to replace `ts-node` (#8162) ([c18a5f3](https://github.com/vitejs/vite/commit/c18a5f3)), closes [#8162](https://github.com/vitejs/vite/issues/8162) + + + +## 3.0.0-alpha.2 (2022-05-23) + +* fix: correctly replace process.env.NODE_ENV (#8283) ([ec52baa](https://github.com/vitejs/vite/commit/ec52baa)), closes [#8283](https://github.com/vitejs/vite/issues/8283) +* fix: dev sourcemap (#8269) ([505f75e](https://github.com/vitejs/vite/commit/505f75e)), closes [#8269](https://github.com/vitejs/vite/issues/8269) +* fix: EPERM error on Windows when processing dependencies (#8235) ([67743a3](https://github.com/vitejs/vite/commit/67743a3)), closes [#8235](https://github.com/vitejs/vite/issues/8235) +* fix: glob types (#8257) ([03b227e](https://github.com/vitejs/vite/commit/03b227e)), closes [#8257](https://github.com/vitejs/vite/issues/8257) +* fix: srcset handling in html (#6419) ([a0ee4ff](https://github.com/vitejs/vite/commit/a0ee4ff)), closes [#6419](https://github.com/vitejs/vite/issues/6419) +* fix: support set NODE_ENV in scripts when custom mode option (#8218) ([adcf041](https://github.com/vitejs/vite/commit/adcf041)), closes [#8218](https://github.com/vitejs/vite/issues/8218) +* fix(css): remove `?used` hack (fixes #6421, #8245) (#8278) ([0b25cc1](https://github.com/vitejs/vite/commit/0b25cc1)), closes [#6421](https://github.com/vitejs/vite/issues/6421) [#8245](https://github.com/vitejs/vite/issues/8245) [#8278](https://github.com/vitejs/vite/issues/8278) +* fix(hmr): catch thrown errors when connecting to hmr websocket (#7111) ([4bc9284](https://github.com/vitejs/vite/commit/4bc9284)), closes [#7111](https://github.com/vitejs/vite/issues/7111) +* fix(plugin-legacy): respect `entryFileNames` for polyfill chunks (#8247) ([baa9632](https://github.com/vitejs/vite/commit/baa9632)), closes [#8247](https://github.com/vitejs/vite/issues/8247) +* fix(plugin-react): broken optimized deps dir check (#8255) ([9e2a1ea](https://github.com/vitejs/vite/commit/9e2a1ea)), closes [#8255](https://github.com/vitejs/vite/issues/8255) +* chore: include 2.9.9 changelog in main (#8250) ([48f03e0](https://github.com/vitejs/vite/commit/48f03e0)), closes [#8250](https://github.com/vitejs/vite/issues/8250) +* chore: remove unneeded worker context param (#8268) ([30a7acc](https://github.com/vitejs/vite/commit/30a7acc)), closes [#8268](https://github.com/vitejs/vite/issues/8268) +* chore: show ws port conflict error (#8209) ([c86329b](https://github.com/vitejs/vite/commit/c86329b)), closes [#8209](https://github.com/vitejs/vite/issues/8209) +* chore: use 'new URL' instead of 'url.parse' (#8254) ([d98c8a7](https://github.com/vitejs/vite/commit/d98c8a7)), closes [#8254](https://github.com/vitejs/vite/issues/8254) +* chore: use typescript for rollup configPlugin (#8290) ([fa538cf](https://github.com/vitejs/vite/commit/fa538cf)), closes [#8290](https://github.com/vitejs/vite/issues/8290) +* feat: sourcemap for importAnalysis (#8258) ([a4e4d39](https://github.com/vitejs/vite/commit/a4e4d39)), closes [#8258](https://github.com/vitejs/vite/issues/8258) +* feat: spa option, `preview` and `dev` for MPA and SSR apps (#8217) ([d7cba46](https://github.com/vitejs/vite/commit/d7cba46)), closes [#8217](https://github.com/vitejs/vite/issues/8217) +* feat: vite connected logs changed to console.debug (#7733) ([9f00c41](https://github.com/vitejs/vite/commit/9f00c41)), closes [#7733](https://github.com/vitejs/vite/issues/7733) +* feat: worker support query url (#7914) ([95297dd](https://github.com/vitejs/vite/commit/95297dd)), closes [#7914](https://github.com/vitejs/vite/issues/7914) +* feat(wasm): new wasm plugin (`.wasm?init`) (#8219) ([75c3bf6](https://github.com/vitejs/vite/commit/75c3bf6)), closes [#8219](https://github.com/vitejs/vite/issues/8219) +* build!: bump targets (#8045) ([66efd69](https://github.com/vitejs/vite/commit/66efd69)), closes [#8045](https://github.com/vitejs/vite/issues/8045) +* feat!: migrate to ESM (#8178) ([76fdc27](https://github.com/vitejs/vite/commit/76fdc27)), closes [#8178](https://github.com/vitejs/vite/issues/8178) +* fix!: do not fixStacktrace by default (#7995) ([23f8e08](https://github.com/vitejs/vite/commit/23f8e08)), closes [#7995](https://github.com/vitejs/vite/issues/7995) + + + ## 3.0.0-alpha.1 (2022-05-18) * feat!: relative base (#7644) ([09648c2](https://github.com/vitejs/vite/commit/09648c2)), closes [#7644](https://github.com/vitejs/vite/issues/7644) diff --git a/packages/vite/package.json b/packages/vite/package.json index dda7781d646936..1eb8250b62ae4e 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -1,6 +1,6 @@ { "name": "vite", - "version": "3.0.0-alpha.1", + "version": "3.0.0-alpha.4", "type": "module", "license": "MIT", "author": "Evan You", @@ -20,6 +20,7 @@ "./client": { "types": "./client.d.ts" }, + "./dist/client/*": "./dist/client/*", "./terser": { "require": "./dist/node-cjs/terser.cjs" } @@ -47,7 +48,7 @@ "scripts": { "dev": "rimraf dist && pnpm run build-bundle -w", "build": "rimraf dist && run-s build-bundle build-types", - "build-bundle": "rollup --config rollup.config.ts --configPlugin esbuild", + "build-bundle": "rollup --config rollup.config.ts --configPlugin typescript", "build-types": "run-s build-temp-types patch-types roll-types", "build-temp-types": "tsc --emitDeclarationOnly --outDir temp/node -p src/node", "patch-types": "esno scripts/patchTypes.ts", diff --git a/packages/vite/rollup.config.ts b/packages/vite/rollup.config.ts index 17f2f08c3c8469..9ad04d55a3658a 100644 --- a/packages/vite/rollup.config.ts +++ b/packages/vite/rollup.config.ts @@ -19,6 +19,7 @@ const envConfig = defineConfig({ input: path.resolve(__dirname, 'src/client/env.ts'), plugins: [ typescript({ + tsconfig: false, target: 'es2020', module: 'esnext', include: ['src/client/env.ts'], @@ -39,6 +40,7 @@ const clientConfig = defineConfig({ external: ['./env', '@vite/env'], plugins: [ typescript({ + tsconfig: false, target: 'es2020', include: ['src/client/**/*.ts'], baseUrl: path.resolve(__dirname, 'src/client'), @@ -230,7 +232,7 @@ function createCjsConfig(isProduction: boolean) { ...Object.keys(pkg.dependencies), ...(isProduction ? [] : Object.keys(pkg.devDependencies)) ], - plugins: [...createNodePlugins(false, false), bundleSizeLimit(50)] + plugins: [...createNodePlugins(false, false), bundleSizeLimit(55)] }) } diff --git a/packages/vite/scripts/patchTypes.ts b/packages/vite/scripts/patchTypes.ts index 38c6300ea1954b..e31f3a39e82325 100644 --- a/packages/vite/scripts/patchTypes.ts +++ b/packages/vite/scripts/patchTypes.ts @@ -7,11 +7,9 @@ import type { File } from '@babel/types' import colors from 'picocolors' import MagicString from 'magic-string' -// @ts-ignore -const __dirname = resolve(fileURLToPath(import.meta.url), '..') - -const tempDir = resolve(__dirname, '../temp/node') -const typesDir = resolve(__dirname, '../types') +const dir = dirname(fileURLToPath(import.meta.url)) +const tempDir = resolve(dir, '../temp/node') +const typesDir = resolve(dir, '../types') // walk through the temp dts dir, find all import/export of types/* // and rewrite them into relative imports - so that api-extractor actually diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts index 77314a896920e7..b32960497e3af4 100644 --- a/packages/vite/src/node/__tests__/config.spec.ts +++ b/packages/vite/src/node/__tests__/config.spec.ts @@ -1,7 +1,8 @@ import { describe, expect, test } from 'vitest' import type { InlineConfig } from '..' import type { UserConfig, UserConfigExport } from '../config' -import { resolveConfig, resolveEnvPrefix } from '../config' +import { resolveConfig } from '../config' +import { resolveEnvPrefix } from '../env' import { mergeConfig } from '../publicUtils' describe('mergeConfig', () => { diff --git a/packages/vite/src/node/__tests__/plugins/import.spec.ts b/packages/vite/src/node/__tests__/plugins/import.spec.ts index 661f35902ab580..d7320a70094a4d 100644 --- a/packages/vite/src/node/__tests__/plugins/import.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/import.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from 'vitest' import { transformCjsImport } from '../../plugins/importAnalysis' describe('transformCjsImport', () => { - const url = './node_modules/.vite/react.js' + const url = './node_modules/.vite/deps/react.js' const rawUrl = 'react' test('import specifier', () => { @@ -14,7 +14,7 @@ describe('transformCjsImport', () => { 0 ) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const useState = __vite__cjsImport0_react["useState"]; ' + 'const Component = __vite__cjsImport0_react["Component"]' ) @@ -24,7 +24,7 @@ describe('transformCjsImport', () => { expect( transformCjsImport('import React from "react"', url, rawUrl, 0) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react' ) @@ -36,7 +36,7 @@ describe('transformCjsImport', () => { 0 ) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react' ) }) @@ -45,7 +45,7 @@ describe('transformCjsImport', () => { expect( transformCjsImport('import * as react from "react"', url, rawUrl, 0) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const react = __vite__cjsImport0_react' ) }) @@ -69,7 +69,7 @@ describe('transformCjsImport', () => { 0 ) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const useState = __vite__cjsImport0_react["useState"]; ' + 'const Component = __vite__cjsImport0_react["Component"]; ' + 'export { useState, Component }' @@ -83,7 +83,7 @@ describe('transformCjsImport', () => { 0 ) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const useStateAlias = __vite__cjsImport0_react["useState"]; ' + 'const ComponentAlias = __vite__cjsImport0_react["Component"]; ' + 'export { useStateAlias, ComponentAlias }' @@ -94,7 +94,7 @@ describe('transformCjsImport', () => { expect( transformCjsImport('export { default } from "react"', url, rawUrl, 0) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const __vite__cjsExportDefault_0 = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react; ' + 'export default __vite__cjsExportDefault_0' ) @@ -107,7 +107,7 @@ describe('transformCjsImport', () => { 0 ) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react; ' + 'export { React }' ) @@ -120,7 +120,7 @@ describe('transformCjsImport', () => { 0 ) ).toBe( - 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' + + 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const __vite__cjsExportDefault_0 = __vite__cjsImport0_react["Component"]; ' + 'export default __vite__cjsExportDefault_0' ) diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 00f68bb5ef60b7..4b1cb0afdc5d4e 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -97,24 +97,24 @@ cli const info = server.config.logger.info + // @ts-ignore + const viteStartTime = global.__vite_start_time ?? false + const startupDurationString = viteStartTime + ? colors.dim( + `ready in ${colors.white( + colors.bold(Math.ceil(performance.now() - viteStartTime)) + )} ms` + ) + : '' + info( - colors.cyan(`\n vite v${VERSION}`) + - colors.green(` dev server running at:\n`), - { - clear: !server.config.logger.hasWarned - } + `\n ${colors.green( + `${colors.bold('VITE')} v${VERSION}` + )} ${startupDurationString}\n`, + { clear: !server.config.logger.hasWarned } ) server.printUrls() - - // @ts-ignore - if (global.__vite_start_time) { - // @ts-ignore - const startupDuration = performance.now() - global.__vite_start_time - info( - `\n ${colors.cyan(`ready in ${Math.ceil(startupDuration)}ms.`)}\n` - ) - } } catch (e) { createLogger(options.logLevel).error( colors.red(`error when starting dev server:\n${e.stack}`), diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 174fa0dd40f3d0..f64ff7c00c6502 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -4,15 +4,13 @@ import { parse as parseUrl, pathToFileURL } from 'url' import { performance } from 'perf_hooks' import { createRequire } from 'module' import colors from 'picocolors' -import dotenv from 'dotenv' -import dotenvExpand from 'dotenv-expand' import type { Alias, AliasOptions } from 'types/alias' import { createFilter } from '@rollup/pluginutils' import aliasPlugin from '@rollup/plugin-alias' import { build } from 'esbuild' import type { RollupOptions } from 'rollup' import type { Plugin } from './plugin' -import type { BuildOptions } from './build' +import type { BuildOptions, ResolvedBuildOptions } from './build' import { resolveBuildOptions } from './build' import type { ResolvedServerOptions, ServerOptions } from './server' import { resolveServerOptions } from './server' @@ -20,7 +18,6 @@ import type { PreviewOptions, ResolvedPreviewOptions } from './preview' import { resolvePreviewOptions } from './preview' import type { CSSOptions } from './plugins/css' import { - arraify, createDebugger, dynamicImport, isExternalUrl, @@ -43,7 +40,7 @@ import type { JsonOptions } from './plugins/json' import type { PluginContainer } from './server/pluginContainer' import { createPluginContainer } from './server/pluginContainer' import type { PackageCache } from './packages' -import type { ResolvedBuildOptions } from '.' +import { loadEnv, resolveEnvPrefix } from './env' const debug = createDebugger('vite:config') @@ -208,6 +205,12 @@ export interface UserConfig { 'plugins' | 'input' | 'onwarn' | 'preserveEntrySignatures' > } + /** + * Whether your application is a Single Page Application (SPA). Set to `false` + * for other kinds of apps like MPAs. + * @default true + */ + spa?: boolean } export interface ExperimentalOptions { @@ -274,6 +277,7 @@ export type ResolvedConfig = Readonly< /** @internal */ packageCache: PackageCache worker: ResolveWorkerOptions + spa: boolean } > @@ -399,7 +403,9 @@ export async function resolveConfig( // Note it is possible for user to have a custom mode, e.g. `staging` where // production-like behavior is expected. This is indicated by NODE_ENV=production // loaded from `.staging.env` and set by us as VITE_USER_NODE_ENV - const isProduction = (process.env.VITE_USER_NODE_ENV || mode) === 'production' + const isProduction = + (process.env.NODE_ENV || process.env.VITE_USER_NODE_ENV || mode) === + 'production' if (isProduction) { // in case default mode was not production and is overwritten process.env.NODE_ENV = 'production' @@ -514,7 +520,8 @@ export async function resolveConfig( ...optimizeDeps.esbuildOptions } }, - worker: resolvedWorkerOptions + worker: resolvedWorkerOptions, + spa: config.spa ?? true } // flat config.worker.plugin @@ -848,80 +855,3 @@ async function loadConfigFromBundledFile( _require.extensions[extension] = defaultLoader return config } - -export function loadEnv( - mode: string, - envDir: string, - prefixes: string | string[] = 'VITE_' -): Record { - if (mode === 'local') { - throw new Error( - `"local" cannot be used as a mode name because it conflicts with ` + - `the .local postfix for .env files.` - ) - } - prefixes = arraify(prefixes) - const env: Record = {} - const envFiles = [ - /** mode local file */ `.env.${mode}.local`, - /** mode file */ `.env.${mode}`, - /** local file */ `.env.local`, - /** default file */ `.env` - ] - - // check if there are actual env variables starting with VITE_* - // these are typically provided inline and should be prioritized - for (const key in process.env) { - if ( - prefixes.some((prefix) => key.startsWith(prefix)) && - env[key] === undefined - ) { - env[key] = process.env[key] as string - } - } - - for (const file of envFiles) { - const path = lookupFile(envDir, [file], { pathOnly: true, rootDir: envDir }) - if (path) { - const parsed = dotenv.parse(fs.readFileSync(path), { - debug: process.env.DEBUG?.includes('vite:dotenv') || undefined - }) - - // let environment variables use each other - dotenvExpand({ - parsed, - // prevent process.env mutation - ignoreProcessEnv: true - } as any) - - // only keys that start with prefix are exposed to client - for (const [key, value] of Object.entries(parsed)) { - if ( - prefixes.some((prefix) => key.startsWith(prefix)) && - env[key] === undefined - ) { - env[key] = value - } else if ( - key === 'NODE_ENV' && - process.env.VITE_USER_NODE_ENV === undefined - ) { - // NODE_ENV override in .env file - process.env.VITE_USER_NODE_ENV = value - } - } - } - } - return env -} - -export function resolveEnvPrefix({ - envPrefix = 'VITE_' -}: UserConfig): string[] { - envPrefix = arraify(envPrefix) - if (envPrefix.some((prefix) => prefix === '')) { - throw new Error( - `envPrefix option contains value '', which could lead unexpected exposure of sensitive information.` - ) - } - return envPrefix -} diff --git a/packages/vite/src/node/env.ts b/packages/vite/src/node/env.ts new file mode 100644 index 00000000000000..5f204488c67efd --- /dev/null +++ b/packages/vite/src/node/env.ts @@ -0,0 +1,82 @@ +import fs from 'fs' +import dotenv from 'dotenv' +import dotenvExpand from 'dotenv-expand' +import { arraify, lookupFile } from './utils' +import type { UserConfig } from './config' + +export function loadEnv( + mode: string, + envDir: string, + prefixes: string | string[] = 'VITE_' +): Record { + if (mode === 'local') { + throw new Error( + `"local" cannot be used as a mode name because it conflicts with ` + + `the .local postfix for .env files.` + ) + } + prefixes = arraify(prefixes) + const env: Record = {} + const envFiles = [ + /** mode local file */ `.env.${mode}.local`, + /** mode file */ `.env.${mode}`, + /** local file */ `.env.local`, + /** default file */ `.env` + ] + + // check if there are actual env variables starting with VITE_* + // these are typically provided inline and should be prioritized + for (const key in process.env) { + if ( + prefixes.some((prefix) => key.startsWith(prefix)) && + env[key] === undefined + ) { + env[key] = process.env[key] as string + } + } + + for (const file of envFiles) { + const path = lookupFile(envDir, [file], { pathOnly: true, rootDir: envDir }) + if (path) { + const parsed = dotenv.parse(fs.readFileSync(path), { + debug: process.env.DEBUG?.includes('vite:dotenv') || undefined + }) + + // let environment variables use each other + dotenvExpand({ + parsed, + // prevent process.env mutation + ignoreProcessEnv: true + } as any) + + // only keys that start with prefix are exposed to client + for (const [key, value] of Object.entries(parsed)) { + if ( + prefixes.some((prefix) => key.startsWith(prefix)) && + env[key] === undefined + ) { + env[key] = value + } else if ( + key === 'NODE_ENV' && + process.env.VITE_USER_NODE_ENV === undefined + ) { + // NODE_ENV override in .env file + process.env.VITE_USER_NODE_ENV = value + } + } + } + } + return env +} + +export function resolveEnvPrefix({ + envPrefix = 'VITE_' +}: UserConfig): string[] { + envPrefix = arraify(envPrefix) + if (envPrefix.some((prefix) => prefix === '')) { + throw new Error( + `envPrefix option contains value '', which could lead unexpected exposure of sensitive information.` + ) + } + return envPrefix +} diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 8e9cafd7d943a1..14e66413fc4a76 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -36,7 +36,8 @@ export type { DepOptimizationResult, DepOptimizationProcessing, OptimizedDepInfo, - OptimizedDeps + OptimizedDeps, + ExportsData } from './optimizer' export type { Plugin } from './plugin' export type { PackageCache, PackageData } from './packages' diff --git a/packages/vite/src/node/logger.ts b/packages/vite/src/node/logger.ts index c0f316ffed2698..1176a265a4fcbe 100644 --- a/packages/vite/src/node/logger.ts +++ b/packages/vite/src/node/logger.ts @@ -171,11 +171,21 @@ function printServerUrls( base: string, info: Logger['info'] ): void { + const urls: Array<{ label: string; url: string }> = [] + if (hostname.host === '127.0.0.1') { - const url = `${protocol}://${hostname.name}:${colors.bold(port)}${base}` - info(` > Local: ${colors.cyan(url)}`) + urls.push({ + label: 'Local', + url: colors.cyan( + `${protocol}://${hostname.name}:${colors.bold(port)}${base}` + ) + }) + if (hostname.name !== '127.0.0.1') { - info(` > Network: ${colors.dim('use `--host` to expose')}`) + urls.push({ + label: 'Network', + url: colors.dim(`use ${colors.white(colors.bold('--host'))} to expose`) + }) } } else { Object.values(os.networkInterfaces()) @@ -189,14 +199,24 @@ function printServerUrls( // Node >= v18 (typeof detail.family === 'number' && detail.family === 4)) ) - .map((detail) => { - const type = detail.address.includes('127.0.0.1') - ? 'Local: ' - : 'Network: ' + .forEach((detail) => { const host = detail.address.replace('127.0.0.1', hostname.name) const url = `${protocol}://${host}:${colors.bold(port)}${base}` - return ` > ${type} ${colors.cyan(url)}` + const label = detail.address.includes('127.0.0.1') ? 'Local' : 'Network' + + urls.push({ label, url: colors.cyan(url) }) }) - .forEach((msg) => info(msg)) } + + const length = urls.reduce( + (length, { label }) => Math.max(length, label.length), + 0 + ) + urls.forEach(({ label, url: text }) => { + info( + ` ${colors.green('➜')} ${colors.bold(label)}: ${' '.repeat( + length - label.length + )}${text}` + ) + }) } diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index b31cd7a1b595bf..9fcd19b92062af 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -16,7 +16,6 @@ import { normalizeId, normalizePath, removeDir, - removeDirSync, renameDir, writeFile } from '../utils' @@ -35,6 +34,8 @@ export type ExportsData = ReturnType & { // es-module-lexer has a facade detection but isn't always accurate for our // use case when the module has default export hasReExports?: true + // hint if the dep requires loading as jsx + jsxLoader?: true } export interface OptimizedDeps { @@ -65,6 +66,12 @@ export interface DepOptimizationOptions { * cannot be globs). */ exclude?: string[] + /** + * Force ESM interop when importing for these dependencies. Some legacy + * packages advertise themselves as ESM but use `require` internally + * @experimental + */ + needsInterop?: string[] /** * Options to pass to esbuild during the dep scanning and optimization * @@ -135,6 +142,11 @@ export interface OptimizedDepInfo { * but the bundles may not yet be saved to disk */ processing?: Promise + /** + * ExportData cache, discovered deps will parse the src entry to get exports + * data used both to define if interop is needed and when pre-bundling + */ + exportsData?: Promise } export interface DepOptimizationMetadata { @@ -262,7 +274,7 @@ export function loadCachedDepOptimizationMetadata( } // Start with a fresh cache - removeDirSync(depsCacheDir) + fs.rmSync(depsCacheDir, { recursive: true, force: true }) } /** @@ -298,12 +310,13 @@ export async function discoverProjectDependencies( ) const discovered: Record = {} for (const id in deps) { - const entry = deps[id] + const src = deps[id] discovered[id] = { id, file: getOptimizedDepPath(id, config), - src: entry, - browserHash: browserHash + src, + browserHash: browserHash, + exportsData: extractExportsData(src, config) } } return discovered @@ -369,17 +382,24 @@ export async function runOptimizeDeps( const qualifiedIds = Object.keys(depsInfo) - if (!qualifiedIds.length) { - return { - metadata, - commit() { - // Write metadata file, delete `deps` folder and rename the `processing` folder to `deps` - return commitProcessingDepsCacheSync() - }, - cancel + const processingResult: DepOptimizationResult = { + metadata, + async commit() { + // Write metadata file, delete `deps` folder and rename the `processing` folder to `deps` + // Processing is done, we can now replace the depsCacheDir with processingCacheDir + // Rewire the file paths from the temporal processing dir to the final deps cache dir + await removeDir(depsCacheDir) + await renameDir(processingCacheDir, depsCacheDir) + }, + cancel() { + fs.rmSync(processingCacheDir, { recursive: true, force: true }) } } + if (!qualifiedIds.length) { + return processingResult + } + // esbuild generates nested directory output with lowest common ancestor base // this is unpredictable and makes it difficult to analyze entry / output // mapping. So what we do here is: @@ -393,51 +413,20 @@ export async function runOptimizeDeps( const { plugins = [], ...esbuildOptions } = config.optimizeDeps?.esbuildOptions ?? {} - await init for (const id in depsInfo) { - const flatId = flattenId(id) - const filePath = (flatIdDeps[flatId] = depsInfo[id].src!) - let exportsData: ExportsData - if (config.optimizeDeps.extensions?.some((ext) => filePath.endsWith(ext))) { - // For custom supported extensions, build the entry file to transform it into JS, - // and then parse with es-module-lexer. Note that the `bundle` option is not `true`, - // so only the entry file is being transformed. - const result = await build({ - ...esbuildOptions, - plugins, - entryPoints: [filePath], - write: false, - format: 'esm' - }) - exportsData = parse(result.outputFiles[0].text) as ExportsData - } else { - const entryContent = fs.readFileSync(filePath, 'utf-8') - try { - exportsData = parse(entryContent) as ExportsData - } catch { - const loader = esbuildOptions.loader?.[path.extname(filePath)] || 'jsx' - debug( - `Unable to parse dependency: ${id}. Trying again with a ${loader} transform.` - ) - const transformed = await transformWithEsbuild(entryContent, filePath, { - loader - }) - // Ensure that optimization won't fail by defaulting '.js' to the JSX parser. - // This is useful for packages such as Gatsby. - esbuildOptions.loader = { - '.js': 'jsx', - ...esbuildOptions.loader - } - exportsData = parse(transformed.code) as ExportsData - } - for (const { ss, se } of exportsData[0]) { - const exp = entryContent.slice(ss, se) - if (/export\s+\*\s+from/.test(exp)) { - exportsData.hasReExports = true - } + const src = depsInfo[id].src! + const exportsData = await (depsInfo[id].exportsData ?? + extractExportsData(src, config)) + if (exportsData.jsxLoader) { + // Ensure that optimization won't fail by defaulting '.js' to the JSX parser. + // This is useful for packages such as Gatsby. + esbuildOptions.loader = { + '.js': 'jsx', + ...esbuildOptions.loader } } - + const flatId = flattenId(id) + flatIdDeps[flatId] = src idToExports[id] = exportsData flatIdToExports[flatId] = exportsData } @@ -484,15 +473,18 @@ export async function runOptimizeDeps( for (const id in depsInfo) { const output = esbuildOutputFromId(meta.outputs, id, processingCacheDir) + const { exportsData, ...info } = depsInfo[id] addOptimizedDepInfo(metadata, 'optimized', { - ...depsInfo[id], - needsInterop: needsInterop(id, idToExports[id], output), + ...info, // We only need to hash the output.imports in to check for stability, but adding the hash // and file path gives us a unique hash that may be useful for other things in the future fileHash: getHash( metadata.hash + depsInfo[id].file + JSON.stringify(output.imports) ), - browserHash: metadata.browserHash + browserHash: metadata.browserHash, + // After bundling we have more information and can warn the user about legacy packages + // that require manual configuration + needsInterop: needsInterop(config, id, idToExports[id], output) }) } @@ -523,25 +515,7 @@ export async function runOptimizeDeps( debug(`deps bundled in ${(performance.now() - start).toFixed(2)}ms`) - return { - metadata, - commit() { - // Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync - return commitProcessingDepsCacheSync() - }, - cancel - } - - async function commitProcessingDepsCacheSync() { - // Processing is done, we can now replace the depsCacheDir with processingCacheDir - // Rewire the file paths from the temporal processing dir to the final deps cache dir - await removeDir(depsCacheDir) - await renameDir(processingCacheDir, depsCacheDir) - } - - function cancel() { - removeDirSync(processingCacheDir) - } + return processingResult } export async function findKnownImports( @@ -736,17 +710,71 @@ function esbuildOutputFromId( ] } +export async function extractExportsData( + filePath: string, + config: ResolvedConfig +): Promise { + await init + let exportsData: ExportsData + + const esbuildOptions = config.optimizeDeps?.esbuildOptions ?? {} + if (config.optimizeDeps.extensions?.some((ext) => filePath.endsWith(ext))) { + // For custom supported extensions, build the entry file to transform it into JS, + // and then parse with es-module-lexer. Note that the `bundle` option is not `true`, + // so only the entry file is being transformed. + const result = await build({ + ...esbuildOptions, + entryPoints: [filePath], + write: false, + format: 'esm' + }) + exportsData = parse(result.outputFiles[0].text) as ExportsData + } else { + const entryContent = fs.readFileSync(filePath, 'utf-8') + try { + exportsData = parse(entryContent) as ExportsData + } catch { + const loader = esbuildOptions.loader?.[path.extname(filePath)] || 'jsx' + debug( + `Unable to parse: ${filePath}.\n Trying again with a ${loader} transform.` + ) + const transformed = await transformWithEsbuild(entryContent, filePath, { + loader + }) + // Ensure that optimization won't fail by defaulting '.js' to the JSX parser. + // This is useful for packages such as Gatsby. + esbuildOptions.loader = { + '.js': 'jsx', + ...esbuildOptions.loader + } + exportsData = parse(transformed.code) as ExportsData + exportsData.jsxLoader = true + } + for (const { ss, se } of exportsData[0]) { + const exp = entryContent.slice(ss, se) + if (/export\s+\*\s+from/.test(exp)) { + exportsData.hasReExports = true + } + } + } + return exportsData +} + // https://github.com/vitejs/vite/issues/1724#issuecomment-767619642 // a list of modules that pretends to be ESM but still uses `require`. // this causes esbuild to wrap them as CJS even when its entry appears to be ESM. const KNOWN_INTEROP_IDS = new Set(['moment']) function needsInterop( + config: ResolvedConfig, id: string, exportsData: ExportsData, - output: { exports: string[] } + output?: { exports: string[] } ): boolean { - if (KNOWN_INTEROP_IDS.has(id)) { + if ( + config.optimizeDeps?.needsInterop?.includes(id) || + KNOWN_INTEROP_IDS.has(id) + ) { return true } const [imports, exports] = exportsData @@ -755,16 +783,19 @@ function needsInterop( return true } - // if a peer dependency used require() on a ESM dependency, esbuild turns the - // ESM dependency's entry chunk into a single default export... detect - // such cases by checking exports mismatch, and force interop. - const generatedExports: string[] = output.exports - - if ( - !generatedExports || - (isSingleDefaultExport(generatedExports) && !isSingleDefaultExport(exports)) - ) { - return true + if (output) { + // if a peer dependency used require() on a ESM dependency, esbuild turns the + // ESM dependency's entry chunk into a single default export... detect + // such cases by checking exports mismatch, and force interop. + const generatedExports: string[] = output.exports + + if ( + !generatedExports || + (isSingleDefaultExport(generatedExports) && + !isSingleDefaultExport(exports)) + ) { + return true + } } return false } @@ -847,14 +878,17 @@ function findOptimizedDepInfoInRecord( export async function optimizedDepNeedsInterop( metadata: DepOptimizationMetadata, - file: string + file: string, + config: ResolvedConfig ): Promise { const depInfo = optimizedDepInfoFromFile(metadata, file) - - if (!depInfo) return undefined - - // Wait until the dependency has been pre-bundled - await depInfo.processing - + if (depInfo?.src && depInfo.needsInterop === undefined) { + depInfo.exportsData ??= extractExportsData(depInfo.src, config) + depInfo.needsInterop = needsInterop( + config, + depInfo.id, + await depInfo.exportsData + ) + } return depInfo?.needsInterop } diff --git a/packages/vite/src/node/optimizer/registerMissing.ts b/packages/vite/src/node/optimizer/registerMissing.ts index 2788f83a52f4f4..6e2a0c75332b48 100644 --- a/packages/vite/src/node/optimizer/registerMissing.ts +++ b/packages/vite/src/node/optimizer/registerMissing.ts @@ -9,6 +9,7 @@ import { depsFromOptimizedDepInfo, depsLogString, discoverProjectDependencies, + extractExportsData, getOptimizedDepPath, loadCachedDepOptimizationMetadata, newDepOptimizationProcessing, @@ -177,11 +178,28 @@ export function createOptimizedDeps(server: ViteDevServer): OptimizedDeps { const newData = processingResult.metadata + const needsInteropMismatch = [] + for (const dep in metadata.discovered) { + const discoveredDepInfo = metadata.discovered[dep] + const depInfo = newData.optimized[dep] + if (depInfo) { + if ( + discoveredDepInfo.needsInterop !== undefined && + depInfo.needsInterop !== discoveredDepInfo.needsInterop + ) { + // This only happens when a discovered dependency has mixed ESM and CJS syntax + // and it hasn't been manually added to optimizeDeps.needsInterop + needsInteropMismatch.push(dep) + } + } + } + // After a re-optimization, if the internal bundled chunks change a full page reload // is required. If the files are stable, we can avoid the reload that is expensive // for large applications. Comparing their fileHash we can find out if it is safe to // keep the current browser state. const needsReload = + needsInteropMismatch.length > 0 || metadata.hash !== newData.hash || Object.keys(metadata.optimized).some((dep) => { return ( @@ -284,6 +302,19 @@ export function createOptimizedDeps(server: ViteDevServer): OptimizedDeps { timestamp: true } ) + if (needsInteropMismatch.length > 0) { + config.logger.warn( + `Mixed ESM and CJS detected in ${colors.yellow( + needsInteropMismatch.join(', ') + )}, add ${ + needsInteropMismatch.length === 1 ? 'it' : 'them' + } to optimizeDeps.needsInterop to speed up cold start`, + { + timestamp: true + } + ) + } + fullReload() } } @@ -378,7 +409,8 @@ export function createOptimizedDeps(server: ViteDevServer): OptimizedDeps { ), // loading of this pre-bundled dep needs to await for its processing // promise to be resolved - processing: depOptimizationProcessing.promise + processing: depOptimizationProcessing.promise, + exportsData: extractExportsData(resolved, config) }) // Debounced rerun, let other missing dependencies be discovered before diff --git a/packages/vite/src/node/plugins/clientInjections.ts b/packages/vite/src/node/plugins/clientInjections.ts index eb5c7183c2ac59..84811dfc10df2a 100644 --- a/packages/vite/src/node/plugins/clientInjections.ts +++ b/packages/vite/src/node/plugins/clientInjections.ts @@ -55,7 +55,8 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { // avoiding inconsistencies between dev and build return code.replace( /\bprocess\.env\.NODE_ENV\b/g, - JSON.stringify(config.mode) + config.define?.['process.env.NODE_ENV'] || + JSON.stringify(process.env.NODE_ENV || config.mode) ) } } diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 4d312636f3e996..b73e7a3806127f 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -309,11 +309,6 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { return [url, resolved.id] } - // Import rewrites, we do them after all the URLs have been resolved - // to help with the discovery of new dependencies. If we need to wait - // for each dependency there could be one reload per import - const importRewrites: (() => Promise)[] = [] - for (let index = 0; index < imports.length; index++) { const { s: start, @@ -403,75 +398,66 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { server?.moduleGraph.safeModulesPath.add(fsPathFromUrl(url)) if (url !== specifier) { - importRewrites.push(async () => { - let rewriteDone = false - if ( - server?._optimizedDeps && - isOptimizedDepFile(resolvedId, config) && - !resolvedId.match(optimizedDepChunkRE) - ) { - // for optimized cjs deps, support named imports by rewriting named imports to const assignments. - // internal optimized chunks don't need es interop and are excluded - - // The browserHash in resolvedId could be stale in which case there will be a full - // page reload. We could return a 404 in that case but it is safe to return the request - const file = cleanUrl(resolvedId) // Remove ?v={hash} - - const needsInterop = await optimizedDepNeedsInterop( - server._optimizedDeps!.metadata, - file - ) - - if (needsInterop === undefined) { - // Non-entry dynamic imports from dependencies will reach here as there isn't - // optimize info for them, but they don't need es interop. If the request isn't - // a dynamic import, then it is an internal Vite error - if (!file.match(optimizedDepDynamicRE)) { - config.logger.error( - colors.red( - `Vite Error, ${url} optimized info should be defined` - ) - ) - } - } else if (needsInterop) { - debug(`${url} needs interop`) - if (isDynamicImport) { - // rewrite `import('package')` to expose the default directly - str().overwrite( - expStart, - expEnd, - `import('${url}').then(m => m.default && m.default.__esModule ? m.default : ({ ...m.default, default: m.default }))`, - { contentOnly: true } + let rewriteDone = false + if ( + server?._optimizedDeps && + isOptimizedDepFile(resolvedId, config) && + !resolvedId.match(optimizedDepChunkRE) + ) { + // for optimized cjs deps, support named imports by rewriting named imports to const assignments. + // internal optimized chunks don't need es interop and are excluded + + // The browserHash in resolvedId could be stale in which case there will be a full + // page reload. We could return a 404 in that case but it is safe to return the request + const file = cleanUrl(resolvedId) // Remove ?v={hash} + + const needsInterop = await optimizedDepNeedsInterop( + server._optimizedDeps!.metadata, + file, + config + ) + + if (needsInterop === undefined) { + // Non-entry dynamic imports from dependencies will reach here as there isn't + // optimize info for them, but they don't need es interop. If the request isn't + // a dynamic import, then it is an internal Vite error + if (!file.match(optimizedDepDynamicRE)) { + config.logger.error( + colors.red( + `Vite Error, ${url} optimized info should be defined` ) + ) + } + } else if (needsInterop) { + debug(`${url} needs interop`) + if (isDynamicImport) { + // rewrite `import('package')` to expose the default directly + str().overwrite( + expStart, + expEnd, + `import('${url}').then(m => m.default && m.default.__esModule ? m.default : ({ ...m.default, default: m.default }))`, + { contentOnly: true } + ) + } else { + const exp = source.slice(expStart, expEnd) + const rewritten = transformCjsImport(exp, url, rawUrl, index) + if (rewritten) { + str().overwrite(expStart, expEnd, rewritten, { + contentOnly: true + }) } else { - const exp = source.slice(expStart, expEnd) - const rewritten = transformCjsImport( - exp, - url, - rawUrl, - index - ) - if (rewritten) { - str().overwrite(expStart, expEnd, rewritten, { - contentOnly: true - }) - } else { - // #1439 export * from '...' - str().overwrite(start, end, url, { contentOnly: true }) - } + // #1439 export * from '...' + str().overwrite(start, end, url, { contentOnly: true }) } - rewriteDone = true } + rewriteDone = true } - if (!rewriteDone) { - str().overwrite( - start, - end, - isDynamicImport ? `'${url}'` : url, - { contentOnly: true } - ) - } - }) + } + if (!rewriteDone) { + str().overwrite(start, end, isDynamicImport ? `'${url}'` : url, { + contentOnly: true + }) + } } // record for HMR import chain analysis @@ -636,14 +622,6 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { }) } - // Await for import rewrites that requires dependencies to be pre-bundled to - // know if es interop is needed after starting further transformRequest calls - // This will let Vite process deeper into the user code and find more missing - // dependencies before the next page reload - for (const rewrite of importRewrites) { - await rewrite() - } - if (s) { return { code: s.toString(), diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 99eef51a4a2bd7..5fa6311e820100 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -1,6 +1,6 @@ import path from 'path' import MagicString from 'magic-string' -import type { EmittedAsset, OutputChunk, TransformPluginContext } from 'rollup' +import type { EmittedAsset, OutputChunk } from 'rollup' import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' import type { ViteDevServer } from '../server' @@ -43,7 +43,6 @@ function saveEmitWorkerAsset( } export async function bundleWorkerEntry( - ctx: TransformPluginContext, config: ResolvedConfig, id: string, query: Record | null @@ -102,11 +101,10 @@ export async function bundleWorkerEntry( } finally { await bundle.close() } - return emitSourcemapForWorkerEntry(ctx, config, query, chunk) + return emitSourcemapForWorkerEntry(config, query, chunk) } function emitSourcemapForWorkerEntry( - ctx: TransformPluginContext, config: ResolvedConfig, query: Record | null, chunk: OutputChunk @@ -166,7 +164,6 @@ function encodeWorkerAssetFileName( } export async function workerFileToUrl( - ctx: TransformPluginContext, config: ResolvedConfig, id: string, query: Record | null @@ -174,7 +171,7 @@ export async function workerFileToUrl( const workerMap = workerCache.get(config.mainConfig || config)! let fileName = workerMap.bundle.get(id) if (!fileName) { - const outputChunk = await bundleWorkerEntry(ctx, config, id, query) + const outputChunk = await bundleWorkerEntry(config, id, query) fileName = outputChunk.fileName saveEmitWorkerAsset(config, { fileName, @@ -226,8 +223,10 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { async transform(raw, id) { const query = parseRequest(id) - if (query && query[WORKER_FILE_ID] != null && query['type'] != null) { - const workerType = query['type'] as WorkerType + if (query && query[WORKER_FILE_ID] != null) { + // if import worker by worker constructor will had query.type + // other type will be import worker by esm + const workerType = query['type']! as WorkerType let injectEnv = '' if (workerType === 'classic') { @@ -259,11 +258,18 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { // stringified url or `new URL(...)` let url: string + const { format } = config.worker + const workerConstructor = + query.sharedworker != null ? 'SharedWorker' : 'Worker' + const workerType = isBuild + ? format === 'es' + ? 'module' + : 'classic' + : 'module' + const workerOptions = workerType === 'classic' ? '' : ',{type: "module"}' if (isBuild) { if (query.inline != null) { - const chunk = await bundleWorkerEntry(this, config, id, query) - const { format } = config.worker - const workerOptions = format === 'es' ? '{type: "module"}' : '{}' + const chunk = await bundleWorkerEntry(config, id, query) // inline as blob data url return { code: `const encodedJs = "${Buffer.from(chunk.code).toString( @@ -273,7 +279,7 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { export default function WorkerWrapper() { const objURL = blob && (window.URL || window.webkitURL).createObjectURL(blob); try { - return objURL ? new Worker(objURL, ${workerOptions}) : new Worker("data:application/javascript;base64," + encodedJs, {type: "module"}); + return objURL ? new ${workerConstructor}(objURL${workerOptions}) : new ${workerConstructor}("data:application/javascript;base64," + encodedJs${workerOptions}); } finally { objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL); } @@ -283,11 +289,12 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { map: { mappings: '' } } } else { - url = await workerFileToUrl(this, config, id, query) + url = await workerFileToUrl(config, id, query) } } else { url = await fileToUrl(cleanUrl(id), config, this) url = injectQuery(url, WORKER_FILE_ID) + url = injectQuery(url, `type=${workerType}`) } if (query.url != null) { @@ -297,15 +304,11 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { } } - const workerConstructor = - query.sharedworker != null ? 'SharedWorker' : 'Worker' - const workerOptions = { type: 'module' } - return { code: `export default function WorkerWrapper() { return new ${workerConstructor}(${JSON.stringify( url - )}, ${JSON.stringify(workerOptions)}) + )}${workerOptions}) }`, map: { mappings: '' } // Empty sourcemap to suppress Rollup warning } diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 3e7426f52cda24..3b8300296f0520 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -115,7 +115,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { ) let url: string if (isBuild) { - url = await workerFileToUrl(this, config, file, query) + url = await workerFileToUrl(config, file, query) } else { url = await fileToUrl(cleanUrl(file), config, this) url = injectQuery(url, WORKER_FILE_ID) diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index e66733d5c971b6..a8cb533e35ad19 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -104,7 +104,7 @@ export async function preview( sirv(distDir, { etag: true, dev: true, - single: true + single: config.spa }) ) diff --git a/packages/vite/src/node/publicUtils.ts b/packages/vite/src/node/publicUtils.ts index 43234eef2bcf0c..e24db763814dec 100644 --- a/packages/vite/src/node/publicUtils.ts +++ b/packages/vite/src/node/publicUtils.ts @@ -11,3 +11,4 @@ export { normalizePath, mergeConfig, mergeAlias } from './utils' export { send } from './server/send' export { createLogger } from './logger' export { searchForWorkspaceRoot } from './server/searchRoot' +export { loadEnv, resolveEnvPrefix } from './env' diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index e59de9f2e50690..a8e16b2b5cf9e5 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -508,8 +508,10 @@ export async function createServer( middlewares.use(serveRawFsMiddleware(server)) middlewares.use(serveStaticMiddleware(root, server)) + const isMiddlewareMode = middlewareMode && middlewareMode !== 'html' + // spa fallback - if (!middlewareMode || middlewareMode === 'html') { + if (config.spa && !isMiddlewareMode) { middlewares.use(spaFallbackMiddleware(root)) } @@ -518,9 +520,12 @@ export async function createServer( // serve custom content instead of index.html. postHooks.forEach((fn) => fn && fn()) - if (!middlewareMode || middlewareMode === 'html') { + if (config.spa && !isMiddlewareMode) { // transform index.html middlewares.use(indexHtmlMiddleware(server)) + } + + if (!isMiddlewareMode) { // handle 404s // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...` middlewares.use(function vite404Middleware(_, res) { diff --git a/packages/vite/src/node/server/pluginContainer.ts b/packages/vite/src/node/server/pluginContainer.ts index 4d124759af0afa..1b7e2e421960ad 100644 --- a/packages/vite/src/node/server/pluginContainer.ts +++ b/packages/vite/src/node/server/pluginContainer.ts @@ -147,6 +147,15 @@ export async function createPluginContainer( const debugPluginTransform = createDebugger('vite:plugin-transform', { onlyWhenFocused: 'vite:plugin' }) + const debugSourcemapCombineFlag = 'vite:sourcemap-combine' + const isDebugSourcemapCombineFocused = process.env.DEBUG?.includes( + debugSourcemapCombineFlag + ) + const debugSourcemapCombineFilter = + process.env.DEBUG_VITE_SOURCEMAP_COMBINE_FILTER + const debugSourcemapCombine = createDebugger('vite:sourcemap-combine', { + onlyWhenFocused: true + }) // --------------------------------------------------------------------------- @@ -424,6 +433,16 @@ export async function createPluginContainer( } _getCombinedSourcemap(createIfNull = false) { + if ( + debugSourcemapCombineFilter && + this.filename.includes(debugSourcemapCombineFilter) + ) { + debugSourcemapCombine('----------', this.filename) + debugSourcemapCombine(this.combinedMap) + debugSourcemapCombine(this.sourcemapChain) + debugSourcemapCombine('----------') + } + let combinedMap = this.combinedMap for (let m of this.sourcemapChain) { if (typeof m === 'string') m = JSON.parse(m) @@ -613,6 +632,10 @@ export async function createPluginContainer( if (result.code !== undefined) { code = result.code if (result.map) { + if (isDebugSourcemapCombineFocused) { + // @ts-expect-error inject plugin name for debug purpose + result.map.name = plugin.name + } ctx.sourcemapChain.push(result.map) } } diff --git a/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts index f69eedc409a007..aecf64b3586faf 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts @@ -13,7 +13,7 @@ test('always throw error when evaluating an wrong SSR module', async () => { const expectedErrors = [] for (const _ of [0, 1]) { try { - await viteServer.ssrLoadModule(badjs) + await viteServer.ssrLoadModule(badjs, { fixStacktrace: true }) } catch (e) { expectedErrors.push(e) } diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index c552d5431881eb..7f9731c417c566 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -206,7 +206,7 @@ async function instantiateModule( ) } catch (e) { mod.ssrError = e - if (e.stack && fixStacktrace !== false) { + if (e.stack && fixStacktrace) { const stacktrace = ssrRewriteStacktrace(e.stack, moduleGraph) rebindErrorStacktrace(e, stacktrace) server.config.logger.error( diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index a3573d2a6dc1de..258551b838f271 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -504,14 +504,7 @@ export function emptyDir(dir: string, skip?: string[]): void { if (skip?.includes(file)) { continue } - const abs = path.resolve(dir, file) - // baseline is Node 12 so can't use rmSync :( - if (fs.lstatSync(abs).isDirectory()) { - emptyDir(abs) - fs.rmdirSync(abs) - } else { - fs.unlinkSync(abs) - } + fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }) } } @@ -532,16 +525,11 @@ export function copyDir(srcDir: string, destDir: string): void { } } -export function removeDirSync(dir: string) { - if (fs.existsSync(dir)) { - const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped - rmSync(dir, { recursive: true }) - } -} - export const removeDir = isWindows ? promisify(gracefulRemoveDir) - : removeDirSync + : function removeDirSync(dir: string) { + fs.rmSync(dir, { recursive: true, force: true }) + } export const renameDir = isWindows ? promisify(gracefulRename) : fs.renameSync export function ensureWatchedFile( @@ -842,10 +830,9 @@ function gracefulRemoveDir( dir: string, cb: (error: NodeJS.ErrnoException | null) => void ) { - const rmdir = fs.rm ?? fs.rmdir // TODO: Remove after support for Node 12 is dropped const start = Date.now() let backoff = 0 - rmdir(dir, { recursive: true }, function CB(er) { + fs.rm(dir, { recursive: true }, function CB(er) { if (er) { if ( (er.code === 'ENOTEMPTY' || @@ -854,7 +841,7 @@ function gracefulRemoveDir( Date.now() - start < GRACEFUL_REMOVE_DIR_TIMEOUT ) { setTimeout(function () { - rmdir(dir, { recursive: true }, CB) + fs.rm(dir, { recursive: true }, CB) }, backoff) if (backoff < 100) backoff += 10 return diff --git a/packages/vite/tsconfig.json b/packages/vite/tsconfig.json new file mode 100644 index 00000000000000..930b4b2776c594 --- /dev/null +++ b/packages/vite/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "strict": false, + "declaration": false, + "resolveJsonModule": true + }, + "include": ["./rollup.config.ts"] +} diff --git a/playground/alias/dir/module/package.json b/playground/alias/dir/module/package.json index dad4e205051042..5065df363f87ee 100644 --- a/playground/alias/dir/module/package.json +++ b/playground/alias/dir/module/package.json @@ -1,5 +1,6 @@ { "name": "@vite/aliased-module", "private": true, + "type": "module", "version": "0.0.0" } diff --git a/playground/define/__tests__/define.spec.ts b/playground/define/__tests__/define.spec.ts index b2eb571734cc54..695d210a822ed6 100644 --- a/playground/define/__tests__/define.spec.ts +++ b/playground/define/__tests__/define.spec.ts @@ -14,6 +14,9 @@ test('string', async () => { expect(await page.textContent('.object')).toBe( JSON.stringify(defines.__OBJ__, null, 2) ) + expect(await page.textContent('.process-node-env')).toBe( + JSON.parse(defines['process.env.NODE_ENV']) + ) expect(await page.textContent('.env-var')).toBe( JSON.parse(defines['process.env.SOMEVAR']) ) diff --git a/playground/define/index.html b/playground/define/index.html index 97a7b9902a1dcb..1260b119149d28 100644 --- a/playground/define/index.html +++ b/playground/define/index.html @@ -6,6 +6,7 @@

Define

Boolean

Object

Env Var

+

process node env:

process as property:

spread object:

spread array:

@@ -23,6 +24,7 @@

Define

text('.number', __NUMBER__) text('.boolean', __BOOLEAN__) text('.object', JSON.stringify(__OBJ__, null, 2)) + text('.process-node-env', process.env.NODE_ENV) text('.env-var', process.env.SOMEVAR) text('.process-as-property', __OBJ__.process.env.SOMEVAR) text( diff --git a/playground/define/vite.config.js b/playground/define/vite.config.js index fb49d658f49e90..ec4a76136e8f47 100644 --- a/playground/define/vite.config.js +++ b/playground/define/vite.config.js @@ -15,6 +15,7 @@ module.exports = { } } }, + 'process.env.NODE_ENV': '"dev"', 'process.env.SOMEVAR': '"SOMEVAR"', $DOLLAR: 456, ÖUNICODE_LETTERɵ: 789, diff --git a/playground/env/__tests__/env.spec.ts b/playground/env/__tests__/env.spec.ts index f9955706101bb6..4583c9039116c8 100644 --- a/playground/env/__tests__/env.spec.ts +++ b/playground/env/__tests__/env.spec.ts @@ -37,7 +37,7 @@ test('inline variables', async () => { }) test('NODE_ENV', async () => { - expect(await page.textContent('.node-env')).toBe(mode) + expect(await page.textContent('.node-env')).toBe(process.env.NODE_ENV) }) test('env object', async () => { diff --git a/playground/nested-deps/test-package-a/package.json b/playground/nested-deps/test-package-a/package.json index 688fab78bab766..e63718eb751c99 100644 --- a/playground/nested-deps/test-package-a/package.json +++ b/playground/nested-deps/test-package-a/package.json @@ -1,6 +1,7 @@ { "name": "test-package-a", "private": true, - "version": "2.0.0", + "version": "1.0.0", + "type": "module", "main": "index.js" } diff --git a/playground/nested-deps/test-package-b/package.json b/playground/nested-deps/test-package-b/package.json index 6e32e8eba153a1..82c2ed430ecc50 100644 --- a/playground/nested-deps/test-package-b/package.json +++ b/playground/nested-deps/test-package-b/package.json @@ -2,5 +2,6 @@ "name": "test-package-b", "private": true, "version": "1.0.0", + "type": "module", "main": "index.js" } diff --git a/playground/nested-deps/test-package-c/package.json b/playground/nested-deps/test-package-c/package.json index 47672d07b3881f..abdd062654b691 100644 --- a/playground/nested-deps/test-package-c/package.json +++ b/playground/nested-deps/test-package-c/package.json @@ -2,6 +2,7 @@ "name": "test-package-c", "private": true, "version": "1.0.0", + "type": "module", "main": "index.js", "module": "index-es.js" } diff --git a/playground/nested-deps/test-package-d/package.json b/playground/nested-deps/test-package-d/package.json index e9f522c2c5ddf6..298a83e01db40f 100644 --- a/playground/nested-deps/test-package-d/package.json +++ b/playground/nested-deps/test-package-d/package.json @@ -2,6 +2,7 @@ "name": "test-package-d", "private": true, "version": "1.0.0", + "type": "module", "main": "index.js", "dependencies": { "test-package-d-nested": "link:./test-package-d-nested" diff --git a/playground/nested-deps/test-package-e/test-package-e-excluded/package.json b/playground/nested-deps/test-package-e/test-package-e-excluded/package.json index 8722324da53499..28be868a731b69 100644 --- a/playground/nested-deps/test-package-e/test-package-e-excluded/package.json +++ b/playground/nested-deps/test-package-e/test-package-e-excluded/package.json @@ -2,5 +2,6 @@ "name": "test-package-e-excluded", "private": true, "version": "0.1.0", + "type": "module", "main": "index.js" } diff --git a/playground/nested-deps/test-package-e/test-package-e-included/package.json b/playground/nested-deps/test-package-e/test-package-e-included/package.json index 37198ee7d6a7c7..f11d8c6f9febf7 100644 --- a/playground/nested-deps/test-package-e/test-package-e-included/package.json +++ b/playground/nested-deps/test-package-e/test-package-e-included/package.json @@ -2,6 +2,7 @@ "name": "test-package-e-included", "private": true, "version": "0.1.0", + "type": "module", "main": "index.js", "dependencies": { "test-package-e-excluded": "link:../test-package-e-excluded" diff --git a/playground/nested-deps/vite.config.js b/playground/nested-deps/vite.config.js index 015598af64b016..85980c5dcfe807 100644 --- a/playground/nested-deps/vite.config.js +++ b/playground/nested-deps/vite.config.js @@ -9,7 +9,7 @@ module.exports = { 'test-package-c', 'test-package-c/side', 'test-package-d > test-package-d-nested', - 'test-package-e-included' + 'test-package-e > test-package-e-included' ], exclude: ['test-package-d', 'test-package-e-excluded'] } diff --git a/playground/optimize-deps/dep-esbuild-plugin-transform/package.json b/playground/optimize-deps/dep-esbuild-plugin-transform/package.json index 4adb0e7032ae20..d5be7992e7a017 100644 --- a/playground/optimize-deps/dep-esbuild-plugin-transform/package.json +++ b/playground/optimize-deps/dep-esbuild-plugin-transform/package.json @@ -2,5 +2,6 @@ "name": "dep-esbuild-plugin-transform", "private": true, "version": "0.0.0", + "type": "module", "main": "index.js" } diff --git a/playground/optimize-deps/dep-linked/package.json b/playground/optimize-deps/dep-linked/package.json index 915340f10ae4a0..1dcbe4e42860b3 100644 --- a/playground/optimize-deps/dep-linked/package.json +++ b/playground/optimize-deps/dep-linked/package.json @@ -2,6 +2,7 @@ "name": "dep-linked", "private": true, "version": "0.0.0", + "type": "module", "main": "index.js", "dependencies": { "lodash-es": "^4.17.21" diff --git a/playground/optimize-deps/dep-node-env/package.json b/playground/optimize-deps/dep-node-env/package.json index 59a00fb153c522..a3471f72268e7f 100644 --- a/playground/optimize-deps/dep-node-env/package.json +++ b/playground/optimize-deps/dep-node-env/package.json @@ -1,5 +1,6 @@ { "name": "dep-node-env", "private": true, - "version": "1.0.0" + "version": "1.0.0", + "type": "module" } diff --git a/playground/optimize-deps/dep-with-dynamic-import/package.json b/playground/optimize-deps/dep-with-dynamic-import/package.json index 81c5d2dda6a62a..0362863f7df12b 100644 --- a/playground/optimize-deps/dep-with-dynamic-import/package.json +++ b/playground/optimize-deps/dep-with-dynamic-import/package.json @@ -2,5 +2,6 @@ "name": "dep-with-dynamic-import", "private": true, "version": "0.0.0", + "type": "module", "main": "index.js" } diff --git a/playground/optimize-deps/nested-exclude/package.json b/playground/optimize-deps/nested-exclude/package.json index d66bf358ec0185..34666b257dde7b 100644 --- a/playground/optimize-deps/nested-exclude/package.json +++ b/playground/optimize-deps/nested-exclude/package.json @@ -2,6 +2,7 @@ "name": "nested-exclude", "private": true, "version": "1.0.0", + "type": "module", "main": "index.js", "dependencies": { "nested-include": "file:../nested-include" diff --git a/playground/optimize-deps/vite.config.js b/playground/optimize-deps/vite.config.js index 623cc413032fdf..fb3bbfc4a33eb5 100644 --- a/playground/optimize-deps/vite.config.js +++ b/playground/optimize-deps/vite.config.js @@ -1,6 +1,9 @@ const fs = require('fs') const vue = require('@vitejs/plugin-vue') +// Overriding the NODE_ENV set by vitest +process.env.NODE_ENV = '' + /** * @type {import('vite').UserConfig} */ diff --git a/playground/ssr-react/__tests__/ssr-react.spec.ts b/playground/ssr-react/__tests__/ssr-react.spec.ts index 62bb7d2014f770..49b031f9e70fd8 100644 --- a/playground/ssr-react/__tests__/ssr-react.spec.ts +++ b/playground/ssr-react/__tests__/ssr-react.spec.ts @@ -40,6 +40,7 @@ test('/', async () => { }) test('hmr', async () => { + await page.goto(url) editFile('src/pages/Home.jsx', (code) => code.replace('

Home', '

changed') ) @@ -47,6 +48,7 @@ test('hmr', async () => { }) test('client navigation', async () => { + await page.goto(url) await untilUpdated(() => page.textContent('a[href="/about"]'), 'About') await page.click('a[href="/about"]') await untilUpdated(() => page.textContent('h1'), 'About') diff --git a/playground/ssr-vue/__tests__/ssr-vue.spec.ts b/playground/ssr-vue/__tests__/ssr-vue.spec.ts index c58cea4cd13e59..4988241581a4a0 100644 --- a/playground/ssr-vue/__tests__/ssr-vue.spec.ts +++ b/playground/ssr-vue/__tests__/ssr-vue.spec.ts @@ -13,7 +13,8 @@ import { const url = `http://localhost:${port}` test('vuex can be import succeed by named import', async () => { - await page.goto(url + '/store') + // wait networkidle for dynamic optimize vuex + await page.goto(url + '/store', { waitUntil: 'networkidle' }) expect(await page.textContent('h1')).toMatch('bar') // raw http request @@ -111,19 +112,20 @@ test('/', async () => { }) test('css', async () => { + await page.goto(url) if (isBuild) { expect(await getColor('h1')).toBe('green') expect(await getColor('.jsx')).toBe('blue') } else { // During dev, the CSS is loaded from async chunk and we may have to wait // when the test runs concurrently. - await page.waitForLoadState('networkidle') await untilUpdated(() => getColor('h1'), 'green') await untilUpdated(() => getColor('.jsx'), 'blue') } }) test('asset', async () => { + await page.goto(url) // should have no 404s browserLogs.forEach((msg) => { expect(msg).not.toMatch('404') @@ -135,36 +137,39 @@ test('asset', async () => { }) test('jsx', async () => { + await page.goto(url) expect(await page.textContent('.jsx')).toMatch('from JSX') }) test('virtual module', async () => { + await page.goto(url) expect(await page.textContent('.virtual')).toMatch('hi') }) test('nested virtual module', async () => { + await page.goto(url) expect(await page.textContent('.nested-virtual')).toMatch('[success]') }) test('hydration', async () => { + await page.goto(url) expect(await page.textContent('button')).toMatch('0') await page.click('button') - await page.waitForLoadState('networkidle') expect(await page.textContent('button')).toMatch('1') }) test('hmr', async () => { + await page.goto(url) editFile('src/pages/Home.vue', (code) => code.replace('Home', 'changed')) - await page.waitForLoadState('networkidle') await untilUpdated(() => page.textContent('h1'), 'changed') }) test('client navigation', async () => { + await page.goto(url) await untilUpdated(() => page.textContent('a[href="/about"]'), 'About') await page.click('a[href="/about"]') await untilUpdated(() => page.textContent('h1'), 'About') editFile('src/pages/About.vue', (code) => code.replace('About', 'changed')) - await page.waitForLoadState('networkidle') await untilUpdated(() => page.textContent('h1'), 'changed') await page.click('a[href="/"]') await untilUpdated(() => page.textContent('a[href="/"]'), 'Home') diff --git a/playground/ssr-vue/vite.config.js b/playground/ssr-vue/vite.config.js index 03277fd6560551..83128683536388 100644 --- a/playground/ssr-vue/vite.config.js +++ b/playground/ssr-vue/vite.config.js @@ -51,5 +51,8 @@ module.exports = { // this package has uncompiled .vue files 'example-external-component' ] + }, + optimizeDeps: { + exclude: ['example-external-component'] } } diff --git a/playground/ssr-vue/vite.config.noexternal.js b/playground/ssr-vue/vite.config.noexternal.js index ac74bf1430e94e..ce9a389defc68b 100644 --- a/playground/ssr-vue/vite.config.noexternal.js +++ b/playground/ssr-vue/vite.config.noexternal.js @@ -18,5 +18,8 @@ module.exports = Object.assign(config, { replacement: '@vue/runtime-core/dist/runtime-core.cjs.js' } ] + }, + optimizeDeps: { + exclude: ['example-external-component'] } }) diff --git a/playground/worker/__tests__/es/es-worker.spec.ts b/playground/worker/__tests__/es/es-worker.spec.ts index b901a7deaeedbc..9959cc29aef0bd 100644 --- a/playground/worker/__tests__/es/es-worker.spec.ts +++ b/playground/worker/__tests__/es/es-worker.spec.ts @@ -8,7 +8,7 @@ test('normal', async () => { await untilUpdated(() => page.textContent('.pong'), 'pong') await untilUpdated( () => page.textContent('.mode'), - isBuild ? 'production' : 'development' + process.env.NODE_ENV // match workerImport.js ) await untilUpdated( () => page.textContent('.bundle-with-plugin'), diff --git a/playground/worker/__tests__/iife/worker.spec.ts b/playground/worker/__tests__/iife/worker.spec.ts index 0aba9611368737..d7b437c8a3272d 100644 --- a/playground/worker/__tests__/iife/worker.spec.ts +++ b/playground/worker/__tests__/iife/worker.spec.ts @@ -9,7 +9,7 @@ test('normal', async () => { await untilUpdated(() => page.textContent('.pong'), 'pong') await untilUpdated( () => page.textContent('.mode'), - isBuild ? 'production' : 'development' + process.env.NODE_ENV // match workerImport.js ) await untilUpdated( () => page.textContent('.bundle-with-plugin'), diff --git a/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts b/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts index 90c1e55798b51f..2b146075f38146 100644 --- a/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts +++ b/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts @@ -8,7 +8,7 @@ test('normal', async () => { await untilUpdated(() => page.textContent('.pong'), 'pong') await untilUpdated( () => page.textContent('.mode'), - isBuild ? 'production' : 'development' + process.env.NODE_ENV // match workerImport.js ) await untilUpdated( () => page.textContent('.bundle-with-plugin'), diff --git a/playground/worker/worker/main-url.js b/playground/worker/worker/main-url.js index ba395926009754..d3a80ea6c80dd4 100644 --- a/playground/worker/worker/main-url.js +++ b/playground/worker/worker/main-url.js @@ -4,7 +4,7 @@ function text(el, text) { document.querySelector(el).textContent = text } -const worker = new Worker(workerUrl, { type: 'classic' }) +const worker = new Worker(workerUrl, { type: 'module' }) worker.addEventListener('message', (ev) => { text('.simple-worker-url', JSON.stringify(ev.data)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e254e35916f7ea..b3a949e094db47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,9 @@ importers: .: specifiers: + '@babel/types': ^7.17.10 '@microsoft/api-extractor': ^7.24.1 + '@rollup/plugin-typescript': ^8.3.2 '@types/babel__core': ^7.1.19 '@types/babel__standalone': ^7.1.4 '@types/convert-source-map': ^1.5.2 @@ -40,6 +42,7 @@ importers: eslint-define-config: ^1.4.1 eslint-plugin-import: ^2.26.0 eslint-plugin-node: ^11.1.0 + esno: ^0.16.3 execa: ^5.1.1 fs-extra: ^10.1.0 kill-port: ^1.6.1 @@ -54,11 +57,10 @@ importers: prompts: ^2.4.2 rimraf: ^3.0.2 rollup: ^2.72.1 - rollup-plugin-esbuild: ^4.9.1 semver: ^7.3.7 simple-git-hooks: ^2.7.0 sirv: ^2.0.2 - ts-node: ^10.7.0 + tslib: ^2.4.0 typescript: ^4.6.4 unbuild: ^0.7.4 vite: workspace:* @@ -66,7 +68,9 @@ importers: vitest: ^0.12.9 vue: ^3.2.35 devDependencies: + '@babel/types': 7.17.10 '@microsoft/api-extractor': 7.24.1 + '@rollup/plugin-typescript': 8.3.2_dsrqihoegvzgycupzno43pt4sq '@types/babel__core': 7.1.19 '@types/babel__standalone': 7.1.4 '@types/convert-source-map': 1.5.2 @@ -96,6 +100,7 @@ importers: eslint-define-config: 1.4.1 eslint-plugin-import: 2.26.0_fpv4l7j2iomztwskn7bai2v6a4 eslint-plugin-node: 11.1.0_eslint@8.16.0 + esno: 0.16.3 execa: 5.1.1 fs-extra: 10.1.0 kill-port: 1.6.1 @@ -110,11 +115,10 @@ importers: prompts: 2.4.2 rimraf: 3.0.2 rollup: 2.72.1 - rollup-plugin-esbuild: 4.9.1_pnyrwy4zazm4ltxxkdqkpc4vuy semver: 7.3.7 simple-git-hooks: 2.7.0 sirv: 2.0.2 - ts-node: 10.7.0_sm5zkxj4s52nbddwl76qwfh6ya + tslib: 2.4.0 typescript: 4.6.4 unbuild: 0.7.4 vite: link:packages/vite @@ -2037,6 +2041,21 @@ packages: rollup: 2.72.1 dev: true + /@rollup/plugin-typescript/8.3.2_dsrqihoegvzgycupzno43pt4sq: + resolution: {integrity: sha512-MtgyR5LNHZr3GyN0tM7gNO9D0CS+Y+vflS4v/PHmrX17JCkHUYKvQ5jN5o3cz1YKllM3duXUqu3yOHwMPUxhDg==} + engines: {node: '>=8.0.0'} + peerDependencies: + rollup: ^2.14.0 + tslib: '*' + typescript: '>=3.7.0' + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.72.1 + resolve: 1.22.0 + rollup: 2.72.1 + tslib: 2.4.0 + typescript: 4.6.4 + dev: true + /@rollup/plugin-typescript/8.3.2_rollup@2.72.1+tslib@2.4.0: resolution: {integrity: sha512-MtgyR5LNHZr3GyN0tM7gNO9D0CS+Y+vflS4v/PHmrX17JCkHUYKvQ5jN5o3cz1YKllM3duXUqu3yOHwMPUxhDg==} engines: {node: '>=8.0.0'} @@ -2216,7 +2235,7 @@ packages: dev: true /@types/json5/0.0.29: - resolution: {integrity: sha1-7ihweulOEdK4J7y+UnC86n8+ce4=} + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true /@types/less/3.0.3: @@ -8164,37 +8183,6 @@ packages: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - /ts-node/10.7.0_sm5zkxj4s52nbddwl76qwfh6ya: - resolution: {integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.7.0 - '@tsconfig/node10': 1.0.8 - '@tsconfig/node12': 1.0.9 - '@tsconfig/node14': 1.0.1 - '@tsconfig/node16': 1.0.2 - '@types/node': 17.0.32 - acorn: 8.7.1 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 4.6.4 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - /tsconfck/2.0.0: resolution: {integrity: sha512-GVDTXF4MdNBBxKfUfjs/wkb7LHd8Ho8WLavESCddTFQFG9AUZOZEm6kODBGmQopQ9OG+EHpkT6dBaIIGhYVt+Q==} engines: {node: ^14.13.1 || ^16 || >=18, pnpm: ^7.0.1} diff --git a/scripts/patchCJS.ts b/scripts/patchCJS.ts index 76edf3fcfa6da5..e225d9455e98dc 100644 --- a/scripts/patchCJS.ts +++ b/scripts/patchCJS.ts @@ -17,7 +17,7 @@ module.exports.parseVueRequest = parseVueRequest; */ import { readFileSync, writeFileSync } from 'fs' -import { bold, red } from 'picocolors' +import colors from 'picocolors' const indexPath = 'dist/index.cjs' let code = readFileSync(indexPath, 'utf-8') @@ -40,7 +40,7 @@ if (matchMixed) { writeFileSync(indexPath, lines.join('\n')) - console.log(bold(`${indexPath} CJS patched`)) + console.log(colors.bold(`${indexPath} CJS patched`)) process.exit() } @@ -49,9 +49,9 @@ const matchDefault = code.match(/\nmodule.exports = (\w+);/) if (matchDefault) { code += `module.exports["default"] = ${matchDefault[1]};\n` writeFileSync(indexPath, code) - console.log(bold(`${indexPath} CJS patched`)) + console.log(colors.bold(`${indexPath} CJS patched`)) process.exit() } -console.error(red(`${indexPath} CJS patch failed`)) +console.error(colors.red(`${indexPath} CJS patch failed`)) process.exit(1)