Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove default manualChunks strategy in core, adds split vendor plugin package #6681

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Expand Up @@ -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

Expand Down
14 changes: 14 additions & 0 deletions docs/guide/build.md
Expand Up @@ -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`:
Expand Down
4 changes: 4 additions & 0 deletions docs/plugins/index.md
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions packages/playground/vue/package.json
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions 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({
Expand All @@ -12,6 +13,7 @@ export default defineConfig({
vuePlugin({
reactivityTransform: true
}),
splitVendorPlugin(),
vueI18nPlugin
],
build: {
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin-split-vendor/CHANGELOG.md
@@ -0,0 +1,3 @@



21 changes: 21 additions & 0 deletions 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.
13 changes: 13 additions & 0 deletions 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.
11 changes: 11 additions & 0 deletions 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
136 changes: 136 additions & 0 deletions 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<string, boolean>} 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 }
31 changes: 31 additions & 0 deletions 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"
}
}
59 changes: 0 additions & 59 deletions packages/vite/src/node/build.ts
Expand Up @@ -12,8 +12,6 @@ import type {
OutputOptions,
RollupOutput,
ExternalOption,
GetManualChunk,
GetModuleInfo,
WatcherOptions,
RollupWatcher,
RollupError,
Expand All @@ -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'
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -642,55 +632,6 @@ function getPkgName(root: string) {
return name?.startsWith('@') ? name.split('/')[1] : name
}

function createMoveToVendorChunkFn(config: ResolvedConfig): GetManualChunk {
const cache = new Map<string, boolean>()
return (id, { getModuleInfo }) => {
if (
id.includes('node_modules') &&
!isCSSRequest(id) &&
staticImportedByEntry(id, getModuleInfo, cache)
) {
return 'vendor'
}
}
}

function staticImportedByEntry(
id: string,
getModuleInfo: GetModuleInfo,
cache: Map<string, boolean>,
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,
Expand Down