如果你对
webpack
不是很了解,请你关注我之前的文章,都是百星以上star
的高质量文- 9102年:手写一个React完美版移动端脚手架
- 前端性能优化不完全手册
- 欢迎你关注我的
《前端进阶》
专栏 一起突破学习 文章内容都会不定期更新 记得一定要收藏
webpack
用了会上瘾,它也是突破你技术瓶颈的好方向,现在基本上任何东西都离不开webpack
,webpack
用得好,什么next nuxt
随便上手(本人体会很深),本人参考了Vue
脚手架,京东的webpack
优化方案,以及本人的其他方面优化,着重在生产模式
下的构建速度优化提升非常明显(当然开发环境下也是~),性能提升很明显哦~
性能优化没有尽头,本人仅表达自己目前掌握的知识点,士别三日,刮目相看:每隔三天,技术就会进步一次
webpack常见配置
// 入口文件
entry: {
app: './src/js/index.js',
},
// 输出文件
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/' //确保文件资源能够在 http://localhost:3000 下正确访问
},
// 开发者工具 source-map
devtool: 'inline-source-map',
// 创建开发者服务器
devServer: {
contentBase: './dist',
hot: true // 热更新
},
plugins: [
// 删除dist目录
new CleanWebpackPlugin(['dist']),
// 重新穿件html文件
new HtmlWebpackPlugin({
title: 'Output Management'
}),
// 以便更容易查看要修补(patch)的依赖
new webpack.NamedModulesPlugin(),
// 热更新模块
new webpack.HotModuleReplacementPlugin()
],
// 环境
mode: "development",
// loader配置
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
}
]
}
webpack
启动后,在读取配置的过程中会先执行new MyPlugin(options)
初始化一个MyPlugin
获得其实例。在初始化compiler
对象后,再调用myPlugin.apply(compiler)
给插件实例传入compiler
对象。
插件实例在获取到 compiler
对象后,就可以通过 compiler.plugin
(事件名称, 回调函数) 监听到 Webpack
广播出来的事件。
并且可以通过 compiler 对象去操作 webpack。
Compiler
对象包含了Webpack
环境所有的的配置信息,包含options,loaders,plugins
这些信息,这个对象在Webpack
启动时候被实例化,它是全局唯一的,可以简单地把它理解为Webpack
实例;Compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的
Compilation将被创建。
Compilation对象也提供了很多事件回调供插件做扩展。通过
Compilation也能读取到
Compiler` 对象。Compiler 和 Compilation
的区别在于:Compiler
代表了整个Webpack
从启动到关闭的生命周期,而Compilation
只是代表了一次新的编译。- 事件流
webpack
通过Tapable
来组织这条复杂的生产线。webpack
的事件流机制保证了插件的有序性,使得整个系统扩展性很好。webpack
的事件流机制应用了观察者模式,和Node.js 中的 EventEmitter
非常相似。
1.2 打包原理
- 识别入口文件
- 通过逐层识别模块依赖。(
Commonjs、amd
或者es6
的import,webpack
都会对其进行分析。来获取代码的依赖) webpack
做的就是分析代码。转换代码,编译代码,输出代码- 最终形成打包后的代码
- 这些都是
webpack
的一些基础知识,对于理解webpack的工作机制很有帮助。
脚手架一般都是遵循了commonjs
模块化方案,如果你不是很懂,那么看起来很费劲,我写的脚手架,就不使用模块化方案了,简单粗
暴
- 开始开发环境配置
- 包管理器 使用
yarn
不解释 就用yarn
- 配置
webpack.dev.js
开发模式下的配置 yarn init -y
yarn add webpack webpack-cli
(yarn
会自动添加依赖是线上依赖还是开发环境的依赖)
配置入口
entry: path.resolve(__dirname, '../src/main.js')}
配置输出目录
output: {
filename: 'js/[name].[hash:5].js',
path: path.resolve(__dirname, '../dist'),
},
引入Vue
脚手架里基本配置的loader
,后面的loader
都是往rules
数组里加就行了~
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name]-[hash:5].[ext]',
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name]-[hash:5].[ext]',
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
name: 'media/[name]-[hash:5].[ext]',
}
}
]
}
]
},
配置识别.vue
文件和tempalte
模板 , yarn add vue vue-loader vue-template-compiler
加入loader
{
test:/\.vue$/,
loader:"vue-loader"
}
加入plugin
const vueplugin = require('vue-loader/lib/plugin')
在webpack的plugin中
new vueplugin()即可
入口指定babel-polifill
,vendor
代码分割公共模块,打包后这些代码都会在一个公共模块
app: ['babel-polyfill', './src/index.js', './src/pages/home/index.js', './src/pages/home/categorys/index.jsx'],
vendor: ['vuex', 'better-scroll', 'mint-ui', 'element-ui']
指定 html
文件为模板打包输出,自动引入打包后的js
文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname,'../index.html'),
filename: 'index.html'
}),
]
省掉.vue
的后缀 ,直接配置在module.exports
对象中,跟entry
同级
resolve: {
extensions: ['.js','.json','.vue'],
}
加入识别html
文件的loader
{
test: /\.(html)$/,
loader: 'html-loader'
}
开启多线程编译
const os = require('os')
{
loader: 'thread-loader',
options: {
workers: os.cpus().length
}
}
加入babel-loader
加入 babel-loader 还有 解析JSX ES6语法的 babel preset
@babel/preset-env解析es6语法
@babel/plugin-syntax-dynamic-import解析vue的 import按需加载,附带code spliting功能
{
test: /\.(js|jsx)$/,
use:
{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env", { "modules": false }] ,//附带`tree shaking`
plugins: ["@babel/plugin-syntax-dynamic-import"]
},
cacheDirectory: true//开启babel编译缓存
}
},
加入插件 热更新plugin和html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin(),
devServer: {
contentBase: '../build',
open: true,
port: 5000,
hot: true
},
加入less-css
识别的模块
{
test: /\.(less|css)$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader'
, options: {
modules: false, //不建议开启css模块化,某些ui组件库可能会按需加载失败
localIdentName: '[local]--[hash:base64:5]'
}
},
{
loader: 'less-loader',
options: { javascriptEnabled: true }
}
]
},
踩坑是好事 为什么这次不放完整的源码 因为不去踩坑 永远提升不了技术
html
杀掉无效的代码
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
}),
加入图片压缩 性能优化很大
{
test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
use:[
{loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[name].[hash:8].[ext]',
outputPath:'/img'
}},
{
loader: 'img-loader',
options: {
plugins: [
require('imagemin-gifsicle')({
interlaced: false
}),
require('imagemin-mozjpeg')({
progressive: true,
arithmetic: false
}),
require('imagemin-pngquant')({
floyd: 0.5,
speed: 2
}),
require('imagemin-svgo')({
plugins: [
{ removeTitle: true },
{ convertPathData: false }
]
})
]
}
}
]
}
加入file-loader 把一些文件打包输出到固定的目录下
{
exclude: /\.(js|json|less|css|jsx)$/,
loader: 'file-loader',
options: {
outputPath: 'media/',
name: '[name].[contenthash:8].[ext]'
}
}
加入压缩css的插件
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
new OptimizeCssAssetsWebpackPlugin({
cssProcessPluginOptions:{
preset:['default',{discardComments: {removeAll:true} }]
}
}),
加入code spliting代码分割 vue
脚手架是同步异步分开割,我是直接一起割
optimization: {
runtimeChunk:true, //设置为 true, 一个chunk打包后就是一个文件,一个chunk对应`一些js css 图片`等
splitChunks: {
chunks: 'all' // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就可以了拆分了,一个入口`JS`,
//打包后就生成一个单独的文件
}
}
加入 WorkboxPlugin , PWA的插件
pwa这个技术其实要想真正用好,还是需要下点功夫,它有它的生命周期,以及它在浏览器中热更新带来的副作用等,需要认真研究。可以参考百度的lavas框架发展历史~
const WorkboxPlugin = require('workbox-webpack-plugin')
new WorkboxPlugin.GenerateSW({
clientsClaim: true, //让浏览器立即servece worker被接管
skipWaiting: true, // 更新sw文件后,立即插队到最前面
importWorkboxFrom: 'local',
include: [/\.js$/, /\.css$/, /\.html$/,/\.jpg/,/\.jpeg/,/\.svg/,/\.webp/,/\.png/],
}),