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文件,找到这个文件,我们可以看到主要的代码如下:

// 引入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函数,我们来看看源码。

./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, /******/ 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的作用),这样,我们就可以直接从内存中获取了,好处就是速度很快。 显然内存的读取速度是大于硬盘的。