美文网首页
webpack学习(一)从一个打包文件开始

webpack学习(一)从一个打包文件开始

作者: 大柚子08 | 来源:发表于2020-03-08 16:10 被阅读0次

    准备一个简单的项目

    webpack.config.js:

    const path = require('path')
    
    module.exports = {
        mode: 'development',
        entry: './src/home/index.js',
        output: {
            filename: 'js/[name].js',
            path: path.resolve(__dirname, `./dist`),
        },
        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: /(node_modules)/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env']
                        }
                    }
                }
            ]
        }
    }
    

    package.json

      "scripts": {
        "build": "webpack --progress"
      },
    

    src/home/index.js

    import initialNum from './a.js'
    export default function add(a, b) {
        return a + b + initialNum()
    }
    

    src/home/a.js

    export default function initialNum() {
        return 1
    }
    

    执行npm run build, 看看打包生成的文件

    (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;
        }
        //...此处省略其余代码
        return __webpack_require__(__webpack_require__.s = "./src/home/index.js");
    })
    ({
     "./src/home/a.js":(function(module, __webpack_exports__, __webpack_require__) {
    eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return initialNum; });\n/**\n * Created by dengxuelian on 2020/3/7\n */\nfunction initialNum() {\n  return 1;\n}\n\n//# sourceURL=webpack:///./src/home/a.js?");
    }),
    
    "./src/home/index.js":(function(module, __webpack_exports__, __webpack_require__) {
    eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return add; });\n/* harmony import */ var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a.js */ \"./src/home/a.js\");\n/**\n * Created by dengxuelian on 2020/2/29\n */\n\nfunction add(a, b) {\n  return a + b + Object(_a_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();\n}\n\n//# sourceURL=webpack:///./src/home/index.js?");
    })
    });
    

    手把手分析代码结构

    1. 整体结构:一个立即执行函数,接收一个参数
    (function (modules) {
        //函数体
    })(/*实参*/)
    
    1. 函数参数modules:一个对象,key是文件相对路径,value是一个函数
    (function (modules) {
        //函数体
    })({
        "./src/home/a.js": function(){}
        "./src/home/index.js":  function () {},
    })
    

    想必你能猜到modules对象是什么了,它就是你项目中所有被引用的文件,准确地说:modules对象是从入口文件(webpack.entry)开始,所有被引用的文件列表,而webpack如何获取到这些文件列表,我们暂时不关注。
    现在我们有了一份js清单,接下来我们要做的就是加载并执行这些js文件。可以猜测,在立即执行函数体内,一定会调用参数modules传入的函数,而其唯一的标识是key,其调用会像这样:modules[moduleId]()

    1. 立即执行函数里调用modules对象上的某个key上的某个方法
    (function (modules) {
        function __webpack_require__(moduleId) {
            modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); //moduleId就是modules的key,也就是文件的相对路径
        }
    })({
        "./src/home/a.js": function(){}
        "./src/home/index.js":  function () {},
    })
    

    我们接着看函数__webpack_require__的调用地方

    1. 函数__webpack_require__的执行时机
    (function (modules) {
        function __webpack_require__(moduleId) {
            modules[moduleId].call(); //moduleId就是modules的key,也就是文件的相对路径
        }
        return __webpack_require__("./src/home/index.js");
    })({
        "./src/home/a.js": function(){}
        "./src/home/index.js":  function () {},
    })
    

    这个立即函数又执行了__webpack_require__(),那么就等于执行了modules对象传进来的某个键上的方法,具体是哪个键(文件)呢?这里是./src/home/index.js,也就是webpack.entry配置的入口文件。

    1. 现在看看"./src/home/index.js": function () {}这个函数里面写了什么
    {
      "./src/home/index.js": (function(module, __webpack_exports__, __webpack_require__) {
          eval(/*我把这段字符格式化成js写在下面了*/);
        __webpack_require__.r(__webpack_exports__)
        __webpack_require__.d(__webpack_exports__, "default", function() { return add; });
        var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/home/a.js")
        function add(a, b) {return a + b + Object(_a_js__WEBPACK_IMPORTED_MODULE_0__["default"])();}
      })
    }
    

    这个函数内部是一个eval()方法加上一堆字符串,我把字符串格式调整了一下,方便分析,首先关注的是这个函数确实加载了./src/home/index.js这个文件,其次是注意import initialNum from './a.js'变成了

    var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/home/a.js")
    

    __webpack_require__("./src/home/a.js")等价于执行"./src/home/a.js": function(){}。假如./src/home/a.js里面又引用了其它文件,那么可以猜测该函数内部也会有形如var XXX = __webpack_require__(XXX)这种定义,以此循环,直到到达从入口文件开始的所有js文件调用树的结尾。

    __webpack_require__上还挂载了一些辅助方法或值,比如判断该模块是否加载过,定义exports属性等等。这里不再展开。

    多个entry

    entrywebpack分析文件依赖关系的入口,多个entry就对应多个打包文件。我们可以看看多个entry的打包结果
    webpack.config.js:

    const path = require('path')
    
    const pages = ['home', 'login'];
    
    let entries = {}
    
    pages.forEach((page) => {
        entries[page] = path.resolve(__dirname, `./src/${page}/index.js`);
    })
    
    module.exports = {
        mode: 'development',
        entry: entries,
        output: {
            filename: 'js/[name].js',
            path: path.resolve(__dirname, `./dist`),
        },
        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: /(node_modules)/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env']
                        }
                    }
                }
            ]
        }
    }
    

    ./src/login/index.js

    import initialNum from '../home/a.js'
    
    export default function desc(value) {
        return value - initialNum()
    }
    

    打包生成的文件:

    //src/login.js
    (function(modules) { // webpackBootstrap
        return __webpack_require__(__webpack_require__.s = "./src/login/index.js");
    })
     ({
         "./src/home/a.js": (function(module, __webpack_exports__, __webpack_require__) {}),
         "./src/login/index.js": (function(module, __webpack_exports__, __webpack_require__) {})
    });
    
    //src/home.js
    (function(modules) { // webpackBootstrap
        return __webpack_require__(__webpack_require__.s = "./src/home/index.js");
    })
     ({
         "./src/home/a.js": (function(module, __webpack_exports__, __webpack_require__) {}),
         "./src/home/index.js": (function(module, __webpack_exports__, __webpack_require__) {})
    });
    

    细心的同学发现,两个入口文件都引用了同一个js,打包后的代码里也有一份相同的代码,在没有增加任何额外的配置之前,webpack只是老实本分地完成了自己的本职工作--找依赖、打包文件,拆包这种事,你不“下命令”,它可是不会做的。

    当然,拆包是很简单的,加一个splitChunks配置就可以解决,不过webpack的世界里配置实在太多,我们一步一步来吧。

    下期预告

    webpack构建依赖图(dependency graph

    相关文章

      网友评论

          本文标题:webpack学习(一)从一个打包文件开始

          本文链接:https://www.haomeiwen.com/subject/qegcdhtx.html