往期回顾:
从0搭建本身的webpack开发环境(一)javascript
前四篇文章咱们已经掌握了webpack
各类常见的配置,这一片文章咱们来看看如何实现webpack
中的优化。java
咱们先来编写最基本的webpack配置
,而后依次实现其中的各类优化。node
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); module.exports = mode => { return { mode: mode, entry: "./src/main.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /\.(png|jpg|gif)$/, use: "file-loader" }, { test: /\.js$/, use: "babel-loader" // .babelrc已经配置支持react }, { test: /\.css$/, use: [ mode !== "development" ? MiniCssExtractPlugin.loader : "style-loader", "css-loader" ] } ] }, plugins: [ new PurgecssPlugin({ paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目录,只匹配文件 }), mode !== "development" && new MiniCssExtractPlugin({ filename: "css/[name].css" }), new HtmlWebpackPlugin({ template: "./src/template.html", filename: "index.html" }) ].filter(Boolean) }; };
.babelrc
配置文件react
{ "presets": [ "@babel/preset-env", "@babel/preset-react" ] }
先来看看编写的代码jquery
import './style.css' import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render(<div>hello</div>,document.getElementById('root'));
body{ background: red } .class1{ background: red }
这里的.class1
显然是无用的,咱们能够搜索src
目录下的文件,删除无用的样式
const glob = require('glob'); const PurgecssPlugin = require('purgecss-webpack-plugin'); // 这里须要配合mini-css-extract-plugin插件 mode !== "development" && new PurgecssPlugin({ paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目录,只匹配文件 }),
将打包后的图片
进行优化webpack
npm install image-webpack-loader --save-dev
在file-loader
以前使用压缩图片插件es6
loader: "image-webpack-loader", options: { mozjpeg: { progressive: true, quality: 65 }, // optipng.enabled: false will disable optipng optipng: { enabled: false, }, pngquant: { quality: [0.90, 0.95], speed: 4 }, gifsicle: { interlaced: false, }, // the webp option will enable WEBP webp: { quality: 75 } }
能够发现图片大小是否有了明显的变化
咱们但愿经过cdn的方式
引入资源
const AddAssetHtmlCdnPlugin = require('add-asset-html-cdn-webpack-plugin') new AddAssetHtmlCdnPlugin(true,{ 'jquery':'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js' })
可是在代码中还但愿引入jquery
来得到提示
import $ from 'jquery' console.log('$',$)
可是打包时依然会将jquery
进行打包
externals:{ 'jquery':'$' }
在配置文件中标注jquery
是外部的,这样打包时就不会将jquery进行打包了
顾名思义就是将没用的内容摇晃掉,来看下代码:
main.js
import { minus } from "./calc"; console.log(minus(1,1));
calc.js
import {test} from './test'; export const sum = (a, b) => { return a + b + 'sum'; }; export const minus = (a, b) => { return a - b + 'minus'; };
test.js
export const test = ()=>{ console.log('hello') } console.log(test());
观察上述代码会发现咱们主要使用minus
方法,test.js
代码实际上是有反作用的!
默认mode:production
时,会自动tree-shaking
,可是打包后'hello'
依然会被打印出来,这时候咱们须要配置规避反作用!
在package.json
中配置
"sideEffects":false,
若是这样设置,默认就不会导入css
文件啦,由于咱们引入css也是经过import './style.css'
的;
这里重点就来了,tree-shaking
主要针对es6模块,咱们可使用require
语法导入css,可是这样用起来又有点格格不入,因此咱们配置css
文件不起反作用。
"sideEffects":[ "**/*.css" ]
在开发环境下默认tree-shaking
不会生效,能够配置标识提示。
optimization:{ usedExports:true }
做用域提高,能够减小代码体积,节约内存
let a = 1; let b = 2; let c = 3; let d = a+b+c; export default d; // 引入d import d from './d'; console.log(d);
最终打包后的结果会变成
console.log(6)
每次构建时第三方模块
都须要从新构建,这个性能消耗比较大,咱们能够先把第三方库打包成动态连接库,之后构建时只须要查找构建好的库就行了,这样能够大大节约构建时间。
import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render(<h1>hello</h1>,document.getElementById('root'))
这里咱们能够先将react
、react-dom
单独进行打包
单独打包建立webpack.dll.js
const path = require('path'); const DllPlugin = require('webpack/lib/DllPlugin'); module.exports = { entry:['react','react-dom'], mode:'production', output:{ filename:'react.dll.js', path:path.resolve(__dirname,'dll'), library:'react' }, plugins:[ new DllPlugin({ name:'react', path:path.resolve(__dirname,'dll/manifest.json') }) ] }
执行"webpack --config webpack.dll.js
命令,能够看到dll目录下建立了两个文件分别是manifest.json
和react.dll.js
关系是这个酱紫的,到时候咱们会经过manifest.json
来找到react.dll.js
文件中的模块进行加载。
在咱们的项目中能够引用刚才打包好的动态连接库
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin'); const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); // 构建时会引用动态连接库的内容 new DllReferencePlugin({ manifest:path.resolve(__dirname,'dll/manifest.json') }), // 须要手动引入react.dll.js new AddAssetHtmlWebpackPlugin( { filepath: path.resolve(__dirname,'dll/react.dll.js') } )
使用DllPlugin
能够大幅度提升构建速度
实现点击后动态加载
文件
let btn = document.createElement('button'); btn.innerHTML = '点击加载视频'; btn.addEventListener('click',()=>{ import('./video').then(res=>{ console.log(res.default); }); }); document.body.appendChild(btn);
给动态引入的文件增长名字
output:{ chunkFilename:'[name].min.js' } import(/* webpackChunkName: "video" */ './video').then(res=>{ console.log(res.default); })
这样打包后的结果最终的文件就是
video.min.js
安装webpack-bundle-analyzer
插件
npm install --save-dev webpack-bundle-analyzer
安装后使用插件
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); mode !== "development" && new BundleAnalyzerPlugin()
默认就会展示当前应用的分析图表
咱们再来看下SplitChunks
这个配置,他能够在编译时抽离第三方模块、公共模块。
先将项目配置成多入口文件
entry:{ a:'./src/a.js', b:'./src/b.js' }
咱们让a,b两个模块同时引用jquery
,这里别忘了去掉以前的externals
配置
配置SplitChunks
插件
默认配置在此,咱们一个个描述下含义
splitChunks: { chunks: 'async', // 分割异步模块 minSize: 30000, // 分割的文件最小大小 maxSize: 0, minChunks: 1, // 引用次数 maxAsyncRequests: 5, // 最大异步请求数 maxInitialRequests: 3, // 最大初始化请求数 automaticNameDelimiter: '~', // 抽离的命名分隔符 automaticNameMaxLength: 30, // 名字最大长度 name: true, cacheGroups: { // 缓存组 vendors: { // 先抽离第三方 test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, // 优先级 reuseExistingChunk: true } } }
咱们将async
改成initial
咱们在为每一个文件动态导入lodash
库,而且改为async
import('lodash')
为每一个入口引入
c.js
,而且改造配置文件
splitChunks: { chunks: 'all', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minSize:1, // 不是第三方模块,被引入两次也会被抽离 minChunks: 2, priority: -20, } } }
这样再反过来看
chunks
的参数是否是就了然于胸了呢?
模块热替换(HMR - Hot Module Replacement)是webpack
提供的最有用的功能之一。它容许在运行时替换、添加、删除各类模块,而无需进行彻底刷新从新加载整个页面。
启用热更新,默认样式能够支持热更新,若是不支持热更新则能够采用强制刷新。
devServer:{ hot:true } new webpack.NamedModulesPlugin(),
让js
支持热更新
import sum from './sum'; console.log(sum(1,2)); if(module.hot){ // 若是支持热更新 module.hot.accept(); // 当入口文件变化后从新执行当前入口文件 }
配置忽略 import
和require
语法
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
能够计算每一步执行的运行速度
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin'); const smw = new SpeedMeasureWebpackPlugin(); module.exports =smw.wrap({ ... });
module.noParse
,对相似jq这类的依赖库,内部不会引用其余库,那么咱们在打包的时候就没有必要再去解析,这样可以增长打包速率。
noParse:/jquery/
resolve: { extensions: [".js",".jsx",".json",".css"], alias:{}, modules:['node_modules'] },
在使用loader
时,能够指定哪些文件不经过loader
,或者指定哪些文件必须经过loader
。
{ test: /\.js$/, use: "babel-loader", // include:path.resolve(__dirname,'src'), exclude:/node_modules/ },
多线程打包
,咱们能够将不一样的逻辑交给不一样的线程来处理。
npm install --save-dev happypack
使用插件
const HappyPack = require('happypack'); rules:[ { test: /\.js$/, use: 'happypack/loader?id=jsx' }, { test: /\.less$/, use: 'happypack/loader?id=styles' }, ] new HappyPack({ id: 'jsx', threads: 4, loaders: [ 'babel-loader' ] }), new HappyPack({ id: 'styles', threads: 2, loaders: [ 'style-loader', 'css-loader', 'less-loader' ] })
至此,咱们五篇连载《从0搭建本身的webpack开发环境》先要告一段落了,但愿你们能够经过这些文章对webpack能有全新的认识,对工做能更有帮助。
有什么想要了解的前端知识,欢迎关注留言!谢谢你们的支持!