• webpack 性能优化


    webpack性能优化

    • 优化开发体验

    • 优化输出质量

    • 优化开发体验

    • 提升效率

    • 优化构建速度

    • 优化使⽤体验

    • 优化输出质量

    • 优化要发布到线上的代码,减少⽤户能感知到的加载时间

    • 提升代码性能,性能好,执⾏就快

    • 缩⼩⽂件范围 Loader

    • 优化loader配置

    • test include exclude三个配置项来缩⼩loader的处理范围

    • 推荐include

      • include: path.resolve(__dirname, "./src"),
    • 优化resolve.modules配置

    resolve.modules⽤于配置webpack去哪些⽬录下寻找第三⽅模块,默认是['node_modules']
    寻找第三⽅模块,默认是在当前项⽬⽬录下的node_modules⾥⾯去找,如果没有找到,就会去上⼀级
    ⽬录../node_modules找,再没有会去../../node_modules中找,以此类推,和Node.js的模块寻找机制
    很类似。
    如果我们的第三⽅模块都安装在了项⽬根⽬录下,就可以直接指明这个路径。
    module.exports={
    resolve:{
    modules: [path.resolve(__dirname, "./node_modules")]
    } }

    • 优化resolve.alias配置
    • resolve.alias配置通过别名来将原导⼊路径映射成⼀个新的导⼊路径

    拿react为例,我们引⼊的react库,⼀般存在两套代码

    cjs
    采⽤commonJS规范的模块化代码
    umd
    已经打包好的完整代码,没有采⽤模块化,可以直接执⾏
    默认情况下,webpack会从⼊⼝⽂件./node_modules/bin/react/index开始递归解析和处理依赖
    的⽂件。我们可以直接指定⽂件,避免这处的耗时。

    alias: {
    	"@": path.join(__dirname, "./pages"),
    	react: path.resolve(
    	__dirname,
    	"./node_modules/react/umd/react.production.min.js"
    	),
    	"react-dom": path.resolve(
    	__dirname,
    	"./node_modules/react-dom/umd/react-dom.production.min.js"
    	)
    }
    
    • 优化resolve.extensions配置

    resolve.extensions在导⼊语句没带⽂件后缀时,webpack会⾃动带上后缀后,去尝试查找⽂件是否存
    在。
    默认值:
    extensions:['.js','.json','.jsx','.ts']
    后缀尝试列表尽量的⼩
    导⼊语句尽量的带上后缀。

    • 使⽤externals优化cdn静态资源

    我们可以将⼀些JS⽂件存储在 CDN 上(减少 Webpack 打包出来的 js 体积),在 index.html 中通过
    标签引⼊,如:

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title>
    </head>
    <body><div id="root">root</div>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    </body>
    </html>
    

    我们希望在使⽤时,仍然可以通过 import 的⽅式去引⽤(如 import $ from 'jquery' ),并且希望
    webpack 不会对其进⾏打包,此时就可以配置 externals 。

    //webpack.config.js
    module.exports = {
    //...
    externals: {
    //jquery通过script引⼊之后,全局中即有了 jQuery 变量
    'jquery': 'jQuery'
    }
    }

    使⽤静态资源路径publicPath(CDN)
    CDN通过将资源部署到世界各地,使得⽤户可以就近访问资源,加快访问速度。要接⼊CDN,需要把⽹⻚的静态资源上传到CDN服务上,在访问这些资源时,使⽤CDN服务提供的URL。

    在webpack.config.js配置

    output:{
     publicPath: '//cdnURL.com', //指定存放JS⽂件的CDN地址
    }
    
    • css⽂件的处理
      • 使⽤less或者sass当做css技术栈

    $ npm install less less-loader --save-dev
    {
    test: /.less$/,
    use: ["style-loader", "css-loader", "less-loader"]
    }

    • 使⽤postcss为样式⾃动补⻬浏览器前缀

    https://caniuse.com/
    npm i postcss-loader autoprefixer -D

    新建postcss.config.js

    module.exports = {
    	plugins: [
    	require("autoprefixer")({
    	overrideBrowserslist: ["last 2 versions", ">1%"]
    	 })
    	 ]
    };
    

    index.less

    body {
    	div {
    	display: flex;
    	border: 1px red solid;
    	 }
     }
    

    在webpack.config.js配置

    {
    	test: /.less$/,
    	include: path.resolve(__dirname, "./src"),
    	use: [
    	"style-loader",
    	 {
    	loader: "css-loader",
    	options: {}
    	 },
    	"less-loader",
    	"postcss-loader"
    	 ]
    },
    
    如果不做抽取配置,我们的 CSS 是直接打包进 JS ⾥⾯的,我们希望能单独⽣成 CSS ⽂件。 因为单独⽣成CSS,CSS可以和JS并⾏下载,提⾼⻚⾯加载效率

    借助MiniCssExtractPlugin 完成抽离css
    npm install mini-css-extract-plugin -D

    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    {
    	test: /.scss$/,
    	use: [
    	// "style-loader", // 不再需要style-loader,⽤MiniCssExtractPlugin.loader
    	代替
    	MiniCssExtractPlugin.loader,
    
    	"css-loader", // 编译css
    	"postcss-loader",
    	"sass-loader" // 编译scss
    	 ]
    	 },
    	plugins: [
    	new MiniCssExtractPlugin({
    	filename: "css/[name]_[contenthash:6].css",
    	chunkFilename: "[id].css"
    	 })
    ]
    
    • 压缩css

    借助 optimize-css-assets-webpack-plugin
    借助cssnano

    安装

    npm install cssnano -D
    npm i optimize-css-assets-webpack-plugin -D

    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    new OptimizeCSSAssetsPlugin({
    	cssProcessor: require("cssnano"), //引⼊cssnano配置压缩选项
    	cssProcessorOptions: {
    	discardComments: { removeAll: true }
    	 }
    })
    
    • 压缩HTML

    借助html-webpack-plugin

    new htmlWebpackPlugin({
     	title: "京东商城",
    	 template: "./index.html",
    	 filename: "index.html",
    	 minify: {
    	 // 压缩HTML⽂件
    	 removeComments: true, // 移除HTML中的注释
    	 collapseWhitespace: true, // 删除空⽩符与换⾏符
    	 minifyCSS: true // 压缩内联css
    	 }
     }),
    
    • development vs Production模式区分打包

    npm install webpack-merge -D

    案例
    const merge = require("webpack-merge")
    const commonConfig = require("./webpack.common.js")
    const devConfig = {
    ...
    }
    module.exports = merge(commonConfig,devConfig)
    //package.js
    "scripts":{
    "dev":"webpack-dev-server --config ./build/webpack.dev.js",
    "build":"webpack --config ./build/webpack.prod.js"
    }
    
    • 基于环境变量区分

    借助cross-env
    npm i cross-env -D

    package⾥⾯配置命令脚本,传⼊参数

    package.json
    "test": "cross-env NODE_ENV=test webpack --config ./webpack.config.test.js",
    
    在webpack.config.js⾥拿到参数
    process.env.NODE_ENV
    //外部传⼊的全局变量
    module.exports = (env)=>{
    	if(env && env.production){
    	return merge(commonConfig,prodConfig)
    	}else{
    	return merge(commonConfig,devConfig)
    	}
    }
    
    //外部传⼊变量
    scripts:" --env.production"
    
    • tree Shaking

    webpack2.x开始⽀持 tree shaking概念,顾名思义,"摇树",清除⽆⽤ css,js(Dead Code)
    Dead Code ⼀般具有以下⼏个特征
    代码不会被执⾏,不可到达
    代码执⾏的结果不会被⽤到
    代码只会影响死变量(只写不读)
    Js tree shaking只⽀持ES module的引⼊⽅式!!!!,

    • Css tree shaking
      ? npm i glob-all purify-css purifycss-webpack --save-dev
    const PurifyCSS = require('purifycss-webpack')
    const glob = require('glob-all')
    plugins:[
    	// 清除⽆⽤ css
    	new PurifyCSS({
    	paths: glob.sync([
    	// 要做 CSS Tree Shaking 的路径⽂件
    	path.resolve(__dirname, './src/*.html'), // 请注意,我们同样需要对 html ⽂
    	件进⾏ tree shaking
    	path.resolve(__dirname, './src/*.js')
    	 ])
    	 })
    ]
    
    • JS tree shaking

    只⽀持import⽅式引⼊,不⽀持commonjs的⽅式引⼊

    案例:

    //expo.js
    export const add = (a, b) => {
    	return a + b;
    };
    export const minus = (a, b) => {
    	return a - b;
    };
    //index.js
    import { add } from "./expo";
    add(1, 2);
    //webpack.config.js
    optimization: {
    	usedExports: true // 哪些导出的模块被使⽤了,再做打包
    }
    

    [注]只要mode是production就会⽣效,develpoment的tree shaking是不⽣效的,因为webpack为了
    ⽅便你的调试
    可以查看打包后的代码注释以辨别是否⽣效。
    ⽣产模式不需要配置,默认开启
    副作⽤
    //package.json

    "sideEffects":false //正常对所有模块进⾏tree shaking , 仅⽣产模式有效,需要配合
    usedExports
    或者 在数组⾥⾯排除不需要tree shaking的模块
    "sideEffects":['*.css','@babel/polyfill']
    
    • 代码分割 code Splitting

    单⻚⾯应⽤spa:
    打包完后,所有⻚⾯只⽣成了⼀个bundle.js
    代码体积变⼤,不利于下载
    没有合理利⽤浏览器资源
    多⻚⾯应⽤mpa:
    如果多个⻚⾯引⼊了⼀些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要
    下载⼀次就缓存起来了,避免了重复下载。

    import _ from "lodash";
    console.log(_.join(['a','b','c','****']))
    假如我们引⼊⼀个第三⽅的⼯具库,体积为1mb,⽽我们的业务逻辑代码也有1mb,那么打包出来的体积⼤
    ⼩会在2mb
    导致问题:
    体积⼤,加载时间⻓
    业务逻辑会变化,第三⽅⼯具库不会,所以业务逻辑⼀变更,第三⽅⼯具库也要跟着变。
    其实code Splitting概念 与 webpack并没有直接的关系,只不过webpack中提供了⼀种更加⽅便的⽅法供我们实现代码分割
    基于https://webpack.js.org/plugins/split-chunks-plugin/

     optimization: {
    	 splitChunks: {
    	 chunks: "all", // 所有的 chunks 代码公共的部分分离出来成为⼀个单独的⽂件
    	 },
     },
    optimization: {
    	splitChunks: {
    	chunks: 'async',//对同步 initial,异步 async,所有的模块有效 all
    	minSize: 30000,//最⼩尺⼨,当模块⼤于30kb
    	maxSize: 0,//对模块进⾏⼆次分割时使⽤,不推荐使⽤
    	minChunks: 1,//打包⽣成的chunk⽂件最少有⼏个chunk引⽤了这个模块
    	maxAsyncRequests: 5,//最⼤异步请求数,默认5
    	maxInitialRequests: 3,//最⼤初始化请求书,⼊⼝⽂件同步请求,默认3
    	automaticNameDelimiter: '-',//打包分割符号
    	name: true,//打包后的名称,除了布尔值,还可以接收⼀个函数function
    	cacheGroups: {//缓存组
    	vendors: {
    		test: /[\/]node_modules[\/]/,
    		name:"vendor", // 要缓存的 分隔出来的 chunk 名称
    		priority: -10//缓存组优先级 数字越⼤,优先级越⾼
    	 },
    	other:{
    		chunks: "initial", // 必须三选⼀: "initial" | "all" | "async"(默认就是async)
    		test: /react|lodash/, // 正则规则验证,如果符合就提取 chunk,
    		name:"other",
    
    		minSize: 30000,
    		minChunks: 1,
    	 },
    	default: {
    		minChunks: 2,
    		priority: -20,
    		reuseExistingChunk: true//可设置是否重⽤该chunk
    		 }
    	 }
    	 }
     }
    #### 使⽤下⾯配置即可:
    ```js
    optimization:{
    	//帮我们⾃动做代码分割
    	splitChunks:{
    	chunks:"all",//默认是⽀持异步,我们使⽤all
    	} 
    }
    
    - Scope Hoisting
    > 作⽤域提升(Scope Hoisting)是指 webpack 通过 ES6 语法的静态分析,分析出模块之间的依赖关系,尽可能地把模块放到同⼀个函数中。下⾯通过代码示例来理解:
    ```js
    // hello.js
    export default 'Hello, Webpack';
    // index.js
    import str from './hello.js';
    console.log(str);
    打包后, hello.js 的内容和 index.js 会分开
    通过配置 optimization.concatenateModules=true:开启 Scope Hoisting
    

    在webpack.config.js配置

    	module.exports = {
    	optimization: {
    	concatenateModules: true
    	 }
    };
    

    我们发现hello.js内容和index.js的内容合并在⼀起了!所以通过 Scope Hoisting 的功能可以让
    Webpack 打包出来的代码⽂件更⼩、运⾏的更快。
    使⽤⼯具量化

    speed-measure-webpack-plugin:可以测量各个插件和 loader 所花费的时间
    npm i speed-measure-webpack-plugin -D

    在webpack.config.js配置

    const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
    const smp = new SpeedMeasurePlugin();
    const config = {
    //...webpack配置
    }
    module.exports = smp.wrap(config);
    
    • webpack-bundle-analyzer:分析webpack打包后的模块依赖关系:

    npm install webpack-bundle-analyzer -D

    const BundleAnalyzerPlugin = require('webpack-bundle￾analyzer').BundleAnalyzerPlugin;
    module.exports = merge(baseWebpackConfig, {
    	//....
    	plugins: [
    		//...
    		new BundleAnalyzerPlugin(),
    	 ]
    })
    
    • 启动webpack 构建,会默认打开⼀个窗⼝
    devServer: {
        contentBase: path.resolve(__dirname,'./dist'),//设置目录的路径
        open: true,//打开窗口
        hot: true,
        hotOnly: true,//即使HMR不生效,浏览器也不会自动刷新//针对css热模块更新
        proxy: {
          "/api": {
            target: "http://localhost:9002"
          }
        },
        port: 8089
      },
    
    • DllPlugin插件打包第三⽅类库 优化构建性能

    Dll动态链接库 其实就是做缓存
    .dll⽂件称为动态链接库,在windows系统会经常看到.
    百度百科:https://baike.baidu.com/item/.dll/2133451?fr=aladdin
    项⽬中引⼊了很多第三⽅库,这些库在很⻓的⼀段时间内,基本不会更新,打包的时候分开打包来提升打包速度,⽽DllPlugin动态链接库插件,其原理就是把⽹⻚依赖的基础模块抽离出来打包到dll⽂件中,当需要导⼊的模块存在于某个dll中时,这个模块不再被打包,⽽是去dll中获取。
    动态链接库只需要被编译⼀次,项⽬中⽤到的第三⽅模块,很稳定,例如react,react-dom,只要
    没有升级的需求
    webpack已经内置了对动态链接库的⽀持

    DllPlugin:⽤于打包出⼀个个单独的动态链接库⽂件
    DllReferencePlugin:⽤于在主要的配置⽂件中引⼊DllPlugin插件打包好的动态链接库⽂件
    新建webpack.dll.config.js⽂件,打包基础模块
    我们在 index.js 中使⽤了第三⽅库 react 、 react-dom ,接下来,我们先对这两个库先进⾏打包。

    const path = require("path");
    const { DllPlugin } = require("webpack");
    module.exports = {
    	mode: "development",
    	entry: {
    	react: ["react", "react-dom"] //! node_modules?
    	 },
    	output: {
    	path: path.resolve(__dirname, "./dll"),
    	filename: "[name].dll.js",
    	library: "react"
    	 },
    	plugins: [
    	new DllPlugin({
    	// manifest.json⽂件的输出位置
    	path: path.join(__dirname, "./dll", "[name]-manifest.json"),
    	// 定义打包的公共vendor⽂件对外暴露的函数名
    	name: "react"
    	 })
    	 ]
    
    };
    

    在package.json中添加
    "dev:dll": "webpack --config ./build/webpack.dll.config.js",
    运⾏
    npm run dev:dll

    你会发现多了⼀个dll⽂件夹,⾥边有dll.js⽂件,这样我们就把我们的React这些已经单独打包了
    dll⽂件包含了⼤量模块的代码,这些模块被存放在⼀个数组⾥。⽤数组的索引号为ID,通过变量讲
    ⾃⼰暴露在全局中,就可以在window.xxx访问到其中的模块
    Manifest.json 描述了与其对应的dll.js包含了哪些模块,以及ID和路径。
    接下来怎么使⽤呢?
    要给web项⽬构建介⼊动态链接库,需要完成以下事情:
    将⽹⻚依赖的基础模块抽离,打包到单独的动态链接库,⼀个动态链接库是可以包含多个模块的。
    当需要导⼊的模块存在于某个动态链接库中时,不要再次打包,直接使⽤构建好的动态链接库即
    可。

    webpack.dev.config.js

    new DllReferencePlugin({
    	manifest: path.resolve(__dirname,"./dll/react-manifest.json")
     }),
    

    ⻚⾯依赖的所有动态链接库都需要被加载。

    <!DOCTYPE html>
    <html lang="en"> <head> <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial￾scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>webpack</title> <link href="css/main_e2bf39.css" rel="stylesheet"></head> <body> <div id="app"></div> <script type="text/javascript" src="react.dll.js"></script> <script type="text/javascript" src="js/main_142e6c.js"></script>
    </body>
    </html>
    

    ⼿动添加使⽤,体验不好,这⾥推荐使⽤add-asset-html-webpack-plugin插件帮助我们做这个事情。
    安装⼀个依赖 npm i add-asset-html-webpack-plugin ,它会将我们打包后的 dll.js ⽂件注⼊到我
    们⽣成的 index.html 中.在 webpack.base.config.js ⽂件中进⾏更改。

    new AddAssetHtmlWebpackPlugin({
    	filepath: path.resolve(__dirname, '../dll/react.dll.js') // 对应的 dll ⽂件路径
     }),
    

    运⾏:
    npm run dev
    这个理解起来不费劲,操作起来很费劲。所幸,在Webpack5中已经不⽤它了,⽽是
    ⽤ HardSourceWebpackPlugin ,⼀样的优化效果,但是使⽤却及其简单
    提供中间缓存的作⽤
    ⾸次构建没有太⼤的变化
    第⼆次构建时间就会有较⼤的节省
    const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
    const plugins = [
    new HardSourceWebpackPlugin()
    ]

    • 使⽤happypack并发执⾏任务
      运⾏在 Node.之上的Webpack是单线程模型的,也就是说Webpack需要⼀个⼀个地处理任务,不能同时处理多个任务。 Happy Pack 就能让Webpack做到这⼀点,它将任务分解给多个⼦进程去并发执⾏,⼦进程处理完后再将结果发送给主进程。从⽽发挥多核 CPU 电脑的威⼒。

    npm i -D happypack
    var happyThreadPool = HappyPack.ThreadPool({ size: 5 });
    //const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })

    在webpack.config.js配置

    rules: [
     {
    	test: /.jsx?$/,
    	exclude: /node_modules/,
    	use: [
    		 {
    		// ⼀个loader对应⼀个id
    		loader: "happypack/loader?id=babel"
    		 }
    		]
    	 },
    	 {
    	test: /.css$/,
    	include: path.resolve(__dirname, "./src"),
    	use: ["happypack/loader?id=css"]
    	 },
     ]
    //在plugins中增加
    plugins:[
    	new HappyPack({
    	// ⽤唯⼀的标识符id,来代表当前的HappyPack是⽤来处理⼀类特定的⽂件
    	id:'babel',
    	// 如何处理.js⽂件,⽤法和Loader配置中⼀样
    	loaders:['babel-loader?cacheDirectory'],
    	threadPool: happyThreadPool,
    	 }),
    	new HappyPack({
    	id: "css",
    	loaders: ["style-loader", "css-loader"]
    	 }),
    ]
    
  • 相关阅读:
    chrome 开发者工具——前端实用功能总结
    而立之年——回顾我的前端转行之路
    编译原理实战入门:用 JavaScript 写一个简单的四则运算编译器(修订版)
    手把手带你入门前端工程化——超详细教程
    手把手教你搭建 Vue 服务端渲染项目
    前端项目自动化部署——超详细教程(Jenkins、Github Actions)
    前端国际化辅助工具——自动替换中文并翻译
    深入了解 webpack 模块加载原理
    实现一个 webpack loader 和 webpack plugin
    博客本地编辑器-OpenLiveWriter安装使用
  • 原文地址:https://www.cnblogs.com/my12-28/p/14891222.html
Copyright © 2020-2023  润新知