Skip to content

Commit 9dc98ad

Browse files
committed
build(vite): code split on react and react-dom
1 parent 646c6bf commit 9dc98ad

File tree

3 files changed

+101
-43
lines changed

3 files changed

+101
-43
lines changed

scripts/splitVendorChunk.js

Lines changed: 99 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,56 @@
33
/** @import * as Rollup from 'rollup' */
44
/** @import * as Vite from 'vite' */
55

6-
export {}
6+
import { createRequire } from 'node:module'
7+
import { join } from 'node:path'
8+
9+
import { isCSSRequest } from 'vite'
710

811
/**
12+
* Modified from Vite deprecated splitVendorChunk plugin
13+
* @see {@link https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/splitVendorChunk.ts}
14+
* @license {@link https://github.com/vitejs/vite/blob/main/LICENSE | MIT}
15+
*
916
* @returns {Vite.Plugin}
1017
*/
1118
export default function splitVendorChunk() {
19+
const frameworkPaths = getTopLevelFrameworkPaths(['react', 'react-dom'])
20+
21+
/**
22+
* @type {Rollup.GetManualChunk}
23+
*/
24+
const manualChunks = (id, { getModuleInfo }) => {
25+
if (
26+
!isCSSRequest(id)
27+
&& isInNodeModules(id)
28+
&& isStaticImported(id)) {
29+
if (frameworkPaths.some((path) => id.includes(path))) {
30+
return 'framework'
31+
}
32+
return 'vendor'
33+
}
34+
35+
/**
36+
* @param {string} moduleId
37+
* @param {string[]} importStack
38+
* @returns {boolean}
39+
*/
40+
function isStaticImported(moduleId, importStack = []) {
41+
if (importStack.includes(moduleId)) {
42+
return false
43+
}
44+
const moduleInfo = getModuleInfo(moduleId)
45+
if (!moduleInfo) {
46+
return false
47+
}
48+
if (moduleInfo.isEntry) {
49+
return true
50+
}
51+
return moduleInfo.importers.some((importerId) =>
52+
isStaticImported(importerId, importStack.concat(moduleId)))
53+
}
54+
}
55+
1256
return {
1357
name: 'split-vendor-chunk',
1458
config() {
@@ -23,38 +67,6 @@ export default function splitVendorChunk() {
2367
}
2468
}
2569

26-
/**
27-
* @type {Rollup.GetManualChunk}
28-
*/
29-
const manualChunks = (id, { getModuleInfo }) => {
30-
if (
31-
isInNodeModules(id)
32-
&& !isCSSRequest(id)
33-
&& isStaticImported(id)) {
34-
return 'vendor'
35-
}
36-
37-
/**
38-
* @param {string} moduleId
39-
* @param {string[]} importStack
40-
* @returns {boolean}
41-
*/
42-
function isStaticImported(moduleId, importStack = []) {
43-
if (importStack.includes(moduleId)) {
44-
return false
45-
}
46-
const moduleInfo = getModuleInfo(moduleId)
47-
if (!moduleInfo) {
48-
return false
49-
}
50-
if (moduleInfo.isEntry) {
51-
return true
52-
}
53-
return moduleInfo.importers.some((importerId) =>
54-
isStaticImported(importerId, importStack.concat(moduleId)))
55-
}
56-
}
57-
5870
/**
5971
* @param {string} id
6072
* @returns {boolean}
@@ -64,11 +76,59 @@ function isInNodeModules(id) {
6476
}
6577

6678
/**
67-
* @param {string} request
68-
* @returns {boolean}
79+
* Modified from Sukka's webpack config
80+
* @see {@link https://blog.skk.moe/post/webpack-react-without-create-react-app/#splitChunks}
81+
* @license {@link https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh | CC BY-NC-SA 4.0}
82+
*
83+
* @param {string[]} packages
84+
* @param {string=} directory
85+
* @returns {string[]}
6986
*/
70-
function isCSSRequest(request) {
71-
return CSS_LANGS_RE.test(request)
72-
}
87+
function getTopLevelFrameworkPaths(packages, directory = import.meta.dirname) {
88+
const require = createRequire(import.meta.url)
89+
90+
/** @type {string[]} */
91+
const toplevelPaths = []
92+
93+
/** @type {Set<string>} */
94+
const visitedPackages = new Set()
95+
96+
/**
97+
* @param {string} packageName
98+
* @param {string} relativePath
99+
*/
100+
function addPath(packageName, relativePath) {
101+
try {
102+
if (visitedPackages.has(packageName)) {
103+
return
104+
}
105+
visitedPackages.add(packageName)
73106

74-
const CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/
107+
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
108+
paths: [relativePath],
109+
})
110+
111+
const packagePath = join(packageJsonPath, '../')
112+
if (toplevelPaths.includes(packagePath)) {
113+
return
114+
}
115+
toplevelPaths.push(packagePath)
116+
117+
const { dependencies } = require(packageJsonPath)
118+
if (dependencies) {
119+
Object.keys(dependencies).forEach((dependencyName) => {
120+
addPath(dependencyName, packagePath)
121+
})
122+
}
123+
}
124+
catch {
125+
// ignore
126+
}
127+
}
128+
129+
packages.forEach((packageName) => {
130+
addPath(packageName, directory)
131+
})
132+
133+
return toplevelPaths
134+
}

uno.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import transformerDirectives from '@unocss/transformer-directives'
44
import transformerVariantGroup from '@unocss/transformer-variant-group'
55
import { defineConfig } from 'unocss'
66

7+
// TODO: self-host fonts
8+
79
export default defineConfig({
810
blocklist: ['?', 'px', 'static'],
911
presets: [

vite.config.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ import split from './scripts/splitVendorChunk'
1212

1313
export const baseConfig = defineConfig({
1414
base: './',
15-
build: {
16-
// TODO: use `rollupOptions.output.manualChunks`
17-
chunkSizeWarningLimit: 1000,
18-
},
1915
define: {},
2016
plugins: [
2117
unocss(),

0 commit comments

Comments
 (0)