zoukankan      html  css  js  c++  java
  • 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 + "
    " +
            "Usage: https://webpack.js.org/api/cli/
    " +
            "Usage without config file: webpack <entry> [<entry>] <output>
    " +
            "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("
    Webpack is watching the files…
    ");
        } 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 ? "
    /*
    //@ source" + "MappingURL=[url]
    //# source" + "MappingURL=[url]
    */" :
                    legacy ? "
    /*
    //@ source" + "MappingURL=[url]
    */" :
                    modern ? "
    //# 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 ? "
    //@ sourceURL=[url]
    //# sourceURL=[url]" :
                    legacy ? "
    //@ sourceURL=[url]" :
                    modern ? "
    //# 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
    " + warnings.join("
    ")));
                        }
                    } 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 {
       80%;
      margin: 0 auto;
      overflow: hidden;
      margin-top: 10px;
      border: thin solid #ccc;
    }
    div.app-wrap div.sub {
      box-shadow: 0 0 10px gray;
      float: left;
       35%;
    }
    div.app-wrap div.sub div.intro {
      margin-bottom: 63px;
    }
    div.app-wrap div.main {
      float: right;
       63%;
      margin-right: 5px;
    }
    div.app-wrap div.main div.skill {
      margin-bottom: 10px;
    }
    ", ""]);
    
    // 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,
                            'u524Du7AEFu5F00u53D1u5DE5u7A0Bu5E08'
                        )
                    )
                );
            }
        }]);
    
        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,
       "u6240u6709u623Fu95F4"
       ),
       _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');
    let b = require('b');
    let c = require('c');
    a();
    b();
    c();
    "
            },
            "/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
    
    module.exports = function () {
        console.log('a')
    };"
            },
            "/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
    
    module.exports = function () {
        console.log('b')
    };"
            },
            "/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 () {
        console.log('c')
    }"
            }
        },
        "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的作用),这样,我们就可以直接从内存中获取了,好处就是速度很快。 显然内存的读取速度是大于硬盘的。 

      

  • 相关阅读:
    【转】Quartz Cron 触发器 Cron Expression 的格式
    [转]MYSQL同时LEFT JOIN 多个表一例
    collapse用法
    flavor用法
    horny
    ever since用法
    be headed for用法
    Lemme用法
    scary用法
    feel like用法
  • 原文地址:https://www.cnblogs.com/zhuzhenwei918/p/7265971.html
Copyright © 2011-2022 走看看