最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 自己实现一个小型的打包工具

    正文概述 掘金(DennisWu)   2021-04-06   337

    自己实现一个小型的打包工具

    一、用打包模板实现最简单的模块的打包

    先试想一下,对于下面这样一个遵循CommonJS规范的模块:

    • tiny-bundler\index.js
    console.log('hello, bundler');
    module.exports = 'hello, world';
    

    将它打包后,需要变成什么样子呢?

    曾在前文 几种常用的模块化规范总结 中提到过,Node.js对于CommonJS模块的处理做法是在外面套一层壳,把原生JS中没有的require、module、exports等方法注进去。这里,我们也可以学习这种做法。此外,为了获得一个模块的作用域,我们把它封在(function(){})()中。

    1. (function() {
    2.   var moduleList = [
    3.     function (require, module, exports) {
    4.       console.log('hello, bundler');
    5.       module.exports = 'hello, world';
    6.     }
    7.   ]
    8. 
    9.   var module = {
    10.     exports: {}
    11.   };
    12. 
    13.   moduleList[0](null, module, null);
    14. })();
    

    通过观察,我们发现,模块的内容只体现在第4-5行,于是,我们想到可以把其余的部分抽成模板。于是,我们建立文件:

    • tiny-bundler\src\index.bundle.boilerplate
    (function() {
      var moduleList = [
        function (require, module, exports) {
          /* template */
        }
      ]
    
      var module = {
        exports: {}
      };
    
      moduleList[0](null, module, null);
    })();
    
    • tiny-bundler\src\bundle.js
    const fs = require('fs');
    const path = require('path');
    
    const boilerplate = fs.readFileSync(path.resolve(__dirname, 'index.bundle.boilerplate'), 'utf-8');
    const target = fs.readFileSync(path.resolve(__dirname, '../index.js'), 'utf-8');
    const content = boilerplate.replace('/* template */', target);
    
    fs.writeFileSync(path.resolve(__dirname, '../dist/index.bundle.js'), content, 'utf-8');
    

    我们在tiny-bundler目录下执行node src/bundle.js,就可以得到打包后的结果:

    • tiny-bundler\dist\index.bundle.js
      var moduleList = [
        function (require, module, exports) {
          console.log('hello, world');
          module.exports = 'hello, world';
        }
      ]
    
      var module = {
        exports: {}
      };
    
      moduleList[0](null, module, null);
    })();
    

    二、自己实现require,完成带有require的模块的打包

    下面,我们新建一个文件:

    • tiny-bundler\moduleA.js
    module.exports = new Date().getTime();
    

    并将tiny-bundler\index.js的内容修改为:

    const moduleA = require('./moduleA');
    console.log(moduleA);
    

    这个时候,打包应该怎么实现呢?我们仍然可以采用倒推的方法,先自己来手写一下index.bundle.js:

    1. (function() {
    2.   var moduleList = [
    3.     // index.js
    4.     function (require, module, exports) {
    5.       const moduleA = require('./moduleA');
    6.       console.log(moduleA);
    7.     },
    8.     // moduleA.js
    9.     function (require, module, exports) {
    10.       module.exports = new Date().getTime();
    11.     }
    12.   ]
    13. 
    14.   var moduleDepIdList = [
    15.     {
    16.       // 表示moduleList中的第0个模块中,如果遇到了require('./moduleA'),就需要去moduleList[1]中找这个模块
    17.       './moduleA': 1,
    18.     },
    19.     {
    20.     }
    21.   ]
    22. 
    23.   // 自己实现一个require
    24.   function require(id, parentId) {
    25.     var currentModuleId = parentId !== undefined ? moduleDepIdList[parentId][id] : id;
    26.     var module = { exports: {} };
    27.     var moduleFunc = moduleList[currentModuleId];
    28.     moduleFunc((id) => require(id, currentModuleId), module, module.exports);
    29.     return module.exports;
    30.   }
    31. 
    32.   require(0);
    33. })();
    

    经过观察,我们发现上面代码中3-11、15-20行是面对不同模块的打包时要变化的部分,其余部分是不变的。因此,我们可以把tiny-bundler\src\index.bundle.boilerplate改成:

    (function() {
      var moduleList = [
        /* template-module-list */
      ]
    
      var moduleDepIdList = [
        /* template-module-dep-id-list */
      ]
    
      function require(id, parentId) {
        var currentModuleId = parentId !== undefined ? moduleDepIdList[parentId][id] : id;
        var module = { exports: {} };
        var moduleFunc = moduleList[currentModuleId];
        moduleFunc((id) => require(id, currentModuleId), module, module.exports);
        return module.exports;
      }
    
      require(0);
    })();
    

    三、实现require.ensure,完成chunk异步加载的打包

    还是采用倒推的方式,我们先手写出来一个实现的效果:

    • tiny-bundler\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>Document</title>
    </head>
    <body>
      <script src="./index.bundle.js"></script>
    </body>
    </html>
    
    • tiny-bundler\index.bundle.js
    (function() {
      var moduleList = [
        function (require, module, exports) {
          require
            .ensure('1')
            .then(res => {
              console.log(res);
            })
        }
      ]
    
      var moduleDepIdList = [];
    
      var cache = {};
    
      function require(id, parentId) {
        var currentModuleId = parentId !== undefined ? moduleDepIdList[parentId][id] : id;
        var module = { exports: {} };
        var moduleFunc = moduleList[currentModuleId];
        moduleFunc((id) => require(id, currentModuleId), module, module.exports);
        return module.exports;
      }
    
      window.__JSONP = function (chunkId, moduleFunc) {
        var resolve = cache[chunkId][0];
        var module = {
          exports: {}
        };
        // 执行这个chunk的代码
        moduleFunc(require, module, module.exports);
        // 然后resolve掉执行模块代码后得到的module.exports
        resolve(module.exports);
      }
    
      require.ensure = function (chunkId, parentId) {
        var currentModuleId = parentId !== undefined ? moduleDepIdList[parentId][chunkId] : chunkId;
    
        // 如果cache[currentModuleId] === undefined,表明是首次加载
        if (cache[currentModuleId] === undefined) {
          // 通过JSONP异步加载这个JS
          var $script = document.createElement('script');
          $script.src = '/chunk_' + chunkId + '.js';
          document.body.appendChild($script);
    
          // 把几个状态都挂到全局的cache里面
          var promise = new Promise(function (resolve) {
            cache[currentModuleId] = [resolve];
            cache[currentModuleId].status = true; // 这个状态为true则表示'/chunk_' + chunkId + '.js'这个JS还在加载过程中
          });
          cache[currentModuleId].push(promise);
    
          return promise;
        } else if (cache[currentModuleId].status) {
          // 正在加载中
          return cache[currentModuleId][1];
        }
    
        return cache[currentModuleId];
      }
    
      moduleList[0](require, null, null);
    })();
    
    • tiny-bundler\chunk_1.js
    window.__JSONP('1', function(require, module, exports) {
      module.exports = 'hello, world';
    });
    

    在tiny-bundler目录下执行anywhere -p 80启动静态服务器,访问 http://localhost 在Chrome Devtools中可看到如下结果:

    自己实现一个小型的打包工具

    自己实现一个小型的打包工具

    自己实现一个小型的打包工具

    具体的实现上,可以如前面一样,抽取出来模板,进行replace替换。

    顺便提一下热更新。因为moduleList中存储着所有编译后的模块的代码,所以,当我们对某个模块进行了更新的时候,websocket server端通过fsevents.watch会监听到,这个时候,就会触发重新打包,打包后就通过websocket通知客户端,客户端获得通知后只需要将moduleList中对应模块的代码进行替换,这个时候就能在内存中直接将moduleList中对应模块的内容改掉,就能实现热更新的效果。


    起源地下载网 » 自己实现一个小型的打包工具

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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