Cet article présente principalement le principe du module d'organisation du webpack. Maintenant, je le partage avec vous et vous donne une référence.
Il est désormais courant d'utiliser Webpack pour empaqueter JS et d'autres fichiers sur le front-end. Couplée à la popularité de Node, la méthode d'ingénierie front-end devient de plus en plus similaire au back-end. Tout est finalement modularisé et compilé. En raison des mises à jour constantes des versions de Webpack et de diverses options de configuration compliquées, des erreurs mystérieuses se produisent lors de l'utilisation, ce qui rend souvent les gens confus. Il est donc très utile de comprendre comment Webpack organise les modules compilés et comment le code généré est exécuté, sinon ce sera toujours une boîte noire. Bien sûr, je suis un novice en front-end et je viens de commencer récemment à étudier les principes de Webpack, je vais donc prendre quelques notes ici.
Module de compilation
Le mot "compiler" sonne comme une technologie très noire, et le code généré est souvent un gros gâchis de choses incompréhensibles, donc c'est souvent C'est intimidant, mais les principes fondamentaux ne sont en réalité pas difficiles du tout. La soi-disant compilation de Webpack n'est en fait qu'une fois que Webpack a analysé votre code source, y a apporté certaines modifications, puis a organisé tout le code source dans un seul fichier. Enfin, un gros fichier bundle JS est généré, qui est exécuté par le navigateur ou un autre moteur Javascript et renvoie le résultat.
Voici un cas simple pour illustrer le principe du module packaging Webpack. Par exemple, nous avons un module mA.js
var aa = 1; function getDate() { return new Date(); } module.exports = { aa: aa, getDate: getDate }
J'ai défini avec désinvolture une variable aa et une fonction getDate, puis je les ai exportées. Ceci est écrit en CommonJS.
Définissez ensuite un app.js comme fichier principal, toujours dans le style CommonJS :
var mA = require('./mA.js'); console.log('mA.aa =' + mA.aa); mA.getDate();
Nous avons maintenant deux modules, packagés avec Webpack, et le fichier d'entrée est app js, dépend du module mA.js, Webpack doit faire plusieurs choses :
À partir du module d'entrée app.js, analyser les dépendances de tous les modules, et mettre tous les modules utilisés Lire dans .
Le code source de chaque module sera organisé dans une fonction qui s'exécutera immédiatement.
Réécrivez la syntaxe liée à require et export dans le code du module, ainsi que leurs variables de référence correspondantes.
Établissez un système de gestion de modules dans le fichier bundle finalement généré, qui peut charger dynamiquement les modules utilisés au moment de l'exécution.
On peut jeter un oeil à l'exemple ci-dessus, résultat du packaging Webpack. Le fichier bundle final est généralement une fonction volumineuse qui est exécutée immédiatement. Le niveau organisationnel est relativement complexe et le grand nombre de noms est relativement obscur, j'ai donc apporté quelques réécritures et modifications ici pour le rendre aussi simple et facile à comprendre. possible.
Tout d'abord, répertoriez tous les modules utilisés et utilisez leurs noms de fichiers (généralement des chemins complets) comme identifiants pour créer une table :
var modules = { './mA.js': generated_mA, './app.js': generated_app }
La clé est la suivante : Qu'est-ce qui est généré_xxx ? Il s'agit d'une fonction qui enveloppe le code source de chaque module à l'intérieur, ce qui en fait une portée locale, de sorte que les variables internes ne soient pas exposées, et transforme en fait chaque module en une fonction d'exécution. Sa définition est généralement la suivante :
function generated_module(module, exports, webpack_require) { // 模块的具体代码。 // ... }
Le code spécifique du module fait ici référence au code généré, que Webpack appelle code généré. Par exemple, mA, après réécriture, obtient ce résultat :
function generated_mA(module, exports, webpack_require) { var aa = 1; function getDate() { return new Date(); } module.exports = { aa: aa, getDate: getDate } }
À première vue, cela semble être exactement le même que le code source. En effet, mA ne nécessite ni n'importe d'autres modules, et l'export utilise également le style traditionnel CommonJS, il n'y a donc aucun changement dans le code généré. Cependant, il convient de noter que le dernier module.exports = ..., le module ici est le paramètre module passé de l'extérieur, qui nous indique en fait que lorsque cette fonction est exécutée, le code source du module mA sera exécuté , et enfin Le contenu qui doit être exporté sera enregistré en externe. Cela marque la fin du chargement de mA, et l'élément externe est en fait le système de gestion de modules dont nous parlerons plus tard.
Ensuite, regardez le code généré de app.js :
function generated_app(module, exports, webpack_require) { var mA_imported_module = webpack_require('./mA.js'); console.log('mA.aa =' + mA_imported_module['aa']); mA_imported_module['getDate'](); }
Comme vous pouvez le voir, la partie concernant le module mA introduit dans le code source de app.js a été modifiée , car ni require/exports ni import/export de style ES6 ne peuvent être directement exécutés par l'interpréteur JavaScript. Il doit s'appuyer sur le système de gestion de modules pour concrétiser ces mots-clés abstraits. En d'autres termes, webpack_require est l'implémentation spécifique de require, qui peut charger dynamiquement le module mA et renvoyer le résultat à l'application.
À ce stade, vous avez peut-être progressivement construit l'idée d'un système de gestion de modules dans votre esprit. Jetons un coup d'œil à la mise en œuvre de webpack_require :
// 加载完毕的所有模块。 var installedModules = {}; function webpack_require(moduleId) { // 如果模块已经加载过了,直接从Cache中读取。 if (installedModules[moduleId]) { return installedModules[moduleId].exports; } // 创建新模块并添加到installedModules。 var module = installedModules[moduleId] = { id: moduleId, exports: {} }; // 加载模块,即运行模块的生成代码, modules[moduleId].call( module.exports, module, module.exports, webpack_require); return module.exports; }
Faites attention au. mots modules dans l'avant-dernière phrase C'est le code généré de tous les modules que nous avons définis précédemment :
var modules = { './mA.js': generated_mA, './app.js': generated_app }
La logique de webpack_require est très clairement écrite. Tout d'abord, vérifiez si le module a été chargé. Si c'est le cas. , renvoie le résultat des exports du module directement depuis le Cache. S'il s'agit d'un tout nouveau module, créez le module de structure de données correspondant et exécutez le code généré de ce module. Ce que cette fonction transmet est l'objet module que nous avons créé et son champ d'exportation. Il s'agit en fait des champs d'exportation et d'exportation dans CommonJS. . L'origine du module. Après avoir exécuté cette fonction, le module est chargé et les résultats qui doivent être exportés sont enregistrés dans l'objet module.
所以我们看到所谓的模块管理系统,原理其实非常简单,只要耐心将它们抽丝剥茧理清楚了,根本没有什么深奥的东西,就是由这三个部分组成:
// 所有模块的生成代码 var modules; // 所有已经加载的模块,作为缓存表 var installedModules; // 加载模块的函数 function webpack_require(moduleId);
当然以上一切代码,在整个编译后的bundle文件中,都被包在一个大的立即执行的匿名函数中,最后返回的就是这么一句话:
return webpack_require(‘./app.js');
即加载入口模块app.js,后面所有的依赖都会动态地、递归地在runtime加载。当然Webpack真正生成的代码略有不同,它在结构上大致是这样:
(function(modules) { var installedModules = {}; function webpack_require(moduleId) { // ... } return webpack_require('./app.js'); }) ({ './mA.js': generated_mA, './app.js': generated_app });
可以看到它是直接把modules作为立即执行函数的参数传进去的而不是另外定义的,当然这和上面的写法没什么本质不同,我做这样的改写是为了解释起来更清楚。
ES6的import和export
以上的例子里都是用传统的CommonJS的写法,现在更通用的ES6风格是用import和export关键词,在使用上也略有一些不同。不过对于Webpack或者其它模块管理系统而言,这些新特性应该只被视为语法糖,它们本质上还是和require/exports一样的,例如export:
export aa // 等价于: module.exports['aa'] = aa export default bb // 等价于: module.exports['default'] = bb
而对于import:
import {aa} from './mA.js' // 等价于 var aa = require('./mA.js')['aa']
比较特殊的是这样的:
import m from './m.js'
情况会稍微复杂一点,它需要载入模块m的default export,而模块m可能并非是由ES6的export来写的,也可能根本没有export default,所以Webpack在为模块生成generated code的时候,会判断它是不是ES6风格的export,例如我们定义模块mB.js:
let x = 3; let printX = () => { console.log('x = ' + x); } export {printX} export default x
它使用了ES6的export,那么Webpack在mB的generated code就会加上一句话:
function generated_mB(module, exports, webpack_require) { Object.defineProperty(module.exports, '__esModule', {value: true}); // mB的具体代码 // .... }
也就是说,它给mB的export标注了一个__esModule,说明它是ES6风格的export。这样在其它模块中,当一个依赖模块以类似import m from './m.js'这样的方式加载时,会首先判断得到的是不是一个ES6 export出来的模块。如果是,则返回它的default,如果不是,则返回整个export对象。例如上面的mA是传统CommonJS的,mB是ES6风格的:
// mA is CommonJS module import mA from './mA.js' console.log(mA); // mB is ES6 module import mB from './mB.js' console.log(mB);
我们定义get_export_default函数:
function get_export_default(module) { return module && module.__esModule? module['default'] : module; }
这样generated code运行后在mA和mB上会得到不同的结果:
var mA_imported_module = webpack_require('./mA.js'); // 打印完整的 mA_imported_module console.log(get_export_default(mA_imported_module)); var mB_imported_module = webpack_require('./mB.js'); // 打印 mB_imported_module['default'] console.log(get_export_default(mB_imported_module));
这就是在ES6的import上,Webpack需要做一些特殊处理的地方。不过总体而言,ES6的import/export在本质上和CommonJS没有区别,而且Webpack最后生成的generated code也还是基于CommonJS的module/exports这一套机制来实现模块的加载的。
模块管理系统
以上就是Webpack如何打包组织模块,实现runtime模块加载的解读,其实它的原理并不难,核心的思想就是建立模块的管理系统,而这样的做法也是具有普遍性的,如果你读过Node.js的Module部分的源代码,就会发现其实用的是类似的方法。这里有一篇文章可以参考。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!