摘要:可以做做不到的事情。看完這段代碼就不難理解文檔中所說(shuō)的會(huì)返回一個(gè)帶有個(gè)的函數(shù)了。進(jìn)階深入探究源碼我們知道在版本后,在加載模塊時(shí),可以指定模塊加載模式,我們能使用幾種方式來(lái)控制我們要加載的模塊。
前言
require.context 其實(shí)是一個(gè)非常實(shí)用的 api。但是 3-4 年過(guò)去了,卻依舊還有很多人不知道如何使用。
而這個(gè) api 主要為我們做什么樣的事情?它可以幫助我們動(dòng)態(tài)加載我們想要的文件,非常靈活和強(qiáng)大(可遞歸目錄)。可以做 import 做不到的事情。今天就帶大家一起來(lái)分析一下,webpack 的 require.context是如何實(shí)現(xiàn)的。
準(zhǔn)備工作在分析這個(gè) api 之前呢,我們需要先了解一下一個(gè)最簡(jiǎn)單的文件,webpack 會(huì)編譯成啥樣。
-- src -- index.ts
// index.ts console.log(123)
編譯之后,我們可以看見(jiàn) webpack 會(huì)編譯成如下代碼
// 源碼 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"); }) });
初次一看是很亂的,所以為了梳理結(jié)構(gòu),我?guī)痛蠹胰コ恍└疚臒o(wú)關(guān)緊要的。其實(shí)主要結(jié)構(gòu)就是這樣而已,代碼不多為了之后的理解,一定要仔細(xì)看下每一行
// 源碼地址 https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/webpack-main.js (function(modules) { // 緩存所有被加載過(guò)的模塊(文件) 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: {} }); // 執(zhí)行我們的模塊(文件) 目前就是 ./src/index.ts 并且傳入 3 個(gè)參數(shù) 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; } // 開(kāi)始加載入口文件 return __webpack_require__((__webpack_require__.s = "./src/index.ts")); })({ "./src/index.ts": function(module, exports, __webpack_require__) { console.log("123"); } });
__webpack_require__ 就是一個(gè)模塊加載器,而我們所有的模塊都會(huì)以對(duì)象的形式被讀取加載
modules = { "./src/index.ts": function(module, exports, __webpack_require__) { console.log("123"); } }
我們把這樣的結(jié)構(gòu)先暫時(shí)稱之為 模塊結(jié)構(gòu)對(duì)象
正片了解了主體結(jié)構(gòu)之后我們就可以寫(xiě)一段require.context來(lái)看看效果。我們先新增 2 個(gè) ts 文件并且修改一下我們的 index.ts,以便于測(cè)試我們的動(dòng)態(tài)加載。
--- src --- demos --- demo1.ts --- demo2.ts index.ts
// index.ts // 稍后我們通過(guò)源碼分析為什么這樣寫(xiě) function importAll(contextLoader: __WebpackModuleApi.RequireContext) { contextLoader.keys().forEach(id => console.log(contextLoader(id))); } const contextLoader = require.context("./demos", true, /.ts/); importAll(contextLoader);
查看我們編譯后的源碼,發(fā)現(xiàn)多了這樣一塊的 模塊結(jié)構(gòu)對(duì)象
// 編譯后代碼地址 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 加載器,通過(guò)之前的模塊加載器 加載模塊(文件) function webpackContext(req) { var id = webpackContextResolve(req); var module = __webpack_require__(id); return module; } // 通過(guò) moduleId 查找模塊(文件)真實(shí)路徑 // 個(gè)人在這不喜歡 webpack 內(nèi)部的一些變量命名,moduleId 它都會(huì)編譯為 request function webpackContextResolve(req) { // id 就是真實(shí)文件路徑 var id = map[req]; // 說(shuō)實(shí)話這波操作沒(méi)看懂,目前猜測(cè)是 webpack 會(huì)編譯成 0.js 1.js 這樣的文件 如果找不到誤加載就出個(gè) 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); }; // 獲取文件真實(shí)路徑方法 webpackContext.resolve = webpackContextResolve; // 該模塊就是返回一個(gè) context 加載器 module.exports = webpackContext; // 該模塊的 moduleId 用于 __webpack_require__ 模塊加載器 webpackContext.id = "./src/demos sync recursive .ts"; }
我在源碼中寫(xiě)了很詳細(xì)的注釋。看完這段代碼就不難理解文檔中所說(shuō)的require.context 會(huì)返回一個(gè)帶有 3 個(gè)API的函數(shù)(webpackContext)了。
接著我們看看編譯后 index.ts 的源碼
"./src/index.ts": function(module, exports, __webpack_require__) { function importAll(contextLoader) { contextLoader.keys().forEach(function(id) { // 拿到所有 moduleId,在通過(guò) context 加載器去加載每一個(gè)模塊 return console.log(contextLoader(id)); }); } var contextLoader = __webpack_require__( "./src/demos sync recursive .ts" ); importAll(contextLoader); }
很簡(jiǎn)單,可以發(fā)現(xiàn) require.context 編譯為了 __webpack_require__加載器并且加載了 id 為./src/demos sync recursive .ts 的模塊,sync表明我們是同步加載這些模塊(之后我們?cè)诮榻B這個(gè)參數(shù)),recursive 表示需要遞歸目錄查找。自此,我們就完全能明白 webpack 是如何構(gòu)建所有模塊并且動(dòng)態(tài)加載的了。
進(jìn)階深入探究 webpack 源碼我們知道 webpack 在 2.6 版本后,在加載模塊時(shí),可以指定 webpackMode 模塊加載模式,我們能使用幾種方式來(lái)控制我們要加載的模塊。常用的 mode一般為sync lazy lazy-once eager
所以在 require.context 是一樣適用的,我們?nèi)绻榭匆幌?b>@types/webpack-env就不難發(fā)現(xiàn)它還有第四個(gè)參數(shù)。
簡(jiǎn)要來(lái)說(shuō)
sync 直接打包到當(dāng)前文件,同步加載并執(zhí)行
lazy 延遲加載會(huì)分離出多帶帶的 chunk 文件
lazy-once 延遲加載會(huì)分離出多帶帶的 chunk 文件,加載過(guò)下次再加載直接讀取內(nèi)存里的代碼。
eager 不會(huì)分離出多帶帶的 chunk 文件,但是會(huì)返回 promise,只有調(diào)用了 promise 才會(huì)執(zhí)行代碼,可以理解為先加載了代碼,但是我們可以控制延遲執(zhí)行這部分代碼。
文檔在這里 https://webpack.docschina.org...。
這部分文檔很隱晦,也可能是文檔組沒(méi)有跟上,所以如果我們?nèi)タ?webpack 的源碼的話,可以發(fā)現(xiàn)真正其實(shí)是有 6 種 mode。
mode類型定義
https://github.com/webpack/we...
那 webpack 到底是如何做到可遞歸獲取我們的文件呢?在剛剛上面的源碼地址里我們能發(fā)現(xiàn)這樣一行代碼。
這一看就是去尋找我們所需要的模塊。所以我們跟著這行查找具體的源碼。
這就是 require.context 是如何加載到我們文件的具體邏輯了。其實(shí)就是fs.readdir而已。最后獲取到文件之后在通過(guò) context 加載器來(lái)生成我們的模塊結(jié)構(gòu)對(duì)象。比如這樣的代碼就是負(fù)責(zé)生成我們sync類型的context加載器。大家可以具體在看別的5種類型。
6種類型加載邏輯并且生成 context 加載器的模塊結(jié)構(gòu)對(duì)象總結(jié)
https://github.com/webpack/we...
1.學(xué)習(xí)了解 webpack 是如何組織加載一個(gè)模塊的,webpack 的加載器如何運(yùn)作,最后如何生成編譯后的代碼。
2.本來(lái)僅僅只是想了解 require.context 如何實(shí)現(xiàn)的,卻發(fā)現(xiàn)了它第三個(gè)參數(shù)有 6 種mode,這部分卻也是 webpack 文檔上沒(méi)有的。
3.從一個(gè)實(shí)用的 API 出發(fā),探索了該 api 的實(shí)現(xiàn)原理,并且一起閱讀了部分 webpack 源碼。
4.探索本質(zhì)遠(yuǎn)比你成為 API 的搬運(yùn)工更重要。只有你不斷地探尋本質(zhì),才可以發(fā)現(xiàn)世界的奧秘。
最后拋磚引玉,可以按照這樣的思路再去學(xué)習(xí)另外 6 種 mode 編譯后的代碼。
文章里編譯后的代碼,都在這里 >>> https://github.com/MeCKodo/re...
個(gè)人網(wǎng)站 >>> http://www.meckodo.com
最后常年招人廈門 RingCentral 外企,福利待遇廈門頂尖
5點(diǎn)半下班 5點(diǎn)半下班 5點(diǎn)半下班
有想法的加我微信 abcdefghijklmnoKodo
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/106037.html
摘要:我們按照提供的思路,對(duì)路由按業(yè)務(wù)模塊進(jìn)行拆分。這時(shí)就可以使用到了打包入口文件自動(dòng)引入子目錄下所有文件參考分享使用的實(shí)現(xiàn)路由去中心化管理之用法的基礎(chǔ)組件的自動(dòng)化全局注冊(cè)官方文檔 概述 You can create your own context with the require.context() function. It allows you to pass in a directo...
摘要:原文發(fā)布與抹橋的博客翻譯向指南上前置定義代碼包代碼塊安裝代碼分割代碼分割是中最引人注目的功能之一。回調(diào)函數(shù)一個(gè)回調(diào)函數(shù)會(huì)被執(zhí)行一次當(dāng)所有依賴都被加載以后。對(duì)象的實(shí)現(xiàn)作為一個(gè)參數(shù)傳遞給這個(gè)回調(diào)函數(shù)。 原文發(fā)布與 抹橋的博客 -【翻譯向】webpack2 指南(上) 前置定義 Bundle 代碼包Chunk 代碼塊 安裝 npm install webpack --save-dev 代碼分...
摘要:基于構(gòu)建的工程篇上一篇基于構(gòu)建的工程一篇里我們已經(jīng)成功構(gòu)建了整個(gè)項(xiàng)目的打包配置。不同的模塊之間都會(huì)有標(biāo)識(shí)來(lái)標(biāo)志,所以不會(huì)說(shuō)存在干擾和污染的問(wèn)題。但是對(duì)于我這個(gè)重構(gòu)的項(xiàng)目,就會(huì)有麻煩要改寫(xiě)的文件有太多了。然而還有一種情況。 基于webpack構(gòu)建的angular 1.x工程(angular篇) ??上一篇基于webpack構(gòu)建的angular 1.x 工程(一)webpack篇里我們已經(jīng)...
摘要:依賴管理該條已在中文網(wǎng)存在,點(diǎn)擊這里表達(dá)式來(lái)調(diào)用當(dāng)你的請(qǐng)求包含表達(dá)式,那個(gè)一個(gè)上下文環(huán)境將被創(chuàng)建。一個(gè)包含所有父文件夾和子及后代文件夾中以結(jié)尾的文件的上下文。一個(gè)函數(shù),返回一個(gè)數(shù)組,包含上下文模塊能夠處理的所有的請(qǐng)求。 依賴管理 Dependency Management 該條已在webpack2.x中文網(wǎng)存在,點(diǎn)擊這里 es6 modules commonjs amd 表達(dá)式...
摘要:直到最近在使用微信機(jī)器人時(shí),遇到了強(qiáng)烈的需求。增刪文件后熱更新上面的代碼已經(jīng)不小心實(shí)現(xiàn)了增加文件后熱更新,因?yàn)楸硎緳z測(cè)的更新,如果增加一個(gè),那么就變成,于是新模塊不等于老模塊不存在,從而使用注冊(cè)事件監(jiān)聽(tīng)器。 背景 剛思考這個(gè)話題的時(shí)候,首先想到的是 Vue 或 React 的組件熱更新(基于 Webpack HMR),后來(lái)又想到了 Lua、Erlang 等語(yǔ)言的熱更新,不過(guò)在實(shí)際開(kāi)發(fā) ...
閱讀 1342·2019-08-30 15:55
閱讀 1645·2019-08-26 10:21
閱讀 3437·2019-08-23 18:28
閱讀 3375·2019-08-23 15:38
閱讀 743·2019-08-23 15:24
閱讀 2135·2019-08-23 13:59
閱讀 775·2019-08-23 11:31
閱讀 2870·2019-08-23 10:53