最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 前端打包路(1) - 基本原理&发展历史&webpack初介绍

    正文概述 掘金(BreadBear)   2021-04-02   1139

    前言

    后记:由于webpack的知识体系太庞大了,而且webpack是一个很有深度的框架,所以我们拆分一下,这节课先来讲一下打包的基本原理和历史,后面会尽可能深的介绍:(2)原理(3)实战&webpack优化 (4) Tapable (5) tree-shaking (6) sourceMap (7) HMR

    前端打包、构建、gulp、grunt、webpack、rollup、一堆名词,之前没有好好的系统性学习过,这次抽空系统的捋一捋。。。
    可以说随着node的出现,前端变得越来越复杂,因为js代码不再是只能运行在浏览器里面的弱鸡语言,随之带来的是同样在服务器上运行的能力。我认为带来最大的利好就是前端项目也可以“工程化”了,就像C一样,具备了:预处理、编译、汇编、链接的能力。当然javascript是一门解释型语言,所以就没有后面三步了,前端打包多少类似于预处理+模块化的过程。

    理解前端模块化

    为啥要模块化:难道都写在main.js里面?如何复用?如何协同开发?
    但是js不像其他

    作用域

    全局作用域、局部作用域 
    全局: window, global
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script src="./moduleA.js"></script>
      <script src="./moduleB.js"></script>
      <script src="./moduleC.js"></script>
    </body>
    </html>
    
    // moduleA.js
    var a = 1;
    
    // moduleB.js
    var a = 2;
    
    // moduleC.js
    var b = a + 1;
    console.log('b = ', b);
    
    结果:b = 3
    

    显然被覆盖了。怎么办呢?

    命名空间

    //moduleA
    var a = {
      value: 1,
    };
    
    //moduleB
    var b = {
      value: 1,
    };
    
    //moduleC
    console.log('moduleA的value', a.value);
    console.log('moduleB的value', b.value);
    
    结果:
    moduleA的value 1
    moduleB的value 2
    

    看上去解决了上面的问题,但是随之而来的问题:

    a.value = 100;
    

    这样很容易就改变内部的一个变量了 所以我们需要利用作用域和闭包来改造一把

      var moduleA = (function() {
        var name = 'Nolan';
    
        return {
          myNameIs: function() {
            console.log('请叫我', name);
          },
        };
      })();
    

    这是一个立即执行函数。

    moduleA.myNameIs();
    // 请叫我Nolan
    
    moduleA.name;
    // undefined
    

    很明显暴露了该暴露的、隐藏了该隐藏的。
    接下来我们再优化一下写法

    (function(window) {
      var name = 'Nolan';
      function myNameIs() {
        console.log('请叫我', name);
      }
      
      window.moduleA = { myNameIs };
    })(window)
    

    如果你撸过webpack打包后的代码,对比一下,是不是很像了?

    总结一下

    所以我们看到一个技术是循序渐进出来的,想想手写一个js继承是不是也是一步一步解决问题,遇到新的问题,再解决问题,最终产生的。
    优点:

    • 作用域封装
    • 重用性
    • 解除耦合

    模块化

    History

    • AMD
    • COMMONJS
    • ES6 MODULE

    AMD

    define('moduleName', ['lodash'], function(_) {
      return function(a, b) {
        console.log(a, b);
      };
    });
    

    比如 requireJS 后来衍生出了 玉伯大神的成名作 sea.js(CMD)

    COMMONJS

    2009年推出,主要为了规范服务端开发,并不是针对浏览器的规范。所以后来Nodejs也引用了此标准。

    const moduleA = require('./moduleA');
    
    exports.getSum = function(a, b) {
      console.log(a + b);
    }
    

    与AMD相同,强调了引入的模块。

    ES6 MODULE

    与COMMONS很像

    import moduleA from './moduleA';
    
    export function getName(name) {
      return name;
    }
    
    

    期间诞生了很多可以打包的工具:
    Gulp,Grunt是自动化构建工具,这里要强调自动化是因为他们不仅可以做打包,自动化是其核心目的。
    而webpack的出现可以说是专注于打包。

    webpack

    先来看一个小例子

    首先我们先创建一个webpack-test的工程 下面包括 index.html、src/index.js和src/util.js。 npm安装 webpack以及webpack-cli工具
    目前使用的是4.x.x版本。v5对tree-shake进行了性能优化,所以构建出的结果会有所不同。后面我们会介绍tree-shake是什么。 index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>My Webpack Test</title>
    </head>
    <body>
      <script src="./src/index.js"></script>
    </body>
    </html>
    

    index.js

    const num = require('./util');
    
    function test() {
      console.log('我是一个小测试!', num);
    }
    
    test();
    

    util.js

    exports.default = 123;
    

    接下来在根目录执行 npx webpack 或者 ./node_module/.bin/webpack 会生成dist/main.js的文件,打开文件我们看下结构

    ! function(e) {
        var t = {};
    
        function n(r) {
            if (t[r]) return t[r].exports;
            var o = t[r] = {
                i: r,
                l: !1,
                exports: {}
            };
            return e[r].call(o.exports, o, o.exports, n), o.l = !0, o.exports
        }
        n.m = e, n.c = t, n.d = function(e, t, r) {
            n.o(e, t) || Object.defineProperty(e, t, {
                enumerable: !0,
                get: r
            })
        }, n.r = function(e) {
            "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
                value: "Module"
            }), Object.defineProperty(e, "__esModule", {
                value: !0
            })
        }, n.t = function(e, t) {
            if (1 & t && (e = n(e)), 8 & t) return e;
            if (4 & t && "object" == typeof e && e && e.__esModule) return e;
            var r = Object.create(null);
            if (n.r(r), Object.defineProperty(r, "default", {
                    enumerable: !0,
                    value: e
                }), 2 & t && "string" != typeof e)
                for (var o in e) n.d(r, o, function(t) {
                    return e[t]
                }.bind(null, o));
            return r
        }, n.n = function(e) {
            var t = e && e.__esModule ? function() {
                return e.default
            } : function() {
                return e
            };
            return n.d(t, "a", t), t
        }, n.o = function(e, t) {
            return Object.prototype.hasOwnProperty.call(e, t)
        }, n.p = "", n(n.s = 0)
    }([function(e, t, n) {
        const r = n(1);
        console.log("我是一个小测试!", r)
    }, function(e, t) {
        t.default = 123
    }]);
    
    

    前面那一大坨我们先不管

    简化就是(function(module){})([index.js, util.js])

    看看结构发现是不是就是一个立即执行函数!所以说,高大上的webpack也只不过是通过前面提到的立即执行函数来实现的。那前面那一大堆不是人写的代码是什么呢?为啥会变成这个鸟样?我们接下来先让代码变得可读一些。

    webpack --help可以看到--mode这样一个参数,developmentproduction两个值。默认是production,我们在运行npx webpack的时候也可以看到这样的输出:

    前端打包路(1) - 基本原理&发展历史&webpack初介绍

    接下来我们执行一把看看吧~npx webpack --mode=development

    (function(modules) { // webpackBootstrap
      // 定义一个缓存
      var installedModules = {};
      // 可以称之为webpack运行在浏览器上的require方法,参数就是立即执行函数的参数中的key值
      function __webpack_require__(moduleId) {
        // 有缓存就返回缓存中的数据
        if(installedModules[moduleId]) {
          return installedModules[moduleId].exports;
        }
        // 开开心心放入缓存,注意这里定义了exports对象
        var module = installedModules[moduleId] = {
          i: moduleId,
          l: false,
          exports: {}
        };
        // 执行函数
        // 思考一下,我们在IDE里面疯狂无脑写着import/require/exports/export这些模块
        // 跑到浏览器上运行的时候,浏览器哪儿知道这些玩意儿是干蛋的,但是浏览器知道啥?
        // 知道对象、知道函数,所以我们把模块的导出存在了module.exports里面,module.exports在前面刚被初始化干干净净的被call
        // 再遇到require不怕了,其实就是__webpack_require__这个方法嘛
        // 所以require()的参数是模块的路径也就是立即执行函数参数中的key
        // 而exports的就是个对象
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        // 不重要
        module.l = true;
        // require(一个文件路径), 这个文件exports的那些玩意儿
        return module.exports;
      }
      // 为内置的require对象添加依赖模块
      __webpack_require__.m = modules;
      // 为内置的require对象添加缓存
      __webpack_require__.c = installedModules;
    
      // exports对象添加一个getter方法
      __webpack_require__.d = function(exports, name, getter) {
        if(!__webpack_require__.o(exports, name)) {
          Object.defineProperty(exports, name, { enumerable: true, get: getter });
        }
      };
      // 下面的例子中讲
      __webpack_require__.r = function(exports) {
        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
          // Object.prototype.toString.call(exports)的时候返回的是Module
          // 感觉就是看上去好看
          Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
        }
        Object.defineProperty(exports, '__esModule', { value: true });
      };
      // 说实话,我也不太清楚这个方法是干啥的
      // 但是我翻了下github上有人提问,解释是:“ESM CJS interop. import("commonjs") will use it.”
      // 地址粘贴在下方: https://github.com/webpack/webpack/issues/11024
      __webpack_require__.t = function(value, mode) {
        if(mode & 1) value = __webpack_require__(value);
        if(mode & 8) return value;
        if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
        var ns = Object.create(null);
        __webpack_require__.r(ns);
        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
        if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
        return ns;
      };
      // 下面的例子中讲
      __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;
      };
      // 包含属性否
      __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
      // 不重要
      __webpack_require__.p = "";
      // require入口文件吧
      return __webpack_require__(__webpack_require__.s = "./src/index.js");
    })({
      // 入口方法
      "./src/index.js": (function(module, exports, __webpack_require__) {
        eval("const num = __webpack_require__(/*! ./util */ \"./src/util.js\");\n\nfunction test() {\n  console.log('我是一个小测试!', num);\n}\n\ntest();\n\n\n//# sourceURL=webpack:///./src/index.js?");
      }),
      // 被引入的方法
      "./src/util.js": (function(module, exports) {
        eval("exports.default = 123;\n\n//# sourceURL=webpack:///./src/util.js?");
      })
    });
    

    这次变得好阅读一些了。我把函数的作用写在注释上。

    那我们再尝试一个ES6 MODULE和CJS混用的
    我们修改一下index.js

    import { num } from './util';
    
    function test() {
      console.log('我是一个小测试!', num);
    }
    
    test();
    

    还有util.js

    exports.num = 123;
    

    让我们再次打包看下结果,重复的部分我们就不说了,主要集中在不同上 __webpack_require__.n__webpack_require__.r还有eval里面

    (function (modules) { // webpackBootstrap
      var installedModules = {};
      function __webpack_require__(moduleId) {
        if (installedModules[moduleId]) {
          return installedModules[moduleId].exports;
        }
        var module = installedModules[moduleId] = {
          i: moduleId,
          l: false,
          exports: {}
        };
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        module.l = true;
        return module.exports;
      }
      __webpack_require__.m = modules;
      __webpack_require__.c = installedModules;
      // 说白了就是以后在调用exports.a的时候用我们传进来的getter,这个例子里面也就是getDefault
      // 如果你在index.js里面这么用的 import X from './util';
      // console.log(X)的话,其实就是.a的getter。打完收工。
      __webpack_require__.d = function (exports, name, getter) {
        if (!__webpack_require__.o(exports, name)) {
          Object.defineProperty(exports, name, {
            enumerable: true,
            get: getter
          });
        }
      };
      // 这个Symbol.toStringTag理解为当我们Object.prototype.toString.call(exports)时,返回[object Module]类型
      __webpack_require__.r = function (exports) {
        if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
          Object.defineProperty(exports, Symbol.toStringTag, {
            value: 'Module'
          });
        }
        // 为exports对象加了一个属性,标明这个文件是ES6MODULE的哟~
        // 回到下面eval
        Object.defineProperty(exports, '__esModule', {
          value: true
        });
      };
      // 传入的exports对象是不是ES6Module啊?
      // 如果是,我们是不是会export default X一个默认值,然后import X就完了
      // 在es6 module中我们export default X其实是这样一个操作 default = X;
      // 没错就是个赋值,所以我们这样是会报错的  export default const X;
      // 转化成default = const X绝逼有问题对吧
      // 所以这里我们就用getDefault方法默认帮你返回exports.default值了
      // .d方法其实就是重写getter方法。
      // 我们看下三个参数getter现在就是 getDefault()这个方法了,'a'是我们命名的一个参数名
      // 我们去.d方法里面瞅一眼
      __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;
      };
      __webpack_require__.o = function (object, property) {
        return Object.prototype.hasOwnProperty.call(object, property);
      };
      __webpack_require__.p = "";
      return __webpack_require__(__webpack_require__.s = "./src/index.js");
    })
    ({
      "./src/index.js":
        (function (module, __webpack_exports__, __webpack_require__) {
          "use strict";
          // 由于我们在index.js中使用了import所以,webpack贴心的在eval前面插入了一行代码调用了r方法
          // 我们去看看r干了啥
          // 回来了继续看下
          // 调用了__webpack_require__.n方法,传递的是import进来的文件中的exports对象
          // 所以我们要去瞅一眼import的文件是谁,没错是下面的./src/util.js
          // 这个文件没有用es6 module所以并没有像这个方法一样插入__webpack_require__.r方法
          // 所以他的exports对象上并没有一个叫__esModule的属性,我们继续
          // 去看下.n方法
          eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ \"./src/util.js\");\n/* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_util__WEBPACK_IMPORTED_MODULE_0__);\n\n\nfunction test() {\n  console.log('我是一个小测试!', _util__WEBPACK_IMPORTED_MODULE_0__[\"num\"]);\n}\n\ntest();\n\n\n//# sourceURL=webpack:///./src/index.js?");
        }),
      "./src/util.js": (function (module, exports) {
        eval("exports.num = 123;\n\n//# sourceURL=webpack:///./src/util.js?");
      })
    });
    
    

    完球了。。。这下清楚了吧~

    聪明的你肯定知道webpack的按需加载,如果这时候让你去实现你会怎么做呢?有几种天然的分隔文件方式呢?多入口?import()?import(/* prefetch 还有 preload*/)了解下?文件分开了,根据分开的文件创建html<link as="script" src="...." rel="prefetch">是不是就可以呢?

    原理

    叽叽喳喳半天,通过上面的例子,其实我们已经知道了webpack的产物,我们也分析了打包后的产物,那么从原始文件到构建后的文件,这其中又经历了什么呢?


    未完待续


    起源地下载网 » 前端打包路(1) - 基本原理&发展历史&webpack初介绍

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元