咱们都知道,webpack做为一个构建工具,解决了前端代码缺乏模块化能力的问题。咱们写的代码,通过webpack构建和包装以后,可以在浏览器以模块化的方式运行。这些能力,都是由于webpack对咱们的代码进行了一层包装,本文就以webpack生成的代码入手,分析webpack是如何实现模块化的。css
PS: webpack的模块不只指js,包括css、图片等资源均可以以模块看待,但本文只关注js。前端
首先咱们建立一个简单入口模块index.js和一个依赖模块bar.js:node
//index.js 'use strict'; var bar = require('./bar'); function foo() { return bar.bar(); }
//bar.js 'use strict'; exports.bar = function () { return 1; }
webpack配置以下:webpack
var path = require("path"); module.exports = { entry: path.join(__dirname, 'index.js'), output: { path: path.join(__dirname, 'outs'), filename: 'index.js' }, };
这是一个最简单的配置,只指定了模块入口和输出路径,但已经知足了咱们的要求。web
在根目录下执行webpack
,获得通过webpack打包的代码以下(去掉了没必要要的注释):segmentfault
(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 }); } }; // 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 = 0); }) /************************************************************************/ ([ /* 0 */ (function(module, exports, __webpack_require__) { "use strict"; var bar = __webpack_require__(1); bar.bar(); }), /* 1 */ (function(module, exports, __webpack_require__) { "use strict"; exports.bar = function () { return 1; } }) ]);
上面webpack打包的代码,总体能够简化成下面的结构:数组
(function (modules) {/* 省略函数内容 */}) ([ function (module, exports, __webpack_require__) { /* 模块index.js的代码 */ }, function (module, exports, __webpack_require__) { /* 模块bar.js的代码 */ } ]);
能够看到,整个打包生成的代码是一个IIFE(当即执行函数),函数内容咱们待会看,咱们先来分析函数的参数。浏览器
函数参数是咱们写的各个模块组成的数组,只不过咱们的代码,被webpack包装在了一个函数的内部,也就是说咱们的模块,在这里就是一个函数。为何要这样作,是由于浏览器自己不支持模块化,那么webpack就用函数做用域来hack模块化的效果。缓存
若是你debug过node代码,你会发现同样的hack方式,node中的模块也是函数,跟模块相关的参数exports
、require
,或者其余参数__filename
和__dirname
等都是经过函数传值做为模块中的变量,模块与外部模块的访问就是经过这些参数进行的,固然这对开发者来讲是透明的。模块化
一样的方式,webpack也控制了模块的module
、exports
和require
,那么咱们就看看webpack是如何实现这些功能的。
下面是摘取的函数内容,并添加了一些注释:
// 一、模块缓存对象 var installedModules = {}; // 二、webpack实现的require function __webpack_require__(moduleId) { // 三、判断是否已缓存模块 if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // 四、缓存模块 var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // 五、调用模块函数 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 六、标记模块为已加载 module.l = true; // 七、返回module.exports return module.exports; } // 八、require第一个模块 return __webpack_require__(__webpack_require__.s = 0);
模块数组做为参数传入IIFE函数后,IIFE作了一些初始化工做:
installedModules
,这个变量被用来缓存已加载的模块。__webpack_require__
这个函数,函数参数为模块的id。这个函数用来实现模块的require。__webpack_require__
函数首先会检查是否缓存了已加载的模块,若是有则直接返回缓存模块的exports
。module
、module.exports
和__webpack_require__
做为参数传入。注意这里作了一个动态绑定,将模块函数的调用对象绑定为module.exports
,这是为了保证在模块中的this指向当前模块。exports
的内容。__webpack_require__
函数,require第0个模块,也就是入口模块。require入口模块时,入口模块会收到收到三个参数,下面是入口模块代码:
function(module, exports, __webpack_require__) { "use strict"; var bar = __webpack_require__(1); bar.bar(); }
webpack传入的第一个参数module
是当前缓存的模块,包含当前模块的信息和exports
;第二个参数exports
是module.exports
的引用,这也符合commonjs的规范;第三个__webpack_require__
则是require
的实现。
在咱们的模块中,就能够对外使用module.exports
或exports
进行导出,使用__webpack_require__
导入须要的模块,代码跟commonjs彻底同样。
这样,就完成了对第一个模块的require,而后第一个模块会根据本身对其余模块的require,依次加载其余模块,最终造成一个依赖网状结构。webpack管理着这些模块的缓存,若是一个模块被require屡次,那么只会有一次加载过程,而返回的是缓存的内容,这也是commonjs的规范。
到这里,webpack就hack了commonjs代码。
原理仍是很简单的,其实就是实现exports
和require
,而后自动加载入口模块,控制缓存模块,that's all。
细心的你必定会发现,文章到这里只介绍了webpack对commonjs的实现,那么ES6 module是如何实现的呢?
欢迎阅读本系列第二篇《webpack模块化原理-ES6 module》。