diff --git a/README.md b/README.md index be96a1bcf73ada..6ae5903b82e544 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,15 @@ Check out the [Migration Guide](https://vitejs.dev/guide/migration.html) if you ## Packages -| Package | Version (click for changelogs) | -| ------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | -| [vite](packages/vite) | [![vite version](https://img.shields.io/npm/v/vite.svg?label=%20)](packages/vite/CHANGELOG.md) | -| [@vitejs/plugin-vue](packages/plugin-vue) | [![plugin-vue version](https://img.shields.io/npm/v/@vitejs/plugin-vue.svg?label=%20)](packages/plugin-vue/CHANGELOG.md) | -| [@vitejs/plugin-vue-jsx](packages/plugin-vue-jsx) | [![plugin-vue-jsx version](https://img.shields.io/npm/v/@vitejs/plugin-vue-jsx.svg?label=%20)](packages/plugin-vue-jsx/CHANGELOG.md) | -| [@vitejs/plugin-react](packages/plugin-react) | [![plugin-react version](https://img.shields.io/npm/v/@vitejs/plugin-react.svg?label=%20)](packages/plugin-react/CHANGELOG.md) | -| [@vitejs/plugin-legacy](packages/plugin-legacy) | [![plugin-legacy version](https://img.shields.io/npm/v/@vitejs/plugin-legacy.svg?label=%20)](packages/plugin-legacy/CHANGELOG.md) | -| [create-vite](packages/create-vite) | [![create-vite version](https://img.shields.io/npm/v/create-vite.svg?label=%20)](packages/create-vite/CHANGELOG.md) | +| Package | Version (click for changelogs) | +| ----------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | +| [vite](packages/vite) | [![vite version](https://img.shields.io/npm/v/vite.svg?label=%20)](packages/vite/CHANGELOG.md) | +| [@vitejs/plugin-vue](packages/plugin-vue) | [![plugin-vue version](https://img.shields.io/npm/v/@vitejs/plugin-vue.svg?label=%20)](packages/plugin-vue/CHANGELOG.md) | +| [@vitejs/plugin-vue-jsx](packages/plugin-vue-jsx) | [![plugin-vue-jsx version](https://img.shields.io/npm/v/@vitejs/plugin-vue-jsx.svg?label=%20)](packages/plugin-vue-jsx/CHANGELOG.md) | +| [@vitejs/plugin-react](packages/plugin-react) | [![plugin-react version](https://img.shields.io/npm/v/@vitejs/plugin-react.svg?label=%20)](packages/plugin-react/CHANGELOG.md) | +| [@vitejs/plugin-legacy](packages/plugin-legacy) | [![plugin-legacy version](https://img.shields.io/npm/v/@vitejs/plugin-legacy.svg?label=%20)](packages/plugin-legacy/CHANGELOG.md) | +| [@vitejs/plugin-split-vendor](packages/plugin-split-vendor) | [![plugin-split-vendor version](https://img.shields.io/npm/v/@vitejs/plugin-split-vendor.svg?label=%20)](packages/plugin-vue-jsx/CHANGELOG.md) | +| [create-vite](packages/create-vite) | [![create-vite version](https://img.shields.io/npm/v/create-vite.svg?label=%20)](packages/create-vite/CHANGELOG.md) | ## Contribution diff --git a/docs/guide/build.md b/docs/guide/build.md index aac86a237b6819..e081c44c94f314 100644 --- a/docs/guide/build.md +++ b/docs/guide/build.md @@ -43,6 +43,20 @@ module.exports = defineConfig({ For example, you can specify multiple Rollup outputs with plugins that are only applied during build. +## Chunking Strategy + +You can configure how chunks are split using `build.rollupOptions.manualChunks` (see [Rollup docs](https://rollupjs.org/guide/en/#outputmanualchunks)). Until Vite 2.7, the default chunking strategy divided the chunks into `index` and `vendor`. It is a good strategy for some SPAs, but it is hard to provide a general solution for every Vite target use case. From Vite 2.8, `manualChunks` is no longer modified by default. You can continue to use the Split Vendor Chunk strategy by adding the `splitVendorPlugin` from the `@vitejs/plugin-split-vendor` package in your config file: + +```js +// vite.config.js +import { splitVendorPlugin } from '@vitejs/plugin-split-vendor' +module.exports = defineConfig({ + plugins: [splitVendorPlugin()] +}) +``` + +This strategy is also provided as a `splitVendor({ cache: SplitVendorCache })` factory, in case composition with custom logic is needed. `cache.reset()` needs to be called at `buildStart` for build watch mode to work correctly in this case. + ## Rebuild on files changes You can enable rollup watcher with `vite build --watch`. Or, you can directly adjust the underlying [`WatcherOptions`](https://rollupjs.org/guide/en/#watch-options) via `build.watch`: diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 6d81a060cf8883..96be62cdde498d 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -22,6 +22,10 @@ Vite aims to provide out-of-the-box support for common web development patterns. - Provides legacy browsers support for the production build. +### [@vitejs/plugin-split-vendor](https://github.com/vitejs/vite/tree/main/packages/plugin-split-vendor) + +- Provides `manualChunks` strategy from Vite <=2.7 + ## Community Plugins Check out [awesome-vite](https://github.com/vitejs/awesome-vite#plugins) - you can also submit a PR to list your plugins there. diff --git a/packages/playground/vue/package.json b/packages/playground/vue/package.json index c88fb7ee6343f4..38a1776aadc59b 100644 --- a/packages/playground/vue/package.json +++ b/packages/playground/vue/package.json @@ -14,6 +14,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "workspace:*", + "@vitejs/plugin-split-vendor": "workspace:*", "js-yaml": "^4.1.0", "less": "^4.1.2", "pug": "^3.0.2", diff --git a/packages/playground/vue/vite.config.ts b/packages/playground/vue/vite.config.ts index 82efdac5e9f876..16222d1f31f794 100644 --- a/packages/playground/vue/vite.config.ts +++ b/packages/playground/vue/vite.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' import vuePlugin from '@vitejs/plugin-vue' +import { splitVendorPlugin } from '@vitejs/plugin-split-vendor' import { vueI18nPlugin } from './CustomBlockPlugin' export default defineConfig({ @@ -12,6 +13,7 @@ export default defineConfig({ vuePlugin({ reactivityTransform: true }), + splitVendorPlugin(), vueI18nPlugin ], build: { diff --git a/packages/plugin-split-vendor/CHANGELOG.md b/packages/plugin-split-vendor/CHANGELOG.md new file mode 100644 index 00000000000000..b28b04f643122b --- /dev/null +++ b/packages/plugin-split-vendor/CHANGELOG.md @@ -0,0 +1,3 @@ + + + diff --git a/packages/plugin-split-vendor/LICENSE b/packages/plugin-split-vendor/LICENSE new file mode 100644 index 00000000000000..9c1b313d7b1816 --- /dev/null +++ b/packages/plugin-split-vendor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/plugin-split-vendor/README.md b/packages/plugin-split-vendor/README.md new file mode 100644 index 00000000000000..76b48478ae49f4 --- /dev/null +++ b/packages/plugin-split-vendor/README.md @@ -0,0 +1,13 @@ +# @vitejs/plugin-split-vendor [![npm](https://img.shields.io/npm/v/@vitejs/plugin-split-vendor.svg)](https://npmjs.com/package/@vitejs/plugin-split-vendor) + +You can configure how chunks are split using `build.rollupOptions.manualChunks` (see [Rollup docs](https://rollupjs.org/guide/en/#outputmanualchunks)). Until Vite 2.7, the default chunking strategy divided the chunks into `index` and `vendor`. It is a good strategy for some SPAs, but it is hard to provide a general solution for every Vite target use case. From Vite 2.8, `manualChunks` is no longer modified by default. You can continue to use the Split Vendor Chunk strategy by adding the `splitVendorPlugin` from the `@vitejs/plugin-split-vendor` package in your config file: + +```js +// vite.config.js +import { splitVendorPlugin } from '@vitejs/plugin-split-vendor' +module.exports = defineConfig({ + plugins: [splitVendorPlugin()] +}) +``` + +This strategy is also provided as a `splitVendor({ cache: SplitVendorCache })` factory, in case composition with custom logic is needed. `cache.reset()` needs to be called at `buildStart` for build watch mode to work correctly in this case. diff --git a/packages/plugin-split-vendor/index.d.ts b/packages/plugin-split-vendor/index.d.ts new file mode 100644 index 00000000000000..2583df3c7f7600 --- /dev/null +++ b/packages/plugin-split-vendor/index.d.ts @@ -0,0 +1,11 @@ +import type { Plugin } from 'vite' + +import type { GetManualChunk } from 'rollup' + +export class SplitVendorChunkCache {} + +export function splitVendorChunk(options?: { + cache?: SplitVendorChunkCache +}): GetManualChunk + +export function splitVendorPlugin(): Plugin diff --git a/packages/plugin-split-vendor/index.js b/packages/plugin-split-vendor/index.js new file mode 100644 index 00000000000000..b8b4985134c49d --- /dev/null +++ b/packages/plugin-split-vendor/index.js @@ -0,0 +1,136 @@ +// @ts-check +const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)` +const cssLangRE = new RegExp(cssLangs) +/** + * Internal CSS Check from vite/src/node/plugins/css.ts + * @param {string} request + */ +const isCSSRequest = (request) => cssLangRE.test(request) + +// Use splitVendorChunkPlugin() to get the same manualChunks strategy as Vite 2.7 +// We don't recommend using this strategy as a general solution moving forward + +// splitVendorChunk is a simple index/vendor strategy that was used in Vite +// until v2.8. It is exposed to let people continue to use it in case it was +// working well for their setups. +// The cache needs to be reset on buildStart for watch mode to work correctly +// Don't use this manualChunks strategy for ssr, lib mode, and 'umd' or 'iife' + +class SplitVendorCache { + cache + constructor() { + this.cache = new Map() + } + reset() { + this.cache = new Map() + } +} + +/** + * manualChunk strategy splitting in index and vendor chunks + * @param {{ cache?: SplitVendorCache }} options + * @returns {import('rollup').GetManualChunk} + */ +function splitVendor(options = {}) { + const cache = options.cache || new SplitVendorCache() + return (id, { getModuleInfo }) => { + if ( + id.includes('node_modules') && + !isCSSRequest(id) && + staticImportedByEntry(id, getModuleInfo, cache.cache) + ) { + return 'vendor' + } + } +} + +/** + * Find if a module has been statically imported by an entry + * @param {string} id + * @param {import('rollup').GetModuleInfo} getModuleInfo + * @param {Map} cache + * @param {string[]} importStack + * @returns {boolean} + */ +function staticImportedByEntry(id, getModuleInfo, cache, importStack = []) { + if (cache.has(id)) { + return cache.get(id) + } + if (importStack.includes(id)) { + // circular deps! + cache.set(id, false) + return false + } + const mod = getModuleInfo(id) + if (!mod) { + cache.set(id, false) + return false + } + + if (mod.isEntry) { + cache.set(id, true) + return true + } + const someImporterIs = mod.importers.some((importer) => + staticImportedByEntry( + importer, + getModuleInfo, + cache, + importStack.concat(id) + ) + ) + cache.set(id, someImporterIs) + return someImporterIs +} + +/** + * Implements Vite 2.7 chunking strategy + * @returns {import('vite').Plugin} + */ +function splitVendorPlugin() { + /** + * @type {SplitVendorCache[]} + */ + const caches = [] + /** + * @param {import('rollup').OutputOptions} output + * @param {import('vite').UserConfig} config + */ + function createSplitVendor(output, config) { + const cache = new SplitVendorCache() + caches.push(cache) + const build = config.build || {} + const format = output.format + if (!build.ssr && !build.lib && format !== 'umd' && format !== 'iife') { + return splitVendor({ cache }) + } + } + return { + name: 'vite:split-vendor-chunk', + config(config) { + let outputs = + config.build && + config.build.rollupOptions && + config.build.rollupOptions.output + if (outputs) { + outputs = Array.isArray(outputs) ? outputs : [outputs] + for (const output of outputs) { + output.manualChunks = createSplitVendor(output, config) + } + } else { + return { + build: { + rollupOptions: { + manualChunks: createSplitVendor({}, config) + } + } + } + } + }, + buildStart() { + caches.forEach((cache) => cache.reset()) + } + } +} + +module.exports = { splitVendorPlugin, splitVendor, SplitVendorCache } diff --git a/packages/plugin-split-vendor/package.json b/packages/plugin-split-vendor/package.json new file mode 100644 index 00000000000000..09962201e349fe --- /dev/null +++ b/packages/plugin-split-vendor/package.json @@ -0,0 +1,31 @@ +{ + "name": "@vitejs/plugin-split-vendor", + "version": "0.1.0", + "license": "MIT", + "author": "Evan You", + "files": [ + "index.js", + "index.d.ts" + ], + "main": "index.js", + "types": "index.d.ts", + "scripts": { + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . --lerna-package plugin-split-vendor", + "release": "ts-node ../../scripts/release.ts --skipBuild" + }, + "engines": { + "node": ">=12.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vitejs/vite.git", + "directory": "packages/plugin-split-vendor" + }, + "bugs": { + "url": "https://github.com/vitejs/vite/issues" + }, + "homepage": "https://github.com/vitejs/vite/tree/main/packages/plugin-split-vendor#readme", + "dependencies": { + "rollup": "^2.59.0" + } +} diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 84ffb93b432f15..bb724f80415113 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -12,8 +12,6 @@ import type { OutputOptions, RollupOutput, ExternalOption, - GetManualChunk, - GetModuleInfo, WatcherOptions, RollupWatcher, RollupError, @@ -37,7 +35,6 @@ import { dataURIPlugin } from './plugins/dataUri' import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild' import { resolveSSRExternal, shouldExternalizeForSSR } from './ssr/ssrExternal' import { ssrManifestPlugin } from './ssr/ssrManifestPlugin' -import { isCSSRequest } from './plugins/css' import type { DepOptimizationMetadata } from './optimizer' import { scanImports } from './optimizer/scan' import { assetImportMetaUrlPlugin } from './plugins/assetImportMetaUrl' @@ -509,13 +506,6 @@ async function doBuild( // #1048 add `Symbol.toStringTag` for module default export namespaceToStringTag: true, inlineDynamicImports: ssr && typeof input === 'string', - manualChunks: - !ssr && - !libOptions && - output?.format !== 'umd' && - output?.format !== 'iife' - ? createMoveToVendorChunkFn(config) - : undefined, ...output } } @@ -642,55 +632,6 @@ function getPkgName(root: string) { return name?.startsWith('@') ? name.split('/')[1] : name } -function createMoveToVendorChunkFn(config: ResolvedConfig): GetManualChunk { - const cache = new Map() - return (id, { getModuleInfo }) => { - if ( - id.includes('node_modules') && - !isCSSRequest(id) && - staticImportedByEntry(id, getModuleInfo, cache) - ) { - return 'vendor' - } - } -} - -function staticImportedByEntry( - id: string, - getModuleInfo: GetModuleInfo, - cache: Map, - importStack: string[] = [] -): boolean { - if (cache.has(id)) { - return cache.get(id) as boolean - } - if (importStack.includes(id)) { - // circular deps! - cache.set(id, false) - return false - } - const mod = getModuleInfo(id) - if (!mod) { - cache.set(id, false) - return false - } - - if (mod.isEntry) { - cache.set(id, true) - return true - } - const someImporterIs = mod.importers.some((importer) => - staticImportedByEntry( - importer, - getModuleInfo, - cache, - importStack.concat(id) - ) - ) - cache.set(id, someImporterIs) - return someImporterIs -} - export function resolveLibFilename( libOptions: LibraryOptions, format: ModuleFormat, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d29d20f832517e..391c7a0afd678c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -615,6 +615,7 @@ importers: packages/playground/vue: specifiers: + '@vitejs/plugin-split-vendor': workspace:* '@vitejs/plugin-vue': workspace:* css-with-exports-field: ^1.0.0 js-yaml: ^4.1.0 @@ -628,6 +629,7 @@ importers: lodash-es: 4.17.21 vue: 3.2.26 devDependencies: + '@vitejs/plugin-split-vendor': link:../../plugin-split-vendor '@vitejs/plugin-vue': link:../../plugin-vue css-with-exports-field: 1.0.0 js-yaml: 4.1.0 @@ -690,6 +692,12 @@ importers: react-refresh: 0.11.0 resolve: 1.20.0 + packages/plugin-split-vendor: + specifiers: + rollup: ^2.59.0 + dependencies: + rollup: 2.62.0 + packages/plugin-vue: specifiers: '@rollup/pluginutils': ^4.1.2