随着咱们项目愈来愈复杂,咱们在用webpack打包的时候,会发现打包的速度愈来愈慢,最后慢到打包一次要几分钟甚至更多的时间,缓慢的打包速度严重影响效率,那么如何提升打包速度就成为了咱们的痛点,通常你们都是用HappyPack、Dellplugin和UglifyJsPlugin(以前是ParallelUglifyPlugin,如今不维护合并到UglifyJsPlugin了)的parallel设为true来提升打包速度,但这样依旧没法解决咱们项目庞大而致使打包速度的缓慢问题,实质上打包速度慢的根本缘由是由于,每次打包都是要把全部的文件都打包一遍,因此若是咱们想提升打包速度,那么咱们能够只打包修改的或者新加的文件,本文基于此提供一个方案。css
咱们使用webpack来打包的时候,会有一个或者多个入口文件,打包到对应的html中,而咱们知道打包最耗时的就是对js进行压缩和混淆的UglifyJsPlugin插件,若是咱们的项目庞大,入口文件过多,那么打包js的速度将严重缓慢,so咱们能够经过一些手段来告诉webpack咱们只想打包指定的入口文件,生成对应的html,经过“大事化小”的方式提升打包速度。html
通常咱们在写webpack的入口文件的时候,咱们不会一个一个手动写上去,像这样node
entry: {
index: './src/views/index/main.js',
bar: './src/views/bar/main.js',
....
},
复制代码
这种方式在项目庞大的时候代码管理起来很麻烦,且要手动维护,因此咱们会按照某种规则管理咱们的文件,而后写一个方法来获取入口文件,好比:webpack
// utils
// 获取入口文件
exports.getEntry = function () {
let globPath = './src/views/**/index.js'
return glob.sync(globPath)
.reduce(function (entry, path) {
let key = exports.getKey(path)
entry[key] = path
return entry
}, {})
}
// 获取单个入口文件对应的key
exports.getKey = (path) => {
let startIndex = path.indexOf('views') + 6
let endIndex = 0
if(path.indexOf('components') > -1){
// 若是修改的是组件,注意这里各个页面的组件是放在各自的目录下的
endIndex = path.indexOf('components') + 1
} else {
endIndex = path.lastIndexOf('/')
}
return path.substring(startIndex, endIndex)
}
// 获取全部入口文件对应的keys
exports.getKeys = (filesPath) => {
let result = []
for(let path of filesPath) {
let key = export.getKey(path)
if(result.indexOf(key) === -1) {
result.push(key)
}
}
return result
}
// 根据入口文件生成HtmlWebpackPlugins
exports.getHtmlWebpackPlugins = () => {
let entyies = exports.getEntry()
let keys = Object.keys(entry)
let plugins = keys
.map((key) => {
// ejs模板,要和index.js在同个目录下
let template = exports.getTemplate(entyies[key])
let filename = `${__dirname}/dist/${key}.html`
return new HtmlWebpackPlugin({
filename,
template: template,
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
// chunks: globals.concat([key]),
chunksSortMode: 'dependency',
excludeChunks: keys.filter(e => e != key)
})
})
return plugins
}
// 获取入口文件对应的模板,模板文件是index.html,本目录没有,会往上级目录找
exports.getTemplate = (path) => {
path = path.subStr(0, path.lastIndexOf('/'))
var path = glob.sync(path + '/index.html')
if(path.length > 0) {
return path[0]
} else {
//取上级目录下的模板文件路径
if(path.lastIndexOf('/') !== -1) {
path = path.substr(0, path.lastIndexOf('/'))
return exports.getTemplate(path)
}
}
}
复制代码
这里,咱们的全部入口文件都以index.js为命名,且key为views下到对应index.js的文件路径,例如./src/views/test/index.js的key就是test。根据这个规则,它会自动获取src/view下的全部入口文件index.js,并生成入口文件对应的html。git
那么若是咱们修改与入口文件同个目录的全部代码,咱们但愿打包的时候就打包这个入口文件,未修改的其余入口文件通通不打包,这样就能够作到精确打包了,github
修改了哪些代码,用户本身最清楚,咱们能够经过运行打包程序时告诉程序咱们修改了哪些模块,咱们可使用inquirer来让用户手动输入,也能够经过命令行的方式输入,关于命令行的输入,如今npm命令能够接受参数的输入,在node咱们只须要经过process.argv来获取用户输入的参数。web
// npm命令经过--接受参数的输入
npm run build -- module
// node经过process.argv来获取
let module = process.argv[2]
复制代码
这种方式的缺点就是须要用户输入,没有作到自动化。正则表达式
咱们知道git能够知道用户修改过哪些文件和新建了哪些文件,那么利用这点咱们就能够知道哪些文件修改过,哪些文件是新增的,咱们针对修改过和新增的文件进行打包,未改动的忽略,如此咱们即可以作到针对性的打包,而避免了全量打包的漫长过程。
咱们知道,当咱们使用git status命令的时候,git会给咱们这样的提示:shell
modified: xxx/xx/xx.js
modified: yyy/yy/yy.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
xxx/xx/index.js
yy/yy/index.js
复制代码
so,咱们能够经过写一些正则表达式,把这些修改的和新增的文件给匹配出来,而后针对性的进行一些处理从而得出是哪一个入口文件的内容须要从新打包。下面基于方法二,讲下如何作。npm
1.安装shelljs
npm install shelljs --save-dev
2.获取git status打印出来的字符串
let shell = require('shelljs')
const result = shell.exec('git status')
复制代码
// build.js
let modifiedFiles = []
// 匹配modified: 后面的修改的文件路径
match = result.match(/modified:\s+(.+)/g)
for(let i = 0, len = match.length; i < len; i++) {
// 匹配views下修改的文件
if(/src\/(views|components)/.test(match[i])) {
let path = match[i].match(/\s+(.+)/)[1]
modifiedFiles.push(path)
}
}
复制代码
这里我以src/views目录下,入口文件以index.js为例
// build.js
// 获取新加的文件
let addFiles = []
// 获取新建的文件列表字符串
let r = /(?<=\(use "git add <file>\.\.\." to include in what will be committed\))((\n|\t|.)+)/.test(result)
// 获取新加文件路径
if(r) {
let addFilesListStr = RegExp.$1
// 匹配src/views下的文件
match = addFilesListStr.match(/\n*\t+(src\/views\/.+)\n+/g)
for(let i = 0, len = match.length; i < len; i++) {
// 去掉回车换行
let path = match[i].replace(/(\t|\n)/g, '')
// 这里根据你的项目来定义,我这边的项目入口是index.js,
// 因此这样设置,若是新增的文件没有index.js入口文件则下面的glob就匹配不出来
let paths = glob.sync(`${path}/**/index.js`)
for(let path of paths) {
addFiles.push(path)
}
}
}
复制代码
有了第二步获取的修改文件的路径,通过一些处理,咱们就能够知道哪些入口key修改了,而后打包的时候就只打包这些修改的key对应的入口文件
// utils.js
exports.getModifiedEntry = (modifiedFiles) => {
let modifiedKeys = exports.getKeys(modifiedFiles)
let modifiedEntry = {}
// 全量entry
let webpackEntry = exports.getEntry()
for(let key of modifiedKeys) {
modifiedEntry[key] = webpackEntry[key]
}
return modifiedEntry
}
复制代码
获取新建文件的entry,经过git咱们能够获取新加的文件列表,而后根据文件列表咱们获取新加的entry,因此咱们扩展getEntry方法,但传入参数为文件列表的时候,咱们重新加的文件列表中获取新建的entry
/**
*
*files参数为第三步获取的新加文件列表
*/
// utils.js
exports.getEntry = function (files) {
// 重新加的文件列表中获取新建的entry
if (files) {
let entry = {}
for (let path of files) {
let key = exports.getKey(path)
entry[key] = './' + path
}
return entry
}
let globPath = './src/views/**/index.js'
return glob.sync(globPath)
.reduce(function (entry, path) {
let key = exports.getKey(path)
entry[key] = path
return entry
}, entry)
}
复制代码
最后咱们要根据修改文件列表和新加的文件列表生成HtmlWebpackPlugins以打包对应的html
// 根据入口配置获取对应的htmlWebpackPlugin
// utils.js
exports.getHtmlWebpackPlugins = (entry) => {
let keys = Object.keys(entry)
let plugins = keys
.map((key) => {
let template = exports.getTemplate(entry[key])
let filename = `${__dirname}/dist/${key}.html`
return new HtmlWebpackPlugin({
filename,
template: template,
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
})
})
return plugins
}
// build.js
var utils = require('utils')
let newEntry = {}
Object.assign(newEntry, addEntry, modifiedEntry)
htmlWebpackPlugins = utils.getHtmlWebpackPlugins(newEntry)
复制代码
若是咱们修改了一些全局的代码,好比各个组件依赖的js,css等等,这个时候须要进行全量打包了,那么候咱们能够经过参数告诉程序咱们要全量打包,参照方法一,经过npm run build -- all
// build.js
var utils = require('utils')
let isBuildAll = process.argv[2] === 'all'
if(isBuildAll) {
// 全量打包
let entry = utils.getEntry()
let plugins = utils.getHtmlWebpackPlugins(entry)
webpackConfig.plugins = webpackConfig.plugins
.concat(plugins)
}
复制代码
以上是我在开发中遇到打包十分缓慢的一种解决方案,简略的代码请查看个人git: github.com/VikiLee/acc…