6. 性能优化之js和css压缩、代码分割及ejs组件抽离

481 阅读1分钟

css文件独立打包

在当前项目中,我们是把所有的css、js及图片都打包到index.js里面的,这样会导致index.js会比较大,其实并不是css文件比较大,而是为了让css代码运行起来,需要加入css-loader 和 style-loader,这两个loader会添加大量的相关代码。这部分的代码量是非常大的,这样会使我们的index.js会变得很大。所以需要单独把css文件单独提取出来。

  1. 安装mini-css-extract-plugin
npm install mini-css-extract-plugin -D
  1. 在webpack.config.js中添加相关配置
// 修改loader
{
   test: /\.css$/,
   use: [MiniCssExtractPlugin.loader, 'css-loader']
}
// 修改plugins
new MiniCssExtractPlugin({
  filename: 'css/[name].[contenthash:8].css',
  chunkFilename: 'css/[name].chunk.css'
})

代码中用MiniCssExtractPlugin.loader替换了style-loader,它的作用是把css-loader写到index.js里面的css代码进行剥离。我们知道对源码继续处理是loader的事情,但是剥离文件,生成文件等是在plugin里面做的,所以还需要在plugin中进行配置。

最后生成的index.css 和 login.css是该页面所有css文件的合并,这样减少请求。

js和css文件压缩

  1. 安装css-minimizer-webpack-plugin,terser-webpack-plugin
npm install css-minimizer-webpack-plugin -D
npm install terser-webpack-plugin -D
  1. 在webpack.config.js中添加相应配置
const TerserPlugin = require('terser-webpack-plugin')
const CssMiniPlugin = require('css-minimizer-webpack-plugin')

// 添加optimization属性
optimization: {
  // 开启压缩,当处于开发模式下,开启这个配置也能压缩
  minimize: true,
  minimizer: [
    new TerserPlugin({
      extractComments: false
    }),
    new CssMiniPlugin({})
  ]
}

执行npm run build命令重新打包,可以看到css和js文件代码都进行了压缩。

tips: 在生成模式会自动进行压缩

tree-shaking

Tree-Shaking 是一个前端术语,本意为摇树的意思,通常用于描述移除 JS 中没用的代码,这样可以有效地缩减打包体积。关于Tree-Shaking的详细介绍可以参考这篇文章Tree-Shaking的使用

在 Webpack 中满足什么条件, tree-shaking才会生效呢?需要满足一下条件:

打包模式mode必须是production 因为只有在生产模式下,webpack 才会利用terser-webpack-plugin去删除无用的代码。

通过解构的方式引用

// 触发 tree-shaking
import { get } from 'lodash-es'
console.log(get({ a: 1 }, 'a'))

// 不会触发 tree-shaking
import _ from 'lodash-es'
console.log(_.get({ a: 1 }, 'a'))

在我们自己写npm包的时候,最好通过 export 导出,比如:

// utils.js
export function a() {}
export function b() {}

// index.js
import { a } from './utils.js'

像上面这种写法是可以出发tree-shaking的,如果通过export default 导出,则不会触发tree-shaking,比如:

// utils.js
export default {
  a() {},
  b() {}
}

// index.js
import utils from './utils.js'
utils.a()

通过export default导出,引用的时候不能通过解构的方式引用,export default它是整体输出一个对象,但是你无法分清对象里面的函数a,b是否被调用,只要有一个方法被调用,那么这个对象必须存在,所以对象是没有办法做tree-shaking。

调用npm包必须使用ESM规范 lodash工具库是使用CJS模块规范的,当你即使通过解构的方式引用,也不会触发tree-shaking。所以,后来就出现了lodash-es,它是使用了ESM规范。所以,我们在引入第三方模块的时候尽量使用ESM规范的库。

// 不会触发tree-shaking
import { get } from 'lodash'

代码分割

我们来看看首页入口文件src/index.js引入了那些库:

import './css/public.css'
import './css/index.css'

import 'jquery'
import './js/public'
import './js/nav'

// 这部分代码只是为了演示如何进行代码分割
import { get } from 'lodash-es'
console.log(get({ a: 1 }, 'a'))

webpack.config.js配置

// 分析打包后的文件
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  
// optimization
optimization: {
  minimize: true,
  minimizer: [
    new TerserPlugin({
      extractComments: false
    }),
    new CssMiniPlugin({})
  ],
  splitChunks: {
    chunks: 'all',
    minSize: 300 * 1024,
    name: 'common'
  }
}

// plugins
plugins: [
  new BundleAnalyzerPlugin()
]

上面对splitChunks的配置的意思是,如果入口文件引入的第三方库总体积大小超过了300kb,那么就个拆分出来,文件名称为common.js,该文件包含了jquery, lodash-es及flexslider。如下如所示:

image.png

如果把 minSize 设置为 400 * 1024,代码就不会分割,第三方库会打到入口文件中,如下图:

image.png

现在如果我想把jquery单独拆分出来怎么办呢?配置如下:

// optimization
optimization: {
  minimize: true,
  minimizer: [
    new TerserPlugin({
      extractComments: false
    }),
    new CssMiniPlugin({})
  ],
  splitChunks: {
    chunks: 'all',
    minSize: 280 * 1024,
    name: 'common',
    cacheGroups: {
      jquery: {
        name: 'jquery',
        test: /jquery\.js/,
        chunks: 'all'
      }
    }
  }
}

因为jquery库的体积是280kb,所以需要把minSize调成小于280kb,执行打包后jquery会被单独抽离出来,其他的库如lodash和flexslider的体积不满足要求,所以不会被拆分,而是被打入到入口文件index.js中,如下图:

image.png

如果我们想把lodash-es也拆分出来呢?只需要把minSize调低到满足lodash-es大小即可:

splitChunks: {
  chunks: 'all',
  minSize: 30 * 1024,
  name: 'common',
  cacheGroups: {
    jquery: {
      name: 'jquery',
      test: /jquery\.js/,
      chunks: 'all'
    },
    'lodash-es': {
      name: 'lodash-es',
      test: /lodash-es/,
      chunks: 'all'
    }
  }
}

image.png

可以看到,jquery,lodash-es及flexslider都被拆分了,因为这三个库都满足所设置的条件。

注意:如果没有设置cacheGroups,那么minSize就是针对的整体包的体积大小,而不是单个库的体积大小。如果设置了cacheGroups,minSize就是针对单个库的大小进行拆分。

ejs组件抽离

  1. 安装 ejs-loader
npm install ejs-loader -D
  1. 修改webpack.config.js配置
{
    test: /\.ejs$/,
    use: {
      loader: 'ejs-loader',
      options: {
        esModule: false
      }
    }
}
  1. 提取模板,新建ejs/footer.ejs、ejs/header.ejs文件
// header.ejs 把原来index.html中的header部分直接移植过来即可
<div class="head">
  <div class="wrapper clearfix">
    ......
  </div>
</div>
  1. 修改index.html
// 引入头部组件
<%=require('./ejs/header.ejs')() %>
  1. require里面可以传入参数,实现组件动态化
// index.html
<%=require('./ejs/header.ejs')({title: '首页'}) %>

// header.ejs 接受页面传递过来的值
<li><a href="index.html"><%=title%></a></li>