Skip to content

🛴 序言

前面的两篇文章中,我们讲解了 webpack 的入门知识。但是呢,入门知识了解了之后,总得应用到具体的案例当中来。

因此,在下面的这篇文章中,将带领大家来了解关于 webpack 的一些实战案例配置,包括第三方库、 PWAts 的打包配置,以及 WebpackDevServer 的进阶操作,还有需重点掌握的,关于 webpack 如何做性能优化处理这个问题。

下面开始本文的介绍~🚦

🚌 一、Library 的打包

假设我们现在要开发一个组件库或者一个函数库时,对于这样的库代码,我们应该如何让 webpack 来进行打包呢?

1. webpack 打包库

假如我们现在写了很多逻辑代码,同时呢,我们将这些逻辑代码进行打包,打包之后它全部生成在 dist 文件夹下的 mondaylib.js 文件中。

好了,现在这个库生成了。那如何让我们的用户,来引入 mondaylib 这个库呢?

一般情况下,别人引入我们的库的方法,具体有以下几种方式:

js
//方式一
import mondaylib from 'mondaylib';
//方式二
const mondaylib = require('mondaylib');
//方式三
require(['mondaylib'], function () {});
//方式一
import mondaylib from 'mondaylib';
//方式二
const mondaylib = require('mondaylib');
//方式三
require(['mondaylib'], function () {});

所以,如果想让我们的用户来引入这个库,我们需要在 webpack.config.js 下进行配置。具体配置如下:

js
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'mondaylib.js',
    //此处配置libraryTarget,umd表示支持commonJS这种语法
    libraryTarget: 'umd',
  },
};
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'mondaylib.js',
    //此处配置libraryTarget,umd表示支持commonJS这种语法
    libraryTarget: 'umd',
  },
};

libraryTarget: 'umd' 表示支持 commonJS 这种语法,因此可以引入上面三种方式。同时, libraryTarget 也可以设置为其他值,比如 thiswindow

libraryTarget: 'this' 的意思为,我要让 mondaylib 这个变量,挂载到页面上。 libraryTarget: 'window' 则表示为, mondaylib 这个变量,将挂载到 window 上。


除了以上三种情况,还有另外特殊情况,具体如下:

html
<script src="mondaylib.js"></script>
<script src="mondaylib.js"></script>

此时我们需要在 webpack.config.js 中进行以下配置:

js
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'mondaylib.js',
    //将打包生成的代码挂载到页面的全局变量上
    library: 'mondaylib',
    //此处配置libraryTarget,umd表示支持commonJS这种语法
    libraryTarget: 'umd',
  },
};
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'mondaylib.js',
    //将打包生成的代码挂载到页面的全局变量上
    library: 'mondaylib',
    //此处配置libraryTarget,umd表示支持commonJS这种语法
    libraryTarget: 'umd',
  },
};

其中,第一个 library 是属性值,第二个 mondaylib 是我们的库名,这个配置的意思为,将打包生成的代码,即 mondaylib.js 这个库,给挂载到全局的变量上。

2. 库引用冲突

假设现在,我们在上面 mondaylib 这个库中,引入了 lodash 这个库。然后呢,当用户使用的时候,用户又再引入了一次 lodash 这个库。像下面代码这样:

js
import _ from 'lodash';
//mondaylib这个库原先已经引入过lodash这个库
import mondaylib from 'mondaylib';
import _ from 'lodash';
//mondaylib这个库原先已经引入过lodash这个库
import mondaylib from 'mondaylib';

所以现在,我们该如何来避免这种问题发生呢?我们再 webpack.config.js 中进行配置,具体代码如下:

js
const path = require('path');

module.exports = {
  externals: ['lodash'],
};
const path = require('path');

module.exports = {
  externals: ['lodash'],
};

从以上代码中我们可以知道,通过 externals: ["lodash"] 这个配置,来告诉 webpack ,告诉它说,如果在打包过程中, mondaylib 这个库中如果有遇到 lodash 这个库,那么就避开它,不要进行打包。通过这种方式,可以有效地避免库多次引用的问题,减少代码的打包大小。


externals 还有另外一种特殊配置,如下代码所示:

js
module.exports = {
  externals: {
    // 表明lodash这个库如果在commonjs这种环境下被使用,那么要求lodash加载的时候必须叫做lodash
    lodash: {
      commonjs: 'lodash',
    },
  },
};
module.exports = {
  externals: {
    // 表明lodash这个库如果在commonjs这种环境下被使用,那么要求lodash加载的时候必须叫做lodash
    lodash: {
      commonjs: 'lodash',
    },
  },
};

如果这样配置,意在表明 lodash 这个库如果在 commonjs 这种环境下被使用,那么要求 lodash 加载的时候,必须命名为 lodash ,而不能随意命名。像下面这样:

js
//✔可使用方式:命名为lodash
import lodash from 'lodash';
//✘不可使用方式:未命名为lodash
import _ from 'lodash';
//✔可使用方式:命名为lodash
import lodash from 'lodash';
//✘不可使用方式:未命名为lodash
import _ from 'lodash';

🚍 二、PWA 的打包配置

1. PWA 是什么

PWA,全称为 Progressive Web Application ,即渐进式 Web 应用程序。

PWA 是一门比较新的前端技术,那它是一种什么样的技术呢?

PWA 可以实现的效果是,如果你访问一个网站,有可能第一次你访问成功了,但是呢,突然间这个网站的服务器挂掉了。那么这个时候你访问网站应该就是没办法访问了。但是呢, PWA 会将你第一次访问的页面给缓存起来。之后即使服务器挂掉了,你依然可以把之前看到的页面再展示出来。

因此, webpack 中有一个插件,可以来实现这样的效果。我们来了解一下~

2. webpack 中的 PWA

第一步: 安装插件。具体代码如下:

bash
npm install workbox-webpack-plugin --save-dev
npm install workbox-webpack-plugin --save-dev

第二步:webpack.prod.js 中引入该插件并使用。具体代码如下:

js
const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
  plugins: [
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true,
    }),
  ],
};
const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
  plugins: [
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true,
    }),
  ],
};

通常情况下,我们只需要在线上环境 prod 引入 PWA ,而在开发环境中不需要考虑这个问题。通过以上的配置,我们在项目打包完成之后, dist 目录下将生成两个新的文件,一个是 service-worker.js ,另外一个是 precache.js 文件,这两个文件就是供我们来使用 PWA 的。


第三步: 引入以上文件。具体代码如下:

js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then((registration) => {
        console.log('service-worker registed');
      })
      .catch((error) => {
        console.log('service-worker regist error');
      });
  });
}
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then((registration) => {
        console.log('service-worker registed');
      })
      .catch((error) => {
        console.log('service-worker regist error');
      });
  });
}

我们需要在入口文件中,写一段业务代码,引入 service-worker.js 文件,来帮我们做 PWA 。这个时候我们对项目进行打包,之后呢,如果出现服务器突然挂了的情况,那也不用担心, PWA 会帮我们加载原先的页面来提供给我们浏览。

🚎 三、TypeScript 的打包配置

1. 引例阐述

我们都知道,对于不同的开发者来说,不同的人写出的代码风格形式各异,这样在后期,项目的维护性就很难得到保证。那么,这个时候,风靡于 2018 年的 Typescript 出现了。 ts 规范了一套 js 的标准,因此,我们在项目代码的编写中,通过 ts ,就可以规范我们的代码,并且使得我们项目的维可维护性和可扩展性变得更好了。

接下来,我们就来了解一下,如何通过 webpack 的配置变更,来实现对 ts 语法的支持。

2. webpack 对 ts 的配置

(1)背景

假设我们现在有这么一段 ts 的代码需要进行编译,具体代码如下:

js
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return 'Hello, ' + this.greeting;
  }
}

let greeter = new Greeter('world');

let button = document.createElement('button');
button.textContent = 'Say Hello';
button.click = function () {
  alert(greeter.greet());
};

document.body.appendChild(button);
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return 'Hello, ' + this.greeting;
  }
}

let greeter = new Greeter('world');

let button = document.createElement('button');
button.textContent = 'Say Hello';
button.click = function () {
  alert(greeter.greet());
};

document.body.appendChild(button);

现在,我们想要让 webpack 来对这段 ts 代码进行编译,该怎么处理呢?

(2)配置步骤

第一步: 安装 ts-loader具体命令如下:

bash
npm install ts-loader typescript -D
npm install ts-loader typescript -D

第二步: 我们在 webpack.config.js 文件下进行配置。具体代码如下:

js
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.tsx',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.tsx',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

第三步: 配置 tsconfig.json 文件。具体代码如下:

json
{
  "compilerOptions": {
    "outDir": "./dist",
    "module": "es6",
    "target": "es5",
    "allowJs": true
  }
}
{
  "compilerOptions": {
    "outDir": "./dist",
    "module": "es6",
    "target": "es5",
    "allowJs": true
  }
}

3. ts 识别第三方库

有时候,我们会调用 lodash 中的 join 方法,但是呢,如果我们不进行特殊处理的话,在 tsx 文件中正常引入 lodash 这个库并使用,是不会报错的。因此,我们需要来安装另外一个 ts 的库,来对它进行处理一下。具体步骤如下:

第一步: 安装 @types/lodash 库。具体命令如下:

bash
npm install @types/lodash --save-dev
npm install @types/lodash --save-dev

安装完这个库之后, ts 就可以去识别 lodash 的一些函数和方法,一旦出现引用不对,就会进行报错提示。

那有小伙伴就会有疑问说,是不是 ts 拥有所有这样的库(像 jQuery 等各种库)的类型文件呢?

答案当然是否定的。我们可以到 github 上的一个网址👉https://microsoft.github.io/TypeSearch/来进行搜索,如果搜索得到,那么我们就可以用 @type/库名 来进行安装,之后 tsx 文件就会支持对该库的类型检查。

🚕 四、WebpackDevServer 进阶操作

1. WebpackDevServer 实现请求转发

一般情况下,我们可以通过 charles fiddler 工具,在本地搭建一个代理服务器。通过这台代理服务器,将我们想要请求的接口地址进行转发。

那在 webpack 中,给我们提供了一个工具, devServer.Proxy 。接下来,我们在 webpack.config.js 中进行配置,具体代码如下:

js
module.exports = {
  devServer: {
    proxy: {
      '/react/api': {
        target: 'http://www.mondaylab.com',
        //实现对https网址的请求转发
        secure: false,
        bypass: function (req, res, proxyOptions) {
          //如果请求的内容是一个html地址,那么就直接返回根路径下index.html的内容
          if (req.headers.accept.indexOf('html') !== -1) {
            console.log('Skipping proxy for browser request');
            return './index.html';
            // return false; //表示如果遇到html请求,那么该给你返回什么就返回什么
          }
        },
        //在前端请求时写header.json,webpack会间接的帮我们拿到demo.json的数据(请求转发】)
        pathRewrite: {
          'header.json': 'demo.json',
        },
        //如果有一些网站做了防爬虫,那么我们可能没办法进行跨域。需要进行如下配置,就可以突破对origin的限制
        changeOrigin: true,
        // 在请求头中自定义一些内容
        headers: {
          host: 'www.mondaylab.com',
          //在做请求转发时模拟一些登录等操作
          cookie: 'gfhgfh',
        },
      },
    },
  },
};
module.exports = {
  devServer: {
    proxy: {
      '/react/api': {
        target: 'http://www.mondaylab.com',
        //实现对https网址的请求转发
        secure: false,
        bypass: function (req, res, proxyOptions) {
          //如果请求的内容是一个html地址,那么就直接返回根路径下index.html的内容
          if (req.headers.accept.indexOf('html') !== -1) {
            console.log('Skipping proxy for browser request');
            return './index.html';
            // return false; //表示如果遇到html请求,那么该给你返回什么就返回什么
          }
        },
        //在前端请求时写header.json,webpack会间接的帮我们拿到demo.json的数据(请求转发】)
        pathRewrite: {
          'header.json': 'demo.json',
        },
        //如果有一些网站做了防爬虫,那么我们可能没办法进行跨域。需要进行如下配置,就可以突破对origin的限制
        changeOrigin: true,
        // 在请求头中自定义一些内容
        headers: {
          host: 'www.mondaylab.com',
          //在做请求转发时模拟一些登录等操作
          cookie: 'gfhgfh',
        },
      },
    },
  },
};

2. WebpackDevServer 解决单页面应用路由问题

对于现代的的主流框架来说,像 vue.jsReact.js 等等框架,基本都是单页面应用。那么,在单页面应用里,比如我们想要从 http://mondaylab.com 跳转到 http://mondaylab.com/list ,该怎么样进行跳转呢?

这就要谈到一个单页面应用的路由问题。我们需要在 webpack.config.js 中进行如下配置:

js
module.exports = {
  devServer: {
    //第一种方式
    historyApiFallback: true,
    /*等同于
        historyApiFallback: {
            rewrites: [{
                from: /\.*\/,
                to: '/list.html/'
            }]
        }
        */

    /*第二种方式
        historyApiFallback: {
            rewrites: [{
                from: /abc.html/,
                //进行转换,当访问abc.html时,会把list.html的内容展示出来
                to: '/list.html/'
            }]
        }
        */

    /*第三种方式:更为灵活
        historyApiFallback: {
            rewrites: [{
            	//表明在做页面的替换时,通过一个函数function的形式,结合context的一些参数
            	//做一些js的逻辑放在里面,来决定最终它跳转到哪
                from: /^\/(libs)\/.*$/,
                to: function(context){
                	return '/bower_components' + context.match[0];
                }
            }]
        }
        */
  },
};
module.exports = {
  devServer: {
    //第一种方式
    historyApiFallback: true,
    /*等同于
        historyApiFallback: {
            rewrites: [{
                from: /\.*\/,
                to: '/list.html/'
            }]
        }
        */

    /*第二种方式
        historyApiFallback: {
            rewrites: [{
                from: /abc.html/,
                //进行转换,当访问abc.html时,会把list.html的内容展示出来
                to: '/list.html/'
            }]
        }
        */

    /*第三种方式:更为灵活
        historyApiFallback: {
            rewrites: [{
            	//表明在做页面的替换时,通过一个函数function的形式,结合context的一些参数
            	//做一些js的逻辑放在里面,来决定最终它跳转到哪
                from: /^\/(libs)\/.*$/,
                to: function(context){
                	return '/bower_components' + context.match[0];
                }
            }]
        }
        */
  },
};

值得注意的是, historyApiFallback 只能用于开发环境下。如果到了线上环境的话,需要让后端的小伙伴去 nginx 或者 apache 上,仿照 webpackDevServer 的一些配置,在后端的服务器上做同样的配置,配置之后前端才可以使用对应的路由。

🚖 五、ESLint 在 Webpack 中的配置

1. ESLint 是什么

在我们日常的团队开发中,每个人写的代码都各式各样,比如有的人喜欢在代码后边加个分号,有的人又不喜欢加。这间接地,就很容易导致我们项目的可维护性变差了。因此呢,我们就引入了 ESLint 这个内容,来约束的代码规范,让项目的可维护性和可扩展性变高。

ESLintWebpack 中是怎么样配置的呢?

2. 如何安装 ESLint

第一步: 安装 ESLint 工具。具体命令如下:

bash
npm install eslint --save-dev
npm install eslint --save-dev

第二步: 约束我们的代码。我们需要新建一个配置文件,来对我们的 ESLint 规范进行配置。具体命令如下:

bash
npx eslint --init
> Use a popular style guide 使用通用的代码检测模板
> Airbnb
> Do you use React 根据自身需求填y或者n
> Javascript
> Would you like to install them now with npm? Y
npx eslint --init
> Use a popular style guide 使用通用的代码检测模板
> Airbnb
> Do you use React 根据自身需求填y或者n
> Javascript
> Would you like to install them now with npm? Y

第三步: 使用 eslint 检测代码规范。具体代码如下:

bash
npx eslint src
npx eslint src

以上代码表示的是,使用 eslint 来检测 src 目录下的代码规范。

3. 为什么要在 webpack 中配置 ESLint

在上面中我们了解到,每个开发的小伙伴都可以用命令行来检测自己的代码规范,但是如果每回写完一次代码,我们都要去运行这样的命令,才能看我们的代码写的合不合理,这样会不会就有点麻烦了。同时,我们又也不能保证每个人的 eslint 的代码规范设置是不是一样的。

因此,我们可以在 webpack 中来进行配置,解决上述所说的问题。具体步骤如下:

第一步: 安装 eslint-loader具体命令行如下:

bash
npm install eslint-loader --save-dev
npm install eslint-loader --save-dev

第二步: 配置 webpack.config.js具体代码如下:

js
module.exports = {
  devServer: {
    /*当我们运行webpack做打包时,
      一旦代码出现规范问题,
      webpack将会在浏览器上弹出一个报错层来提示我们*/
    overlay: true,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader', 'eslint-loader'],
      },
    ],
  },
};
module.exports = {
  devServer: {
    /*当我们运行webpack做打包时,
      一旦代码出现规范问题,
      webpack将会在浏览器上弹出一个报错层来提示我们*/
    overlay: true,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader', 'eslint-loader'],
      },
    ],
  },
};

了解完基础配置之后,接下来我们来了解一些 eslint-loader 的一些其他配置。具体代码如下:

js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          'babel-loader',
          {
            loader: 'eslint-loader',
            options: {
              //如果代码有一些比较浅显的问题,eslint-loader将会帮助我们自动修复
              fix: true,
              //降低eslint在打包过程中对项目性能的损耗
              cache: true,
            },
            //强制eslint-loader先执行
            fore: 'pre',
          },
        ],
      },
    ],
  },
};
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          'babel-loader',
          {
            loader: 'eslint-loader',
            options: {
              //如果代码有一些比较浅显的问题,eslint-loader将会帮助我们自动修复
              fix: true,
              //降低eslint在打包过程中对项目性能的损耗
              cache: true,
            },
            //强制eslint-loader先执行
            fore: 'pre',
          },
        ],
      },
    ],
  },
};

🏎️ 六、Webpack 性能优化

细心的小伙伴可能已经发现,webpack 的打包速度有时候可能会有一点点慢。这间接地,会浪费我们很多不该浪费的时间。所以,接下来就来谈谈,提升 Webpack 打包速度的几种方法。

1. 跟上技术的迭代(Node,Npm,Yarn)

如果我们想提升 webpack 的打包速度,我们可以升级 webpack 的版本,或者升级我们的 node、npm 管理器或者 yarn 的版本。

那为什么升级这些工具可以提升 webpack 的打包速度呢。

大家想一下, webpack 在做每一个版本的更新时,内部肯定会做很多版本的优化,因此,当我们做 webpack 的版本更新时,在速度上肯定会有所提升。 nodenpmyarn 的更新也是同样的道理。

试想一下,如果不提升,那它升级的意义又在哪呢?对吧。

2. 在尽可能少的模块上应用 Loader

一般情况下,第三方模块的库都是已经进行打包编译过的,所以我们需要在引入 loader 来进行编译时,对 node_module 的文件给忽略掉,或者只在某个文件夹下使用某个 loader ,以此来增加我们的打包速度。我们可以在 webpack.config.js 中进行配置,具体代码如下:

js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        //或者以下这种方式->include
        //include: path.resolve(__dirname, '../src'),
        use: [
          {
            loader: 'babel-loader',
          },
        ],
      },
    ],
  },
};
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        //或者以下这种方式->include
        //include: path.resolve(__dirname, '../src'),
        use: [
          {
            loader: 'babel-loader',
          },
        ],
      },
    ],
  },
};

当然,依据上面这个思路,还有其他的 loader 也都有其相对应的注意事项,这里不再展开细述。

3. 合理使用插件

插件要合理的使用,不要使用那些冗余的,没有意义的插件。同时呢,也要选择那些,性能比较好的,官方认可的插件来使用,这样,可以有效的来提升 webpack 的打包速度。

4. resolve 参数合理配置

(1)常见配置

有时候,我们想要对我们引入的文件进行一些自定义的配置,该怎么处理呢?具体配置如下:

js
module.exports = {
  resolve: {
    extensions: ['js', 'jsx'],
    /**
     * 1.当你只引入一个目录时,比如 import Child from './child/child' ,
     * 这个时候webpack不知道我们具体是要引入哪个文件,
     * 那么会先去找index文件,如果找不到,那它会继续去找child文件
     */
    mainFiles: ['index', 'child'],
    /**
     * alias, 顾名思义是别名。
     * 比如,想要将某个文件的引入方式改为自己的名字
     * import Child from './Child' -> import Child from 'monday'
     */
    alias: {
      monday: path.resolve(__dirname, '../src/Child'),
    },
  },
};
module.exports = {
  resolve: {
    extensions: ['js', 'jsx'],
    /**
     * 1.当你只引入一个目录时,比如 import Child from './child/child' ,
     * 这个时候webpack不知道我们具体是要引入哪个文件,
     * 那么会先去找index文件,如果找不到,那它会继续去找child文件
     */
    mainFiles: ['index', 'child'],
    /**
     * alias, 顾名思义是别名。
     * 比如,想要将某个文件的引入方式改为自己的名字
     * import Child from './Child' -> import Child from 'monday'
     */
    alias: {
      monday: path.resolve(__dirname, '../src/Child'),
    },
  },
};

接下来对以上的几个参数进行详细讲解。

(2)参数讲解

1)extensions

  • 比如当引入 import Child from './child/child' 时,会先去找 './child/child.js' 文件,找不到再去找 './child/child.jsx' 文件。
  • 一般不配置 css 和图片文件,因为 css 和图片可能数量会比较多,相应的会去执行很多次查找。间接地,本来想提升性能,结果又变成了浪费性能了。
  • 所以,如果是像 cssjpg 等之类的资源文件,应该进行显式的引入;如果是像 jsjsx 之类的逻辑文件,可以在 extesions 中配置,进行显式的引入。

2)mainFiles

当你只引入一个目录时,比如 import Child from './child/child' ,这个时候 webpack 不知道我们具体是要引入哪个文件,那么会先去找 index 文件,如果找不到,那它会继续去找 child 文件。

3)alias

  • alias , 顾名思义是别名。比如,想要将某个文件的引入方式改为自己的名字 import Child from './Child' -> import Child from 'monday'
  • 常用于多层级目录的情况下。

5. 使用 DllPlugin 提高打包速度

有时候,我们希望引入的第三方模块,只在第一次做打包的时候进行分析,而后续再做打包时就不要再进行分析了。那该怎么处理呢?

第一步: 新建 webpack.dll.js 文件,并进行配置。具体代码如下:

js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'production',
  entry: {
    //此处填写我们想要单独进行打包的第三方库名
    vendors: ['react', 'react-dom', 'lodash'],
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, '../dll'),
    /*用library把第三方模块里面的所有代码,
        通过全局变量的方式暴露出去*/
    library: '[name]',
  },
  plugins: [
    /*暴露完成之后,借助DllPlugin插件对暴露出来的模块代码进行分析,最终生成manifest.json的文件 */
    new webpack.DllPlugin({
      //对生成的vendors的库进行DllPlugin的分析(文件映射)
      name: '[name]',
      // 分析结果所放的路径,分析之后结合全局变量,在common.js中进行配置
      path: path.resolve(__dirname, '../dll/[name].manifest.json'),
    }),
  ],
};
const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'production',
  entry: {
    //此处填写我们想要单独进行打包的第三方库名
    vendors: ['react', 'react-dom', 'lodash'],
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, '../dll'),
    /*用library把第三方模块里面的所有代码,
        通过全局变量的方式暴露出去*/
    library: '[name]',
  },
  plugins: [
    /*暴露完成之后,借助DllPlugin插件对暴露出来的模块代码进行分析,最终生成manifest.json的文件 */
    new webpack.DllPlugin({
      //对生成的vendors的库进行DllPlugin的分析(文件映射)
      name: '[name]',
      // 分析结果所放的路径,分析之后结合全局变量,在common.js中进行配置
      path: path.resolve(__dirname, '../dll/[name].manifest.json'),
    }),
  ],
};

第二步: 安装 add-asset-html-webpack-plugin 插件。具体命令如下:

bash
npm install add-asset-html-webpack-plugin --save
npm install add-asset-html-webpack-plugin --save

第三步: 配置 webpack.common.js 文件。具体配置如下:

js
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  entry: {
    main: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
        template: 'src/index.html',
      }),
      /**
       * 指的是要往HtmlWebpackPlugin插件生成的index.html中添加一些内容
       */
      new AddAssetHtmlWebpackPlugin({
        filepath: path.resolve(__dirname, '../dll/vendors.dll.js'),
      }),
      /**
       * 1.使用DllReferencePlugin插件,
       * 这个插件会到'../dll/vendors.manifest.json'中找第三方模块的映射关系,
       * 如果能找到映射关系,那么webpack就会知道,这个第三方模块没有必要再打包进来了,
       * 直接从vendors.dll.js中拿过来用就可以了
       * 2.如果发现并不再映射关系里面,那么才会再到node_modules中去找
       *
       */
      new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, '../dll/vendors.manifest.json'),
      }),
    ],
  },
};
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  entry: {
    main: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
        template: 'src/index.html',
      }),
      /**
       * 指的是要往HtmlWebpackPlugin插件生成的index.html中添加一些内容
       */
      new AddAssetHtmlWebpackPlugin({
        filepath: path.resolve(__dirname, '../dll/vendors.dll.js'),
      }),
      /**
       * 1.使用DllReferencePlugin插件,
       * 这个插件会到'../dll/vendors.manifest.json'中找第三方模块的映射关系,
       * 如果能找到映射关系,那么webpack就会知道,这个第三方模块没有必要再打包进来了,
       * 直接从vendors.dll.js中拿过来用就可以了
       * 2.如果发现并不再映射关系里面,那么才会再到node_modules中去找
       *
       */
      new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, '../dll/vendors.manifest.json'),
      }),
    ],
  },
};

第四步: 配置 package.json 文件。具体代码如下:

json
{
  "scripts": {
    "build:dll": "webpack --config ./build/webpack.dll.js"
  }
}
{
  "scripts": {
    "build:dll": "webpack --config ./build/webpack.dll.js"
  }
}

通过运行 npm run build:dll 命令,来实现对第三方库的打包。

6. 控制包文件大小

在我们做项目打包时,我们应该让我们打包生成的文件尽可能的小。有时我们在写代码的时候,经常会在页面里面引入一些没有用的模块,也就是引入一些我们使用的模块。

这个时候如果你没有配置 Tree-Shaking ,就会很容易造成打包的时候,出现大量冗余的代码。这些冗余代码呢,间接地,就会拖累我们 webpack 的打包速度。

所以呢,我们在做打包时,要控制好文件的大小。具体步骤参考如下:

  • 配置 Tree-shaking
  • 通过 SplittingChunk 来将一个大的文件分割成多个小的文件。

7. 多进程打包

webpack 是通过 node 来运行的,所以它的打包过程是单线程的。那有时候呢,我们也可以借助 node 里面的多进程,来帮助我们提升 webpack 的打包速度。

常见工具有 thread-loaderparallel-webpackhappypack 等工具,大家可以依据自身需求查找相关资料,选出最适合自己项目的工具,进行打包。这一块不再进行详细讲解~

8. 合理使用 sourceMap

通常情况下, sourceMap 越详细,那么打包的速度就会越慢。所以在做打包的时候,要根据当前是开发环境还是生产环境,选出最合适的 sourceMap 配置,来生成我们对应的代码调错文件。

这样,一方面能保证我们即使发现代码里的错误问题。另一方面呢,也可以尽可能地提升打包速度。

9. 结合 stats 分析打包结果

在打包项目时,我们可以通过命令,来生成这次打包情况的 stats 文件,之后呢,通过借助一些线上或者本地的打包分析工具,来达到分析我们本次打包过程中的打包情况。

比如说,分析哪个模块打包的时间比较长,哪个模块打包分析的时间比较短等等内容,依据具体的情况进行优化。

10. 开发环境内存编译

使用 webpackDevServer 来进行编译,它不会把打包生成的文件放在 dist 目录中去,而是把其放到了我们电脑的内存中。

11. 开发环境无用插件剔除

比如说,我们在开发环境的情况下,并不需要对代码进行压缩。因此,对应的压缩插件我们就不要在开发环境中配置,只在生产环境中配置就好了。这样,可以减少一些不必要的打包时间。

🏍️ 七、多页面打包配置

通常情况来说,但凡我们在做打包的时候,基本上都是对单页面应用做打包。什么是单页面呢,也就是只有一个 index.html 文件。像目前比较主流的框架, vuereact 等框架,都是单页面应用。但是如果像一些比较老的框架,比如 jqueryzepto ,可能就需要进行多页面应用打包。

因此,顺着这个话题,我们来谈谈,在 webpack 中,如何对多页面应用,进行打包配置。

我们在 webpack.common.js 中进行配置,具体代码如下:

js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {
    //引入多个入口文件
    main: './src/index.js',
    list: './src/list.js',
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'index.html',
      //chunk表明这些html文件要引入的文件有哪些
      chunks: ['runtime', 'vendors', 'main'],
    }),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'list.html',
      chunks: ['runtime', 'vendors', 'list'],
    }),
  ],
};
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {
    //引入多个入口文件
    main: './src/index.js',
    list: './src/list.js',
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'index.html',
      //chunk表明这些html文件要引入的文件有哪些
      chunks: ['runtime', 'vendors', 'main'],
    }),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'list.html',
      chunks: ['runtime', 'vendors', 'list'],
    }),
  ],
};

通过以上代码我们可以知道,增加 entryplugins 的配置,来增加多个入口页面,从而达到了多页面应用配置的效果。

🛵 八、结束语

通过上文的讲解,相信大家对 webpack 在一些场景中的实战配置有了一定的了解。上面讲述的也是比较浅显的内容,大家可以根据相对应的知识点,进行知识面的拓宽,以便更好的应用到实际的项目当中。

到这里,关于 webpack 的实战案例配置就讲解结束啦!希望对大家有帮助~

如文章有误或有不理解的地方,欢迎小伙伴们评论区留言哦 💬

本文代码已上传至公众号,后台回复关键词 webpack 即可获取~

🐣 彩蛋 One More Thing

(:往期推荐

Released under the MIT License.