webpack原理探究 && 打包優化

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

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

 

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

     當一個項目使用webpack打包時,webpack會認爲全部的文件都是模塊,並將其打包到一個文件中。 可是webpack只能識別js文件,因此對於其餘文件,咱們須要使用loader來完成打包。 css

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

 

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

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

  

執行腳本 bin/webpack.js

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

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

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

// 引入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風格的異步回調
    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函數,咱們來看看源碼。 react

 

 

 ./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 等調用和綁定插件的方法jquery

        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;

 

注意: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 編譯對象進行逐一編譯,接下來咱們再來仔細看看這個模塊。git

 

 

調用 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, /******/ 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 = 86); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports) { console.log('index'); /***/ },

/* 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/react@15.6.1/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編碼,獲得的結果以下所示:

/* 204 */
/***/ (function(module, exports) {

// 下面的編碼內容省略了大部分 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的做用),這樣,咱們就能夠直接從內存中獲取了,好處就是速度很快。 顯然內存的讀取速度是大於硬盤的。 

相關文章
相關標籤/搜索