背景
在我们项目开发中,经常需要 import 或者 export 各种模块,但随着我们项目越来越大,如果还是通过 import 分别引入文件,那将是一件非常麻烦的事情。
这里先举一个十分常见的例子,比如 vue 项目的 vuex 文件非常多,每次新增一个模块,都需要重新引入一次,以及注册一次,不仅不美观而且很麻烦。怎么可以做到只需要新建 js 完成逻辑,自动就会注入到vuex中呢?
这就说到了今天的主题,使用到了 Webpack 中的 require.context() 。
解析与实现
官网解释:给这个函数传入三个参数:一个要搜索的目录,一个标记表示是否还搜索其子目录, 以及一个匹配文件的正则表达式。
Webpack 会在构建中解析代码中的 require.context() 。
语法如下:
require.context(
directory,
(useSubdirectories = true),
(regExp = /^./.*$/),
(mode = 'sync')
);
示例:
require.context('./test', false, /.test.js$/);
//(创建出)一个 context,其中文件来自 test 目录,request 以 `.test.js` 结尾。
下面来解析一下这个方法的实现。
1. 解析 require.context()
在解析之前,需要先看一下 store 目录,包含很多模块,childModule
里还有子模块,按正常来写 index.js 里将会引入很多这样的 js ,然后再去 store 注册,就像上面所说的,这是不美观且麻烦的。接下来我们采用上面所说的 require.context ,先在 index.js 里将它打印出来,让我们揭开它的真面目。
//index.js
let context = require.context('./modules', true, /.js$/)
console.log('context', context);
这里会打印出来一个叫 webpackContext 的方法,点击这个webpackContext 方法,可以看到以下内容。
var map = {
"./moduleA.js": "./src/store/modules/moduleA.js",
"./moduleB.js": "./src/store/modules/moduleB.js",
"./childModule/moduleD.js": "./src/store/modules/childModule/moduleD.js",
...
};
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/store/modules sync recursive \.js$";
代码很容易看懂,require.context 执行后,返回一个方法 webpackContext ,这个方法又返回一个 __webpack_require__ ,这个 __webpack_require__ 就是一个模块加载器,而所有的模块都会以对象的形式被读取加载。同时 webpackContext 还有二个静态方法 keys 与 resolve ,一个 id 属性。
- keys:经过打印,可以发现是以引入模块的相对路径为 key 组成的数组。
const context = require.context('./modules', true, /.js$/)
console.log('keys', context.keys());
//['./childModule/moduleE.js', './childModule/moduleF.js', './childModule/moduleG.js', './childModule/moduleH.js', './moduleA.js', './moduleB.js', './moduleC.js', './moduleD.js']
- resolve:接受一个参数 request ,request 为 modules 文件夹下面匹配文件的相对路径,也就是上面的 key ,然后返回这个匹配文件相对于整个工程的相对路径。
const context = require.context('./modules', true, /.js$/)
console.log('resolve', context.resolve('./moduleA.js'));
//resolve ./src/store/modules/moduleA.js
- id:执行环境的 id ,返回的是一个字符串。
再来打印一下 webpackContext 方法,可以看出来返回了一个模块。
const context = require.context('./modules', true, /.js$/)
console.log('webpackContext', context('./moduleA.js'));
//webpackContext Module {default: {…}, __esModule: true, Symbol(Symbol.toStringTag): 'Module'}
了解了上面的代码,接下来可以就可以进行实践了。
2.实现vuex模块自动化注入
import Vue from 'vue';
import Vuex from 'vuex';
import camelcase from 'camelcase'; //驼峰命名的一个npm包
Vue.use(Vuex);
const context = require.context('./modules', false, /.js$/);
//获取moudules文件下所有js文件;
const modules = context.keys().reduce((modules, modulePath) => {
const moduleName = modulePath.replace(/(.*/)*([^.]+).js$/ig,"$2");
const module = context(modulePath)
modules[moduleName] = module.default
return modules
}, {})
export default new Vuex.Store({
modules
});
通过以上代码就可以实现自动分 module 注册 store 。
其他使用场景
除了上面的vuex的例子以外还可以用到其他地方,在这里简略介绍一下。
1.vue中组件自动化全局注册
在我们的项目中,可能会有很多全局的基础组件,例如button、input等。
如果有很多这样的组件,放在入口文件 main.js 中也会很冗长,所以可以把全局组件放在一起,通过 require.context 引入,也省去了每次都往 component 里注册的繁琐。
例如放在 components 文件夹下,简单说一下实现方法。
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst' //首字母大写
import camelCase from 'lodash/camelCase' //驼峰命名
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/.\w+$/, '')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
大家可以直接看官方文档,有很详细的描述。
vue官方文档-基础组件的全局注册
2.路由“去中心化”管理
这个主要是在路由过多时,分模块管理路由,例如下面这样。
// rootRoute.js
const rootRoute = {
childRoutes: [
{
path: '/',
component: Home,
childRoutes: [
require('./modules/home/route'), //首页模块
require('./modules/class/route'), // 课堂模块
require('./modules/teacher/route'), // 教师模块
// ...
// 其他大量新增模块
// ...
]
}
]
};
这里可以改写成
const rootRoute = {
childRoutes: [
{
path: '/',
component: Home,
childRoutes: (r => {
return r.keys().map(r);
})(require.context('./', true, /^./modules/((?!/)[\s\S])+/route.js$/))
}
]
};
这样即使新增模块,也不需要考虑其他的事情,专注于模块开发。
# 结尾
希望对大家有所帮助~