webpack原理探究 && 打包優化

  在做vue項目和react項目時,都用到了webpack。webpack幫助我們很好地提高了工作效率,但是一直以來沒有對其原理進行探究,略有遺憾。 因爲使用一個工具,能夠深入瞭解其原理才能更好地使用。 這篇文章將大致分爲三個部分進行解讀:

  • webpack打包簡單介紹
  • 輸入webpack後發生了什麼,整個運行機制大致是怎樣的? 
  • 如何理解打包出的bundle.js?
  • 如何實現一個簡單的webpack打包工具? 
  • 打包優化

 

第一部分: webpack打包簡單介紹

     當一個項目使用webpack打包時,webpack會認爲所有的文件都是模塊並將其打包到一個文件中但是webpack只能識別js文件,所以對於其他文件,我們需要使用loader來完成打包。 

  通過webpack打包,我們能很好地解決前端項目中的依賴問題,這樣可以幫助我們專注於實現項目的代碼邏輯,而非是依賴、命名衝突等。

 

第二部分: 輸入webpack後發生了什麼, 整個運行機制大致是怎樣的?   

    一般情況下,我們都會在根目錄下配置一個 webpack.config.js 文件,用於配置webpack打包。 當我們打開控制檯時,輸入webpack, 就會根據配置文件對項目進行打包了。但是,在這個過程中究竟發生了什麼呢? 

  

執行腳本 bin/webpack.js

  當在cmd中輸入一個命令執行時,實際上執行的都是一個類似於可執行的二進制文件,比如執行node命令、ping命令時都是這樣的, 在項目的node_modules下的webpack根目錄下找到package.json, 可以看到下面的一個kv:

  "bin": {
    "webpack": "./bin/webpack.js"
  },

  這就說明在執行二進制文件時,會運行 ./bin/webpack.js文件,找到這個文件,我們可以看到主要的代碼如下:

 這就說明在執行二進制文件時,會運行 ./bin/webpack.js文件,找到這個文件,我們可以看到主要的代碼如下:

// 引入nodejs的path模塊
var path = require("path");

// 獲取 /bin/webpack.js的絕對路徑
try {
    var localWebpack = require.resolve(path.join(process.cwd(), "node_modules", "webpack", "bin", "webpack.js"));
    if(__filename !== localWebpack) {
        return require(localWebpack);
    }
} catch(e) {}

//  引入yargs模塊,用於處理命令行參數
var yargs = require("yargs")
    .usage("webpack " + require("../package.json").version + "\n" +
        "Usage: https://webpack.js.org/api/cli/\n" +
        "Usage without config file: webpack <entry> [<entry>] <output>\n" +
        "Usage with config file: webpack");

// 使用yargs來初始化命令行對象
require("./config-yargs")(yargs);

var DISPLAY_GROUP = "Stats options:";
var BASIC_GROUP = "Basic options:";


// 命令行參數的基本配置
yargs.options({
    "json": {
        type: "boolean",
        alias: "j",
        describe: "Prints the result as JSON."
    },
    "progress": {
        type: "boolean",
        describe: "Print compilation progress in percentage",
        group: BASIC_GROUP
    },
    // 省略若干
});

// yargs模塊提供的argv對象,用來讀取命令行參數,alias可以設置某個命令的簡稱,方便輸入。 
var argv = yargs.argv;

if(argv.verbose) {
    argv["display"] = "verbose";
}

// argv爲讀取命令行的參數,通過conver-argv配置文件將命令行中的參數經過處理保存在options對象中
var options = require("./convert-argv")(yargs, argv);


function ifArg(name, fn, init) {
    if(Array.isArray(argv[name])) {
        if(init) init();
        argv[name].forEach(fn);
    } else if(typeof argv[name] !== "undefined") {
        if(init) init();
        fn(argv[name], -1);
    }
}

// /bin/webpack.js的核心函數
function processOptions(options) {

    // 支持promise風格的異步回調(promise是承諾的意思)
    if(typeof options.then === "function") {
        options.then(processOptions).catch(function(err) {
            console.error(err.stack || err);
            process.exit(1); // eslint-disable-line
        });
        return;
    }

    // 得到webpack編譯對象時數組情況下的options
    var firstOptions = [].concat(options)[0];
    var statsPresetToOptions = require("../lib/Stats.js").presetToOptions;

    // 設置輸出option
    var outputOptions = options.stats;
    if(typeof outputOptions === "boolean" || typeof outputOptions === "string") {
        outputOptions = statsPresetToOptions(outputOptions);
    } else if(!outputOptions) {
        outputOptions = {};
    }


    // 省略若干。。。。。


    // 引入主入口模塊 /lib/webpack.js
    var webpack = require("../lib/webpack.js");

    
    var compiler;
    try {

        // 使用webpack函數開始對獲得的配置對象進行編譯, 返回compiler
        compiler = webpack(options);
    } catch(e) {
        // 省略若干。。。
    }



    function compilerCallback(err, stats) {
        // 編譯完成之後的回調函數
    }


    // 如果有watch配置,則及時進行編譯。
    if(firstOptions.watch || options.watch) {
        var watchOptions = firstOptions.watchOptions || firstOptions.watch || options.watch || {};
        if(watchOptions.stdin) {
            process.stdin.on("end", function() {
                process.exit(0); // eslint-disable-line
            });
            process.stdin.resume();
        }
        compiler.watch(watchOptions, compilerCallback);
        console.log("\nWebpack is watching the files…\n");
    } else
        compiler.run(compilerCallback);

}


// 處理這些配置選項,即調用上面的函數
processOptions(options);

   實際上上面的這段代碼還是比較好理解的,就是使用相關模塊獲取到配置對象,然後從./lib/webpack.js 中獲取到webpack來進行編譯, 然後根據配置選項進行相應的處理。 這裏比較重要的就是webpack.js函數,我們來看看源碼。 

 ./lib/webpack.js解析

// 建立webpack主函數,下面某些代碼被省略了。
function webpack(options, callback) {
    
    let compiler;
    if(Array.isArray(options)) {
        // 如果webapck是一個數組,則一次執行
        compiler = new MultiCompiler(options.map(options => webpack(options)));
    } else if(typeof options === "object") {

        // 一般情況下webpack配置應該是一個對象,使用默認的處理配置中的所有選項
        new WebpackOptionsDefaulter().process(options);

         // 實例化一個 Compiler,Compiler 會繼承一個 Tapable 插件框架
         // Compiler 實例化後會繼承到 apply、plugin 等調用和綁定插件的方法

        compiler = new Compiler();
        
        compiler.context = options.context;
        compiler.options = options;
        new NodeEnvironmentPlugin().apply(compiler);
        if(options.plugins && Array.isArray(options.plugins)) {
            // 對於選項中的插件,進行使用、編譯
            compiler.apply.apply(compiler, options.plugins);
        }
        compiler.applyPlugins("environment");
        compiler.applyPlugins("after-environment");
        compiler.options = new WebpackOptionsApply().process(options, compiler);
    } else {
        throw new Error("Invalid argument: options");
    }

    return compiler;
}
exports = module.exports = webpack;

注意:

  一是 Compiler,實例化它會繼承 Tapable ,這個 Tapable 是一個插件框架,通過繼承它的一系列方法來實現註冊和調用插件,我們可以看到在 webpack 的源碼中,存在大量的 compiler.apply、compiler.applyPlugins、compiler.plugin 等Tapable方法的調用。Webpack 的 plugin 註冊和調用方式,都是源自 Tapable 。Webpack 通過 plugin 的 apply 方法安裝該 plugin,同時傳入一個 webpack 編譯對象(Webpack compiler object)。
  二是 WebpackOptionsApply 的實例方法 process (options, compiler),這個方法將會針對我們傳進去的webpack 編譯對象進行逐一編譯,接下來我們再來仔細看看這個模塊。

 

調用 lib/WebpackOptionsApply.js 模塊的 process 方法來逐一編譯 webpack 編譯對象的各項(這裏的文件纔是比較核心的)

/*
    MIT License http://www.opensource.org/licenses/mit-license.php
    Author Tobias Koppers @sokra
*/
"use strict";

// 這裏引入了若干插件(數十個)


// 給webpack中的配置對象使用插件
class WebpackOptionsApply extends OptionsApply {
    constructor() {
        super();
    }

    // 處理配置獨享主要函數
    process(options, compiler) {
        let ExternalsPlugin;
        // 根據options來配置options
        compiler.outputPath = options.output.path;
        compiler.recordsInputPath = options.recordsInputPath || options.recordsPath;
        compiler.recordsOutputPath = options.recordsOutputPath || options.recordsPath;
        compiler.name = options.name;
        compiler.dependencies = options.dependencies;
        if(typeof options.target === "string") {
            let JsonpTemplatePlugin;
            let NodeSourcePlugin;
            let NodeTargetPlugin;
            let NodeTemplatePlugin;

            switch(options.target) {
                case "web":
                    // 省略處理代碼
                case "webworker":
                    // 省略處理代碼
                case "node":
                case "async-node":
                    // 省略處理代碼
                    break;
                case "node-webkit":
                    // 省略處理代碼
                    break;
                case "atom":
                case "electron":
                case "electron-main":
                    // 省略處理代碼
                case "electron-renderer":
                    // 省略處理代碼
                default:
                    throw new Error("Unsupported target '" + options.target + "'.");
            }
        } else if(options.target !== false) {
            options.target(compiler);
        } else {
            throw new Error("Unsupported target '" + options.target + "'.");
        }

        // 根據配置來決定是否生成sourcemap
        if(options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0)) {
            // 省略若干
            // sourcemap代碼下通常都會指明源地址
            comment = legacy && modern ? "\n/*\n//@ source" + "MappingURL=[url]\n//# source" + "MappingURL=[url]\n*/" :
                legacy ? "\n/*\n//@ source" + "MappingURL=[url]\n*/" :
                modern ? "\n//# source" + "MappingURL=[url]" :
                null;
            let Plugin = evalWrapped ? EvalSourceMapDevToolPlugin : SourceMapDevToolPlugin;
            compiler.apply(new Plugin({
                filename: inline ? null : options.output.sourceMapFilename,
                moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate,
                fallbackModuleFilenameTemplate: options.output.devtoolFallbackModuleFilenameTemplate,
                append: hidden ? false : comment,
                module: moduleMaps ? true : cheap ? false : true,
                columns: cheap ? false : true,
                lineToLine: options.output.devtoolLineToLine,
                noSources: noSources,
            }));
        } else if(options.devtool && options.devtool.indexOf("eval") >= 0) {
            legacy = options.devtool.indexOf("@") >= 0;
            modern = options.devtool.indexOf("#") >= 0;
            comment = legacy && modern ? "\n//@ sourceURL=[url]\n//# sourceURL=[url]" :
                legacy ? "\n//@ sourceURL=[url]" :
                modern ? "\n//# sourceURL=[url]" :
                null;
            compiler.apply(new EvalDevToolModulePlugin(comment, options.output.devtoolModuleFilenameTemplate));
        }


        compiler.apply(
            new CompatibilityPlugin(),
            // 使用相關插件進行處理
        );
        
        return options;
    }
}

module.exports = WebpackOptionsApply;

不出意外,這個構造函數被實例化後會返回一個對象。 然後由compiler處理

到這基本上就是大致流程了,我們可以再介紹上一步中的常用的插件:UglifyJsPlugin.js 

lib/optimize/UglifyJsPlugin.js

// 引入一些依賴,主要是與壓縮代碼、sourceMap 相關
var SourceMapConsumer = require("webpack-core/lib/source-map").SourceMapConsumer;
var SourceMapSource = require("webpack-core/lib/SourceMapSource");
var RawSource = require("webpack-core/lib/RawSource");
var RequestShortener = require("../RequestShortener");
var ModuleFilenameHelpers = require("../ModuleFilenameHelpers");
var uglify = require("uglify-js");

// 定義構造器函數
function UglifyJsPlugin(options) {
    ...
}
// 將構造器暴露出去
module.exports = UglifyJsPlugin;

// 按照 Tapable 風格編寫插件
UglifyJsPlugin.prototype.apply = function(compiler) {
    ...
    // 編譯器開始編譯
    compiler.plugin("compilation", function(compilation) {
        ...
        // 編譯器開始調用 "optimize-chunk-assets" 插件編譯
        compilation.plugin("optimize-chunk-assets", function(chunks, callback) {
            var files = [];
            ...
            files.forEach(function(file) {
                ...
                try {
                    var asset = compilation.assets[file];
                    if(asset.__UglifyJsPlugin) {
                        compilation.assets[file] = asset.__UglifyJsPlugin;
                        return;
                    }
                    if(options.sourceMap !== false) {
                    // 需要 sourceMap 時要做的一些操作...
                    } else {
                        // 獲取讀取到的源文件
                        var input = asset.source(); 
                        ...
                    }
                    // base54 編碼重置
                    uglify.base54.reset(); 
                    // 將源文件生成語法樹
                    var ast = uglify.parse(input, {
                        filename: file
                    });
                    // 語法樹轉換爲壓縮後的代碼
                    if(options.compress !== false) {
                        ast.figure_out_scope();
                        var compress = uglify.Compressor(options.compress); // eslint-disable-line new-cap
                        ast = ast.transform(compress);
                    }
                    // 處理混淆變量名
                    if(options.mangle !== false) {
                        ast.figure_out_scope();
                        ast.compute_char_frequency(options.mangle || {});
                        ast.mangle_names(options.mangle || {});
                        if(options.mangle && options.mangle.props) {
                            uglify.mangle_properties(ast, options.mangle.props);
                        }
                    }
                    // 定義輸出變量名
                    var output = {};
                    // 處理輸出的註釋
                    output.comments = Object.prototype.hasOwnProperty.call(options, "comments") ? options.comments : /^\**!|@preserve|@license/;
                    // 處理輸出的美化
                    output.beautify = options.beautify;
                    for(var k in options.output) {
                        output[k] = options.output[k];
                    }
                    // 處理輸出的 sourceMap
                    if(options.sourceMap !== false) {
                        var map = uglify.SourceMap({ // eslint-disable-line new-cap
                            file: file,
                            root: ""
                        });
                        output.source_map = map; // eslint-disable-line camelcase
                    }
                    // 將壓縮後的數據輸出
                    var stream = uglify.OutputStream(output); // eslint-disable-line new-cap
                    ast.print(stream);
                    if(map) map = map + "";
                    stream = stream + "";
                    asset.__UglifyJsPlugin = compilation.assets[file] = (map ?
                        new SourceMapSource(stream, file, JSON.parse(map), input, inputSourceMap) :
                        new RawSource(stream));
                    if(warnings.length > 0) {
                        compilation.warnings.push(new Error(file + " from UglifyJs\n" + warnings.join("\n")));
                    }
                } catch(err) {
                    // 處理異常
                    ...
                } finally {
                    ...
                }
            });
            // 回調函數
            callback();
        });
        compilation.plugin("normal-module-loader", function(context) {
            context.minimize = true;
        });
    });
};

現在我們回過頭來再看看整體流程,當我們在命令行輸入 webpack 命令,按下回車時都發生了什麼:

  1. 執行 bin 目錄下的 webpack.js 腳本,解析命令行參數以及開始執行編譯。
  2. 調用 lib 目錄下的 webpack.js 文件的核心函數 webpack ,實例化一個 Compiler,繼承 Tapable 插件框架,實現註冊和調用一系列插件。
  3. 調用 lib 目錄下的 /WebpackOptionsApply.js 模塊的 process 方法,使用各種各樣的插件來逐一編譯 webpack 編譯對象的各項。
  4. 在3中調用的各種插件編譯並輸出新文件。

 

第三部分:如何理解打包出的bundle.js?

一個入口文件

// webpack.config.js 
module.exports = {
    entry: ["./index.js"],  //輸入
    output: {               //輸出
        path: __dirname + "/dist",
        filename: "bundle.js"
    },
    watch: true,
    module: {
        loaders: [
        {
            test: /\.jsx?$/,
            loader: 'babel-loader',
            exclude: /node_modules/,
            query: {
                presets: ['es2015', 'react']
            }
        },
        {
            test: /\.css$/,
            loader: 'style-loader!css-loader'
        },
        {
            test: /\.less$/,
            use: [{
                 loader: "style-loader" // creates style nodes from JS strings
             }, {
                 loader: "css-loader" // translates CSS into CommonJS
             }, {
                 loader: "less-loader" // compiles Less to CSS
             }]
        },
        {
            test: /\.(jpg|png|svg)$/,
            loader: 'url-loader'
        }
        ]
    }
}


// index.js 

  import React from "react";
  import ReactDom from 'react-dom'

  import App from './pages/app.jsx'
  ReactDom.render(
    <App/>,
         document.querySelector('#app')
  )

// 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] = {
/******/             i: moduleId,  //模塊ID
/******/             l: false,  //是否load
/******/             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;  //m是modules
/******/
/******/     // expose the module cache  //暴露模塊緩存
/******/     __webpack_require__.c = installedModules;  //c是cache
/******/
/******/     // define getter function for harmony exports  //定義協調輸出的getter函數
/******/     __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 = 86);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */  //第0個函數
/***/ function(module, exports) {

    console.log('index');

/***/ },

/* 1 */  //第1個函數
/***/ (function(module, exports, __webpack_require__) {

      "use strict";

    function reactProdInvariant(code) {
    var argCount = arguments.length - 1;

    var message = 'Minified React error #' + code + '; visit ' + 'http://facebook.github.io/react/docs/error-decoder.html?invariant=' + code;

    for (var argIdx = 0; argIdx < argCount; argIdx++) {
      message += '&args[]=' + encodeURIComponent(arguments[argIdx + 1]);
    }

    message += ' for the full message or use the non-minified dev environment' + ' for full errors and additional helpful warnings.';

    var error = new Error(message);
    error.name = 'Invariant Violation';
    error.framesToPop = 1; // we don't care about reactProdInvariant's own frame

    throw error;
  }

  module.exports = reactProdInvariant;

/***/ }),

  // 省略若干。。。。

/******/ ]);
  1. 可以看到,這個bundle.js是一個自執行函數,前65行都在定義這個自執行函數,最後傳入了一個數組作爲參數,因爲只有一個js文件,這裏的數組長度爲1,並且數組裏的每一個元素都是一個自執行函數,自執行函數中包含着index.js裏的內容。 
  2. 即整個bundle.js文件是一個傳入了 包含若干個模塊的數組 作爲參數即傳入的modules是一個數組。 
  3. 在這個bundle.js文件中的自執行函數中定義了一個webpack打包的函數 __webpack_require__, 這個函數式一個打包的核心函數, 接收一個moduleId作爲參數,moduleId是一個數字,實際上就是整個自執行函數接收的數組參數的index值。 即整個傳入的module數組,每一個元素都是一個module,我們爲之定義一個特定的moduleId,進入函數,首先判斷要加載的模塊是否已經存在,如果已經存在, 就直接返回installedModules[moduleId].exports,這樣就保證了所有的模塊只會被加載一次,而不會被多次加載。 如果說這個模塊還沒有被加載,那麼我們就創建一個installedModules[moduleId], 他是一個對象,包括i屬性(即moduleId),l屬性(表示這個模塊是否已經被加載, 初始化爲false), exports 屬性它的內容是每個模塊想要導出的內容, 接下來執行  modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 函數進行調用,那麼這個函數具體是如何執行的呢? 首先保證在module.exports上進行調用這個函數,然後傳入了module參數,即我們想要調用的這個模塊,傳入module.exports ,那麼在每一個模塊中使用的module和module.exports就都是屬於這個模塊的了, 同時再傳入 __webpack_require__這樣我們就可以在每一個模塊中繼續使用了加載器了,最後,導出這個模塊。 調用完成之後,將l設置爲true,表示已經加載,最後導出module.exports,即導出加載到的模塊。
  4. 在自執行函數的末尾我們可以看到這個自執行函數最終返回了一個 __webpack_require__ 調用,也就是說返回了一個模塊,因爲__webpck_require__函數本身就會返回一個模塊。 並且這個 __webpack_require__調用接收的參數是一個 moduleId ,且指明瞭其值爲86。 也就是說入口文件的 moduleId 爲86, 我們來看一看模塊 86 的內容是什麼。即在這個bundle.js函數執行之後,實際上得到的第一部分內容是 86 模塊的內容。 
/* 86 */
/***/ (function(module, exports, __webpack_require__) {

module.exports = __webpack_require__(87);


/***/ }),

   模塊86非常簡單,就是首先通過 __webpack_require__(87) 引入了 moduleId 爲87的模塊, 然後我們看看87模塊是什麼。

/* 87 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

var _react = __webpack_require__(9);
var _react2 = _interopRequireDefault(_react);
var _reactDom = __webpack_require__(103);
var _reactDom2 = _interopRequireDefault(_reactDom);
var _app = __webpack_require__(189);
var _app2 = _interopRequireDefault(_app);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_reactDom2.default.render(_react2.default.createElement(_app2.default, null), document.querySelector('#app'));
/***/ }),

  在這一部分的開頭,我們也看到了index.js的內容,主要任務就是引入了 react 、react-dom、引入了App組件、最後進行渲染。 同樣地,這裏我們可以看到,在這個模塊中,通過 __webpack_reuqire__(9) 引入了_react(這裏的react添加了下劃線,表示這裏的react是沒有對外暴露的), 然後使用_interopRequireDefault這個函數處理 --- 首先判斷引入的是否是一個對象並且同時滿足這個對象是否滿足es6中的module導出,如果滿足,就直接返回這個對象,如果不滿足, 就返回一個值爲obj的對象來進一步處理。 最後一步就是使用引入的各個方法來講 App 模塊掛載到 id爲app爲的元素下。 到這裏,可以看出引入了多個模塊,我們下面分別分析 __webpack_require__(9) 的react模塊以及__webpack_require__(189) 的 app 模塊,即一個是從外部定義的模塊,一個是我們自己寫的模塊。這兩個類型不同的模塊有了區分之後,我們就可以大致理清楚整個 bundle.js 的脈絡了。 

__webpack_require__(9)

/* 9 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = __webpack_require__(19);
/***/ }),

進入了__webpack_require__(9)模塊我們看到,我們需要去尋找 19 模塊。 下面我們看看19模塊。 

/* 19 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
// 這裏說明了react是從外部注入的。 
/* WEBPACK VAR INJECTION */(function(process) {/**

// 下面的這幾行和我們直接打開react.js代碼的前幾行是一樣的,說明這些代碼確實是直接引入的。
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 */


var _assign = __webpack_require__(4);

var ReactBaseClasses = __webpack_require__(53);
var ReactChildren = __webpack_require__(88);
var ReactDOMFactories = __webpack_require__(92);
var ReactElement = __webpack_require__(15);
var ReactPropTypes = __webpack_require__(96);
var ReactVersion = __webpack_require__(99);

var createReactClass = __webpack_require__(100);
var onlyChild = __webpack_require__(102);

var createElement = ReactElement.createElement;
var createFactory = ReactElement.createFactory;
var cloneElement = ReactElement.cloneElement;

if (process.env.NODE_ENV !== 'production') {
  var lowPriorityWarning = __webpack_require__(36);
  var canDefineProperty = __webpack_require__(27);
  var ReactElementValidator = __webpack_require__(57);
  var didWarnPropTypesDeprecated = false;
  createElement = ReactElementValidator.createElement;
  createFactory = ReactElementValidator.createFactory;
  cloneElement = ReactElementValidator.cloneElement;
}

var __spread = _assign;
var createMixin = function (mixin) {
  return mixin;
};

if (process.env.NODE_ENV !== 'production') {
  var warnedForSpread = false;
  var warnedForCreateMixin = false;
  __spread = function () {
    lowPriorityWarning(warnedForSpread, 'React.__spread is deprecated and should not be used. Use ' + 'Object.assign directly or another helper function with similar ' + 'semantics. You may be seeing this warning due to your compiler. ' + 'See https://fb.me/react-spread-deprecation for more details.');
    warnedForSpread = true;
    return _assign.apply(null, arguments);
  };

  createMixin = function (mixin) {
    lowPriorityWarning(warnedForCreateMixin, 'React.createMixin is deprecated and should not be used. ' + 'In React v16.0, it will be removed. ' + 'You can use this mixin directly instead. ' + 'See https://fb.me/createmixin-was-never-implemented for more info.');
    warnedForCreateMixin = true;
    return mixin;
  };
}

var React = {
  // Modern

  Children: {
    map: ReactChildren.map,
    forEach: ReactChildren.forEach,
    count: ReactChildren.count,
    toArray: ReactChildren.toArray,
    only: onlyChild
  },

  Component: ReactBaseClasses.Component,
  PureComponent: ReactBaseClasses.PureComponent,

  createElement: createElement,
  cloneElement: cloneElement,
  isValidElement: ReactElement.isValidElement,

  // Classic

  PropTypes: ReactPropTypes,
  createClass: createReactClass,
  createFactory: createFactory,
  createMixin: createMixin,

  // This looks DOM specific but these are actually isomorphic helpers
  // since they are just generating DOM strings.
  DOM: ReactDOMFactories,

  version: ReactVersion,

  // Deprecated hook for JSX spread, don't use this for anything.
  __spread: __spread
};

if (process.env.NODE_ENV !== 'production') {
  var warnedForCreateClass = false;
  if (canDefineProperty) {
    Object.defineProperty(React, 'PropTypes', {
      get: function () {
        lowPriorityWarning(didWarnPropTypesDeprecated, 'Accessing PropTypes via the main React package is deprecated,' + ' and will be removed in  React v16.0.' + ' Use the latest available v15.* prop-types package from npm instead.' + ' For info on usage, compatibility, migration and more, see ' + 'https://fb.me/prop-types-docs');
        didWarnPropTypesDeprecated = true;
        return ReactPropTypes;
      }
    });

    Object.defineProperty(React, 'createClass', {
      get: function () {
        lowPriorityWarning(warnedForCreateClass, 'Accessing createClass via the main React package is deprecated,' + ' and will be removed in React v16.0.' + " Use a plain JavaScript class instead. If you're not yet " + 'ready to migrate, create-react-class v15.* is available ' + 'on npm as a temporary, drop-in replacement. ' + 'For more info see https://fb.me/react-create-class');
        warnedForCreateClass = true;
        return createReactClass;
      }
    });
  }

  // React.DOM factories are deprecated. Wrap these methods so that
  // invocations of the React.DOM namespace and alert users to switch
  // to the `react-dom-factories` package.
  React.DOM = {};
  var warnedForFactories = false;
  Object.keys(ReactDOMFactories).forEach(function (factory) {
    React.DOM[factory] = function () {
      if (!warnedForFactories) {
        lowPriorityWarning(false, 'Accessing factories like React.DOM.%s has been deprecated ' + 'and will be removed in v16.0+. Use the ' + 'react-dom-factories package instead. ' + ' Version 1.0 provides a drop-in replacement.' + ' For more info, see https://fb.me/react-dom-factories', factory);
        warnedForFactories = true;
      }
      return ReactDOMFactories[factory].apply(ReactDOMFactories, arguments);
    };
  });
}

module.exports = React;
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0)))

/***/ }),

這就是react.js的核心代碼,但是爲什麼一共就100行左右的代碼呢? 這裏應該引入了整個 react 文件啊。 我們從內部代碼可以看到,在react模塊中同樣又使用了 __webpack_require__  來引入了更多的文件, 這時因爲react.js本身就是這麼引入的文件的, https://unpkg.com/[email protected]/dist/react.js, 從源碼上可以看到, 它採用的也是分塊的模式,所以在webpack打包的時候,自然也是使用一個一個模塊的形式進行打包引入了。 這樣做的好處是什麼呢?  因爲這樣可以增加代碼的重用,就19模塊的 var ReactBaseClasses = __webpack_require__(53); 而言, 即react的 ReactBaseClasses 模塊需要使用,另外,在19模塊的createReactClass也是需要的,它先引入了100模塊,然後又引入了 19 模塊。  並且對於大型的框架、庫而言,都是需要按照模塊進行編寫的,不可能直接寫在一個模塊中。 react的19模塊就介紹到這裏。 

下面我們再看看189的App模塊。(這個模塊是jsx文件,所以需要通過babel-loader進行轉譯)

/* 189 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = __webpack_require__(9);

var _react2 = _interopRequireDefault(_react);

var _title = __webpack_require__(35);

var _title2 = _interopRequireDefault(_title);

var _item = __webpack_require__(85);

var _item2 = _interopRequireDefault(_item);

var _experience = __webpack_require__(193);

var _experience2 = _interopRequireDefault(_experience);

var _skill = __webpack_require__(199);

var _skill2 = _interopRequireDefault(_skill);

var _personal = __webpack_require__(202);

var _personal2 = _interopRequireDefault(_personal);

var _intro = __webpack_require__(203);

var _intro2 = _interopRequireDefault(_intro);

var _others = __webpack_require__(207);

var _others2 = _interopRequireDefault(_others);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

__webpack_require__(214);

var App = function (_React$Component) {
    _inherits(App, _React$Component);

    function App() {
        _classCallCheck(this, App);

        return _possibleConstructorReturn(this, (App.__proto__ || Object.getPrototypeOf(App)).apply(this, arguments));
    }

    _createClass(App, [{
        key: 'render',
        value: function render() {
            return _react2.default.createElement(
                'div',
                { className: 'app-wrap' },
                _react2.default.createElement(
                    'div',
                    { className: 'sub' },
                    _react2.default.createElement(
                        'div',
                        { className: 'intro' },
                        _react2.default.createElement(_intro2.default, null)
                    ),
                    _react2.default.createElement(
                        'div',
                        { className: 'others' },
                        _react2.default.createElement(_others2.default, null)
                    )
                ),
                _react2.default.createElement(
                    'div',
                    { className: 'main' },
                    _react2.default.createElement(
                        'div',
                        { className: 'experience' },
                        _react2.default.createElement(_experience2.default, null)
                    ),
                    _react2.default.createElement(
                        'div',
                        { className: 'skill' },
                        _react2.default.createElement(_skill2.default, null)
                    ),
                    _react2.default.createElement(
                        'div',
                        { className: 'personal' },
                        _react2.default.createElement(_personal2.default, null)
                    )
                )
            );
        }
    }]);

    return App;
}(_react2.default.Component);

exports.default = App;

/***/ }),

而下面是app.jsx 的源代碼:

import React from "react";

import Title from '../components/title.jsx'
import Item2 from '../components/item2.jsx'
import Experience from '../components/experience.jsx'
import Skill from '../components/skill.jsx'
import Personal from '../components/personal.jsx'
import Intro from '../components/intro.jsx'
import Others from '../components/others.jsx'

require('../css/app.less')

class App extends React.Component{
    
    render () {
        return (
            <div className='app-wrap'>
                <div className="sub">
                    <div className="intro">
                        <Intro/>
                    </div>
                    <div className="others">
                        <Others/>
                    </div>
                </div>
                <div className="main">
                    <div className="experience">
                        <Experience/>
                    </div>
                    <div className="skill">
                        <Skill/>
                    </div>
                    <div className="personal">
                        <Personal/>
                    </div>
                </div>
            </div>
            )
    }
}

export default App; 

在模塊的開始,我們就看到這個模塊的 _esModule 就被定義爲了 true,那麼代表這個模塊是符合 es6 的module規範的,這樣我們就可以直接導入導出了。 

接下來,我們又看到了 var _react = __webpack_require__(9); 因爲我們在這個文件中引入了 react 模塊,但是在bundle.js最開始定義模塊的時候我們知道,只要加載了一次,這個模塊就會被放在 installedModules 對象中,這樣,我們就可以在第二次及以後使用的過程中,直接返回 installedModules 的這個模塊,而不需要重新加載了

app模塊下的app.less

接着又引入了一些依賴和更底層的組件(不是隻嵌套組件的組件),比如,在 app.jsx 中我又引入了 app.less 這個less組件, 在模塊189中,我們可以看到確實有一個單獨引入的less組件, __webpack_require__(214); (稍後我們看看這個模塊)

最後開始創建app組件,最後返回這個組件。 

 

模塊 214 (一個less模塊)

/* 214 */
/***/ (function(module, exports, __webpack_require__) {

// style-loader: Adds some css to the DOM by adding a <style> tag

// load the styles
var content = __webpack_require__(215);
if(typeof content === 'string') content = [[module.i, content, '']];
// Prepare cssTransformation
var transform;

var options = {}
options.transform = transform

// add the styles to the DOM
var update = __webpack_require__(18)(content, options);

if(content.locals) module.exports = content.locals;
// Hot Module Replacement
if(false) {
    // When the styles change, update the <style> tags
    if(!content.locals) {
        module.hot.accept("!!../node_modules/css-loader/index.js!../node_modules/less-loader/dist/cjs.js!./app.less", function() {
            var newContent = require("!!../node_modules/css-loader/index.js!../node_modules/less-loader/dist/cjs.js!./app.less");
            if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
            update(newContent);
        });
    }
    // When the module is disposed, remove the <style> tags
    module.hot.dispose(function() { update(); });
}

/***/ }),

在這個模塊中,我們可以看到這裏首先提到使用 style-loader 將css添加到html中。 接着開始加載 style ,即 215 模塊(css代碼),然後判斷 content 是否是一個字符串,如果是,就創建一個數組,包含這個字符串, 接下來, 使用熱更新機制。 這裏最重要的就是18模塊,將css代碼添加到html中,這個模塊中的的核心函數爲 addStylesToDom , 如下所示:

function addStylesToDom (styles, options) {
    for (var i = 0; i < styles.length; i++) {
        var item = styles[i];
        var domStyle = stylesInDom[item.id];

        if(domStyle) {
            domStyle.refs++;

            for(var j = 0; j < domStyle.parts.length; j++) {
                domStyle.parts[j](item.parts[j]);
            }

            for(; j < item.parts.length; j++) {
                domStyle.parts.push(addStyle(item.parts[j], options));
            }
        } else {
            var parts = [];

            for(var j = 0; j < item.parts.length; j++) {
                parts.push(addStyle(item.parts[j], options));
            }

            stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts};
        }
    }
}

即接收兩個參數,第一個就是將要添加的style,第二個就是一些選項, 內部對所有的style進行遍歷, 然後添加進入。 

我們可以看到215模塊如下所示:

/* 215 */
/***/ (function(module, exports, __webpack_require__) {

exports = module.exports = __webpack_require__(17)(undefined);
// imports


// module
exports.push([module.i, "div.app-wrap {\n  width: 80%;\n  margin: 0 auto;\n  overflow: hidden;\n  margin-top: 10px;\n  border: thin solid #ccc;\n}\ndiv.app-wrap div.sub {\n  box-shadow: 0 0 10px gray;\n  float: left;\n  width: 35%;\n}\ndiv.app-wrap div.sub div.intro {\n  margin-bottom: 63px;\n}\ndiv.app-wrap div.main {\n  float: right;\n  width: 63%;\n  margin-right: 5px;\n}\ndiv.app-wrap div.main div.skill {\n  margin-bottom: 10px;\n}\n", ""]);

// exports


/***/ })

即這裏首先引入了 17 模塊, 17模塊的作用是通過css-loader注入基礎代碼(這個基礎css代碼是一個數組), 接着再push進入我寫的app.less代碼(注意:這裏的css代碼已經被less-loader轉化爲了css代碼), 然後進行注入的,最後是導出的這個css代碼。  

app模塊下的introl.jsx模塊(203模塊)

  這個模塊的jsx代碼如下:

import React from "react"
require('../css/intro.less')
import protrait from '../images/portrait.png'


class Intro extends React.Component{
    render () {

        return (
            <div className='intro-wrap'>
                <div className="portrait">
                    <img src={protrait}/>
                 </div>
                <div className="name">WayneZhu</div>
                <div className="position">
                <span>
                    前端開發工程師
                </span>
                </div>
            </div>
            )
    }
}

export default Intro; 

  選用這個模塊的目的是因爲這裏有一個導入圖片的步驟,這樣,我們就可以觀察圖片的打包過程了。

  下面是bundle.js中的該模塊:

/* 203 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = __webpack_require__(9);

var _react2 = _interopRequireDefault(_react);

var _portrait = __webpack_require__(204);

var _portrait2 = _interopRequireDefault(_portrait);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

__webpack_require__(205);

var Intro = function (_React$Component) {
    _inherits(Intro, _React$Component);

    function Intro() {
        _classCallCheck(this, Intro);

        return _possibleConstructorReturn(this, (Intro.__proto__ || Object.getPrototypeOf(Intro)).apply(this, arguments));
    }

    _createClass(Intro, [{
        key: 'render',
        value: function render() {

            return _react2.default.createElement(
                'div',
                { className: 'intro-wrap' },
                _react2.default.createElement(
                    'div',
                    { className: 'portrait' },
                    _react2.default.createElement('img', { src: _portrait2.default })
                ),
                _react2.default.createElement(
                    'div',
                    { className: 'name' },
                    'WayneZhu'
                ),
                _react2.default.createElement(
                    'div',
                    { className: 'position' },
                    _react2.default.createElement(
                        'span',
                        null,
                        '\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08'
                    )
                )
            );
        }
    }]);

    return Intro;
}(_react2.default.Component);

exports.default = Intro;

/***/ }),

在這個模塊中,我們可以看到webpack將圖片也當做了一個模塊204,然後引入了這個模塊,最後直接在 圖片的src中引用, 所以我們有必要看看 204 模塊的內容。 

 

204模塊(png圖片)


這個模塊很簡單,就是將圖片進行了base64編碼,得到的結果如下所示:

1

2

3

4

5

/* 204 */

/***/ (function(module, exports) {<br><br>// 下面的編碼內容省略了大部分

module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYUAAAFRCAYAAACbjLFxAAACC"

 

/***/ }),

這樣,就可以直接將這個編碼當做src,而不會發出請求來當做http請求了。

 當然並不是所有的圖片都會被當做 模塊 進行打包, 我們完全可以去請求一個本地資源, 但是對於本地資源,我們需要提前進行設置, 一般,需要在node的服務器文件中添加下面的代碼:

// node你服務器使用的靜態文件
app.use('/', express.static('./www'))

這樣,我們就可以發現,在使用圖片時,可以直接是:

  <img src='/images/add.png' className='create' onClick={this.createRoom}/>

即這裏 /images/add.png 默認是在 www 這個文件夾下的,因爲在node中,我們已經設置了靜態文件的位置了。 這樣,webpack 也只是引用,而不會將至轉化爲base64編碼:

_react2.default.createElement(
       "div",
  { className: "channel" },
   _react2.default.createElement(
  "span",
   null,
   "\u6240\u6709\u623F\u95F4"
   ),
   _react2.default.createElement("img", { src: "/images/add.png", className: "create", onClick: this.createRoom })
 ),

  這樣,我們就可以發現: 這裏直接使用的就是路徑,引用 www 文件夾下的文件。 當然,我們也可以把www下的文件直接以模塊的形式打包進來。  但是,在使用靜態文件時,我們只能使用 www 下這個制定文件夾下的文件,而不能使用其他文件夾下的文件。 

  可以發現的是,在尋找文件的過程中,採用的是深度優先的遍歷原則。   

  ok!  bundle.js 的內容到這裏大致就比較清楚了。下面,我們嘗試着實現一個簡單的webpack打包工具吧。

 第四部分: 如何實現一個簡單的webpack打包工具? 

前言:

   一個webpack工具是需要很大的時間和精力來創造的,我們不可能實現所有的功能,這裏只是提供一個大體的思路,完成最簡單的功能,如實現使用符合commonjs規範的幾個文件打包爲一個文件。 

  當然,瀏覽器是沒有辦法執行commonjs規範的js文件的,所以,我們需要寫成自執行函數的形式,就像webpack打包出來的bundle.js一樣。

需求: 

  我們實現的需求就是一個入口文件example.js依賴於文件a、b、c,其中a和b是和example.js在同一目錄文件下的,而c是在node_modules中的, 我們要將這幾個模塊構建成一個js文件,輸入bundle.js。 

  • bundle.js 的頭部信息都是一致的,如都是一個自執行函數的定義,其中有一個核心函數 __webpack_require__ ,最終這個自執行函數返回的是入口文件的模塊。 然後依次向下執行。 
  • 需要分析出各個模塊之間的依賴關係,比如這裏的example.js是依賴於a、b、c的。 
  • 並且我們使用require('c')的時候,會自動導入node_modules中的相關文件,那麼這一定是有一個詳細的查詢機制的。 
  • 在生成的bundle.js文件中,每一個模塊都是具有一個唯一的模塊id的,引用時我們只需要引用這個id即可。 

 

分析模塊依賴關係:

  CommonJS不同於AMD,是不會在一開始聲明所有依賴的。CommonJS最顯著的特徵就是用到的時候再require,所以我們得在整個文件的範圍內查找到底有多少個require

  webpack是使用commonjs的規範來寫腳本的,但是對amd、cmd的書寫方式也支持的很好。 這裏簡單區分一下幾種模塊化的方法。 ADM/CMD是專門爲瀏覽器端的模塊化加載來制定的, 通常使用的方式就是define() 的方式,其中amd要求必須在文件的開頭聲明所有依賴的文件,而cmd則沒有這個要求,而是在使用的時候require即可, 即: amd是提前加載的,而cmd是在使用時再加載的,這是兩者的區別之一。Commonjs是服務器端node的書寫方式,如使用的時候require,而在導出的時候使用module.export,但是如今Commonjs規範已經不僅僅只適用於服務器端了,而是也適用於桌面端,但是隨着其使用越來越廣泛,名字由之前的severjs改爲了common.js。 而es6中的 export 和 import會在babel的編譯下編譯爲瀏覽器可以執行的方式。

  怎麼辦呢?

   最先蹦入腦海的思路是正則。然而,用正則來匹配require,有以下兩個缺點:

  1. 如果require是寫在註釋中,也會匹配到。
  2. 如果後期要支持require的參數是表達式的情況,如require('a'+'b'),正則很難處理。

   因此,正則行不通。

   一種正確的思路是:使用JS代碼解析工具(如esprima或者acorn),將JS代碼轉換成抽象語法樹(AST),再對AST進行遍歷。這部分的核心代碼是parse.js。

   在處理好了require的匹配之後,還有一個問題需要解決。那就是匹配到require之後需要幹什麼呢?
舉個例子:

// example.js
let a = require('a');
let b = require('b');
let c = require('c');

   這裏有三個require,按照CommonJS的規範,在檢測到第一個require的時候,根據require即執行的原則,程序應該立馬去讀取解析模塊a。如果模塊a中又require了其他模塊,那麼繼續解析。也就是說,總體上遵循深度優先遍歷算法。這部分的控制邏輯寫在buildDeps.js中。

 

尋找模塊:

在完成依賴分析的同時,我們需要解決另外一個問題,那就是如何找到模塊?也就是模塊的尋址問題。
舉個例子:

// example.js
let a = require('a');
let b = require('b');
let c = require('c');

在模塊example.js中,調用模塊a、b、c的方式都是一樣的。
但是,實際上他們所在的絕對路徑層級並不一致:a和bexample同級,而c位於與example同級的node_modules中。所以,程序需要有一個查找模塊的算法,這部分的邏輯在resolve.js中。

目前實現的查找邏輯是:

  1. 如果給出的是絕對路徑/相對路徑,只查找一次。找到?返回絕對路徑。找不到?返回false。
  2. 如果給出的是模塊的名字,先在入口js(example.js)文件所在目錄下尋找同名JS文件(可省略擴展名)。找到?返回絕對路徑。找不到?走第3步。
  3. 在入口js(example.js)同級的node_modules文件夾(如果存在的話)查找。找到?返回絕對路徑。找不到?返回false。

當然,此處實現的算法還比較簡陋,之後有時間可以再考慮實現逐層往上的查找,就像nodejs默認的模塊查找算法那樣。

 

拼接 bundle.js :

 這是最後一步了。
在解決了模塊依賴模塊查找的問題之後,我們將會得到一個依賴關係對象depTree,此對象完整地描述了以下信息:都有哪些模塊,各個模塊的內容是什麼,他們之間的依賴關係又是如何等等。具體的結構如下

{
    "modules": {
        "/Users/youngwind/www/fake-webpack/examples/simple/example.js": {
            "id": 0,
            "filename": "/Users/youngwind/www/fake-webpack/examples/simple/example.js",
            "name": "/Users/youngwind/www/fake-webpack/examples/simple/example.js",
            "requires": [
                {
                    "name": "a",
                    "nameRange": [
                        16,
                        19
                    ],
                    "id": 1
                },
                {
                    "name": "b",
                    "nameRange": [
                        38,
                        41
                    ],
                    "id": 2
                },
                {
                    "name": "c",
                    "nameRange": [
                        60,
                        63
                    ],
                    "id": 3
                }
            ],
            "source": "let a = require('a');\nlet b = require('b');\nlet c = require('c');\na();\nb();\nc();\n"
        },
        "/Users/youngwind/www/fake-webpack/examples/simple/a.js": {
            "id": 1,
            "filename": "/Users/youngwind/www/fake-webpack/examples/simple/a.js",
            "name": "a",
            "requires": [],
            "source": "// module a\n\nmodule.exports = function () {\n    console.log('a')\n};"
        },
        "/Users/youngwind/www/fake-webpack/examples/simple/b.js": {
            "id": 2,
            "filename": "/Users/youngwind/www/fake-webpack/examples/simple/b.js",
            "name": "b",
            "requires": [],
            "source": "// module b\n\nmodule.exports = function () {\n    console.log('b')\n};"
        },
        "/Users/youngwind/www/fake-webpack/examples/simple/node_modules/c.js": {
            "id": 3,
            "filename": "/Users/youngwind/www/fake-webpack/examples/simple/node_modules/c.js",
            "name": "c",
            "requires": [],
            "source": "module.exports = function () {\n    console.log('c')\n}"
        }
    },
    "mapModuleNameToId": {
        "/Users/youngwind/www/fake-webpack/examples/simple/example.js": 0,
        "a": 1,
        "b": 2,
        "c": 3
    }
}

 打包優化

  使用了react全家桶之後,打包出的bundle.js是非常大的, 所以對之進行優化是十分有必要的。

(1)、使用壓縮插件,如下:

  在webpack.config.js中進行配置下面的代碼:

    plugins: [
       new webpack.optimize.UglifyJsPlugin({
         compress: {
           warnings: false
         }
       })
     ]

  這樣打包出來的文件可以從5M減少到1.7左右。 

 

(2)、開發過程中使用 webpack-dev-server.

  我們當然可以每次使用打包出來的文件,但是更好的做法是將不把文件打包出來,然後從硬盤中獲取,而是直接打包到內存中(即webapck-dev-server的作用),這樣,我們就可以直接從內存中獲取了,好處就是速度很快。 顯然內存的讀取速度是大於硬盤的。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章