VillainHR

看啥双拱门,来学 webpack 3啊

webpack HTML5 HTML5.0 2017-10-27

webpack 是前端开发者一个跨不过去的编译工具。不过由于他的快速迭代,让很多同学在学一个版本的时候,下一个新版本就发布了,让人感觉非常蛋疼和无奈:

“我是谁,我在干嘛,我要做什么?”

不过,如果你已经掌握 webpack 比较老的版本,对于新版本的学习而言,应该只需要 0.5 天的工作量就可以完成。因为,其基本理念都是以 JS 为中心,增加的只是其辅助特性。

webpack 到目前为止已经走过了三个大版本,每个版本之间都,增加了非常多可用的特性,但是每个版本的具体配置项都有些差异,很容易让老版本用户形成依赖。现在 webpack 3 已经走到了 3.8 版本。现版本对于 webpack 2.x 的配置项来说,最突出的就是 loader 的写法的改变和内置了 webpack-dev-server 的特性。

所以,本文将带你全面了解一下 webpack 3.x 版本的新特性和一些常用的 webpack 3.x 的配置。

entry-入口 JS 配置

entry 选项主要用来定制 webpack 引用 JS 文件的主入口,它可以配置多个值来定义多个 JS 主文件入口。具体配置项为:

entry可以是三种值[string | object | array]

  • 字符串:如 entry:’./src/index.js’,字符串也可以是函数的返回值,如entry: () => ‘./demo’。

  • 数组形式,如[react,react-dom],可以把数组中的多个文件打包转换为一个chunk;

entry: ["./app/entry1", "./app/entry2"],
  • 对象形式,如果我们需要配置的是多页应用,或者我们要抽离出指定的模块做为公共代码,就需要采用这种形式了,属性名是占位符name的值,属性值可以是上面的字符串和数组,如下:
// 值得注意的是入口文件有几个就会生成几个独立的依赖图谱。
entry:{
    main:'./src/index.js',
    second:'./src/index2.js',
    vendor: ['react','react-dom']  // 将 react,react-dom 一起打包
}

有时候,开发者还会利用 require.resolve(route) 来在 entry 中引入具体路径的文件。该方法只是用来作为路径模块的搜索,其返回的是指定文件的绝对路径名。解析机制有点类似,require(route)

entry: [
    require.resolve('react-dev-utils/webpackHotDevClient'),
    require.resolve('./polyfills'), //返回 /Users/villainHR/Desktop/file/polyfills/index.js
    require.resolve('react-error-overlay'),
    'src/index.js'
  ],

entry 还可以接受 function 返回的 string,array,object 值:

entry: () => './demo'

context 上下文基础目录

context 用来设置 entry 和 loaders 的上下文索引依赖。默认情况下,entry 等参考的是当前目录(CWD)

// 手动设置
context: path.resolve(__dirname, "app")

通过,entry 设置的相对路径,会优先寻找 ./app 路径下的文件。

output-输出 JS 配置

基础配置项

output 里面又很多基本的配置项,基础配置项有:

output:{
        path: path.join(__dirname,'./dist'),
        filename:'js/bundle-[name]-[hash].js',
        chunkFilename:'js/[name].chunk.js',
        chunkLoadTimeout: 12000,
        publicPath:'/dist/'
    }
  • path: 用来决定输出目录。通常结合 path.join/resolve() 设置绝对路径。
  • filename[alias: name]: 决定具体输出的文件名,上面 output 选项中,可以用到很多全局的占位符,比如:name、[hash] 等。
  • chunkFilename: 主要是用来对没在 entry 中定义的文件来设定具体的输出目录和文字的。常常用来按需的异步加载当中,例如:
require.ensure(["modules/tips.jsx"], function(require) {
    var a = require("modules/tips.jsx");
    // ...
}, 'tips');

这里,chunkFilename 就可以将 tips.jsx 具体输出为 js/tips.chunk.js。

  • publicPath:并不是用来输出打包路径,而是在 runtime 的时候,手动修改 src 引用路径的。常常配合 css/style/html-loader 来修改。例如,会将: src="picture.jpg" Re-writes ➡ src="/assets/picture.jpg"。相当于,手动修改请求 url 的地址。
  • chunkLoadTimeout[Number]: 设置 webpack chunk 的请求超时时间。// TODO

上面也提到了,在 output 中会存在一起全局占位符,比如::name、[hash] 等。代表的含义为:

  • [id]:webpack给块分配的内部chunk id,如果你没有隐藏,你能在打包后的命令行中看到;
  • [hash]:每次构建过程中,生成的唯一 hash 值;(每次都变)
  • [chunkhash]: 依据于打包生成文件内容的 hash 值,内容不变,值不变;

hash 控制

在全局占位符中,有一个特殊的值 [hash]。我们可以在 webpack 中,针对 hash 输出做相关的控制。(这里会一并影响到 [chunkhash])

  • output.hashDigest: 具体输出 hash 的 encoding,有 ‘hex’, ‘latin1’ or ‘base64’。默认为 hex
  • output.hashDigestLength: 输出 hash 具体长度。默认为 20。
  • output.hashFunction: 引入文件的具体算法,默认为 md5。
  • output.hashSalt: 给 hash 加盐,很少用。

如果,有使用 ExtractTextWebpackPlugin 的话,它会通过 contenthash 选项,直接覆盖上面 4 个的 hash 选项。

自定义库的打包

在 webpack 中,如果想要自己写一个包并发布的话,就需要独立打包成为一个 library。这里,在 webpack 中有指定的配置项,和我们平常写的 webpack 打包项目不同。

独立打包一个库,常常会遇到应用外部库的事情,你可以选择大伯啊,也可以选择不打包。这里,除了 output 选项之外,还需要 externals 选项。下面内容主要对 output 里面内容做详解解释,而 externals 会放到后面进行讲解。

externals 主要是用来解决外部库打包问题,基本设置为:

 externals: {
    key: "value"
  },
  • value: 用来指明 外部库 通过全局(window,global)的方式暴露的模块名。例如:
window.jQuery = {
  //xxx
}

webapck 会直接处理 window 或者 global 对象进行模块化包裹。否则,则不会对指定模块进行 externals 处理。这点非常关键。

  • key: 具体指代,在经过 value 处理过后的外部库,能够在 webpack 通过 require 或者 import 的包名,例如:
 externals: {
    $: "jQuery"
  }
  
// index.js

var $ = require("$");

在 output 中主要有 4 个关键选项设置值:

  • output.library[String]: 用来设置通过,CDN 引入时的全局 Name。对于,require/import 引入的没影响(模块引入具体是根据具体代码中的 exports 来决定)
  • output.libraryExport: 限定通过模块具体暴露的接口。例如:
libraryExport: ["MyModule", "MySubModule"]; // 只能到处前面两个模块

最重要的模块输出选项应该是 libraryTarget,它是用来决定模块以何种规范输出,在全局变量的 Name 是啥。其基本格式为:

output.libraryTarget[String]: var | assign | this | window | global | commonjs | commonjs2 | amd | umd

上面那些全部是都是可选项值。这里先告诉大家,在现代 JS 的写法中,最后一个 umd 的选项是最常用的。如果你还是想通过 script 标签引入的话,则前面 5 个比较适合给你灵活自定义。当然,umd 选项也是适合的,不过灵活性差了点。

上面选项可以分为三种类型:

  • 全局变量型[var,assign]:用来表示模块导出,是直接在全局中定义变量。比如,jQuery 的 $,不需要额外引入处理。
  • 对象绑定型[this,window,global,commonjs]:用来将导出的模块,绑定到具体的对象中。比如,jQuery 的 window[’$’],this[’$’] 这样的引入绑定。
  • 模块绑定型[commonjs2,amd,umd]: 需要通过,全局 require() 方法引入的模块。

libraryTarget 常常和 library 选项一起结合使用。下面内容,统一 library 设置为 Villainhr

output.library: 'Villainhr'

接下来,我们具体一个一个来细分讲解一下:

全局变量型

var

output.libraryTarget: 'var' // 默认值

在 js 脚本实际编译结果为:

var Villainhr = _entry_return_;

// In a separate script...
Villainhr.doSomething();

通过 var 变量定义关键字来申明一个变量,将导出的模块直接复制过去。

assign

output.libraryTarget: assign

编译结果为:

Villainhr = _entry_return_;

// In a separate script...
Villainhr.doSomething();

这里,就直接全局绑定,如果前面已经定义过 Villainhr 则会默认覆盖。其实和 var 定义没啥区别。

对象绑定型

对象绑定则是直接通过 Object 的方法,将导出的模块直接绑定到具体对象:

this

output.libraryTarget: "this"

编译结果为:

this["Villainhr"] = _entry_return_;

// In a separate script...
this.Villainhr.doSomething();
Villainhr.doSomething(); // if this is window

导出的模块,直接绑定在 this 上,在全局中直接使用 this.xxx 来进行模块调用。

window

output.libraryTarget: "window"

编译结果为:

window["Villainhr"] = _entry_return_;

window.Villainhr.doSomething();
// or
Villainhr.doSomething();

这里就是通常的导出方案,直接将输出模块绑定到 window 对象上。其实也相当于 var 绑定,直接通过模块名使用。

global

output.libraryTarget: "global"

编译结果为:

global["Villainhr"] = _entry_return_;

直接通过 global 对象调用,这个通常用在 NodeJS 的环境。

commonjs

output.libraryTarget: "commonjs"

编译结果为:

exports["Villainhr"] = _entry_return_;

require("Villainhr").doSomething();

在引用调用时,直接通过 require() 方法引入。

模块绑定型

模块绑定型有 commonjs2,amd,umd 这三个选项。他们具体的规范就是根据选项值对应的规范来的——CommonJSAMD

commonjs2

编译结果为:

module.exports = _entry_return_;

这和 commonjs 类似,不过遵循的是 commonjs2 的规范,是直接将其赋值到 module.exports 上的。具体使用还是需要 require() 引用:

require("Villainhr").doSomething();

amd

编译结果为:

define("Villainhr", [], function() {
  // main body
});

这里通过 amd 异步加载的规范,来导出具体的文件模块。具体使用是通过 require 方法直接引入使用:

require(['Villainhr'], function(Villainhr) {
  // xxx
});

umd

这个模块标准应该属于最方便的一种,它实际上集合了 commonjs1/2,amd,this 的绑定方式。具体编译结果为:

(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["Villainhr"] = factory();
  else
    root["Villainhr"] = factory();
})(this, function() {
  //what this module returns is what your entry chunk returns
});

这里其实一共有 4 个绑定,不过它会优先进行绑定选择,具体顺序是:

commonjs2 > AMD > commonjs > this

也就是 4 选一,如果已经通过 commonjs2 引入的话,则不能在通过其它方式使用。比如,this.Villainhr 这样是访问不到的。

module 编译设置

在 module 选项主要就是用设置 webpack 中常用的 loaders。通过 rules 规则来匹配具体应用的文件和 loaders。

noParse 防止预编译

noParse 主要用来设置相关的匹配规则,来防止 webpack loaders 对某些文件进行预编译。

基本设置为:

noParse: RegExp | [RegExp] | function

通常,设置的值可以直接为:

noParse: /jquery|lodash/

// or

noParse: function(content) {
  return /jquery|lodash/.test(content);
}

这样,jquery 和 loadsh 就不会被 webpack 中的 loaders 捕获并编译了。

rules 设置匹配规则

module.rules 的选项具体是用来设置 loaders 匹配文件的规则。其基本接受类型为 [Array]。

module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules\/dist/,
      loader: 'babel-loader'
    }]
  },

rules 里面的每个对象都决定了 loaders 的具体类型以及 loaders 作用的具体文件。在 webpack3 中,继承了 webpack2 的基本限定功能,比如 excludeinclude等,还额外加入了通过 query 匹配,以及多个 query 的 loader 匹配规则。

文件匹配

在每一条具体的 rule 里面,用来进行匹配文件的选项有:Rule.test, Rule.exclude, Rule.include, Rule.and, Rule.or, Rule.not。

上面三个对象其实挂载到的是 Rule.resource 对象上的,你可以直接写到 Rule 上,也可以写到 Rule.resource 上。这里简单起见,以 rule 为基准。他们的值统一都为 condition。这个概念,是 webpack 匹配文件提出的。

condition

主要用来设置匹配文件的条件规则。可接受的值有:

condition:  [string | RegExp | function | array | object]
  • string: 匹配文件的绝对路径
  • RegExp: 匹配文件的正则
  • function: 参数为 input 的路径,根据返回的 boolean 来决定是否匹配。
  • array: 里面可以传入多个 condition 匹配规则。
  • object: 不常用,用来匹配 key。

接下来,我们来看一下 condition 在各个选项值中的具体实践。

test

test 用来匹配符合条件的 input。设置的值为:

test: [condition]

最常用的还是直接写入正则:

test: /\.jsx$/

include

和 test 一样,也是用来匹配符合条件的 input,主要用途是用来设置 依赖文件索引目录。在 include 中,通常设置 string 或者 array of strings。

// 在 entry css 文件中,只能在 app/styles 以及 vendor/styles 中索引依赖文件
{
  test: /\.css$/,
  include: [
    path.resolve(__dirname, "app/styles"),
    path.resolve(__dirname, "vendor/styles")
  ]
}

exclude

设置依赖文件不会存在的目录。

实际上,上面三个命令是一起配合来提高编译速度的。因为默认情况下,webpack loaders 会对所有引入的文件进行 loader 编译,当然,对于 node_modules 里面成熟的库,我们没比较在进行额外的 loader 编译,直接将其 bundle 即可。

常用设置可以为:

test: /\.js$/,
loader: 'babel-loader',
include: [
    path.resolve(__dirname, "app/src"),
    path.resolve(__dirname, "app/test")
],
exclude: /node_modules/ // 排除编译 node_modules 中的文件

剩下的 ornot 也是用来设置规则,根据名字大家也可以猜出这两者的含义:

  • or[condition]:满足其中一种条件即可。例如:or: [/.*src\/index.*/,/.*abc.*/]
  • not[condition]:都不满足所有的条件。设置方法同上

query 匹配

query 匹配具体指的是匹配 url 中的 ? 后的字符串。当然,我们也可以在 test 中通过正则来写,不过,webpack3 既然已经提供了 query 的选项,我们也可以直接使用它的配置–resourceQuery

resourceQuery 用来设置匹配 query 的规则,接受的内容是 condition。不过,一般直接设置 正则 或者 string 就行:

// 匹配 query 含有 inline 的路径,例如:./villainhr.css?inline 
{
  test: /.css$/,
  resourceQuery: /inline/,
  use: 'url-loader'
}

另外,如果你想对不同的 query 使用不同的 loader 话,可以直接使用 oneOf 配置。文件资源会默认找到 oneOf 中第一个匹配规则,来调用对应的 loader 处理。

{
  test: /.css$/,
  oneOf: [
    {
      resourceQuery: /inline/, // villainHR.css?inline
      use: 'url-loader'
    },
    {
      resourceQuery: /external/, // villainHR.css?external
      use: 'file-loader'
    }
  ]
}

loader 编译设置

在 webpack2 的时候,主要写法是根据 loadersloader 来进行设定的。不过,在 webpack3 该为根据文件来决定 loader 的加载。这其中,最大的特点就是,将 loaders 替换为了 rules。例如:

module: {
    loaders: [
      {test: /\.css$/, loader: 'style-loader!css-loader'}
    ]
  }

// 替换为:

module: {
    rules: [
      {
        test: /\.css$/, use:[
        'style-loader','css-loader'
      ]}
    ]
  }

按照规范,use 是用来实际引入 loader 的标签。在 webpack3 时代,还保留了 loader字段,废除了 query 字段,其实可以在 use 中找到替代。

loader

用来定义具体使用的 loader,这里等同于: use:[loader]。

query

用来限定具体的 loader 使用的配置参数,例如 babel 的配置:

test: /\.js$/,
loader: 'babel-loader',
query: {
    presets: ['es2015']
}

不过,在 webpack3 中已经废弃了 query,使用 useoptions 选项:

test: /\.js$/,
use:[
   {
        loader: 'babel-loader',
        options:{
            presets: ['es2015']
        }
   }
]

resolve 模块解析路径

resolve 主要是用来解析具体入口文件中,引入依赖文件解析。例如,通过 import/require 引入的文件:

import foo from 'demo/test/villainhr.js')
// or
require('demo/test/villainhr.js')

在 webpack 中,提供了 3 种路径解析方式:

  • 相对路径:直接根据入口文件的路径,来对路径进行解析。相当于 path.resolve() 这个方法。
import "../src/villainhr.js"; // 等同于 path.resolve(__dirname,'../src/villainhr.js')
  • 绝对路径:根据 resolve.modules 设置的参考目录来进行解析。默认是根据 webpack 所在的目录。设置了之后,绝对路径的解析,只会在设置的参考目录中查找。
modules: [path.resolve(__dirname, "src"), "node_modules"] // 设置绝对路径搜索目录

import "/villainhr.js"; // 只会搜索 src/villainhr.js 以及 node_modules/villain.js 。
  • 模块路径:直接引入模块,路径前面不加任何修饰符号。例如:import "es5";。webpack 的模块解析规则比上面两个规则要复杂一点。因为还牵扯到 modulesaliasextensions 等。主要规则为:
    • 搜索 resolve.modules 定义的目录,如果有 alias 标签,则还会参考 alias 定义的模块路径
    • 检查引用的路径是文件
      • 检查文件是否存在,如果没有尾缀,则根据 resolve.extensions 定义的尾缀来索引。
    • 检查引用的路径是目录
      • 检查含有 package.json,根据 resolve.mainFields 定义的字段来索引文件。
      • 如果不含有 package.json,直接根据 resolve.mainFiles 来索引文件。
      • 具体文件解析,根据 resolve.extensions 来解决。

具体模块路径解析,可以参考如下:

# /src/villain.js 存在
import VR from 'villain'; 
    /** 搜索 /src、node_modules 目录,
        找到 villain 文件,如果没有,
        根据 extensions 添加尾缀查询。
        找到 villain.js
    */
        
# node_modules/es6 模块存在
import VR from 'es6';
    /** 搜索 /src、node_modules 目录,
        找到 es6 文件夹
        查看 package.json 文件,
        根据 mainFields 定义的字段索引 packjson,找到 main 字段定义的文件。
    */
    
# src/demo/dev/index.js 存在
import VR from 'ABC/dev';
     /** 根据 alias 定义的 ABC 查找,src/demo/ 目录下的文件
         找到 dev 目录,里面没有 package.json 文件
         根据 mainFiles 定义的文件名和 extensions 定义的尾缀找到 index.js 文件
         

// webpack.config.js
 resolve:{
        alias: {
            ABC: path.resolve(__dirname, 'src/demo/')
          },
        modules: [path.resolve(__dirname, "src"), "node_modules"],
        mainFields: ["main"],
        mainFiles: ["index"],
        extensions: [".js", ".json"]
    }

设置模块索引目录

在 webpack 中,主要提供 aliasmodules 来进行索引目录的设置。

  • alias:设置导入路径的简便写法。如果你有大量的文件依赖路径非常长的话,除了使用 modules 设置参考路径之外,还可以根据 alias 来设置路径映射的 key。另外,它还提供了 $ 来进行文件的精确匹配,这个看看文档就行,感觉不常用。例如:
alias: {
  Villainhr: path.resolve(__dirname, 'src/Villainhr/')
}

# 如果要获取 src/Villainhr/index.js 的话,可以直接在 import 写入:

import test from 'Villainhr/index';
  • modules: 用来设置模块解析的目录。这样,在进行模块查找的时候,webpack 会优先遍历你定义的目录。如果,你还有自定义模块在 src 目录下,通过普通模块解析是找不到的,这时候可以直接在 modules 中添加 src。
modules: [path.resolve(__dirname, "src"), "node_modules"]

package.json 索引解析

和模块搜索最关键的文件是 package.json、在 node 环境下,如果模块文件中存在 package.json,会根据其 main 字段定义的文件来进行索引。通过 webpack 设定的模块索引,可以更灵活的设置查找规则。其中,主要依赖的是 mainFieldsmainFilesextensions 这三个字段。

  • mainFields[Array]: 接受的是数组,用来设置在 package.json 文件中,用来定义具体查找文件的字段。例如:mainFields: ["browser", "module", "main"],则会按顺序搜索:browsermodulemain 这三个字段。
  • mainFiles[Array]:如果文件不含有 package.json,则直接根据 mainFiles 定义的文件来索引。
  • extensions[Array]:设置默认文件后缀名。当索引文件时,如果具体路径没带文件名的话,会根据 exntensions 定义的内容来获取。

loader 简便书写

modules 指令下,添加具体调用的 loader 一定需要带上后面的 -loader 尾缀。如果你不想带,则会默认报错。不想报错的话,可以使用 moduleExtensions 指令来说明:

moduleExtensions: ['-loader']

这样,就可以直接在 module 中直接不带 -loader 引用:

 loader: 'eslint'

externals 定义外部依赖

externals 是用来排除某些你并不想打包的库,并且可以通过 import/require 在全局环境中调用。这个选项一般是提供给一些开源库或者组件作者使用的。

相当于,你调用了一个库,而最终实际打包的文件里面剔除该库的存在,只是留下了一个引用接口。

// 剔除 sw-router 库
externals: {
  Router: 'sw-router'
}

// 在代码中使用
import Router from 'Router';

它可以接收如下类型:(array object function regex)

  • array:用来过滤打包文件中的子模块。
// 将 ./math 文件中的 subtract 过滤不打包。
externals: {
  subtract: ['./math', 'subtract']
}

// 忽略如下文件打包:
import {subtract} from './math';
  • object:通过 key 值,来决定在不同模块规范下,文件暴露的接口名。其中,commonjs 代表 require 模块规则,amd 则代表 define 模块规则,root 则是代表该模块在全局环境,例如 window 访问的对象名。
externals : {
  ex_loadsh : {
    commonjs: "lodash", // require('loadsh')
    amd: "lodash",  // define(['loadsh'])
    root: "_" // window._
  }
}

通过 externals 定义之后,我们在不同环境访问 loadsh 的方式就变为:

// 全局环境
window.ex_loadsh;

// 模块包中
require('ex_loadsh');
  • function: 前面几个写法都是将需要提出的文件写死,而 function 提供了可以灵活配置的 external list。比如,你想剔除所有 node_modules 包中的库,而里面又有几个包需要引用的话,可以写为:
externals: [
  function(context, request, callback) {
    if (/^yourregex$/.test(request)){ // 剔除具体的包
      return callback(null, 'amd ' + request);  // 通过 amd 定义剔除文件的引用方式
    }
    callback(); 
  }
]

不过,更方便的直接使用 webpack-node-externals 来完成。如果项目不是特别复杂,这个配置选项一般用不上。

  • regexp: 使用正则选项,相当于就是通过正则的 test 方法,通过路径名的检查,来决定是否剔除。
externals: /^(jquery|\$)$/i; //剔除 jquery 和 $ 文件导入

# 剔除如下文件
import $ from 'jquery';
// or
import $ from '$';

target 设定打包环境

这里,如果项目并不复杂的话,可以直接忽略该属性设置

因为 webpack 本身都是多环境应用,你可以在 node、web、webworker 甚至其他跨平台环境中:node-webkit、electron-main。其常用选项有三个:

  • node:在后台环境中使用
  • web:在浏览器主线程环境中使用
  • webworker:在 web-worker 环境中使用

具体设置方式为:

 {
  target: 'node'
}

不过,该选项常常用于复杂的打包项目中。比如,一个项目你需要同时输出在 web 环境中和在 webworker 环境中运行的打包脚本文件。这时候,就涉及到多入口文件的 webpack.config.js 设置。这里,可以利用 webpack-merge 来帮助我们完成多入口的合并设置。

var baseConfig = {  
  target: 'async-node',
  entry: {
    entry: './entry.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './dist')
  }
};

let targets = ['web', 'webworker', 'node', 'async-node', 'node-webkit', 'electron-main'].map((target) => {
  let base = webpackMerge(baseConfig, {
    target: target,
    output: {
      path: path.resolve(__dirname, './dist/' + target),
      filename: '[name].' + target + '.js'
    }
  });
  return base;
});

module.exports = targets;

在导出的就会同时编译出 web、webworker、node 等目录,里面的文件就是对应环境中使用的包。如果想用原生的可以直接导出,数组形式的配置项:

var path = require('path');
var serverConfig = {
  target: 'node',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.node.js'
  }
};

var clientConfig = {
  target: 'web', // <=== can be omitted as default is 'web'
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.js'
  }
};

module.exports = [ serverConfig, clientConfig ];

通过,在 webpack 主文件中,具体设置需要使用的配置,即可完成指定环境的编译操作。

node 提供模拟包

node 指令主要是让 webpack 将在 node 环境运行的包,提供相关的设置,能够直接在打包文件中访问。其策略可以是提供一个桩(空对象),或者直接将现成的包直接打进去。

其基本可选择的值主要是直接参考 node 的模块包名:

console
global
process
__filename
__dirname
Buffer
setImmediate

不过,主要用到的还是 Buffer 和 global 两个配置项。其打包策略选项有 4 个值:

  • true: 直接将该包的 polyfill 直接打进去
  • “mock”: 提供空接口。可以访问对应的接口名,但是没有任何作用。
  • “empty”: 提供一个空对象。这个连接口名都不能访问
  • false: 啥都没有。

上述默认配置值为:

node: {
  console: false,
  global: true,
  process: true,
  __filename: "mock",
  __dirname: "mock",
  Buffer: true,
  setImmediate: true
}

这样设置了之后,我们可以直接在主文件入口中访问:

const Buffer = require('Buffer');

// doSth()

如果你想啥都不提供,最好直接将 node 设置为 false

node:false

devtool 开启 sourceMap 选项

devtool 主要是给 JS 文件添加对一个的 sourceMap 文件。不过,webpack 可以生成多种 sourceMap 文件,具体的区别是,映射还原度的区别。这里直接参考 webpack 官方文档即可。

image.png-192.8kB

其中,最常用的选项就是 source-mapcheap-eval-source-map。(这里实在太多了,各位看着选个合适的就行)如果你想关闭,sourceMap 的话,可以直接使用 hidden-source-map,或者不传。比如,下面的配置:

if (COMPILE) {
    config.devtool = 'hidden-source-map';
    config.output.path = path.join(__dirname,'public');
}

devServer 本地 localhost 调试

devServer 是 webpack 提供一个简便的调试服务。它可以根据字段配置,自动起一个 server 来进行调试。如果用过 webpack-dev-server 的同学,应该就很熟悉了。那如果利用 webpack,起一个简单的 server 服务呢?这里,直接给一份配置即可:

devServer: {
  contentBase: path.join(__dirname, "dist"), // 设置静态资源访问路径
  compress: true, // 执行压缩
  port: 9000 // 监听的端口
}

不过,更常用的是利用 webpack server 的热更新机制。配置列表为:

devServer: {
  contentBase: path.join(__dirname, "dist"),
  compress: true,
  port: 9000,
  hot: true,
  hotOnly: true
}

接着,你就可以通过 localhost:9000 来访问你编译过后的目录。

剩下 server 配置,大家可以直接参考 webpack devServer

最后说几句

到这里,webpack 3.x 的整个内容差不多已经阐述完了。不过,关于 plugin 的内容,我们这里并没有过多提及,因为,对于一般 webpack 纯使用者来说,直接使用现有成熟的 plugin 即可。还有一个原因是,webpack plugin 内容真的有点多,这里篇幅有限就不写了。后面专门针对其内容详述一篇。

宣传宣传

更多精彩内容,欢迎关注我的公众号 前端小吉米:

image.png-951.5kB

原文链接: https://www.villianhr.com/2017/10/27/看啥双拱门,来学 webpack 3啊