一文读懂require.context,实现前端工程自动化
你越是认真生活,你的生活就会越美好
——弗兰克·劳埃德·莱特
《人生果实》经典语录
带表达式的 require 语句
如果你的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项目动态导入全局组件
目录结构如下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))
})
}
}
打印如下
控制台点击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))
})
}
}
打印如下
Vue源码学习目录
谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强