一文读懂require.context,实现前端工程自动化

一文读懂require.context,实现前端工程自动化

你越是认真生活,你的生活就会越美好——弗兰克·劳埃德·莱特
《人生果实》经典语录

带表达式的 require 语句

webpack官网 依赖管理

如果你的request 含有表达式(expressions),就会创建一个上下文(context),因为webpack在编译时(compile time)并不清楚具体导入哪个模块。

示例,考虑到我们有包含 .ejs 文件的如下目录结构:

example_directory
│
└───template
│   │   table.ejs
│   │   table-row.ejs
│   │
│   └───directory
│       │   another.ejs

当下面的require()调用被评估解析:

require('./template/' + name + '.ejs');

webpack 解析require()调用,然后提取出如下一些信息:

Directory: ./template
Regular expression: /^.*\.ejs$/

意思是: 在当前文件下的template文件下,匹配以.ejs为结尾的文件

不熟悉正则的同学,可以看看下面的解析
正则解析:

/^.*\.(jpg|gif|png|bmp)$/i
  • ^: 匹配字符串的开始位置
  • .*: .匹配任意字符,*匹配数量0到正无穷
  • \.: 斜杠用来转义,\.匹配.
  • (jpg|gif|png|bmp): 匹配jpg或gif或png或bmp
  • $: 匹配字符串的结束位置
  • i: 不区分大小写。

合起来就是匹配以.jpg.GIF或…结尾的任意字符串,不区分大小写

context module

会生成一个context module(上下文模块)。会生成一个context module(上下文模块)。它包含目录下的所有模块的引用,如果一个request符合正则表达式,就能require进来。

context module包含一个map(映射)对象,会把 requests 翻译成对应的模块 id。

示例 map(映射):

{
  "./table.ejs": 42,
  "./table-row.ejs": 43,
  "./directory/another.ejs": 44
}

此 context module 还包含一些访问这个 map 对象的 runtime 逻辑。

这意味着webpack 能够支持动态地 require,但会导致所有可能用到的模块都包含在bundle中。

require.context

我们可以通过require.context() 函数来创建自己的context

可以给这个函数传入三个参数:

  • 要搜索的目录
  • 标记表示是否还搜索其子目录
  • 匹配文件的正则表达式

webpack 会在构建中解析代码中的 require.context()

语法如下:

require.context(
  directory,
  (useSubdirectories = true),
  (regExp = /^\.\/.*$/),
  (mode = 'sync')
);

示例:

require.context('./test', false, /\.test\.js$/);
//(创建出)一个 context,其中文件来自 test 目录,request 以 `.test.js` 结尾。
require.context('../', true, /\.stories\.js$/);
// (创建出)一个 context,其中所有文件都来自父文件夹及其所有子级文件夹,request 以 `.stories.js` 结尾。

ps:
传递给 require.context 的参数必须是字面量(literal)!

context module API (resolve(), keys(), id)

一个context module会导出一个(require)函数,此函数可以接收一个参数:request

此导出函数有三个属性:resolve, keys, id

  • resolve 是一个函数,它返回 request 被解析后得到的模块 id。
  • keys 也是一个函数,它返回一个数组,由所有可能被此 context module处理的请求(译者注:参考下面第二段代码中的 key)组成。

如果想引入一个文件夹下面的所有文件,或者引入能匹配一个正则表达式的所有文件,这个功能就会很有帮助,例如:

function importAll(r) {
  r.keys().forEach(r);
}

importAll(require.context('../components/', true, /\.js$/));
const cache = {};

function importAll(r) {
  r.keys().forEach((key) => (cache[key] = r(key)));
}

importAll(require.context('../components/', true, /\.js$/));
// 在构建时(build-time),所有被 require 的模块都会被填充到 cache 对象中
  • id 是context module的模块 id. 它可能在你使用 module.hot.accept 时会用到。

vue项目动态导入全局组件

目录结构如下
一文读懂require.context,实现前端工程自动化
src\components\common\index.js

export default {
  install(Vue) {
    const ctxRequire = require.context('@/components/common', false, /\.vue$/)
    debugger
    console.log('ctxRequire:', ctxRequire)
    // console.log('ctxRequire.resolve():', ctxRequire.resolve())
    console.log('ctxRequire.keys:', ctxRequire.keys())
    console.log('ctxRequire.id:', ctxRequire.id)
    ctxRequire.keys().forEach((filePath) => {
      const fileName = filePath.replace(/(.*\/)*([^.]+).vue$/ig, '$2') // 获取Vue组件文件名
      // "./breadcrumb.vue".replace(/(.*\/)*([^.]+).vue$/ig, '$2') 返回breadcrumb
      Vue.component(fileName, ctxRequire(filePath).default || ctxRequire(filePath))
    })
  }
}

打印如下
一文读懂require.context,实现前端工程自动化
控制台点击ctxRequire打印的内容,会跳到源码页面,源码如下

var map = {
	"./VTitle.vue": "./src/components/common/VTitle.vue",
	"./breadcrumb.vue": "./src/components/common/breadcrumb.vue",
	"./common-title.vue": "./src/components/common/common-title.vue",
	"./custom-switch.vue": "./src/components/common/custom-switch.vue",
	"./file-list.vue": "./src/components/common/file-list.vue",
	"./more-button.vue": "./src/components/common/more-button.vue",
	"./nodata.vue": "./src/components/common/nodata.vue",
	"./pagination.vue": "./src/components/common/pagination.vue",
	"./previewDialog.vue": "./src/components/common/previewDialog.vue",
	"./server-type.vue": "./src/components/common/server-type.vue",
	"./step-nav.vue": "./src/components/common/step-nav.vue",
	"./svg-icon.vue": "./src/components/common/svg-icon.vue",
	"./tags.vue": "./src/components/common/tags.vue",
	"./upload-img.vue": "./src/components/common/upload-img.vue",
	"./upload-imgs.vue": "./src/components/common/upload-imgs.vue"
};


function webpackContext(req) {
	var id = webpackContextResolve(req);
	return __webpack_require__(id);
}
function webpackContextResolve(req) {
	if(!__webpack_require__.o(map, req)) {
		var e = new Error("Cannot find module '" + req + "'");
		e.code = 'MODULE_NOT_FOUND';
		throw e;
	}
	return map[req];
}
webpackContext.keys = function webpackContextKeys() {
	return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = "./src/components/common sync \\.vue$";

src\plugins\common.js

import Vue from 'vue'
import commonComps from '../components/common/index'

Vue.use(commonComps) // 会执行commonComps的install方法

src\main.js

import './plugins/common'
...

这样后,就可以在Vue任意地方用这些全局组件,不需要另外注册

PS:
ctxRequire(filePath).default || ctxRequire(filePath)相当于import进来的模块,假如filePath的值是"./breadcrumb.vue"
等同于
mport breadcrumb from './breadcrumb.vue'

import breadcrumb from './breadcrumb.vue'

export default {
  install(Vue) {
    const ctxRequire = require.context('@/components/common', false, /\.vue$/)
    ctxRequire.keys().forEach((filePath) => {
      const fileName = filePath.replace(/(.*\/)*([^.]+).vue$/ig, '$2') // 获取Vue组件文件名
      if (fileName === 'breadcrumb') {
        console.log('breadcrumb:', breadcrumb)
        console.log(ctxRequire(filePath).default || ctxRequire(filePath))
        console.log(breadcrumb === (ctxRequire(filePath).default || ctxRequire(filePath)))
      }
      console.log('ctxRequire(filePath):', ctxRequire(filePath))
      Vue.component(fileName, ctxRequire(filePath).default || ctxRequire(filePath))
    })
  }
}

打印如下
一文读懂require.context,实现前端工程自动化

Vue源码学习目录

Vue源码学习完整目录


谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强

上一篇:配置Samba服务器和创建逻辑卷


下一篇:YsoSerial 工具常用Payload分析之Common-Collections7(四)