Webpack 核心概念与常用配置

114 阅读15分钟

webpack 是什么?用来做什么?有哪些优点?webpack 的核心概念有哪些?如何配置?


一 Webpack 基础知识

1. Webpack 是什么?

Webpack 是前端的一种建构工具,它的兴起,源于前端模块化工程化的普及。它是用 Node.js 写的。

Webpack 适用于多种语言,本文示例及后续相关文章,都以 JavaScript 语法为例。

1) 模块化

模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。

前端模块化一般指的是 JavaScript 的模块,比如最常见的 NPM 包,可以被抽象封装的最小/最优代码集合,模块化解决的是功能耦合问题。目前,css 也在一定程度上实现了模块化,如 Less / Sass 可以使用 @import 引入自己的依赖。

[注:] 大家可能也经常听到组件化这个名词,组件化则更像是模块化进一步封装,根据业务特点或者不同的场景封装出具有一定功能特性的独立整体;另外,前端提到组件化更多的是具有模板、样式和 JS 交互的 UI 组件。

主流的模块化的规范:

CommonJS

CommonJS 是 Nodejs 广泛使用的一套模块化规范,是一种同步加载模块依赖的方式。

  • require: 引入一个模块。
  • exports: 导出模块内容。
  • module: 模块本身。

AMD

模块加载库 RequireJS 提出并且完善的一套模块化规范,是一种异步加载模块依赖的方式。

  • id: 模块 id。
  • dependencies:模块依赖。
  • factory:模块的工厂函数,即模块的初始化操作函数
  • require:引入模块。

ES6 Module

ES6 推出的一套模块化规范。

  • import: 引入模块。
  • export:导出模块。

2) 工程化

一切以提高效率、降低成本、质量保证为目的的手段,都属于工程化。

前端工程化的必要性,如下列问题:

  • 模块多了,依赖管理怎么做?
  • 页面复杂度提升之后,多页面、多系统、多状态怎么办?
  • 团队扩大之后,团队合作怎么做?
  • 怎么解决多人研发中的性能、代码风格等问题?
  • 如何权衡研发效率和产品迭代的问题?

3) Webpack 的作用

Webpack 是实现工程化的一个工具。

本质上,Webpack 是一个现代 JavaScript 应用程序的静态模块打包器,在 Webpack 处理应用程序时,它会在内部创建一个依赖图,用于映射到项目需要的每个模块,然后将所有这些依赖生成到一个或多个 bundle。

Webpack 跟其他构建工具(Grunt、Gulp)本质上不同之处在于:Webpack 是从入口文件开始,经过模块依赖加载、分析和打包三个流程完成项目的构建。在加载、分析和打包的三个过程中,可以针对性的做一些解决方案,达到按需加载的目的。

Webpack 还可以轻松的解决传统构建工具解决的问题:

  • 模块化打包,一切皆模块,JS 是模块,CSS 等也是模块;
  • 语法糖转换:比如 ES6 转 ES5、TypeScript;
  • 预处理器编译:比如 Less、Sass 等;
  • 项目优化:比如压缩、CDN;
  • 解决方案封装:通过强大的 Loader 和插件机制,可以完成解决方案的封装,比如 PWA;
  • 流程对接:比如测试流程、语法检测等。

2. Webpack 无配置文件打包操作示例

安装 webpack 及 webpack-cli

npm i webpack --save-dev
npm i webpack-cli --save-dev

命令脚本配置

在 package.json 文件,添加scripts字段:

"scripts": {
  "build": "webpack"
}

执行npm run build,即可完成项目打包。

Webpack 默认的入口文件是src/index.js,默认的输出目录是dist/main.js。

"scripts": {
  "dev": "webpack --mode development", // 开发模式
  "build": "webpack --mode production" // 生产模式
}
// 指定输入文件
"scripts": {
  "dev": "webpack --mode development ./src/ex/index.js",
  "build": "webpack --mode production ./src/ex/index.js"
}
// 指定输出文件
"scripts": {
  "dev": "webpack --mode development --output ./output/main.js",
  "build": "webpack --mode production --output ./output/main.js"
}

// 显示进度和颜色
"scripts": {
  "build": "webpack --progress --colors"
}

// 打印错误详情
"scripts": {
  "build": "webpack --display-error-details"
}

// 指定 Webpack 配置文件的路径
"scripts": {
  "build": "webpack –-config config.js"
}

3. Webpack 配置文件及配置类型

Webpack 默认的配置文件是 webpack.config.js,该 js 文件是一个 Node.js 的模块,遵循 CommonJS 模块规范,可以通过 require() 语法导入其他文件或者使用 Node.js 内置的模块,也可以使用普通的 JavaScript 编写语法,包括变量、函数、表达式等。

如果需要指定某个配置文件,可以使用下面的命令:

webpack --config xx.js

如果 Webpack 不是全局安装,则可以在项目目录下实行:

node ./node_modules/webpack/bin/webpack --config xx.js

或者使用npx:

npx webpack --config xx.js

npx 是一个方便开发者访问 node_modules 内的 bin 命令行的小工具,npx webpack -v 相当于执行了 node ./node_modules/webpack/bin/webpack -v ,npx 在开发中非常方便,推荐安装:npm install -g npx

简单的 webpack.config.js 示例

const path = require('path');

module.exports = {
    mode: 'development',
    entry: './foo.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'foo.bundle.js'
    }
};

函数类型的 Webpack 配置

// 函数类型的配置必须返回一个配置对象
// 函数接受两个参数env和argv:分别对应着环境对象和 Webpack-CLI 的命令行选项
module.exports = (env, argv) => {
    return {
        mode: env.production ? 'production' : 'development',
        devtool: env.production ? 'source-maps' : 'eval',
        plugins: [
            new TerserPlugin({
                terserOptions: {
                    compress: argv['optimize-minimize'] // 只有传入 -p 或 --optimize-minimize
                }
            })
        ]
    };
};

Promise 类型的 Webpack 配置

module.exports = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({
                entry: './app.js'
                /* ... */
            });
        }, 5000);
    });
};

支持多个配置、数组类型的 Webpack 配置

module.exports = [
    {
        mode: 'production'
        // 配置1
    },
    {
        // 配置2
    }
];

二 Webpack 核心概念与常用配置

webpack 是一个模块打包工具,能够从一个需要处理的 js 文件开始,构建一个关系图,该图映射到了项目中的每个模块,然后将这个依赖关系图输出到一个或者多个 bundle 中。

webpack 的核心模块包括:入口 entry,输出 output,loader 及 插件 plugin 四大部分。除了这四个核心概念外,还有resolve、module等一些重要的配置项。

常见配置解释

参数说明
entry项目入口
output项目出口
loader模块转化器/处理器,对模块进行转换处理
plugin扩展插件,打包优化,资源管理,注入环境变量等
mode模式,开发环境development,生成环境production
module开发中每一个文件都可以看作 module,包括 css/图片 等
resolve帮助 Webpack 快速查找依赖
chunk代码块,一个 chunk 可以由多个模块组成
bundle最终打包完成的文件,一般和chunk一一对应,bundle就是对chunk进行压缩打包等处理后的产出
context项目打包的相对路径上下文,如 entry 的相对路径是基于此的,默认为process.cwd()即工作目录

1. 入口 entry

webpack 打包的入口起点,默认值是./src/index.js

entry 支持多种数据类型,包括字符串、对象、数组。从形式上来说,包括了单文件入口和多文件入口两种方式。

1) 单文件入口

// 字符串形式
module.exports = {
  entry: './path/to/my/entry/file.js',
};

// 对象形式
module.exports = {
  entry: {
    main: './path/to/my/entry/file.js',
  }
};

// 数组形式
// webpack 会自动生成一个新的入口模块,将数组中每个文件加载进来,绘制在一个"chunk"中,
// 并将最后一个模块的 module.exports 作为入口模块的 module.exports 导出
module.exports = {
  entry: ['./src/file_1.js', './src/file_2.js'],
  output: {
    filename: 'bundle.js',
  }
};

2) 多文件入口

多文件入口是使用对象语法来通过支持多个entry,多文件入口的对象语法相对于单文件入口,具有较高的灵活性。

module.exports = {
  entry: {
    app: './src/app.js',
    adminApp: './src/adminApp.js',
  },
};

2. 输出 output

output指定了 entry 对应文件编译打包后的输出 bundle,默认输出到 dist/main.js。output 的常用属性有:

属性说明
path指定输出的 bundle 存放的路径
filenamebundle 的名称
publicPath指定一个在浏览器中被引用的 URL 地址
library指定库名(打包生成一个供别人使用的库)
libraryTarget指定库打包出来的规范
externals去除输出的打包文件中依赖的某些第三方 js 模块(例如 jquery等)
target针对多种平台应用,指定构建的目标(web/node/electron等)
devtool用来控制怎么显示sourcemap, 通过sourcemap可以快速还原代码的错误位置

filename

output 只能配置一个 filename,如果要使不同的 entry 输出到不同的 chunk,需要使用占位符

占位符说明
[name]模块名称
[id]模块标识符
[hash]模块标识符的 hash
[chunkhash]chunk 内容的hash
[query]模块的 query,例如,文件名 ? 后面的字符串
[function]一个 return 出一个 string 作为 filename 的函数
// 写入到硬盘:./dist/app.js, ./dist/search.js
module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js',
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist',
  },
};

[hash][chunkhash] 的长度可以使用 [hash:16](默认为 20)来指定。或者,通过指定 output.hashDigestLength 在全局配置长度。

占位符是可以组合使用的,例如[name]-[hash:8]。

[hash]:整个项目的 hash 值,其根据每次编译内容计算得到,每次编译之后都会生成新的 hash,即修改任何文件都会导致所有文件的 hash 发生改变;在一个项目中虽然入口不同,但是 hash 是相同的;hash 无法实现前端静态资源在浏览器上长缓存,这时候应该使用 chunkhash;

[chunkhash]:根据不同的入口文件(entry)进行依赖文件解析,构建对应的 chunk,生成相应的 hash;只要组成 entry 的模块文件没有变化,则对应的 hash 也是不变的,所以一般项目优化时,会将公共库代码拆分到一起,因为公共库代码变动较少的,使用 chunkhash 可以发挥最长缓存的作用。

publicPath

对于使用<script><link>标签时,当文件路径不同于他们的本地磁盘路径(由output.path指定)时,output.publicPath 被用来作为 src 或者 link 指向该文件。这种做法在需要将静态文件放在不同的域名或者 CDN 上面的时候是很有用的。

module.exports = {
    output: {
        path: '/home/git/public/assets',
        publicPath: '/assets/'
    }
};
// 输出:
<head>
    <link href="/assets/logo.png" />
</head>

library / libraryTarget

如果我们打包的目的是生成一个供别人使用的库,那么可以使用library来指定库的名称,可以使用libraryTarget指定库打包出来的规范。

库的名称支持占位符和普通字符串。

libraryTarget 的值可以为:var、assign、this、window、global、commonjs、commonjs2、commonjs-module、amd、umd、umd2、jsonp,默认是var。

// var
// 这种导出方式,只能以 <script> 标签的形式引入我们的库,以全局变量的形式提供这些被依赖的模块
{
    output: {
        library: 'myLib',
        filename: 'var.js',
        libraryTarget: 'var'
    }
}
// output
var myLib = (function(modules) {})({
    './src/index.js': function(module, exports) {}
});
// assign
{
    output: {
        library: 'myLib',
        filename: 'assign.js',
        libraryTarget: 'assign'
    }
}
// output: 变量没有 var 声明
 myLib = (function(modules) {})({
    './src/index.js': function(module, exports) {}
});
// this
{
    output: {
        library: 'myLib',
        filename: 'this.js',
        libraryTarget: 'this'
    }
}
// output
this["myLib"] = (function(modules) {})({
    './src/index.js': function(module, exports) {}
});
// window
{
    output: {
        library: 'myLib',
        filename: 'window.js',
        libraryTarget: 'window'
    }
}
// output
window["myLib"] = (function(modules) {})({
    './src/index.js': function(module, exports) {}
});

// global
{
    output: {
        library: 'myLib',
        filename: 'global.js',
        libraryTarget: 'global'
    }
}
// output:注意 target=node 的时候才是 global,默认 target=web下global 为 window
window["myLib"] = (function(modules) {})({
    './src/index.js': function(module, exports) {}
});
// commonjs
{
    output: {
        library: 'myLib',
        filename: 'commonjs.js',
        libraryTarget: 'commonjs'
    }
}
// output
exports["myLib"] = (function(modules) {})({
    './src/index.js': function(module, exports) {}
});
// amd
{
    output: {
        library: 'myLib',
        filename: 'amd.js',
        libraryTarget: 'amd'
    }
}
// output
define('myLib', [], function() {
    return (function(modules) {})({
        './src/index.js': function(module, exports) {}
    });
});

// umd
{
    output: {
        library: 'myLib',
        filename: 'umd.js',
        libraryTarget: 'umd'
    }
}
// output
(function webpackUniversalModuleDefinition(root, factory) {
    if (typeof exports === 'object' && typeof module === 'object') module.exports = factory();
    else if (typeof define === 'function' && define.amd) define([], factory);
    else if (typeof exports === 'object') exports['myLib'] = factory();
    else root['myLib'] = factory();
})(window, function() {
    return (function(modules) {})({
        './src/index.js': function(module, exports) {}
    });
});
// commonjs2
{
    output: {
        library: 'myLib',
        filename: 'commonjs2.js',
        libraryTarget: 'commonjs2'
    }
}
// output
module.exports = (function(modules) {})({
    './src/index.js': function(module, exports) {}
});
// umd2
{
    output: {
        library: 'myLib',
        filename: 'umd2.js',
        libraryTarget: 'umd2'
    }
}
// output
(function webpackUniversalModuleDefinition(root, factory) {
    if (typeof exports === 'object' && typeof module === 'object') module.exports = factory();
    else if (typeof define === 'function' && define.amd) define([], factory);
    else if (typeof exports === 'object') exports['myLib'] = factory();
    else root['myLib'] = factory();
})(window, function() {
    return (function(modules) {})({
        './src/index.js': function(module, exports) {
        }
    });
});
// commonjs-module
{
    output: {
        library: 'myLib',
        filename: 'commonjs-module.js',
        libraryTarget: 'commonjs-module'
    }
}
// output
module.exports = (function(modules) {})({
    './src/index.js': function(module, exports) {}
});
// jsonp
{
    output: {
        library: 'myLib',
        filename: 'jsonp.js',
        libraryTarget: 'jsonp'
    }
}
// output
myLib((function(modules) {})({
    './src/index.js': function(module, exports) {}
}));

devtool

devtool是来控制怎么显示 sourcemap,通过 sourcemap 我们可以快速还原代码的错误位置。

由于 sourcemap 包含的数据量较大,而且生成算法需要计算量支持,所以 sourcemap 的生成会消耗打包的时间,下面的表格整理了不同的devtool值对应不同的 sourcemap 类型对应打包速度和特点。

devtool构建速度重新构建速度生产环境品质
留空,none++++++yes打包后的代码
eval++++++no生成后的代码
cheap-eval-source-map+++no转换过的代码(仅限行)
cheap-module-eval-source-map0++no原始源代码(仅限行)
eval-source-map-+no原始源代码
cheap-source-map+0no转换过的代码(仅限行)
cheap-module-source-map0-no原始源代码(仅限行)
inline-cheap-source-map+0no转换过的代码(仅限行)
inline-cheap-module-source-map0-no原始源代码(仅限行)
source-map----yes原始源代码
inline-source-map----no原始源代码
hidden-source-map----yes原始源代码
nosources-source-map----yes原始源代码

[注:]+++ 非常快速, ++ 快速, + 比较快, 0 中等, - 比较慢, -- 慢。

3. resolve

通过 resolve 的配置,可以帮助 Webpack 快速查找依赖,也可以替换对应的依赖(比如开发环境用 dev 版本的 lib 等)。resolve 的基本配置语法如下:

module.exports = {
    resolve: {
        // resolve的配置
    }
};

extensions

resolve.extensions 是帮助 Webpack 解析扩展名的配置,默认值:['.wasm', '.mjs', '.js', '.json'],所以我们引入 js 和 json 文件,可以不写它们的扩展名,通常我们可以加上 .css、.less等,但是要确保同一个目录下面没有重名的 css 或者 js 文件,如果存在的话,还是写全路径吧。

module.exports = {
    resolve: {
        extensions: ['.js', '.json', '.css']
    }
};

alias

通过设置 alias 可以帮助 webpack 更快查找模块依赖,而且也能使我们编写代码更加简便。

alias 的名字可以使用@ ! ~等这些特殊字符,实际使用中 alias 都使用一种,或者不同类型使用一种,这样可以跟正常的模块引入区分开,增加辨识度。

在 src/pages/demo/index.js 中如果要引用 src/lib/utils.js 那么可以通过:import utils from '../../lib/utils'; ,如果目录更深一些,会越来越难看,这时可以通过设置 alias 来缩短这种写法,例如:

module.exports = {
    resolve: {
        alias: {
            src: path.resolve(__dirname, 'src'),
            '@lib': path.resolve(__dirname, 'src/lib')
        }
    }
};

那么可以通过 import utils from '@lib/utils' 直接引入 src/lib/utils.js。

alias 还常被用于给生产环境和开发环境配置不同的 lib 库:

module.exports = {
    resolve: {
        alias: {
            san: process.env.NODE_ENV === 'production' ? 'san/dist/san.min.js' : 'san/dist/san.dev.js'
        }
    }
};

alias 还支持在名称末尾添加$符号来缩小范围只命中以关键字结尾的导入语句,这样可以做精准匹配:

module.exports = {
    resolve: {
        alias: {
            react$: '/path/to/react.min.js'
        }
    }
};

import react from 'react'; // 精确匹配,所以 react.min.js 被解析和导入
import file from 'react/file.js'; // 非精确匹配,触发普通解析

mainFields

有一些我们用到的模块会针对不同宿主环境提供几份代码,例如提供 ES5 和 ES6 的两份代码,或者提供浏览器环境和 nodejs 环境两份代码,这时候在package.json文件里会做如下配置:

{
    "jsnext:main": "es/index.js", //采用ES6语法的代码入口文件
    "main": "lib/index.js", //采用ES5语法的代码入口文件,node
    "browser": "lib/web.js" //这个是专门给浏览器用的版本
}

在 Webpack 中,会根据resolve.mainFields的设置去决定使用哪个版本的模块代码,在不同的target下对应的resolve.mainFields默认值不同,默认target=web对应的默认值为:

module.exports = {
    resolve: {
        mainFields: ['browser', 'module', 'main']
    }
};

resolve 的其他配置:

配置说明
mainFiles解析目录时候的文件名,默认是 index(后缀为resolve.extensions值)
modules要查找的模块依赖,默认是node_modules
symlinks是否解析符合链接
plugins添加解析插件,数组格式
cachePredicate是否缓存,支持 boolean 和 function,function 传入一个带有 path 和 require 的对象,必须返回 boolean 值

4. module

在 webpack 解析模块的同时,不同的模块需要使用不同类型的模块处理器来处理,这部分的设置就在module配置中。module 有两个配置:module.noParse和module.rules。

noParse

防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。

module.exports = {
    module: {
        // 使用正则表达式
        noParse: /jquery|lodash/

        // 使用函数,从 Webpack 3.0.0 开始支持
        noParse: (content) => {
            // content 代表一个模块的文件路径
            // 返回 true or false
            return /jquery|lodash/.test(content);
        }
    }
}

[注:]parser 来控制模块化语法

webpack 是以模块化的 JavaScript 文件为入口,所以内置了对模块化 JavaScript 的解析功能,支持AMD、Commonjs、SystemJs、ES6。

parser 属性可以更细粒度的配置哪些模块语法要解析,哪些不解析。简单来说,如果设置 parser.commonjs = false,那么代码里面使用 commonjs 的 require 语法引入模块,对应的模块就不会被解析到依赖中,也不会被处理,支持的选项包括:

module: {
    rules: [{
        test: /\.js$/,
        use: ['babel-loader'],
        parser: {
            amd: false, // 禁用 AMD
            commonjs: false, // 禁用 CommonJS
            system: false, // 禁用 SystemJS
            harmony: false, // 禁用 ES6 import/export
            requireInclude: false, // 禁用 require.include
            requireEnsure: false, // 禁用 require.ensure
            requireContext: false, // 禁用 require.context
            browserify: false, // 禁用 browserify
            requireJs: false, // 禁用 requirejs
        }
    }]
}

rules

在处理模块时,将符合规则条件的模块,提交给对应的处理器来处理,通常用来配置 loader,其类型是一个数组,数组里每一项都描述了如何去处理部分文件。

每一项 rule 大致可以由以下三部分组成:

  • 条件匹配:通过 test、include、exclude 等配置来命中可以应用规则的模块文件
  • 应用规则:对匹配条件通过后的模块,使用use配置项来应用loader
  • 重置顺序:一组 loader 的执行顺序默认是从后到前(或者从右到左)执行,通过 enforce 选项可以让其中一个 loader 的执行顺序放到最前(pre)或者是最后(post)

条件匹配相关的配置有test、include、exclude、resource、resourceQuery和issuer。条件匹配的对象包括三类:resource,resourceQuery和issuer。

  • resource:请求文件的绝对路径。它已经根据 resolve 规则解析;
  • issuer: 被请求资源(requested the resource)的模块文件的绝对路径,即导入时的位置。

举例来说明:从 app.js 导入 './style.css?inline':resource 是/path/to/style.css;resourceQuery 是?之后的inline;issuer 是/path/to/app.js。

// 匹配的条件为:来自src和test文件夹,
// 不包含node_modules和bower_modules子目录
// 模块的文件路径为.tsx和.jsx结尾的文件
{
    test: [/\.jsx?$/, /\.tsx?$/],
    include: [
        path.resolve(__dirname, 'src'),
        path.resolve(__dirname, 'test')
    ],
    exclude: [
        path.resolve(__dirname, 'node_modules'),
        path.resolve(__dirname, 'bower_modules')
    ]
}

5. Loader

在使用对应的loader之前,需要先安装它。

配置方式:

// webpack.config.js
module.exports = {
    module:{
        rules:[
            // test: /\.html$/, use: ['html-loader']
            test: /\.less$/, use:'less-loader'
        ]
    }
}
// config内写法,通过 options 传入
module: {
    rules: [{
        test: /\.html$/,
        use: [{
            loader: 'html-loader',
            options: {
                minimize: true,
                removeComments: false,
                collapseWhitespace: false
            }
        }]
    }]
}
// config内写法,通过 query 传入
module: {
    rules: [{
      test: /\.html$/,
      use: [ {
        loader: 'html-loader?minimize=true&removeComments=false&collapseWhitespace=false',
      }]
    }]
}

// 内联配置
const html = require('html-loader!./loader.html');
console.log(html);

loader 解析顺序是从右到左、从后到前的,如果需要调整执行顺序,可以使用 enforce,enforce 取值是 pre|post,pre 表示把放到最前,post 是放到最后。

// query 写法从右到左,使用!隔开
const styles = require('css-loader!less-loader!./src/index.less');
// 数组写法,从后到前
module.exports = {
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'less-loader',
                        enforce: 'post'
                    }
                ]
            }
        ]
    }
};

oneOf:只应用第一个匹配的规则

oneOf表示对该资源只应用第一个匹配的规则,一般结合resourceQuery。

module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                oneOf: [
                    {
                        resourceQuery: /inline/, // foo.css?inline
                        use: 'url-loader'
                    },
                    {
                        resourceQuery: /external/, // foo.css?external
                        use: 'file-loader'
                    }
                ]
            }
        ]
    }
};

6. plugin 插件

loader 面向的是解决某个或者某类模块的问题,而 plugin 面向的是项目整体。plugin可以解决loader解决不了的问题。

// 内置插件
module.exports = {
    //....
    plugins: [
        // 压缩js
        new webpack.optimize.UglifyJsPlugin();
    ]
}

// 非内置插件
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
    //....
    plugins: [
        // 导出css文件到单独的内容
        new ExtractTextPlugin({
            filename: 'style.css'
        })
    ]
};

记忆题: Webpack 的配置有几种写法,分别可以应用到什么场景?

我们要开发一个 jQuery 插件、Vue 组件等,需要怎么配置 Webpack?

Webpack 的占位符 [hash] 、[chunkhash] 和 [contenthash] 有什么区别和联系?最佳实践是什么?

Webpack 的 SourceMap 有几种形式?分别有什么特点?SourceMap 配置的最佳实践是什么?

什么是 bundle ,什么是 chunk,什么是 module?

能不能手写一个 Webpack 配置?记住重点配置项:entry、output、module.rules(loader)和plugin。

在 JS 文件中怎么调用 Loader 来处理一个模块?

Loader 的解析顺序是怎样的?