предисловие
require.contextНа самом деле, это очень полезный API. Но спустя 3-4 года все еще есть много людей, которые не знают, как им пользоваться.
И какие вещи этот API в основном делает для нас?Это может помочь нам динамически загружать файлы, которые мы хотим, очень гибкие и мощные (рекурсивные каталоги).Вы можете делать то, что импортировать не может. Сегодня я возьму вас на анализ, вебпакrequire.context
как это достигается.
Готов к работе
Прежде чем анализировать этот API, нам нужно понять самый простой файл, в который будет компилироваться webpack.
-- src
-- index.ts
// index.ts
console.log(123)
После компиляции мы видим, что webpack будет скомпилирован в следующий код
// 源码 https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/bundle-only-index.js
(function(modules) { // webpackBootstrap
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
// define __esModule on exports
__webpack_require__.r = function(exports) {
Object.defineProperty(exports, '__esModule', { value: true });
};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
__webpack_require__.p = "";
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.ts");
})
({
"./src/index.ts": (function(module, exports) {
console.log('123');
})
});
На первый взгляд очень сумбурно, поэтому, чтобы разобраться со структурой, я помогу вам удалить некоторые, не относящиеся к этой статье. Собственно, основная структура именно такая.Кода не так много, чтобы потом разбираться, обязательно внимательно смотрите каждую строчку
// 源码地址 https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/webpack-main.js
(function(modules) {
// 缓存所有被加载过的模块(文件)
var installedModules = {};
// 模块(文件)加载器 moduleId 一般就是文件路径
function __webpack_require__(moduleId) {
// 走 cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache) 解释比我清楚
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
});
// 执行我们的模块(文件) 目前就是 ./src/index.ts 并且传入 3 个参数
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
// Flag the module as loaded 解释比我清楚
module.l = true;
// Return the exports of the module 解释比我清楚
return module.exports;
}
// 开始加载入口文件
return __webpack_require__((__webpack_require__.s = './src/index.ts'));
})({
'./src/index.ts': function(module, exports, __webpack_require__) {
console.log('123');
}
});
__webpack_require__
Это загрузчик модулей, и все наши модули будут читаться и загружаться в виде объектов.
modules = {
'./src/index.ts': function(module, exports, __webpack_require__) {
console.log('123');
}
}
Назовем такую структуру условно模块结构对象
положительный
Поняв основную структуру, мы можем написать абзацrequire.context
Посмотрим на эффект. Давайте сначала добавим 2 файла ts и изменим наш index.ts, чтобы проверить нашу динамическую загрузку.
--- src
--- demos
--- demo1.ts
--- demo2.ts
index.ts
// index.ts
// 稍后我们通过源码分析为什么这样写
function importAll(contextLoader: __WebpackModuleApi.RequireContext) {
contextLoader.keys().forEach(id => console.log(contextLoader(id)));
}
const contextLoader = require.context('./demos', true, /\.ts/);
importAll(contextLoader);
Глядя на наш скомпилированный исходный код, мы обнаружили, что есть такой кусок模块结构对象
// 编译后代码地址 https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/contex-sync.js#L82-L113
{
'./src/demos sync recursive \\.ts': function(module, exports, __webpack_require__) {
var map = {
'./demo1.ts': './src/demos/demo1.ts',
'./demo2.ts': './src/demos/demo2.ts'
};
// context 加载器,通过之前的模块加载器 加载模块(文件)
function webpackContext(req) {
var id = webpackContextResolve(req);
var module = __webpack_require__(id);
return module;
}
// 通过 moduleId 查找模块(文件)真实路径
// 个人在这不喜欢 webpack 内部的一些变量命名,moduleId 它都会编译为 request
function webpackContextResolve(req) {
// id 就是真实文件路径
var id = map[req];
// 说实话这波操作没看懂,目前猜测是 webpack 会编译成 0.js 1.js 这样的文件 如果找不到误加载就出个 error
if (!(id + 1)) {
// check for number or string
var e = new Error('Cannot find module "' + req + '".');
e.code = 'MODULE_NOT_FOUND';
throw e;
}
return id;
}
// 遍历得到所有 moduleId
webpackContext.keys = function webpackContextKeys() {
return Object.keys(map);
};
// 获取文件真实路径方法
webpackContext.resolve = webpackContextResolve;
// 该模块就是返回一个 context 加载器
module.exports = webpackContext;
// 该模块的 moduleId 用于 __webpack_require__ 模块加载器
webpackContext.id = './src/demos sync recursive \\.ts';
}
Я написал очень подробные комментарии в исходном коде. Прочитав этот код, нетрудно понять, что написано в документацииrequire.context
вернет функцию (webpackContext) с 3 API.
Затем смотрим на скомпилированныйindex.ts
исходный код
'./src/index.ts': function(module, exports, __webpack_require__) {
function importAll(contextLoader) {
contextLoader.keys().forEach(function(id) {
// 拿到所有 moduleId,在通过 context 加载器去加载每一个模块
return console.log(contextLoader(id));
});
}
var contextLoader = __webpack_require__(
'./src/demos sync recursive \\.ts'
);
importAll(contextLoader);
}
Легко найтиrequire.context
скомпилировано как__webpack_require__
загрузчик и загружается с идентификатором как./src/demos sync recursive \\.ts
модуль,sync
Указывает, что мы загружаем эти модули синхронно (мы введем этот параметр позже),recursive
Указывает, что требуется рекурсивный поиск в каталоге. С тех пор мы можем полностью понять, как webpack собирает все модули и загружает их динамически.
Расширенное углубленное изучение исходного кода веб-пакета
Мы знаем, что после версии webpack 2.6 при загрузке модулей можно указыватьwebpackMode
Режим загрузки модуля, мы можем использовать несколько способов управления модулями, которые мы хотим загрузить. Обычно используемые режимыsync
lazy
lazy-once
eager
Итак, в require.context то же самое, если мы посмотрим на@types/webpack-env
Нетрудно найти, что у него есть и четвертый параметр.
Кратко
-
sync
Упаковывать непосредственно в текущий файл, загружать и выполнять синхронно -
lazy
Ленивая загрузка выделяет отдельные файлы чанков -
lazy-once
Ленивая загрузка выделит отдельный файл фрагмента, загрузит код в следующий раз и прочитает код непосредственно в памяти. -
eager
Отдельный файл чанка выделяться не будет, а будет возвращен промис, и код будет выполняться только при вызове промиса, можно понять, что сначала загружается код, но мы можем контролировать отложенное выполнение этой части кода.
Документация здесьWebpack.doc — это China.org/API/module-…
Эта часть документации очень неясна, и, возможно, группа документации не поспевает за ней, поэтому, если мы посмотрим на исходный код веб-пакета, мы обнаружим, что на самом деле существует 6 режимов.
определение типа режимаGitHub.com/Webpack/Веб…
Так как же webpack рекурсивно получает наши файлы? Мы можем найти такую строку кода в адресе исходного кода чуть выше.
Этот взгляд заключается в том, чтобы найти нужные нам модули. Итак, мы следуем этой строке, чтобы найти конкретный исходный код.
Это конкретная логика того, как require.context загружается в наш файл. На самом деле этоfs.readdir
Вот и все. Получив, наконец, файл, мы генерируем наш объект структуры модуля через загрузчик контекста. Например, такой код отвечает за генерацию нашегоsync
Тип загрузчика контекста. Вы можете специально посмотреть на другие 5 типов.
6 типов объектов структуры модуля, которые загружают логику и генерируют загрузчик контекстаGitHub.com/Webpack/Веб…
Суммировать
1. Организация учится о том, как WebPack загрузит модуль, WebPack погрузчика, как он работает, и, наконец, как генерировать скомпилированный код.
2. Я просто хотел знатьrequire.context
Как его добиться, но обнаружил, что его третий параметр имеет 6 режимов, но этой части нет в документе вебпака.
3. Начав с практического API, я изучил принцип реализации API и вместе прочитал часть исходного кода веб-пакета.
4.Изучение сути гораздо важнее, чем быть портировщиком API. Только продолжая исследовать сущность, вы сможете открыть тайны мира.
Наконец, мы можем использовать эту идею для изучения скомпилированного кода других 6 режимов.
Скомпилированный код в статье находится здесь >>>GitHub.com/me CK О, да / тепло...
Персональный сайт >>>www.meckodo.com
Последний круглогодичный набор
Xiamen RingCentral, иностранная компания с лучшими преимуществами в Сямыне
5:30 выходной 5:30 выходной 5:30 выходной
Свяжитесь со мной, если нужно~