Webpack-源码一,使用require加载并打包模块

最近有同学致力于写一个脚手架工具,在研究webpack源码,问了我几个问题,然而我完全不能解答。于是开始研究webpack。

webpack做的事情主要是实现前端模块化(即:让前端也可以像node端一样适用require方法加载模块)和借助插件实现编译、热加载等功能。webpack源码系列第一部分,就分享最简单的内容——如何使用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官网有说明)。

使用CommonsChunkPlugin的情况

上面的依赖情况下,index.jsindex_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.jsindex_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),也就是上文中方式加载。

你可能感兴趣的:(webpack,源码)