Maison > interface Web > js tutoriel > Une explication approfondie des principes de base des modules Webpack

Une explication approfondie des principes de base des modules Webpack

亚连
Libérer: 2018-05-31 13:58:49
original
1259 Les gens l'ont consulté

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
}
Copier après la connexion

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();
Copier après la connexion

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 :

  1. À 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 .

  2. Le code source de chaque module sera organisé dans une fonction qui s'exécutera immédiatement.

  3. Réécrivez la syntaxe liée à require et export dans le code du module, ainsi que leurs variables de référence correspondantes.

  4. É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
}
Copier après la connexion
Copier après la connexion

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) {
  // 模块的具体代码。
  // ...
}
Copier après la connexion

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
 }
}
Copier après la connexion

À 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']();
}
Copier après la connexion

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;
}
Copier après la connexion

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
}
Copier après la connexion
Copier après la connexion

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);
Copier après la connexion

当然以上一切代码,在整个编译后的bundle文件中,都被包在一个大的立即执行的匿名函数中,最后返回的就是这么一句话:

return webpack_require(‘./app.js');
Copier après la connexion

即加载入口模块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
});
Copier après la connexion

可以看到它是直接把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
Copier après la connexion

而对于import:

import {aa} from './mA.js'
// 等价于
var aa = require('./mA.js')['aa']
Copier après la connexion

比较特殊的是这样的:

import m from './m.js'
Copier après la connexion

情况会稍微复杂一点,它需要载入模块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
Copier après la connexion

它使用了ES6的export,那么Webpack在mB的generated code就会加上一句话:

function generated_mB(module, exports, webpack_require) {
 Object.defineProperty(module.exports, '__esModule', {value: true});
 // mB的具体代码
 // ....
}
Copier après la connexion

也就是说,它给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);
Copier après la connexion

我们定义get_export_default函数:

function get_export_default(module) {
 return module && module.__esModule? module['default'] : module;
}
Copier après la connexion

这样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));
Copier après la connexion

这就是在ES6的import上,Webpack需要做一些特殊处理的地方。不过总体而言,ES6的import/export在本质上和CommonJS没有区别,而且Webpack最后生成的generated code也还是基于CommonJS的module/exports这一套机制来实现模块的加载的。

模块管理系统

以上就是Webpack如何打包组织模块,实现runtime模块加载的解读,其实它的原理并不难,核心的思想就是建立模块的管理系统,而这样的做法也是具有普遍性的,如果你读过Node.js的Module部分的源代码,就会发现其实用的是类似的方法。这里有一篇文章可以参考。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

vue iview组件表格 render函数的使用方法详解

微信小程序实现换肤功能

nodejs实现解析xml字符串为对象的方法示例

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!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal