JS模块化如何管理模块

【JS模块化如何管理模块】我们平时书写模块代码,或依据CommonJS,或遵循AMD范式,或直接使用ES6的import和export,这些模块经常相互依赖,那么是什么机制在背后帮我们做到了打包工作和依赖管理呢?
Node的模块实现 在NodeJS中,我们使用require,module.exports来操作模块,但是它们并没有在模块文件中定义,究竟从何而来?
其实是NodeJS在编译的过程中,获取了模块文件然后进行了一层包装,每个文件就进行了作用域隔离。包装之后的代码通过原生模块runInThisContext()方法执行,返回一个具体的function对象。最后将当前模块对象的exports属性、require方法、module(模块对象自身),以及在文件中得到的完整文件路径和文件目录作为参数传递给这个function执行。
这就是这些变量没有定义却可以在某个模块文件中存在的原因,执行之后,模块的exports属性返回给了调用方,所以调用方可以读取到exports上任何属性。
这里实现一个简单的require方法

// test.js var str = 'I am test' module.exports = str; // require.js // 模拟require的实现 function _require(path) { // 定义一个Module对象 var module = { exports: {} } // 引入nodejs 文件模块 下面是nodejs中原生的require方法 var fs = require('fs'); // 同步读取该文件,utf8表示当前是以字符串编码提取的 var sourceCode = fs.readFileSync(path, 'utf8'); var packFunc = new Function('exports', '_require', 'module', '__filename', '__diranme', sourceCode + '\n return module.exports; ') // 把module和module.exports作为参数传进去 // 并得到挂在到module.exports 或 exports上的功能 var res = packFunc(module.exports, _require, module, __filename, __dirname); // 然后返回包装过的内容 return res; } var test = _require('./test.js'); console.log(test) // 'I am test'

requireJS的模块实现
var factories = {}; function define(moduleName, dependencies, factory) { factory.dependencies = dependencies; factories[moduleName] = factory; } function require(mods, callback) { var results = mods.map(function(mod) { var factory = factories[mod]; var dependencies = factory.dependencies; var exports; require(dependencies, function() { exports = factory.apply(null, arguments) }) return exports }); callback.apply(null, results); } define('a', [], function() { return 'a'; }); define('b', ['a'], function(a) { return a + '-->' + 'b'; }) require(['b'], function(str) { console.log(str); })

webpack模块打包机制 说到webpack打包模块的机制,就是把依赖的模块转化成为可以代表这些包的静态文件。在webpack里面,无论是CommonJS或是AMD范式还是ES模块机制,webpack都会对其进行分析,来获取代码的依赖。
webpack做的就是
  1. 分析代码(识别各种模块范式,分析后建立依赖关系)
  2. 转换代码(不同的形式的资源使用loader转换为JS模块)
  3. 处理依赖(调用 acorn 对JS代码进行语法分析,然后收集其中的依赖关系,对于依赖树进行深度优先且为先序优先遍历)
  4. 编译代码、最后输出代码。
    想从webpack打包后的代码分析一波
// index.js 入口文件 import hello from './hello' console.log(hello) function a (hh) { console.log(hh) } // hello.js var hello = 'hello world' export default hello

// 打包后的文件 module.exports = /******/ (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, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__hello__ = __webpack_require__(1); console.log(__WEBPACK_IMPORTED_MODULE_0__hello__["a" /* default */]) function a (hh) { console.log(hh) } /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) ; "use strict"; var hello = 'hello world' /* harmony default export */ __webpack_exports__["a"] = (hello); /***/ }) /******/ ]);

首先简化一波代码,整个bundle是一个自执行函数,参数是项目中使用到的模块文件,这些JS被包装了一层。包装的意义在于浏览器是不支持模块化的,那么我们只能利用函数的作用域来hack私有作用域达到模块化的功能。
这些被包装了的函数,webpack通过传入变量来控制函数里面的模块的导出导入
(function (modules) {...}) ( // 这里是传入的模块数组,作为自执行函数的参数 [(function (module, exports, __webpack_require__) { /* index.js的代码 */ }),(function (module, exports, __webpack_require__) { /* hello.js的代码 */ })] );

模块数组作为参数传入IIFE函数后,IIFE做了一些初始化工作:
  1. 定义installedModules ,缓存已加载的模块。
  2. 定义webpack_require 这个函数,函数参数为模块的id。这个函数用来实现模块的import(require)。
  3. webpack_require 函数首先会检查是否缓存了已加载的模块,如果有则直接返回缓存模块的exports。
  4. 如果没有缓存,初始化模块,然后将模块缓存到installedModules。
  5. 然后调用webpack_require模块函数,将module、module.exports和webpack_require作为参数传入。
  6. 调用完成后,模块标记为已加载。
  7. 返回模块exports的内容。
  8. 利用前面定义的webpack_require 函数,require第0个模块,也就是入口模块。
    然后这个IIFE函数的执行第一步是,找到入口模块执行,然后发现入口模块内部有对其它模块的引用,就再依次加载其他模块,最终形成一个依赖网状结构。webpack管理着这些模块的缓存,如果一个模块被require多次,那么只会有一次加载过程,而返回的是缓存的内容。
(function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__hello__ = __webpack_require__(1); })

探索 JavaScript 中的依赖管理及循环依赖
webpack模块化原理-commonjs
webpack打包原理
Webpack 源码解析-一系列文章

    推荐阅读