如果你对webpack
不是很了解,请你关注我之前的文章,都是百星以上star
的高质量文
《前端进阶》
专栏 一起突破学习文章内容都会不定期更新 记得一定要收藏
webpack
webpack
用了会上瘾,它也是突破你技术瓶颈的好方向,现在基本上任何东西都离不开webpack
,webpack
用得好,什么next nuxt
随便上手(本人体会很深),本人参考了Vue
脚手架,京东的webpack
优化方案,以及本人的其他方面优化,着重在生产模式
下的构建速度优化提升非常明显(当然开发环境下也是~),性能提升很明显哦~.Vue
文件和template模板
tree shaking
摇树优化 删除掉无用代码babel polifill
并且按需加载,识别一切代码async / await
和 箭头函数 PWA
功能,热刷新,安装后立即接管浏览器 离线后仍让可以访问网站 还可以在手机上添加网站到桌面使用preload
预加载资源 prefetch
按需请求资源 ,这里除了dns
预解析外,建议其他的使用按需加载组件,顺便代码分割,这也是京东的优化方案nginx
,拦截非预期请求(京东的方案)CSS
模块化,不怕命名冲突base64
处理sx js json
等VueRouter
路由懒加载,按需加载 , 代码分割 指定多个路由同个chunkName
并且打包到同个chunk
中 实现代码精确分割less sass stylus
等预处理code spliting
优化首屏加载时间 不让一个文件体积过大chunkhash
,每个文件有对应的contenthash
,方便浏览器区别缓存CSS
压缩CSS
前缀 兼容各种浏览器code spliting
CSS
文件单独抽取出来每隔三天,技术就会进步一次
正式开始吧,假设你已经懂什么是
entry output loader plugin
,如果不懂,看我上面的文章哦~
// 入口文件
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
非常相似。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
的路由懒加载了import('./Foo.vue') // 返回 Promise
注意
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 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
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 }
]
})
]
}
}
]
}
{
exclude: /\.(js|json|less|css|jsx)$/,
loader: 'file-loader',
options: {
outputPath: 'media/',
name: '[name].[contenthash:8].[ext]'
}
}
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
new OptimizeCssAssetsWebpackPlugin({
cssProcessPluginOptions:{
preset:['default',{discardComments: {removeAll:true} }]
}
}),
vue
脚手架是同步异步分开割,我是直接一起割 optimization: {
runtimeChunk:true, //设置为 true, 一个chunk打包后就是一个文件,一个chunk对应`一些js css 图片`等
splitChunks: {
chunks: 'all' // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就可以了拆分了,一个入口`JS`,
//打包后就生成一个单独的文件
}
}
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
的使用~