当前位置: 首页 > news >正文

手写一个CommonJS打包工具(一)

本文首发于知乎专栏:
http://zhuanlan.zhihu.com/starkwang


CommonJS 是一个流行的前端模块化规范,也是目前 NodeJS 以及其模块托管仓库 npm 使用的规范,但目前暂无浏览器支持 CommonJS 。要想让浏览器用上这些模块,必须转换格式。

这个系列的文章,我们会一步步完成一个基于 CommonJS 的打包工具,类似于一个简单版的 Browserify 或者 Webpack 。


一、原理

与 NodeJS 环境不同,浏览器中不支持 CommonJS 的主要原因是缺少了以下几个环境变量:

  • module

  • exports

  • require

  • global

换句话说,打包器的原理就是模拟这四个变量的行为。

比如我们有一个index.js文件,依赖了module1module2两个模块,并且module1依赖module2

//index.js
var module1 = require("./module1");
var module2 = require("./module2");

module1.foo();
module2.foo();

function hello(){
    console.log("Hello!");
}

module.exports = hello;
//module1.js
var module2 = require(module2);
console.log("initialize module1");

console.log("this is module2.foo() in module1:");
module2.foo();
console.log("\n")

module.exports = {
    foo: function(){
        console.log("module1 foo !!!");
    }
};
//module2.js
console.log("initialize module2");
module.exports = {
    foo: function(){
        console.log("module2 foo !!!");
    }
};

把它放入一个匿名函数内,通过这个匿名函数注入 requiremodulesexportglobal变量(我们暂时不实现global)

function(module, exports, require, global){
    var module1 = require("./module1");
    var module2 = require("./module2");

    module1.foo();
    module2.foo();

    function hello(){
        console.log("Hello!");
    }
    
    module.exports = hello;
}

现在我们用一个 modules 对象来存入这些匿名函数:

//modules
{
    "entry": function(module, exports, require, global){
        //index.js
        var module1 = require("./module1");
        var module2 = require("./module2");
        module1.foo();
        module2.foo();
        function hello(){
            console.log("Hello!");
        }
        module.exports = hello;
    },
    "./module1": function(module, exports, require, global){
        var module2 = require("./module2");
        console.log("initialize module1");

        console.log("this is module2.foo() in module1:");
        module2.foo();
        console.log("\n")

        module.exports = {
            foo: function(){
                console.log("module1 foo !!!");
            }
        };
    },
    "./module2": function(module, exports, require, global){
        console.log("initialize module2");
        module.exports = {
            foo: function(){
                console.log("module2 foo !!!");
            }
        };
    }
}

下面我们实现一个简单的 require 函数:

//这个对象用于储存已导入的模块
var installedModules = {};

function require(moduleName) {
    //如果模块已经导入,那么直接返回它的exports
    if(installedModules[moduleName]){
        return installedModules[moduleName].exports;
    }
    //模块初始化
    var module = installedModules[moduleName] = {
        exports: {},
        name: moduleName,
        loaded: false
    };
    //执行模块内部的代码,这里的 modules 变量即为我们在上面写好的 modules 对象
    modules[moduleName].call(module.exports, module, module.exports,require);
    //模块导入完成
    module.loaded = true;
    //将模块的exports返回
    return module.exports;
}

最后只要把我们上面写好的 modules 对象以立即执行函数的形式传入这个 require 函数就可以了,以下是完整的代码:

(function(modules){
    //这个对象用于储存已导入的模块
    var installedModules = {};
    function require(moduleName) {
        //如果模块已经导入,那么直接返回它的exports
        if(installedModules[moduleName]){
            return installedModules[moduleName].exports;
        }
        //模块初始化
        var module = installedModules[moduleName] = {
            exports: {},
            name: moduleName,
            loaded: false
        };
        //执行模块内部的代码,这里的 modules 变量即为我们在上面写好的 modules 对象
        modules[moduleName].call(module.exports, module,         module.exports,require);
        //模块导入完成
        module.loaded = true;
        //将模块的exports返回
        return module.exports;
    }
    //入口函数
    return require("entry");
})({
    "entry": function(module, exports, require, global){
        //index.js
        var module1 = require("./module1");
        var module2 = require("./module2");
        module1.foo();
        module2.foo();
        function hello(){
            console.log("Hello!");
        }
        module.exports = hello;
    },
    "./module1": function(module, exports, require, global){
        var module2 = require("./module2");
        console.log("initialize module1");

        console.log("this is module2.foo() in module1:");
        module2.foo();
        console.log("\n")

        module.exports = {
            foo: function(){
                console.log("module1 foo !!!");
            }
        };
    },
    "./module2": function(module, exports, require, global){
        console.log("initialize module2");
        module.exports = {
            foo: function(){
                console.log("module2 foo !!!");
            }
        };
    }
});

事实上,我们短短的这几十行代码模仿了 Webpack 的部分实现。但我们依然在使用诸如 "./module1" 这样的字符串作为模块的唯一识别码,这是一个明显的缺陷,存在多层级文件时,这个名称很容易冲突。

在 Browserify 或 Webpack 这样的生产级工具里,一般使用数字作为函数的唯一识别码,例如它可能会把(以 Webpack 为例):

var module1 = require("./module1");

编译成:

var module1 = __webpack_require__(1);

二、小结

我们在这里实现了一个最简单的 CommonJS 标准的执行器,接下来的文章中我们会做以下事情:

1、实现 global 变量

2、用 moduleID 替代 moduleName

3、写一个命令行小工具

4、支持 node_modules 和多层级文件

相关文章:

  • shell脚本使用echo输出带颜色
  • 调音台使用
  • H3C Static
  • postgreSQl导数据
  • 前端工程化-构建
  • 深入理解移动开发的模板复用机制
  • 9、二叉树存储结构结点定义:三叉链表
  • 内部排序算法:冒泡排序
  • 详解MathType中如何更改公式颜色
  • Ubuntu 12.10 安装JDK7
  • 软件对存储性能的影响​
  • 剖析curator的分布式互斥锁原理
  • LVM Linear vs Striped Logical Volumes
  • Centos Linux kernel内核升级
  • ZenHub Epics创造了GitHub中敏捷Epics
  • 自己简单写的 事件订阅机制
  • 【跃迁之路】【641天】程序员高效学习方法论探索系列(实验阶段398-2018.11.14)...
  • angular2 简述
  • Druid 在有赞的实践
  • Spring核心 Bean的高级装配
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 简单数学运算程序(不定期更新)
  • 前端面试之闭包
  • 日剧·日综资源集合(建议收藏)
  • 详解NodeJs流之一
  • 因为阿里,他们成了“杭漂”
  • 原生js练习题---第五课
  • 智能合约开发环境搭建及Hello World合约
  • 浅谈sql中的in与not in,exists与not exists的区别
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • ​油烟净化器电源安全,保障健康餐饮生活
  • # Panda3d 碰撞检测系统介绍
  • #include到底该写在哪
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (ibm)Java 语言的 XPath API
  • (南京观海微电子)——COF介绍
  • (四)Controller接口控制器详解(三)
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • (转载)CentOS查看系统信息|CentOS查看命令
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .net core webapi 大文件上传到wwwroot文件夹
  • .net 流——流的类型体系简单介绍
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .Net语言中的StringBuilder:入门到精通
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • /deep/和 >>>以及 ::v-deep 三者的区别
  • /usr/lib/mysql/plugin权限_给数据库增加密码策略遇到的权限问题
  • [ solr入门 ] - 利用solrJ进行检索
  • []sim300 GPRS数据收发程序
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [AIGC] MySQL存储引擎详解
  • [Android]How to use FFmpeg to decode Android f...