阅读 1515

渐进式配置webpack4单页面和多页面(二)

渐进式配置webpack4单页面和多页面

前言

使用包的版本

webpack ->4.3.0
babel-loader ->8.0.5
npm ->6.4.1
webpack-cli ->3.3.1
复制代码

每个章节对应一个demo

模块化拆包1

参考代码 demo8

什么是模块化拆包。比如我们在项目里面需要引入echarts、xlsx、lodash等比较大的包的时候。如果不做代码拆包,都会打包到一个js文件里面。如果每次打包发版都会生成一个新的js打包文件,用户重新请求页面的时候会再次请求echarts、xlsx、lodash这些不变的代码,就会降低用户体验。模块化拆包将这些不变的依赖包打包成一个新的js文件,每次打包发版的时候用户就不会再次请求echarts、xlsx、lodash这些不变的代码了。

代码更改

webpack-bundle-analyzer 可视化显示打包的JS引用的包

npm i webpack-bundle-analyzer -D
复制代码

在项目里面引入echarts、xlsx、lodash

npm i echarts xlsx lodash -S
复制代码

app.js

import "regenerator-runtime/runtime";
import _ from 'lodash';
import echarts from 'echarts';
import xlsx from 'xlsx';
console.log(echarts)
console.log(xlsx);
document.getElementById('app').innerHTML = _.ceil(2,3,4);

复制代码

webpack.base.conf.js 配置可视化现在打包文件。

var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

  plugins:[
    new BundleAnalyzerPlugin(),
    new htmlWebpackPlugin({
      filename:'index.html',
      template:'./index.html',
      inject:true,
      minify: {
        // 压缩 HTML 文件
        removeComments: isPord, // 移除 HTML 中的注释
        collapseWhitespace: isPord, // 删除空白符与换行符
        minifyCSS: isPord // 压缩内联 css
      },
    })
  ],
复制代码

运行打包命令

npm run build

复制代码

打包提示代码太大,需要进行拆包。

默认配置

optimization.splitChunks是webpack4新的特性,默认进行代码拆包。

上图是默认配置。

chunks:

  • all: 不管文件是异步还是同步引入,都可以使用splitChunks进行代码拆包。
  • async: 只将异步加载的文件分离,首次一般不引入,到需要异步引入的组件才会引入。
  • initial:将异步和非异步的文件分离,如果一个文件被异步引入也被非异步引入,那它会被打包两次(注意和all区别),用于分离页面首次需要加载的包。

minSize: 文件最小打包体积,单位byte,默认30000。

比如说某个项目下有三个入口文件,a.js和b.js和c.js都是100byte,当我们将minSize设置为301,那么webpack就会将他们打包成一个包,不会将他们拆分成为多个包。

automaticNameDelimiter: 连接符。

假设我们生成了一个公用文件名字叫vendor,a.js,和b.js都依赖他,并且我们设置的连接符是"~"那么,最终生成的就是 vendor~a~b.js

maxInitialRequests 入口点处的最大并行请求数,默认为3。

如果我们设置为1,那么每个入口文件就只会打包成为一个文件

maxAsyncRequests 最大异步请求数量,默认5

如果我们设置为1,那么每个入口文件就只会打包成为一个文件

优先级关系。

maxInitialRequest / maxAsyncRequests <maxSize <minSize。

cacheGroups 定制分割包的规则列表

test可以配置正则和写入function作为打包规则。其他属性均可继承splitChunks。

minChunks

依赖最少引入多少次才能进行拆包。

简单修改配置

复制默认配置,将chunks 改为'all'。

    optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  },
复制代码

运行打包命令

npm run build
复制代码

多了一个vendors~app.js。这个js里面储层的就是需要拆分的代码。

再次拆包

vendors~app.js是一个1.71mb的文件,太大了,继续拆包。
添加一个新的拆包规则

      cacheGroups: {
        echarts:{ // 新增拆包规则
          name:'echarts', // 规则名字
          chunks:'all', // 同步引入和异步引入都可以使用该规则
          priority:10, 
          // 该规则的优先级,比如 webpack中进行拆包的时候,
          // echarts包会有先匹配priority高的规则,如果满足这个规则,
          // 则将代码导入到该规则里面,不会将代码导入到后面的规则里面了。
          test:/(echarts)/, // 正则匹配规则
          minChunks:1 // 代码里面最少被引入1次就可以使用该规则。
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
复制代码

运行打包命令

npm run build
复制代码

echarts包就被单独拆出来了储层在echarts.js。
其实这个就是Code Splitting的概念之一官方demo

模块化拆包2

参考代码 demo9
这一节只使用splitChunks的默认配置来实现异步加载,理解异步加载的好处。
。 使用react作为开发框架。

同步引入的拆包

redux -> 16.8.6
react-dom -> 16.8.6
react-router-dom -> 5.0.0
复制代码

安装 react

npm i react react-router-dom react-dom -S
复制代码

解析 jsx代码还需要@babel/preset-react

npm i @babel/preset-react -D
复制代码

详细代码请看demo9
两个路由分别对应不同的组件。

    import About from './src/view/about';
    import Inbox from './src/view/inbox';
    // ....
    
   <App>
        <Route path="/about" component={About} />
        <Route path="/inbox" component={Inbox} />
    </App>
复制代码

inbox.js引入了echarts并且使用了。

import React from 'react';
import echarts from 'echarts';
class Inbox extends React.Component {
  componentDidMount() {
    var myChart = echarts.init(document.getElementById('main'));
    var option = {
      tooltip: {
        show: true
      },
      legend: {
        data: ['销量']
      },
      xAxis: [
        {
          type: 'category',
          data: ["衬衫", "羊毛衫2", "雪纺衫222", "裤子111", "高跟鞋", "袜子"]
        }
      ],
      yAxis: [
        {
          type: 'value'
        }
      ],
      series: [
        {
          "name": "销量",
          "type": "bar",
          "data": [5, 20, 40, 10, 10, 20]
        }
      ]
    };

    // 为echarts对象加载数据 
    myChart.setOption(option);
  }
  render() {
    return (
      <div>
        <h2>Inbox</h2>
        <div style={{ height: '400px' }} id="main"></div>
      </div>
    )
  }
}
export default Inbox;
复制代码

splitChunks 使用默认配置

    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
复制代码

运行打包命令

npm run build
复制代码

打包的时候已经提示了,打包的代码太大影响性能。可以使用import()或require来限制包的大小。确保延迟加载应用程序的某些部分。

WARNING in webpack performance recommendations: 
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/
复制代码

我们在首页的时候,并没有/inbox路由,但是/inbox路由里面的代码都打包在了app.js这一个js里面。首屏渲染变的很慢。按照官方提示需要做组件的动态引入,只有路由切换到/inbox的时候才加载对应组件的代码。

异步引入拆包

异步引入组件需要用到babel的一个插件@babel/plugin-syntax-dynamic-impor

{
	test: /\.(js|jsx)?$/,  // 正则匹配,所有的.js文件都使用这个规则进行编译
	exclude: /node_modules/, // 排除的文件夹。这个文件夹里面的的文件不进行转译
	loader: "babel-loader", // 转译的插件
	options: {  // 转译的规则
		presets: [ //转译器配置
			[
				"@babel/preset-env", {
					useBuiltIns: "usage",
					corejs: 3
				},
			],
			["@babel/preset-react"]
		],
		plugins: ["@babel/plugin-syntax-dynamic-import"] // 转译插件配置
	}
},
复制代码

新建AsyncComponent.js。

import React, { Component } from "react";

export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    constructor(props) {
      super(props);

      this.state = {
        component: null
      };
    }

    async componentDidMount() {
      const { default: component } = await importComponent();
    // 组件引入完成后渲染组件
      this.setState({
        component: component
      });
    }

    render() {
      const C = this.state.component;

      return C ? <C {...this.props} /> : null;
    }
  }

  return AsyncComponent;
}
复制代码

更换引入模式

//import Inbox from './src/view/inbox';
const Inbox = asyncComponent(() => import('./src/view/inbox'));
复制代码

运行打包命令

npm run build
复制代码

echarts被引入到1.js里面了。

npm run server
复制代码

首页并没有加载1.js

路由跳转后会引入。

react的动态引入组件就OK了。 按照官方文档,还可以自定义chunk名称

const Inbox = asyncComponent(() => import(/* webpackChunkName: "echarts" */ './src/view/inbox'));
复制代码

其实还可以异步引入依赖包

async componentDidMount() {
    const {default:echarts}= await import('echarts');
    var myChart = echarts.init(document.getElementById('main'));
}
复制代码

总结

  • 一般情况下推荐splitChunks.chunks: 'all',其他参数可以不变。'all'的意思是同步引入和异步引入都可以进行拆包。
  • 尽量小的引入所需要的插件,比如echarts官方提供了按需加载的方法。
  • 在首页没有用的某个插件的时候,尽量使用异步加载的方式进行引入。

JS Tree Shaking

参考代码 demo10

Tree Shaking

树抖动是JavaScript上下文中常用于消除死代码的术语。 它依赖于ES2015模块语法的静态结构,即导入和导出。 名称和概念已由ES2015模块捆绑器汇总推广。

摇树的意思。在webpack4.0里面会自动的把不需要的js在打包的时候剔除。当然需要开发进行配合:按需加载
我们拿lodash为例。 为了清楚我们摇掉了多少代码,先将react的包拆出来单独打包。

splitChunks: {
      chunks: 'all',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        reactVendors: {
          chunks: 'all',
          test: /(react|react-dom|react-dom-router)/,
          priority: 100,
          name: 'react',
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
复制代码

在about.js里面使用lodash

import React from 'react';
import _ from 'lodash';
class About extends React.Component {

    render() {
      const s = _.concat([1],[2,3,1,2,3,4,6,78,41]);
      return (
        <h3>{s}</h3>
      )
    }
  }
  export default About;
复制代码

运行打包命令

npm run build
复制代码

合并引入的vendors有95kb。我们只使用了lodash的concat方法,其他的方法没使用。 我们使用lodash的模块化包lodash-es

npm i lodash-es -S
复制代码

改变about.js的代码

import React from 'react';
//import _ from 'lodash';
import { concat } from 'lodash-es';
class About extends React.Component {

    render() {
      const s = concat([1],[2,3,1,2,3,4,6,78,41]);
      return (
        <h3>{s}</h3>
      )
    }
  }
  export default About;
复制代码

运行打包命令

npm run build
复制代码

现在只有28kb。缩小的部分就是loader的按需加载的部分。

Gzip压缩。

Gzip压缩的好处。

打开掘金首页
随便找一个js资源。

Response Headers 里面的 content-encoding 代表着资源传输格式。说明这段资源使用的是Gzip文件。说明很多公司用的是Gzip进行资源传输,因为代码打包成Gzip传输时需要的资源很小。

其实Gzip传输是服务器需要配置的。即使我们上传代码没有进行Gzip压缩,服务器经过配置后会将资源进行Gzip压缩然后传输。例如nginx的Gzip配置

Gzip配置都是服务器配置的工作,我们要做什么呢?其实服务器会寻找资源对应的gzip文件,如果没有则会进行Gzip压缩然后传输资源。这就多出了压缩的时间。如果我们通过webpack生成gzip文件,可以减少服务器生成gzip文件的时间,也减缓了服务器压力。

js打包压缩配置 optimization.minimiz

这个属性的属性值可以是布尔、数组。

当属性值是布尔时

在mode是prduction时,minimiz默认是true。自动使用terser-webpack-plugin进行压缩。

当属性值是数组时

如图是默认配置。

配置Gzip压缩

默认的配置不用改,使用压缩插件compression-webpack-plugin

npm i  compression-webpack-plugin -D
复制代码

配置 optimization.minimiz

minimizer: [
      new CompressionPlugin({
        filename: '[path].gz[query]', // 生成资源的名字 [path].gz[query] 是默认名字
        algorithm: 'gzip', // 压缩算法
        test: /\.(js|css|html|svg)$/, // 匹配的资源
        compressionOptions: { level: 8 }, // 压缩等级 默认9级
        threshold: 10240, // 多大资源使用才压缩 10kb
        minRatio: 0.8,
        //仅处理压缩比此比率更好的资源(minRatio =压缩尺寸/原始尺寸)。
        //示例:您拥有1024b大小的image.png文件,压缩版本的文件大小为768b,
        //因此minRatio等于0.75。换句话说,当压缩大小/原始大小值小于minRatio值时,
        //将处理资源。默认 0.8 。
        deleteOriginalAssets: false, // 是否删除原始资产。
      }),
      new TerserPlugin({
        parallel: true,
      }),
    ]
复制代码

运行打包命令

npm run build
复制代码

.zip 就是打包生成的gzip资源。对比原来的文件压缩了 38/121≈0.3。压缩了很多。

文章分类
前端
文章标签