webpack开发、使用及优化总结
构建流程
初始化参数
:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;开始编译
:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件和生命周期,执行对象的 run 方法开始执行编译;确定入口
:根据配置中的 entry 找出所有的入口文件编译模块
:从入口文件出发,调用所有配置的 Loader 对模块进行翻译(转化为AST树进行处理),再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;完成模块编译
:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;输出资源
:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;输出完成
:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
Hash&ChunkHash&ContentHash的区别
hash
:根据构建目录生成,只要项目中有文件修改,则整个项目构建出来的hash都会改变chunkhash
:和webpack打包到chunk有关,不同entry会生成不同的ChunkHush。chunk之间互不影响。(一般用来打包公用js)contenthash
:根据文件内容生成hash,只要文件内容不变,生成的ContentHash就不变(一般用来打包css)
webpack是解决什么问题而生的?
webpack 为前端的工程化开发提供了一套相对容易和完整的解决方案
tree-shaking使用
- 使用ES6模块
- Babel默认会将任何模块类型都转译成CommonJS类型;所以需要在
.babelrc
文件中或在webpack.config.js
文件中设置modules: false
- 使用
UglifyJsPlugin
插件
如何打包,到浏览器如何解包
利用webpack如何优化前端性能?
webpack的plugins的实现原理
- Webpack 启动后,在读取配置的过程中会先执行 new myPlugin(options) 初始化一个 myPlugin 获得其实例。
- 在初始化 compiler 对象后,再调用 myPlugin.apply(compiler) 给插件实例传入 compiler 对象。
- 插件实例在获取到 compiler 对象后,就可以通过 hooks(webpack 把编译过程中触发的各类关键事件封装成事件接口的钩子)监听到 Webpack 发布出来的事件。 并且可以通过 compiler 对象去操作 Webpack。
webpack的loaders的实现原理
每个 Webpack 的 Loader 都需要导出一个函数,这个函数就是我们这个 Loader 对资源的处理过程,它的输入就是加载到的资源文件内容,输出就是我们加工后的结果。我们通过 source 参数接收输入,通过返回值输出
code split 介绍
为了减少HTTP请求,通常我们会讲所有代码打包到一个文件中,但是如果文件过大,也会影响页面的加载速度,尤其是会增加白屏时间,所以我们不妨将代码分成一块一块的;并且只在需要的时候去加载它;还可以利用浏览器将这些模块缓存起来;一般情况下分离有两种
- 将第三方代码从业务代码中抽离(verdor)
- 根据不同入口的业务代码按需加载(利用 import()语法)
webpack优化
webpack优化的瓶颈主要有两个方面:构建过程耗时
、打包的体积太大
;
针对构建时间的优化(从依赖分析、依赖编译、处理输出三个方向):
- 开发环境下启动
hard-source-webpack-plugin
,通过缓存编译过程优化启动速度 - loader处理结果缓存
- babel-loader缓存,通过
cacheDirectory
开启缓存webpack.module.rules:{ test: /\.js$/, use: [ { loader: resolve('babel-loader'), options: { babelrc: false, // 将 babel 编译过的模块缓存在 webpack_cache 目录下,下次优先复用 cacheDirectory: true, }, }, ] }
- eslint-loader缓存,通过
cache
选项指定缓存路径;注:配置更后如果不起作用则需要将cache
文件删除webpack.module.rules:{ test: /\.(js|vue)$/, enforce: 'pre', use: [ { options: { // 启动缓存 cache: true, }, loader: require.resolve('eslint-loader'), }, ] }
- css/scss缓存通过
cache-loader
辅助构建结果缓存
- babel-loader缓存,通过
- 缩小编译范围,减少不必要的检索与递归遍历、编译工作
- 指定模块解析路径,加速模块查找
webpack.resolve: { // 指定以下目录寻找第三方模块,避免webpack往父级目录递归搜索 modules: { 'node_modules', path.resolve(projectDir, 'src'), path.resolve(projectDir, 'node_modules') } }
- 忽略未用模块化的库,不去解析它们以提升编译速度
webpack.module: { noParse: /node_modules\/(jQuery|chart\.js)/, }
- 通过
alias
配置别名, 减小查找过程
webpack.resolve: { alias: { react: path.resolve(__dirname, './node_modules/react/dist/react.min.js') } }
- 为loader添加
include/exclude
缩小babel的编译范围
webpack.module.rules: { test: /\.js$/, use: [{ loader: 'babel-loader', query: { // 将 babel 编译过的模块缓存在 webpack_cache 目录下,下次优先复用 cacheDirectory: './webpack_cache/', }, }], // 减少 babel 编译范围,忘记设置会让 webpack 编译慢上好几倍 include: path.resolve(__dirname, 'src'), }
- 指定模块解析路径,加速模块查找
- 通过
uglifyjs-webpack-plugin
插件开启多个子线程压缩代码 - 通过
Happypack
开启多线程加速loader对文件的转换速度;使用 HappyPack 也有一些限制,它只兼容部分主流的 loader;webpack4已经弃用,推荐用thread-loader
module.exports = { module: { rules: [ { test: /\.js?$/, use: ['happypack/loader?id=babel'], include: path.resolve(__dirname, 'src'), }, ] }, plugins: [ new HappyPack({ id: 'babel', loaders: [{ loader: 'babel-loader', query: { cacheDirectory: './webpack_cache/', }, }], }) ], };
- 使用
thread-loader
放置在其他 loader 之前;放置在这个 loader 之后的 loader 就会在一个单独的 worker【worker pool】 池里运行;请仅在耗时的 loader 上使用
;文档
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
// 创建一个 js worker 池
use: [
'thread-loader',
'babel-loader'
]
},
{
test: /\.s?css$/,
exclude: /node_modules/,
// 创建一个 css worker 池
use: [
'style-loader',
'thread-loader',
'css-loader'
},
'postcss-loader'
]
}
]
}
}
针对打包体积的优化
- 用
DllPlugin
和DllReferencePlugin
提前打包公共模块;注:不建议将所有公共模块放在dll中,否则无法用webpack的代码分割
和按需加载功能
- 使用
ModuleConcatenationPlugin
插件开启scope-hoisting(作用域提升)
功能,尽可能的把打包出来的模块合并到一个函数中去(代码需要符合es6的module规范)注意: webpack4中,去掉了这个插件改为
optimization.concatenateModules
配置,默认在生产环境是开启的
- 使用 ES6 模块来开启
tree shaking
,css通过purgecss-webpack-plugin
插件进行tree-shanking
优化 - 按照路由拆分代码,实现按需加载
- 使用
webpack-bundle-analyzer
对打包文件进行分析,将大文件模块提取出来在html中单独加载
webpack配置:html:// ... externals: { 'element-ui': 'Element', 'v-charts': 'VCharts' }
<script src="https://unpkg.com/element-ui@2.10.0/lib/index.js"></script>
webpack 热更新原理
- 开启热更新后会启动一个
HMR Server
服务,同时在打包文件中注入HMR Runtime
(用来启动websocket,来保持和server的通信,通过修改entry
配置注入)
// 修改后的entry入口
{ entry:
{ index:
[
// 添加websocket代码
'xxx/node_modules/webpack-dev-server/client/index.js?http://localhost:8080',
// 检查更新逻辑
'xxx/node_modules/webpack/hot/dev-server.js',
// 自己的入口
'./src/index.js'
],
},
}
- 热更新插件会在webpack的
compiler.hooks.done
钩子中注册监听编译完成事件,当修改了一个或多个文件是,webpack会重新编译,编译完成后触发钩子
setupHooks() {
const {done} = compiler.hooks;
// 监听webpack的done钩子,tapable提供的监听方法
done.tap('webpack-dev-server', (stats) => {
this._sendStats(this.sockets, this.getStats(stats));
this._stats = stats;
});
};
- server服务会在编译完成后通过
websoket
想浏览器发送hash
和ok
事件
// 通过websoket给客户端发消息
_sendStats() {
this.sockWrite(sockets, 'hash', stats.hash); // 发送最新hash
this.sockWrite(sockets, 'ok'); // 通知刷新
}
- 浏览器利用
hash
,发送xxx/${hash}.hot-update.json
请求,获取下次更新的hash
和更新模块
{
"h": "0c256052432b51ed32c8", // 下次更新hash
"c": {
"302": true // 更新的模块,如果没有则不需要更新
}
}
- 同时浏览器利用
hash
,发送xxx/${hash}.hot-update.js
请求,利用jsop
方式返回更新模块代码,jsonp
的方式对返回的代码可以直接执行
webpack环境变量
- package.json中设置,为了兼容性需要用cross-env包
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server"
}
- 在命令行中设置
webpack --env.production // 设置 env.production == true
webpack --env.platform=web // 设置 env.platform == "web"
注意,这种方式需要对
webpack配置
的module.exports原来指向对象的方式改为函数
module.exports = env => {
console.log('NODE_ENV: ', env.NODE_ENV) // 'local'
console.log('platform: ', env.platform) // web
return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
}
- 通过
DefinePlugin
配置
const env = {
BASE_API:'"https://www.xxxx.com"' // 其它一些参数配置
}
plugins: [
new webpack.DefinePlugin({
'process.env' : env,
'process.env.NODE_ENV' : JSON.stringify(process.env.NODE_ENV)
})
]
- 配置文件
mode
module.exports = {
// 定义环境变量
mode: 'development',
// JavaScript 执行入口文件
entry: './main.js',
output: {
// 把所有依赖的模块合并输出到一个 bundle.js 文件
filename: 'bundle.js',
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, './dist'),
},
};
// 或者执行时设置
webpack --mode=production
注意: 设置
mode
webpack内部会自动执行DefinePlugin
设置变量
注意:npm脚本里的设置多用于配置相关
;而DefinePlugin和mode选项定义的NODE_ENV 作用于webpack入口文件下的业务代码
gulp和webpack的区别
- gulp强调的是工作流;通过配置一系列task,顺序执行来构建工作流程
- webpack是一个前端模块化解决方案,侧重于模块化打包,将一切(图片、文件)都视为模块;通过loader(加载器)和plugins(插件)对资源进行处理
小红包免费领
小礼物走一走
部分内容来源于网络,如有侵权,请留言或联系994917123@qq.com;访问量:waiting;访客数:waiting