webpack中require.context的作用

在我们项目开发中,经常需要import或者export各种模块,那么有没有什么办法可以简化这种引入或者导出操作呢?答案是肯定的,下面就为大家介绍一下require.context
我们会这样引入组件:

  1. import A from 'components/A'
  2. import B from 'components/B'
  3. import C from 'components/C'
  4. import D from 'components/D'
  5. // ...

这样很蛋疼,因为每加一个组件,可能都要写这么一句,这样有规律的事,是否可以通过自动化完成?
看下Webpack [Dependency Management | webpack]

require.context


  1. require.context(directory, useSubdirectories, regExp)
  1. directory: 要查找的文件路径
  2. useSubdirectories: 是否查找子目录
  3. regExp: 要匹配文件的正则

用法

  1. require.context('./components/', true, /\.js$/)

image.png

目录结构
上面调用方法,到底返回的是什么?

  1. var map = {
  2. "./A.js": "./src/components/test/components/A.js",
  3. "./B.js": "./src/components/test/components/B.js",
  4. "./C.js": "./src/components/test/components/C.js",
  5. "./D.js": "./src/components/test/components/D.js"
  6. };
  7. function webpackContext(req) {
  8. var id = webpackContextResolve(req);
  9. return __webpack_require__(id);
  10. }
  11. function webpackContextResolve(req) {
  12. var id = map[req];
  13. if(!(id + 1)) { // check for number or string
  14. var e = new Error("Cannot find module '" + req + "'");
  15. e.code = 'MODULE_NOT_FOUND';
  16. throw e;
  17. }
  18. return id;
  19. }
  20. webpackContext.keys = function webpackContextKeys() {
  21. return Object.keys(map);
  22. };
  23. webpackContext.resolve = webpackContextResolve;
  24. module.exports = webpackContext;
  25. webpackContext.id = "./src/components/test/components sync recursive \\.js$";

代码很简单,require.context执行后,返回一个方法webpackContext,这个方法又返回一个webpack_require,这个webpack_require就相当于require或者import。同时webpackContext还有二个静态方法keys与resolve,一个id属性。

  1. keys: 返回匹配成功模块的名字组成的数组
  2. resolve: 接受一个参数request,request为test文件夹下面匹配文件的相对路径,返回这个匹配文件相对于整个工程的相对路径
  3. id: 执行环境的id,返回的是一个字符串,主要用在module.hot.accept,应该是热加载

看下keys是作用

  1. const ctx = require.context('./components/', true, /\.js$/)
  2. console.log(ctx.keys())
  3. // ["./A.js", "./B.js", "./C.js", "./D.js"]

其实就是

  1. var map = {
  2. "./A.js": "./src/components/test/components/A.js",
  3. "./B.js": "./src/components/test/components/B.js",
  4. "./C.js": "./src/components/test/components/C.js",
  5. "./D.js": "./src/components/test/components/D.js"
  6. };
  7. Object.keys(map)

只不过map是模块内部变量,无法直接访问,所以通过其实提供的keys方法访问
那么如何引入ABCD组件呢?

  1. const ctx = require.context('./components/', true, /\.js$/)
  2. const map = {}
  3. for (const key of ctx.keys()) {
  4. map[key] = ctx(key)
  5. }
  6. console.log(map)

看到了吧!成功import进来了,但’./A.js’这样的key有点不太好,自己可以处理字符串生成自己想要的key

可以优化一下,生成一个公共的方法

  1. const importAll = context => {
  2. const map = {}
  3. for (const key of context.keys()) {
  4. const keyArr = key.split('/')
  5. keyArr.shift() // 移除.
  6. map[keyArr.join('.').replace(/\.js$/g, '')] = context(key)
  7. }
  8. return map
  9. }
  10. export default importAll

使用

  1. import importAll from '$common/importAll'
  2. export default importAll(require.context('./', true, /\.js$/))

什么时候需要用到require.context

如果有以下情况就可以用到

index批量导入modules文件

image.png
在Vue写的项目中,我把路由通过不同的功能划分成不同的模块,在index.js中一个个导入(原谅ide的警告-.-),但是如果项目变大了之后,每次手动import会显得有些力不从心,这里可以使用require.context函数遍历modules文件夹的所有文件一次性导入到index.js中

写法如下:

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. import getters from './getters'
  4. Vue.use(Vuex)
  5. // https://webpack.js.org/guides/dependency-management/#requirecontext
  6. const modulesFiles = require.context('./modules', true, /\.js$/)
  7. // you do not need `import app from './modules/app'`
  8. // it will auto require all vuex module from modules file
  9. const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  10. // set './app.js' => 'app'
  11. const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
  12. const value = modulesFiles(modulePath)
  13. modules[moduleName] = value.default
  14. return modules
  15. }, {})
  16. const store = new Vuex.Store({
  17. modules,
  18. getters
  19. })
  20. export default store

小记:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
image.png
工程思路:

  • 首先调用require.context导入某个文件夹的所有匹配文件,返回执行上下文的环境赋值给files变量
  • 声明一个configRouters用来暴露给外层index.js作为vue-router的数组
  • 调用files函数的keys方法返回modules文件夹下所有以.js结尾的文件的文件名,返回文件名组成的数组
  • 遍历数组每一项,如果是index.js就跳过(index.js并不是路由模块),调用files函数传入遍历的元素返回一个Modules模块
  • 因为我的路径是用export default导出的,所以在Module模块的default属性中获取到我导出的内容(即路由的结构),类似这种样子


svg优雅引入require.context的应用

require.context另外一个常用的地方是svg图标,可以不用每次导入图标文件,相对于以前的iconfont,svg有很多好处强烈推荐,详情可以看这个
手摸手,带你优雅的使用 icon