深刻浅出的webpack构建工具---tree shaking打包性能优化(十二)

阅读目录javascript

1. 什么是tree-shaking?css

2. 在webpack中如何使用 tree-shaking 呢?html

3. 使用webpack-deep-scope-plugin 优化 java

1. 什么是tree-shaking?node

在webpack中,tree-shaking的做用是能够剔除js中用不上的代码,可是它依赖的是静态的ES6的模块语法。
也就是说没有被引用到的模块它是不会被打包进来的,能够减小咱们的包的大小,减小文件的加载时间,提升用户体验。webpack

webpack2版本中就开始引入了 tree shaking的概念,它能够在打包时能够忽略哪些没有被使用到的代码。git

注意:要让 Tree Shaking 正常工做的前提是:提交给webpack的javascript代码必须采用了 ES6的模块化语法,由于ES6模块化语法是静态的(在导入,导出语句中的路径必须是静态的字符串)。github

2. 在webpack中如何使用 tree-shaking 呢?web

在配置代码前,咱们来看看咱们项目中的目录结构以下:npm

### 目录结构以下:
demo1                                       # 工程名
|   |--- dist                               # 打包后生成的目录文件             
|   |--- node_modules                       # 全部的依赖包
|   |--- js                                 # 存放全部js文件
|   | |-- demo1.js  
|   | |-- main.js                           # js入口文件
|   |--- common                             # js公用的文件
|   | |-- util.js                           # 公用的util.js文件
|   |--- webpack.config.js                  # webpack配置文件
|   |--- index.html                         # html文件
|   |--- styles                             # 存放全部的css样式文件   
|   | |-- main.styl                         # main.styl文件   
|   | |-- index.styl                        
|   |--- .gitignore  
|   |--- README.md
|   |--- package.json
|   |--- .babelrc                           # babel转码文件

webpack.config.js 代码以下:

const path = require('path');

// 引入 mini-css-extract-plugin 插件 
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// 清除dist目录下的文件
const ClearWebpackPlugin = require('clean-webpack-plugin');

const webpack = require('webpack');

// 引入打包html文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 引入HappyPack插件 
const HappyPack = require('happypack');

module.exports = {
  // 入口文件
  entry: {
    main: './js/main.js'
  },
  output: {
    filename: '[name].[contenthash].js',
    // 将输出的文件都放在dist目录下
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 使用正则去匹配
        test: /\.styl$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {}
          },
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: [
                require('postcss-cssnext')(),
                require('cssnano')(),
                require('postcss-pxtorem')({
                  rootValue: 16,
                  unitPrecision: 5,
                  propWhiteList: []
                }),
                require('postcss-sprites')()
              ]
            }
          },
          {
            loader: 'stylus-loader',
            options: {}
          }
        ]
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'happypack/loader?id=css-pack'
        ]
      },
      {
        test: /\.(png|jpg)$/,
        use: ['happypack/loader?id=image']
      },
      {
        test: /\.js$/,
        // 将对.js文件的处理转交给id为babel的HappyPack的实列
        use: ['happypack/loader?id=babel'],
        // loader: 'babel-loader',
        exclude: path.resolve(__dirname, 'node_modules') // 排除文件
      }
    ]
  },
  resolve: {
    extensions: ['*', '.js', '.json']
  },
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    port: 8081,
    host: '0.0.0.0',
    headers: {
      'X-foo': '112233'
    },
    inline: true,
    overlay: true,
    stats: 'errors-only'
  },
  mode: 'development',
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html' // 模版文件
    }),
    new ClearWebpackPlugin(['dist']),

    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css'
    }),
    /****   使用HappyPack实例化    *****/
    new HappyPack({
      // 用惟一的标识符id来表明当前的HappyPack 处理一类特定的文件
      id: 'babel',
      // 如何处理.js文件,用法和Loader配置是同样的
      loaders: ['babel-loader']
    }),
    new HappyPack({
      id: 'image',
      loaders: [{
        loader: require.resolve('url-loader'),
        options: {
          limit: 10000,
          name: '[name].[ext]'
        }
      }]
    }),
    // 处理styl文件
    new HappyPack({
      id: 'css-pack',
      loaders: ['css-loader']
    })
  ]
};

.babelrc 配置以下:

{
  "plugins": [
     [
      "transform-runtime",
      {
        "polyfill": false
      }
     ]
   ],
   "presets": [
     [
       "env",
       {
         "modules": false   // 关闭Babel的模块转换功能,保留ES6模块化语法
       }
     ],
     "stage-2"
  ]
}

common/util.js 代码以下:

export function a() {
  alert('aaaa');
}

export function b() {
  alert('bbbbb');
}

export function c() {
  alert('cccc');
}

js/main.js 代码以下:

import { a } from '../common/util';

a();

执行 webpack后,打包文件以下:

而后继续查看 dist/main.xxx.js代码以下:

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return a; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return b; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return c; });


function a() {
  alert('aaaa');
}

function b() {
  alert('bbbbb');
}

function c() {
  alert('cccc');
}

/***/ }),

如上代码,仍是会包含 b,c 两个函数代码进来,那是由于 webpack 想要使用tree-shaking功能的话,咱们须要压缩代码,就能把没有引用的代码剔除掉,所以咱们须要在webpack中加上压缩js代码以下:

// 引入 ParallelUglifyPlugin 插件
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

module.exports = {
  plugins: [
    // 使用 ParallelUglifyPlugin 并行压缩输出JS代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS的参数以下:
      uglifyJS: {
        output: {
          /*
           是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果,
           能够设置为false
          */
          beautify: false,
          /*
           是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,能够设置为false
          */
          comments: false
        },
        compress: {
          /*
           是否在UglifyJS删除没有用到的代码时输出警告信息,默认为输出,能够设置为false关闭这些做用
           不大的警告
          */
          warnings: false,

          /*
           是否删除代码中全部的console语句,默认为不删除,开启后,会删除全部的console语句
          */
          drop_console: true,

          /*
           是否内嵌虽然已经定义了,可是只用到一次的变量,好比将 var x = 1; y = x, 转换成 y = 5, 默认为不
           转换,为了达到更好的压缩效果,能够设置为false
          */
          collapse_vars: true,

          /*
           是否提取出现了屡次可是没有定义成变量去引用的静态值,好比将 x = 'xxx'; y = 'xxx'  转换成
           var a = 'xxxx'; x = a; y = a; 默认为不转换,为了达到更好的压缩效果,能够设置为false
          */
          reduce_vars: true
        }
      }
    })
  ]
}

再运行下打包命令后。咱们继续查看代码,以下所示:

能够看到仍是会把无用的 b函数 和 c函数代码打包进去。这是什么状况?那是由于咱们在webpack中配置了 mode: 'development',咱们如今把它改为 mode: 'production',后,就能够看到只用 a函数了,咱们能够到dist目录下的main.js代码内部搜索下 alert, 就能够看到了,只有一个alert('a')了。说明b函数和c函数被剔除掉了。

tree-shaking 目前的缺陷:

tree-shaking 可以利用ES6的静态引入规范,减小包的体积,避免没必要要的代码引入,可是webpack只能作一点简单的事情。
好比 我如今在main.js代码改为以下:

import { func2 } from '../common/util';

var a = func2(222);

alert(a);

common/util.js 代码以下:

import lodash from 'lodash-es'

var func1 = function(v) {
  alert('111');
  return lodash.isArray(v);
}

var func2 = function(v) {
  return v;
};

export {
  func1,
  func2
}

如上代码,在main.js中引入了 func2, 可是并无引入func1, 可是func1引入了lodash-es。webpack在检查的时候发现func1中确实用到了lodash-es,所以不会把lodash去掉,可是func1函数会去掉的。可是咱们在js中也并无使用到lodash。所以在这种状况下,webpack中的 tree-shaking 解决不了这种状况,所以 webpack-deep-scope-plugin 插件就能够解决这种问题了,以下没有使用 webpack-deep-scope-plugin 插件打包后的文件大小。以下:

如上main.js 打包压缩后的js代码大小有81.1kb。打开dist/main.js代码搜索 lodash后,能够搜索到,所以lodash插件被打包进去main.js中了,可是实际上咱们项目并无使用到lodash,所以lodash的库咱们按常理来说并不须要打包进去的。

3. 使用webpack-deep-scope-plugin 优化

该插件 的github上的代码

1. 首先须要安装 webpack-deep-scope-plugin, 安装命令以下:

npm i -D webpack-deep-scope-plugin

在webpack.config.js 代码引入以下:

// 引入 webpack-deep-scope-plugin 优化
const WebpackDeepScopeAnalysisPlugin = require('webpack-deep-scope-plugin').default;

module.exports = {
  plugins: [
    new WebpackDeepScopeAnalysisPlugin()
  ]
}

而后咱们继续打包以下所示:

打包后发现如上,只有969字节,1kb都不到,再打开dist/main.js 查看代码,搜索下 lodash, 发现搜索不到。

注意点:
1. 要使用 tree-shaking,必须保证引用的插件的模块是ES6模块规范编写的,这也是我为何引用了的是 lodash-es,而不是 'lodash', 若是引用的是lodash的话,是不能去掉的。

2. 在 .babelrc 中,babel设置 module: false, 避免babel将模块转换为成 CommonJS规范。引入模块包也必须符合ES6规范的。以下 babelrc代码:

{
  "plugins": [
     [
      "transform-runtime",
      {
        "polyfill": false
      }
     ]
   ],
   "presets": [
     [
       "env",
       {
         "modules": false   // 关闭Babel的模块转换功能,保留ES6模块化语法
       }
     ],
     "stage-2"
  ]
}

且须要在 package.json 中定义 sideEffect: false, 这也是为了不出现 import xxx 致使模块内部的一些函数执行后影响全局环境, 却被去除掉的状况.

3. webpack-deep-scope-plugin 插件依赖 node8.0+ 和 webpack 4.14.0 +

查看github上的demo

相关文章
相关标签/搜索