最近有同学致力于写一个脚手架工具,在研究webpack源码,问了我几个问题,然而我完全不能解答。于是开始研究webpack。
webpack做的事情主要是实现前端模块化(即:让前端也可以像node端一样适用require方法加载模块)和借助插件实现编译、热加载等功能。webpack源码系列第一部分,就分享最简单的内容——如何使用require方法加载模块并打包。
待打包的文件bundle_require.js
// bundle_require.js
console.log('success');
仅使用最简单的打包,不使用插件。打包后的文件index.bundle.js
// index.bundle.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] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = 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;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/bundle";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
__webpack_require__(1);
/***/ },
/* 1 */
/***/ function(module, exports) {
'use strict';
/*(function(a){
console.log(a.name);
})
({name: 'hello'})*/
console.log('success');
/***/ }
/******/ ]);
bundle文件中是一个立即执行函数,形如(function(modules){})([module_1, module_2, ...])
形参modules
对应的实参为一个模块数组[module_1, module_2, ...]
,该模块数组的每个成员都是使用require
加载的一个模块,每个被加载的模块都被封装成一个函数。
var installedModules = {};
是加载模块的缓存,如果已经加载过无需再次加载。
__webpack_require__
方法通过installedModules
对象缓存第一次加载的模块,通过modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
执行形参中的各个模块。使用call
是因为为了确保每个module中的this
指向的是module本身。然后给它传__webpack_require__
函数是想让module有加载其他module的能力。
模块依赖:bundle_require.js
依赖dependency.js
// bundle_require.js
var dependency = require('./dependency.js');
console.log(dependency.name);
console.log('success');
// dependency.js
module.exports = {
name: 'hello'
}
打包后的文件index.bundle.js
// index.bundle.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] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = 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;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/bundle";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
__webpack_require__(1);
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var dependency = __webpack_require__(2);
console.log(dependency.name);
console.log('success');
/***/ },
/* 2 */
/***/ function(module, exports) {
'use strict';
module.exports = {
name: 'hello'
};
/***/ }
/******/ ]);
实参数组的第一个成员是bundle_require.js
,第二个成员是dependency.js
。同时,在第一个成员函数中,使用__webpack_require__(2)
加载下一个模块,形成链式调用。
需要注意,打包的文件中moudleId是不会重复的,如果有两个入口文件的情况,则入口模块id都为0,其他依赖模块id不重复。
index.js
为入口文件一,index_two.js
为入口文件二,它们共同引用common.js
。此外,入口文件一还引用dependency.js
。
// index.js
var dependency = require('./src/require/dependency.js');
var common = require('./src/require/common.js');
console.log('index: ', dependency.name, common.name);
// index_two.js
var common = require('./src/require/common.js');
console.log('index_two', common.name);
// common.js
module.exports = {
name: 'common'
}
// dependency.js
module.exports = {
name: 'dependency'
}
打包以后
// index.bundle.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] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = 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;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/bundle";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var dependency = __webpack_require__(1);
var common = __webpack_require__(2);
console.log('index: ', dependency.name, common.name);
/***/ },
/* 1 */
/***/ function(module, exports) {
'use strict';
module.exports = {
name: 'dependency'
};
/***/ },
/* 2 */
/***/ function(module, exports) {
'use strict';
module.exports = {
name: 'common'
};
/***/ }
/******/ ]);
分别加载两个模块,id为1和2,入口文件id为0.
// index_two.bundle.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] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = 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;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/bundle";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var common = __webpack_require__(2);
console.log('index_two', common.name);
/***/ },
/* 1 */,
/* 2 */
/***/ function(module, exports) {
'use strict';
module.exports = {
name: 'common'
};
/***/ }
/******/ ]);
只加载一个模块,其id为2,入口文件id为0。
这就是说,每个模块都有一个全局唯一的id,当重复require模块时,会使用第一次加载时的id。
其实,入口参数是字符串不管是多入口还是单入口,最后都会将入口模块的导出项导出,没有导出项就导出{},而入口参数是数组,就会将最后一个模块导出(webpackg官网有说明)。
上面的依赖情况下,index.js
和 index_two.js
有公共依赖 common.js
,这种情况在开发中我们会使用一个插件CommonsChunkPlugin
,使用该插件的情况下,打包又是怎样的呢。
// index.bundle.js
webpackJsonp([0],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var dependency = __webpack_require__(1);
var common = __webpack_require__(2);
console.log('index: ', dependency.name, common.name);
/***/ },
/* 1 */
/***/ function(module, exports) {
'use strict';
module.exports = {
name: 'dependency'
};
/***/ }
]);
// index_two.bundle.js
webpackJsonp([1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var common = __webpack_require__(2);
console.log('index_two', common.name);
/***/ }
]);
//common.js (打包公共模块的common.js)
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, callbacks = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId])
/******/ callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/ while(callbacks.length)
/******/ callbacks.shift().call(null, __webpack_require__);
/******/ if(moreModules[0]) {
/******/ installedModules[0] = 0;
/******/ return __webpack_require__(0);
/******/ }
/******/ };
/******/ // The module cache
/******/ var installedModules = {};
/******/ // object to store loaded and loading chunks
/******/ // "0" means "already loaded"
/******/ // Array means "loading", array contains callbacks
/******/ var installedChunks = {
/******/ 2:0
/******/ };
/******/ // 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] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/ // "0" is the signal for "already loaded"
/******/ if(installedChunks[chunkId] === 0)
/******/ return callback.call(null, __webpack_require__);
/******/ // an array means "currently loading".
/******/ if(installedChunks[chunkId] !== undefined) {
/******/ installedChunks[chunkId].push(callback);
/******/ } else {
/******/ // start chunk loading
/******/ installedChunks[chunkId] = [callback];
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = 'text/javascript';
/******/ script.charset = 'utf-8';
/******/ script.async = true;
/******/ script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"index","1":"index_two"}[chunkId]||chunkId) + ".bundle.js";
/******/ head.appendChild(script);
/******/ }
/******/ };
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/bundle";
/******/ })
/************************************************************************/
/******/ ({
/***/ 2:
/***/ function(module, exports) {
'use strict';
module.exports = {
name: 'common'
};
/***/ }
/******/ });
index.bundle.js
和index_two.bundle.js
都使用了webpackJsonp
方法来加载模块。下面我们具体看看这个函数。
var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, callbacks = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId])
/******/ callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/ while(callbacks.length)
/******/ callbacks.shift().call(null, __webpack_require__);
/******/ if(moreModules[0]) {
/******/ installedModules[0] = 0;
/******/ return __webpack_require__(0);
/******/ }
/******/ };
/******/ // The module cache
/******/ var installedModules = {};
/******/ // object to store loaded and loading chunks
/******/ // "0" means "already loaded"
/******/ // Array means "loading", array contains callbacks
/******/ var installedChunks = {
/******/ 2:0
/******/ };
chunkIds
是待加载模块的id组成的数组,moreModules
是待加载模块封装的函数组成的数组。webpackJsonp
的作用就是把installedChunks //存放公共模块
中的模块通过callbacks.shift().call(null, __webpack_require__);
加载,并把其余模块写进modules
对象,然后通过__webpack_require__(0)
,也就是上文中方式加载。