前言:在看本文前,建议你看下,下面这两篇文章 顺便给个赞和github
的赞哦~
如果你对
webpack
不是很了解,请你关注我之前的文章,都是百星以上star
的高质量文- 9102年:手写一个React完美版移动端脚手架
- 前端性能优化不完全手册
- GIT仓库地址
- 欢迎你关注我的
《前端进阶》
专栏 一起突破学习 文章内容都会不定期更新 记得一定要收藏
- 本文书写于2019年5月17日 未经作者允许不得转载 使用最新版4.31版本
webpack
webpack
用了会上瘾,它也是突破你技术瓶颈的好方向,现在基本上任何东西都离不开webpack
,webpack
用得好,什么next nuxt
随便上手(本人体会很深),本人参考了Vue
脚手架,京东的webpack
优化方案,以及本人的其他方面优化,着重在生产模式
下的构建速度优化提升非常明显(当然开发环境下也是~),性能提升很明显哦~- 本配置完成功能:
- 识别
.Vue
文件和template模板
tree shaking
摇树优化 删除掉无用代码- 引入
babel polifill
并且按需加载,识别一切代码 - 识别
async / await
和 箭头函数 PWA
功能,热刷新,安装后立即接管浏览器 离线后仍让可以访问网站 还可以在手机上添加网站到桌面使用preload
预加载资源prefetch
按需请求资源 ,这里除了dns
预解析外,建议其他的使用按需加载组件,顺便代码分割,这也是京东的优化方案- 配置
nginx
,拦截非预期请求(京东的方案) CSS
模块化,不怕命名冲突- 小图片的
base64
处理 - 文件后缀省掉j
sx js json
等 - 实现
VueRouter
路由懒加载,按需加载 , 代码分割 指定多个路由同个chunkName
并且打包到同个chunk
中 实现代码精确分割 - 支持
less sass stylus
等预处理 code spliting
优化首屏加载时间 不让一个文件体积过大- 提取公共代码,打包成一个chunk
- 每个chunk有对应的
chunkhash
,每个文件有对应的contenthash
,方便浏览器区别缓存 - 图片压缩
CSS
压缩- 增加
CSS
前缀 兼容各种浏览器 - 对于各种不同文件打包输出指定文件夹下
- 缓存babel的编译结果,加快编译速度
- 每个入口文件,对应一个chunk,打包出来后对应一个文件 也是
code spliting
- 删除HTML文件的注释等无用内容
- 每次编译删除旧的打包代码
- 将
CSS
文件单独抽取出来 - 让babel不仅缓存编译结果,还在第一次编译后开启多线程编译,极大加快构建速度
性能优化没有尽头,本人仅表达自己目前掌握的知识点,士别三日,刮目相看:每隔三天,技术就会进步一次
正式开始吧,假设你已经懂什么是entry output loader plugin
,如果不懂,看我上面的文章哦~
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'
]
}
]
}
这里面我们重点关注module
和plugins
属性,因为今天的重点是编写loader
和plugin
,需要配置这两个属性。
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]',
}
}
]
}
]
},
有人会问 这么多我怎么看啊 别急 第一个url-loader
是处理base64
图片的,让低于limit
大小的文件以base64
形式使用,后面两个一样的套路,只是换了文件类型而已 ,不会的话,先复制过去跑一把?
配置识别.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功能
{
loader: 'babel-loader',
options: { //jsx语法
presets: ["@babel/preset-react",
//tree shaking 按需加载babel-polifill
["@babel/preset-env", { "modules": false, "useBuiltIns": "false", "corejs": 2 }]],
plugins: [
//支持import 懒加载
"@babel/plugin-syntax-dynamic-import",
//andt-mobile按需加载 true是less,如果不用less style的值可以写'css'
["import", { libraryName: "antd-mobile", style: true }],
//识别class组件
["@babel/plugin-proposal-class-properties", { "loose": true }],
],
cacheDirectory: true
},
}
在使用上面的babel
配置后 我们躺着就可以用vueRouter
的路由懒加载了
路由懒加载
- 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
- 结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
- 首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身):
- const Foo = () => Promise.resolve({ / 组件定义对象 / })
第二,在 Webpack 中,我们可以使用动态 import语法来定义代码分块点 (split point):
import('./Foo.vue') // 返回 Promise
注意
- 如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。
- 结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。
const Foo = () => import('./Foo.vue')
在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
# 把组件按组分块
有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
加入插件 热更新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/],
}),
单页面应用的优化核心 :
- 最重要的是路由懒加载 代码分割
- 部分渲染在服务端完成 极大加快首屏渲染速度
VUE
首选nuxt
框架,也可以使用它的脚手架 - 图片压缩和图片懒加载是对页面层次最大的优化之一
- 后面继续书写
next nuxt
和pwa
的使用~
脚手架的搭建过程很多坑,但是却能大大提升你的技术天花板,跟着作者一起踩坑吧,别忘了来我的github
点赞哦~ 仓库源码地址~欢迎star
查看原文