三石的webpack.config.js(resolve篇)

2,242 阅读5分钟

webpack中使用resolve字段来配置模块的相关解析策略。本质上是通过对resolve库的使用来解析引入模块路径,帮助 webpack 找到 bundle 中以require/import引入的模块代码。

模块可以是一个文件也可以是一个文件夹,可以是相对路径, 也可以是绝对路径 ,还可以是模块路径/npm路径(如:import lodash "loadash";)。无论是哪一种情况的引入,最终解析得到的都是一个绝对路径的文件,这个文件可能有扩展名,也可能没有扩展名

  1. 绝对路径
import '/home/me/file';

import 'C:\Users\me\file';

由于已经获得文件的绝对路径,因此不需要再做进一步解析

  1. 相对路径
import '../src/file1';
import './file2';

在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录。在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径

  1. 模块路径 比较复杂,下面会举例

modules

告诉 webpack 去哪些目录下寻找第三方模块(也就是使用模块路径引入的时候,例如'import lodash from 'lodash''),默认是只会去 node_modules 目录下寻找,是一个数组;
绝对路径和相对路径都能使用,但是之间有一点差异。

  1. 使用绝对路径,将只在给定目录中搜索。
module.exports = {
  resolve: {
      modules: ['node_modules']
  }
};
  1. 如果你想要添加一个目录到模块搜索目录,此目录优先于 node_modules/ 搜索:        
module.exports = {
  resolve: {
      modules: [path.resolve(__dirname, 'src'), 'node_modules'] //从左到右依次查找
  }
};

descriptionFiles

这个选项会定义一个用于描述的 JSON 文件,默认为descriptionFiles:['package.json']

这个json里会包括某些描述路径的字段如main、module等,这些字段的值会指定一个具体模块指向的路径。

举例

我们在自己的项目里,需要引入 lodash2 模块;我们进行相关配置

// 本项目
// webpack.config.js
module.exports = {
  resolve: {
      modules: ['node_modules2'],
      descriptionFiles: ['package2.json']
      mainFields: ['main2', 'browser', 'main'],
  }
};
// 本项目
import lodash2 from 'lodash2';
// lodash2项目
// package2.json
  ...
  "main2""main2.js"
  "browser": "build/upstream.js",
  1. 首先,通过本项目webpack配置里的modules: ['node_modules2']字段,会去node_modules2下寻找lodash2
  2. 发现lodash是个文件夹,再通过本项目webpack配置里的descriptionFiles:['package2.json']寻找lodash2下的 package2.json 文件(下面的两步我们可以看完下节mainFields后再理解)
  3. 再通过本项目webpack配置里的mainFields: ['main2', 'browser', 'main']字段,在loadash2的package2.json中依次寻找main2 browser main字段,看先命中哪一个
  4. 发现命中main2字段,那么我们读其值,main2.js,这个文件也就是 import lodash2 from 'lodash2';最终引入的文件

mainFields

当从 npm 包中导入模块时(例如,import D3 from 'd3'),此选项将决定在 package.json 中使用哪个字段导入模块。根据 webpack 配置中指定的 target 不同,默认值也会有所不同。

默认值

当 target 属性设置为 webworkerweb 或者没有指定,默认值为mainFields: ['browser', 'module', 'main']
对于其他任意的 target(包括 node),默认值为: mainFields: ['module', 'main']

怎么用

在 package.json 文件里,新增字段并指定导入路径 如:

// package.json
{
  ...
  "browser": "build/upstream.js",
  "module": "index.js",
  "main""main.js"
}

Demo

有一些第三方模块会针对不同环境提供几分代码。 例如分别提供采用 ES5 和 ES6 的2份代码,这2份代码的位置写在  package.json  文件里,如下:

{
  "jsnext:main": "es/index.js",// 采用 ES6 语法的代码入口文件
  "main": "lib/index.js" // 采用 ES5 语法的代码入口文件
}

Webpack会根据mainFields数组的顺序去 package.json  文件里寻找,只会使用找到的第一个。
假如你想优先采用 ES6 的那份代码,可以这样配置:

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

这样就会命中 jsnext:main,会优先去 "es/index.js"里寻找

实际应用

多库联调场景

Notice

  • 如果字段指定的路径文件没有扩展名,如main:'lodash',这时其实还是要用到 extensions 做扩展名补充的
  • 若 package.json 中找不到 resolve.mainFields 中定义的字段,那么这时候会去找resolve.mainFiles选项,此选项就是在 package.json 文件不存在或者 package.json 文件中的 mainFields 字段没有返回一个有效路径,则按照顺序查找resolve.mainFiles配置选项中指定的文件名,看是否能匹配到一个存在的文件名

mainFiles

当你引入路径只精确到一个文件夹时, 由这个字段来判断到底指向哪个文件, 默认为: index(建议不要修改,就使用index)

import { a } from '../utils'

通常我们这样写就行,并不会指定../utils/index.js,这就是使用了mainFiles字段

extensions

是一个数组,尝试按顺序解析这些后缀名,这样用户在使用时可以省略相关扩展名

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

alias

定义别名来把原导入路径映射成一个新的导入路径

module.exports = {
  //...  
  resolve:{
    alias:{
      components: './src/components/'
    }
  }
};

当你通过 import Button from 'components/button' 导入时,实际上被等价替换成了 import Button from './src/components/button'

注:这样做可能会命中太多的导入语句,alias 还支持 $ 符号来缩小范围到只命中以关键字结尾的导入语句:

resolve:{
  alias:{
    'react$': path.resolve(__dirname, 'src/utilities/')
  }
}

注:resolve.alias 优先级高于其它模块解析方式。

aliasFields

指定一个字段进行解析。

module.exports = {
  //...
  resolve: {
    aliasFields: ['browser'],
  },
};

不太能get这个作用

Other

  1. fallback 当正常解析失败时,重定向模块请求

  2. exportsFields 在 package.json 中用于解析模块请求的字段

  3. unsafeCache

  • 传递 true 将缓存一切模块,并 不安全
  • 正则表达式,或正则表达式数组,可以用于匹配文件路径或只缓存某些模块。
module.exports = {
  //...
  resolve: {
    unsafeCache: /src/utilities/,
  },
};