由require.context引发的思考

本文探讨了webpack的API require.context,用于自动化导入模块,并详细解释了其参数和返回的函数特性。同时,文章对比了ES6模块与CommonJS模块的差异,包括导出方式和加载机制。最后,讨论了全局注册与按需引入组件的策略及其适用场景。

require.context

一个webpack的api,通过执行require.context函数获取一个特定的上下文,主要用来实现自动化导入模块,在前端工程中,如果遇到从一个文件夹引入很多模块的情况,可以使用这个api,它会遍历文件夹中的指定文件,然后自动导入,使得不需要每次显式的调用import导入模块

我想使用require.context引入components目录下的所有组件:
webpack会在构建的时候解析代码中的require.context()。
require.context函数接收三个参数:

  • directory {String} -读取文件的路径
  • useSubdirectories {Boolean} -是否遍历文件的子目录
  • regExp {RegExp} -匹配文件的正则
// 语法
let files = require.context(directory, useSubdirectories = false, regExp = /^\.\//);

require.context模块导出(返回)一个(require)函数files,这个函数有三个属性:

  • resolve:是一个函数,接受一个参数:directory下面匹配文件的相对路径,返回这个匹配文件相对于整个工程的相对路径。
  • keys:也是一个函数,它返回一个数组,返回匹配成功模块的名字组成的数组(不包括文件夹名称)。
  • id:执行环境的id,返回的是一个字符串,主要用在module.hot.accept
const files = require.context('.', true, /index.vue$/);
/* resolve */
console.log('resolve', files.resolve(files.keys()[0]));
// 输出:resolve ./src/components/ke-fu/index.vue

/* keys */
./ke-fu/index.vue
./layout/index.vue
./loading/src/index.vue
./operation-status/index.vue
./product-grade/index.vue
./scroll-list/index.vue
./slide/index.vue

同时files作为一个函数,也接受一个req参数,这个和resolve方法的req参数是一样的,即匹配的文件名的相对路径,而files函数返回的是一个模块,这个模块才是真正我们需要的。这个Module模块和使用import导入的模块是一样的。

得到components目录下所有的模块之后,需要导出,那么问题来了。这种方式引入的模块,似乎只能通过export default全部导出,而不能通过export { …allModules }导出。

参考:vue官方示例基础组件的自动化全局注册

模块导出方式

ES6 模块与 CommonJS 模块完全不同。重大差异:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • ES6 模块之中,顶层的this指向undefined;CommonJS 模块的顶层this指向当前模块
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。【CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。】
// 报错
let allModules = {
  Layout,
  VLoading,
};
export allModules

// 正确
export {
  Layout,
  VLoading,
};

// 正确
export let allModules = {
  Layout,
  VLoading,
}

// 正确
// 本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。export default a的含义是将变量a的值赋给变量default
export default allModules
ES6 模块加载 CommonJS 模块

CommonJS 模块的输出都定义在module.exports这个属性上面。Node 的import命令加载 CommonJS 模块,Node 会自动将module.exports属性,当作模块的默认输出,即等同于export default xxx。【CommonJS 模块的输出缓存机制,在 ES6 加载方式下依然有效。】

// a.js
module.exports = {
  foo: 'hello',
  bar: 'world'
};

// 等同于
export default {
  foo: 'hello',
  bar: 'world'
};

import命令加载上面的模块,module.exports会被视为默认输出,即import命令实际上输入的是这样一个对象{ default: module.exports }

// 写法一
import baz from './a';
// baz = {foo: 'hello', bar: 'world'};

// 写法二
import {default as baz} from './a';
// baz = {foo: 'hello', bar: 'world'};

// 写法三
import * as baz from './a';
// baz = {
//   get default() {return module.exports;},
//   get foo() {return this.default.foo}.bind(baz),
//   get bar() {return this.default.bar}.bind(baz)
// }

由于 ES6 模块是编译时确定输出接口,CommonJS 模块是运行时确定输出接口,所以采用import命令加载 CommonJS 模块时,不允许采用下面的写法。

// 不正确
import { readFile } from 'fs';

上面的写法不正确,因为fs是 CommonJS 格式,只有在运行时才能确定readFile接口,而import命令要求编译时就确定这个接口。解决方法就是改为整体输入。

// 正确的写法一
import * as express from 'express';
const app = express.default();

// 正确的写法二
import express from 'express';
const app = express();
CommonJS 模块加载 ES6 模块

CommonJS 模块加载 ES6 模块,不能使用require命令,而要使用import()函数。ES6 模块的所有输出接口,会成为输入对象的属性。

参考:Module 的加载实现

模块的全局注册和按需引入

还有个问题就是,我们在加载页面的过程中,希望组件也是按需加载的。如果是通过export default全部导出,那么当页面需要引用其中某些组件时,是不是将所有的组件都打包引入了呢?全局注册的意思是不是已经全部被引入?

对于所有组件都在项目中被用到的情况来说,全局注册和按需引入的方式在打包和运行效率上并没有什么区别,只是相对来说按需引入可读性更强一点。
对于引入外部组件库来说,按需引入的方式则更好。(webpack 摇树处理 Tree Shaking)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值