webpack的打包顺序:css
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
}
};
复制代码
1,找到入口文件html
2,根据入口文件,找出具备依赖关系的文件js/cssnode
3,最后,把css/js所有打包成一个js包react
好的,打包完成,打包了整个世界,那么问题来了:jquery
产品说:按钮颜色不对,给我改为#cccwebpack
技术:好的,这就改。ios
而后就有了以下流程:web
1,找到了entry -> js -> componet -> button.less修改了一个色值json
2,执行webpack打包redux
1,明明只是修改了一个色值,却要从入口开始从新打包
2,业务代码明明没有变化,却也被牵连了
3,最后生成的js要所有推到线上,覆盖掉线上本来没问题的业务js,纯粹是增长风险
首先想到的是,既然只修改一个文件,那能不能从新打包一个文件呢?
这种方案,很快就被自我否认了。
由于:
1,从入口打包的文件,已经经过依赖关系,把老版本button.less打入了最终输出的js文件
2,单独打包button.less输出了一个独立的button.js,这个文件须要手动引入到html中,一旦这类文件制造的多了,根本没法维护
通过反复思考,单独打包每个文件的想法,不符合webpack的设计初衷,从入口打包的流程是不可以产生变化的
在这个问题上卡了真的好久.....好久
因为我面临的场景是多页应用,因此存在多个入口,那么既然如此,那么可否经过依赖关系,找到须要更新的入口呢?
这种方案,也思索了好久,后来也被否认了。
由于:
1,webpack没有适合输出模块依赖关系的插件,遍寻无果啊
2,经过webpack的stats分析指标,可以输出依赖关系,但数据量太大,若是不加过滤,目前项目输出12W行json信息,还须要花力气处理一遍这个信息才能拿到关系
3,若是一个组件被多个入口引用,那么须要找到每个引用的入口点,再从新打包每一个被波及的入口
上面尤为是第三点,彻底不符合咱们想增量发布的目的,若是改了一个button组件,要从新打包二三十个入口,这彻底没有增量发布的意义
在这个问题上又纠结了好久......好久
通过前面两个问题后,我发现思考的方向彻底是错误的,老是妄想改变webpack的打包方式,简直就是跟它的理念对着干。
既然不能改变webpack的打包方式,那么我可否改变webpack的输出结果呢?
其实webpack关于缓存方面的功能,提供了不少功能强大的插件,例如:
CommonsChunkPlugin能够用来在打包的时候提取公共js代码
ExtractTextPlugin能够用来从js中提出css,将其输出到一个独立的文件
利用这两个插件,咱们可以将咱们打包的精度加以划分,将公共引用的部分打包为一个单独的文件
若是公共引用的部分变为了一个单独的文件,再添加上hash进行缓存,当再次修改的时候只要更新hash,这样咱们不就可以肯定,究竟改动了哪一个文件了吗
既然如此,咱们一步一步进行探索:
如今咱们建立测试入口文件:
src/one.js:
import jquery from 'jquery';
console.log('one');
复制代码
src/two.js:
import jquery from 'jquery';
console.log('two');
复制代码
webpack.config.js
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
}
};
复制代码
执行webpack
输出了2个文件,大小都是271kb,这是由于one.js和two.js都引用了jquery,jquery打包了2次,分别打包到了两个文件中
这样显然不是很友好,像jquery这种文件,显然平时不会改动,仍是缓存起来比较好,修改webpack.config.js
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
复制代码
如今咱们添加了CommonsChunkPlugin插件,它的做用是提取公共js,再次执行webpack
能够看到one.js和two.js的大小已经不到1k了,而common则274k,能够看到jquery已经被打包到了common.js当中
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[hash:6].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
复制代码
上面修改了output的输出内容[name].[hash].js
如今执行webpack:
能够看到打包的三个文件都有了hash,但须要主意,此时每一个文件的hash都是同样的
再次执行一遍webpack:
能够看到,两次构建输出的结果一致,这很好,由于没有修改文件,天然不但愿hash发生改变
那么接下来,修改一下文件:one.js
import jquery from 'jquery';
console.log('修改one');
复制代码
悲剧了,全部文件所有修改了hash,查看输出的结果:
能够发现只修改一个文件,却修改了所有文件的hash,这个问题很严重,显然不是咱们想要的
webpack中关于缓存,提供了好几种添加hash的方法,其中就有chunkhash
chunkhash简单来讲,就是根据模块内容来添加hash,既然这样的话,只要文件没有改变,就不会生成新的hash
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
复制代码
如上图,修改filename:[name].[chunkhash:8]/js
执行webpack
能够看到这一次生成的hash是4897....
可是输出的每一个文件的hash却不是4897....
很好,接下来再执行一次webpack:
能够看到两次输出之间hash并无发生变化
如今,修改one.js,再执行webapck
import jquery from 'jquery';
console.log('使用chunkhash后修改one');
复制代码
能够看到two.js的hash没有改变one.js的hash改变了,但common.js的hash居然也改了...
前面用CommonsChunkPlugin提取代码后,公共的代码已经被抽离,可是他们之间确定存在一个映射关系,例如
之因此commonjs的hash会变,是由于修改one.js生成了新的hash,而jquery又与one.js存在映射关系,映射关系会更新
,也就是说common.js它要重新的one.js中提取了jquery
而manifest
就能够简单理解为模块映射关系的集合,而这个manifest将随着这些被分离出来的代码共同打包!!!
因此如今分离manifest
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' // 用于提取manifest
})
]
};
复制代码
这里主要是利用CommonsChunkPlugin的一个功能,经过默认的名字,来提取公共代码,由于webpack打包的是有有一个默认模块就是manifest,因此咱们能够经过这个来实现
如今咱们执行webpack:
能够看到,多输出了一个manifest.js
接下来,再修改one.js
import jquery from 'jquery';
console.log('分离manifest后修改one');
复制代码
能够看到,如今只有one.js和manifest.js的hash发生了改变,common.js被成功缓存了
使用代码对比工具,比较两次manifest之间的区别,能够看到确实是映射的chunkid发生了改变
前面咱们输出了一个manifest.js,但这样还须要单独处理这个manifest.js,因此可使用webpack的另外一个插件webpack-md5-hash
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new WebpackMd5Hash(),
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
复制代码
执行一次打包:
没有manifest输出,修改one.js
import jquery from 'jquery';
console.log('使用WebpackMd5Hash修改one');
复制代码
再次打包:
这一次仅有one.js的hash发生了改变
虽然webpack-md5-hash解决了咱们的问题,但这也让打包的模块关系变成了黑盒,存在必定的未知风险,还须要仔细实践评估是否有问题
前面已经抽离出来了公共代码,可是还存在问题,假如这时候又须要引入lodash,那common的hash是否会改变?
修改one.js
import jquery from 'jquery';
import lodash from 'lodash';
console.log('引入lodash修改one');
复制代码
修改two.js
import jquery from 'jquery';
import lodash from 'lodash';
console.log('引入lodash修改two');
复制代码
这一次,全部文件的hash都发生了改变,不只如此,并且更显著的是common的体积增大了
这就意味者lodash也被打进了common当中,但这自己是一个错误的行为,lodash和jquery,平时根本不会对其进行修改,既然如此,那还须要优化,把他们单独打包出去
如今修改webapack.config.js
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
two: "./src/two.js",
one: "./src/one.js",
common:['jquery','lodash']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new WebpackMd5Hash(),
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
复制代码
这一次在入口处添加了一个common,common单独指向了jquery和lodash,这一次咱们执行打包
此时,输出的内容没有明显变化,一样是3个文件,大小也彻底一致,hash也没有问题
能够看到,common的大小是817k
若是这时,再应用了其余的包呢?例如引入react
修改one.js
import jquery from 'jquery';
import lodash from 'lodash';
import react from 'react';
console.log('引入react修改one');
复制代码
修改two.js
import jquery from 'jquery';
import lodash from 'lodash';
import react from 'react';
console.log('引入react修改one');
复制代码
执行webpack
问题来了,common的大小增长了,很显然react被打包进去了,但若是咱们此时,只想永久缓存jquery和lodash呢,这该怎么办?
修改webpack.config.js
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
two: "./src/two.js",
one: "./src/one.js",
common:['jquery','lodash']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new WebpackMd5Hash(),
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks:Infinity
})
]
};
复制代码
这一次,添加了一句话minChunks:Infinity
minChunks属性的能够设置为2,意思是引用次数为2的模块就抽离出来,而Infinity
则表示无限,无限就意味着不会有多余的被打包进来
如今执行webpack打包
能够看到如今common又恢复了816k,固然react也没有抽出来,还在两个文件当中,接下来继续抽离react
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
two: "./src/two.js",
one: "./src/one.js",
common:['jquery','lodash'],
react:['react','react-redux']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: ['react','common'], // 用于提取manifest
minChunks:Infinity
}),
new WebpackMd5Hash(),
]
};
复制代码
经过上面的构建,咱们已经将不会改动的类库,单独打包并维持住了hash。
前面看似完美,但若是咱们如今改变一下入口的顺序
entry: {
react:['react','react-redux'],
two: "./src/two.js",
one: "./src/one.js",
common:['jquery','lodash'],
}
复制代码
能够看到common和react公共库的hash又变了,这是由于,模块id是根据webpack的解析顺序增量的,若是变换解析顺序,那模块id也会随之改变。
因此就须要HashedModuleIdsPlugin了,它是根据模块相对路径生成模块标识,若是模块没有改变,那模块标识也不会改变
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
common:['jquery','lodash'],
react:['react','react-redux'],
two: "./src/two.js",
one: "./src/one.js",
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: ['react','common'], // 用于提取manifest
minChunks:Infinity
}),
new webpack.HashedModuleIdsPlugin(),
new WebpackMd5Hash(),
]
};
复制代码
如今打包后,模块的标识再也不是id了,而是一个四位的编码了,这样就能够固定住ip地址了。
在src下建立one.css:
body{
color:blue;
}
复制代码
two.css
h1{
font-size:24px;
}
复制代码
修改one.js和two.js引入css
import jquery from 'jquery';
import lodash from 'lodash';
import react from 'react';
import './one.css'
console.log('引入css修改one');
复制代码
修改webpack.config.js
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
common: ['jquery', 'lodash'],
react: ['react', 'react-redux'],
two: "./src/two.js",
one: "./src/one.js",
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['react', 'common'], // 用于提取manifest
minChunks: Infinity
}),
new ExtractTextPlugin("[name].[chunkhash:8].css"),
new webpack.HashedModuleIdsPlugin(),
new WebpackMd5Hash()
]
};
复制代码
执行webpack:
能够看到,成功输出了js和css,可是有点疑问的是,one.css和one.js的hash是同样的,这样的话,若是咱们改变one.css呢?
修改one.css,再次打包:
发现css的hash没有任何变化。
接着再修改one.js,再次打包:
这一次one.js和one.css的hash同时改变了。
webpack output文档种有写,当提取css后,用contenthash添加hash
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
common: ['jquery', 'lodash'],
react: ['react', 'react-redux'],
two: "./src/two.js",
one: "./src/one.js",
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['react', 'common'], // 用于提取manifest
minChunks: Infinity
}),
new ExtractTextPlugin("[name].[contenthash:8].css"),
new webpack.HashedModuleIdsPlugin(),
new WebpackMd5Hash()
]
};
复制代码
这一次,只是修改了输出的hash,conenthash表明的是文本文件内容的hash值,也就是只有style文件的hash值。
执行webpack:
one.js和one.css的hash变的不同了
接下来,修改one.css
body{
color:white;
}
复制代码
再次执行webpack:
至此,只有one.css发生了变化,准备工做基本就到这里了
由于是多页应用,是经过扫入口文件来进行的打包,规则为js文件为入口文件,jsx为引用的资源不被识别为入口
经过BundleAnalyzerPlugin插件分析,发现有部分组件被打包为了入口,梳理一遍后,从新打包,打包时间减小了2/3,固然这是在填之前的坑
生产打包时间是74578ms
此时压缩和不压缩的打包时间也是3倍的关系:
开发打包时间是24780ms
好的,围绕这两个时间,咱们开始优化
首先要作的实际上是稳定hash,但由于生产环境的打包速度太慢,因此咱们先优化打包速度,webpack默认提供的打包是单线程的
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyJSPlugin({
parallel: true
})
]
}
复制代码
这个插件是webpack3提供的,至于低版本webapck的话,须要谨慎处理,不过效果很明显
如今生产打包时间是51690ms
,比以前提速了1/3
var HappyPack = require('happypack');
var os = require('os');
var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
...
module: {
rules: [ {
test: /\.js[x]?$/,
exclude: /(node_modules|bower_components)/,
loader: 'happypack/loader?id=happybabel',
include: path.join(__dirname, 'static/assets/js')
}
}
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happyThreadPool,
cache: true,
verbose: true
}),
复制代码
上面module的rules属性中loader本来事babel-loader,如今将它变成了一个任务,其中有一个id,id对应的就是plugins中的happyPack实例
此时,咱们开启了babel-loader的多线程模式
如今生产打包时间是43855ms
,比以前又提速了1/9,这只是babel-loader,咱们还能够为其它的loader开启
接着处理less,css,style等loader,这些结合能够一口气搞定
module: {
rules: [{
test: require.resolve('zepto'),
loader: 'exports-loader?window.Zepto!script-loader'
}, {
test: /\.js[x]?$/,
exclude: /(node_modules|bower_components)/,
loader: 'happypack/loader?id=happybabel',
include: path.join(__dirname, 'static/assets/js')
}, {
test: /\.less$/,
use: extractTextPlugin.extract({
fallback: "style-loader",
// use: ["css-loader" + (ENV ? '?minimize' : ''), "less-loader", "postcss-loader"]
use: ["happypack/loader?id=postcss"]
})
}]
}
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happyThreadPool,
// cache: true,
verbose: true
}),
new HappyPack({
id: 'postcss',
loaders: ["css-loader" + (ENV ? '?minimize' : ''), "less-loader",'postcss-loader'],
threadPool: happyThreadPool,
// cache: true,
verbose: true
}),
复制代码
这样,咱们即处理了babel,同时也搞定了css,less,postcss这些loader
上图happy[任务名],能够看到打包行为全都开启了多线程,效果显著
如今生产打包时间是35130ms
,此时已经比第一此非优化的时候,提高了一倍的速度
通过前面的过程,想必已经意识到了纯静态得库和组件都须要与打包环节分离开,这就须要dll技术了
dll技术,其实就是将修改频率低或基本不修改且引用次数多的内容,单独打包
由于设计dll后,config文件的数量剧增,因此须要从新整理目录结构
例如上图,将每个webpack拆分出去,把全部配置文件分离开,例webpack.dev.js:
var base = require('./webpack.base.js');
var config = {
entry: require('./dev/entry.js'),
output: require('./dev/output.js'),
plugins: require('./dev/plugins.js'),
devtool: 'eval-source-map'
}
//把配置文件暴露出去;
module.exports = Object.assign(base,config);
复制代码
ok,基础拆分webpack完成后,咱们建立一个webpack.dll.libs.js用于打包类库
module.exports = {
libs: [
'react',
'react-dom',
'react-motion',
'react-redux',
'redux',
'axios',
'prop-types',
'classnames',
]
}
复制代码
修改plugins插件:
var webpack = require('webpack');
var dirVars = require('../common/dir.js');
var path = require('path');
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');//多线程打包
var getDefaultPlugins = require('../common/plugins.js').getDefaultPlugins;
var AssetsPlugin = require('assets-webpack-plugin');//输出映射表
var plugins =[
new webpack.DllPlugin({
path: dirVars.dllLibsManiFest,
}),
new UglifyJsPlugin({
parallel: true,
cache: true
}),
new AssetsPlugin({
filename: 'static/dll/libs-rev-manifest.json'
}),
]
module.exports = plugins.concat(getDefaultPlugins())
复制代码
如今执行webpack
能够看到,只须要1s,就打包了全部的类库,接下来,修改webpack.prod.js
在plugins中添加:
new webpack.DllReferencePlugin({
manifest: 'static/dll/libs-rev-manifest.json'
}),
复制代码
此时当咱们执行webpack.prod.js进行打包,当扫描到libs中的打包的内容时,就不会重复打包
前面已经完全搞定了打包,但破坏性很大,因此须要系统的验证hash是否存在问题
case1:js改变
修改一个业务代码的js,添加一句注释,再次打包
能够看到文件hash发生了改变,但很不幸,vendor也发生了改变
解决方案:添加webpack-md5-hash插件,使用以后,再次验证,发现vendorjs的hash再也不发生变化
case2:less改变
只有一个css的hash发生了变化,没问题
case3:修改一个入口下本身封装出去的公共方法
上面修改了一个入口内公共使用的tools插件,最终是入口的hash发生了改变,没问题
case4:修改公共方法组件js
主要是多个入口都会引用的组件
测试,只有单独打包出去的components的hash修改了
case5:修改公共方法组件less
只有一个hash发生了改变
case6:添加一个公共组件
只有components的hash发生了改变
未优化前打包时间180-200s
优化:
1,约束入口,严格明确入口文件筛选条件后
生产打包:74578ms
开发打包:24780ms
2,开启多线程压缩后
生产打包:51690ms
3,开启多线程编译
生产打包:35130ms
开发打包:15031ms
4,拆包
分解了打包过程,类库4s,组件4s,业务20s,整体30s左右
复制代码
最终,流程变得可控,打包实现了定制化,hash获得了保持。