我们在 Code Spliting 一章中介绍了一些动态加载的技术。本章,我们介绍一种更加灵活的动态加载技术,require.context

通过 require.context 来动态加载

require.contextopen in new window 提供了一种通用的代码分割形式。

假设我们使用 Webpack 来构建一个静态网站。网站内容都在 ./pages/ 目录下,以 Markdown 形式保存。每个 Markdown 文件都有一个 YAML frontmatter 来定义元数据。我们可以通过如下方式来加载 Markdown 文件。

// 通过 `yaml-frontmatter-loader` and `json-loader` 来处理文件。
// `yaml-frontmatter-loader` 将文件中的 frontmatter 和文件内容解析出来,
// `json-loader` 再将其转换成 JSON 结构
// 在这个过程中,Markdown 不会被处理。
const req = require.context(
  'json-loader!yaml-frontmatter-loader!./pages',
  true, // 递归加载文件
  /^\.\/.*\.md$/ // 匹配 `.md` 结尾的文件
);
1
2
3
4
5
6
7
8
9

require.context 返回一个函数,我们使用这个函数来加载文件。require.context 会创建一个模块,有自己的模块 id,同时提供一个 .keys() 方法,返回模块的内容(文件列表)。

req.keys(); // ["./demo.md", "./another-demo.md"]
req.id; // 42

// {title: "Demo", body: "# Demo page\nDemo content\n\n"}
const demoPage = req('./demo.md');
1
2
3
4
5

如果与 TypeScript 一起使用,要确保安装了 @types/webpack-envopen in new window,否则 require.context 无法工作。

import 中的动态路径

当我们给 import 的路径信息是一个动态路径的时候,Webpack 会内部创建一个 context.

const target = "fi";

import(`translations/${target}.json`).then(...).catch(...);
1
2
3

require 在遇到动态路径的处理方式与 import 相同。比如 require(assets/modals/${imageSrc}.js);,将会创建一个 context,并解析到一个 imageSrc 的文件。

在使用动态加载的时候,建议明确指定文件后缀,这样可以减小 context 的大小,同时提高构建性能。

组合多个 require.context

我们可以将多个独立的 require.context 合并成一个。

const { concat, uniq } = require('lodash');

const combineContexts = (...contexts) => {
  function webpackContext(req) {
    // Find the first match and execute
    const matches = contexts
      .map((context) => context.keys().indexOf(req) >= 0 && context)
      .filter((a) => a);

    return matches[0] && matches[0](req);
  }
  webpackContext.keys = () =>
    uniq(
      concat.apply(
        null,
        contexts.map((context) => context.keys())
      )
    );
  return webpackContext;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

处理运行时的动态路径

Webpack 的动态加载还是要基于静态分析依赖关系的。如果动态加载的模块在其他地方,比如是一个网络文件,那么 Webpack 将无法完成动态加载。我们需要借助于其他工具,比如 script.jsopen in new window 或者 little-loaderopen in new window

总结

当我们需要大量文件的时候,require.context 会是一个非常有用的特性。

动态 import 会在内部调用 require.context

require.context 仅适用于文件系统,如果要加载网络文件等形式的文件,需要借助于其他工具。

关注微信公众号,获取最新推送~