-
如果你对
webpack
不是很了解,请你关注我之前的文章,都是百星以上star
的高质量文- 9102年:手写一个React完美版移动端脚手架
- 前端性能优化不完全手册
- GIT仓库地址
- 欢迎你关注我的
《前端进阶》
专栏 一起突破学习 文章内容都会不定期更新 记得一定要收藏
- 本文书写于2019年5月17日 未经作者允许不得转载 使用最新版4.31版本
webpack
-
webpack
用了会上瘾,它也是突破你技术瓶颈的好方向,现在基本上任何东西都离不开webpack
,webpack
用得好,什么next nuxt
随便上手(本人体会很深),本人参考了Vue
脚手架,京东的webpack
优化方案,以及本人的其他方面优化,着重在生产模式
下的构建速度优化提升非常明显(当然开发环境下也是~),性能提升很明显哦~ - 本配置完成功能:
- 识别
.Vue
文件和template模板
-
tree shaking
摇树优化 删除掉无用代码 - 识别
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不仅缓存编译结果,还在第一次编译后开启多线程编译,极大加快构建速度
性能优化没有尽头,本人仅表达自己目前掌握的知识点,士别三日,刮目相看:每隔三天,技术就会进步一次
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功能 { 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编译缓存 } },
在使用上面的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
的使用~
-