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: avoid splitting vendor chunk by default #6534

Merged
merged 10 commits into from Mar 4, 2022
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 `splitVendorChunkPlugin` in your config file:

```js
// vite.config.js
import { splitVendorChunkPlugin } from 'vite'
module.exports = defineConfig({
plugins: [splitVendorChunkPlugin()]
})
```

This strategy is also provided as a `splitVendorChunk({ cache: SplitVendorChunkCache })` 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
15 changes: 13 additions & 2 deletions packages/playground/vue/vite.config.ts
@@ -1,4 +1,4 @@
import { defineConfig } from 'vite'
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import vuePlugin from '@vitejs/plugin-vue'
import { vueI18nPlugin } from './CustomBlockPlugin'

Expand All @@ -12,11 +12,22 @@ export default defineConfig({
vuePlugin({
reactivityTransform: true
}),
splitVendorChunkPlugin(),
vueI18nPlugin
],
build: {
// to make tests faster
minify: false
minify: false,
rollupOptions: {
output: {
// Test splitVendorChunkPlugin composition
manualChunks(id) {
if (id.includes('src-import')) {
return 'src-import'
}
}
}
}
},
css: {
modules: {
Expand Down
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 @@ -483,13 +480,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 @@ -616,55 +606,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
5 changes: 5 additions & 0 deletions packages/vite/src/node/index.ts
Expand Up @@ -7,6 +7,10 @@ export { send } from './server/send'
export { createLogger, printHttpServerUrls } from './logger'
export { transformWithEsbuild } from './plugins/esbuild'
export { resolvePackageEntry } from './plugins/resolve'
export {
splitVendorChunkPlugin,
splitVendorChunk
} from './plugins/splitVendorChunk'
export { resolvePackageData } from './packages'
export { normalizePath } from './utils'

Expand Down Expand Up @@ -96,6 +100,7 @@ export type { Terser } from 'types/terser'
export type { RollupCommonJSOptions } from 'types/commonjs'
export type { RollupDynamicImportVarsOptions } from 'types/dynamicImportVars'
export type { Matcher, AnymatchPattern, AnymatchFn } from 'types/anymatch'
export type { SplitVendorChunkCache } from './plugins/splitVendorChunk'

import type { ChunkMetadata } from './plugins/metadata'

Expand Down
131 changes: 131 additions & 0 deletions packages/vite/src/node/plugins/splitVendorChunk.ts
@@ -0,0 +1,131 @@
import type { UserConfig } from '../../node'
import type { Plugin } from '../plugin'
import type {
OutputOptions,
GetManualChunk,
GetManualChunkApi,
GetModuleInfo
} from 'rollup'
import { isCSSRequest } from './css'

// 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'

export class SplitVendorChunkCache {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class seems unnecessary. The Map object has a clear method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we will need to expand the cache later. I wanted to expose the cache for the method, not a particular Map that is an implementation detail and may change

cache: Map<string, boolean>
constructor() {
this.cache = new Map<string, boolean>()
}
reset() {
this.cache = new Map<string, boolean>()
}
}

export function splitVendorChunk(
options: { cache?: SplitVendorChunkCache } = {}
): GetManualChunk {
const cache = options.cache ?? new SplitVendorChunkCache()
return (id, { getModuleInfo }) => {
if (
id.includes('node_modules') &&
!isCSSRequest(id) &&
staticImportedByEntry(id, getModuleInfo, cache.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 splitVendorChunkPlugin(): Plugin {
const caches: SplitVendorChunkCache[] = []
function createSplitVendorChunk(output: OutputOptions, config: UserConfig) {
const cache = new SplitVendorChunkCache()
caches.push(cache)
const build = config.build ?? {}
const format = output?.format
if (!build.ssr && !build.lib && format !== 'umd' && format !== 'iife') {
return splitVendorChunk({ cache })
}
}
return {
name: 'vite:split-vendor-chunk',
config(config) {
let outputs = config?.build?.rollupOptions?.output
if (outputs) {
outputs = Array.isArray(outputs) ? outputs : [outputs]
for (const output of outputs) {
const viteManualChunks = createSplitVendorChunk(output, config)
if (viteManualChunks) {
if (output.manualChunks) {
if (typeof output.manualChunks === 'function') {
const userManualChunks = output.manualChunks
output.manualChunks = (id: string, api: GetManualChunkApi) => {
return userManualChunks(id, api) ?? viteManualChunks(id, api)
}
}
// else, leave the object form of manualChunks untouched, as
// we can't safely replicate rollup handling.
} else {
output.manualChunks = viteManualChunks
}
}
}
} else {
return {
build: {
rollupOptions: {
output: {
manualChunks: createSplitVendorChunk({}, config)
}
}
}
}
}
},
buildStart() {
caches.forEach((cache) => cache.reset())
}
}
}