资讯专栏INFORMATION COLUMN

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

GHOST_349178 / 1127人阅读

摘要:一原理与环境不同,浏览器中不支持的主要原因是缺少了以下几个环境变量换句话说,打包器的原理就是模拟这四个变量的行为。

本文首发于知乎专栏:
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("
")

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("
")

        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("
")

        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 和多层级文件

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79155.html

相关文章

  • ES6 系列之模块加载方案

    摘要:感谢感谢和在推动模块化发展方面做出的贡献。与引用阮一峰老师的标准参考教程规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。规定了新的模块加载方案。与引用阮一峰老师的入门它们有两个重大差异。 前言 本篇我们重点介绍以下四种模块加载规范: AMD CMD CommonJS ES6 模块 最后再延伸讲下 Babel 的编译和 webpack 的打包原理。 require....

    pinecone 评论0 收藏0
  • 预告:JavaScript模块全览

    摘要:之前写的文章急速全栈教程得到了不错的阅读量,霸屏掘金头条天,点赞过千,阅读近万,甚至还有人在评论区打广告,可见也是一个小小的生态了。今天看到的霸屏的,也是讲全栈的,见参考文章接下来要写的是模块。全局命名污染和命名冲突依赖管理。 之前写的文章急速Js全栈教程得到了不错的阅读量,霸屏掘金头条3天,点赞过千,阅读近万,甚至还有人在评论区打广告,可见也是一个小小的生态了;)。看来和JS全栈有关...

    focusj 评论0 收藏0
  • 2019-我的前端面试题

    摘要:先说下我面试情况,我一共面试了家公司。篇在我面试的众多公司里,只有同城的面问到相关问题,其他公司压根没问。我自己回答的是自己开发组件面临的问题。完全不用担心对方到时候打电话核对的问题。 2019的5月9号,离发工资还有1天的时候,我的领导亲切把我叫到办公室跟我说:阿郭,我们公司要倒闭了,钱是没有的啦,为了不耽误你,你赶紧出去找工作吧。听到这话,我虎躯一震,这已经是第2个月没工资了。 公...

    iKcamp 评论0 收藏0
  • 我他喵的到底要怎样才能在生产环境中用上 ES6 模块化?

    摘要:因此,你还是需要各种各样杂七杂八的工具来转换你的代码噢,我可去你妈的吧,这些东西都是干嘛的我就是想用个模块化,我到底该用啥子本文正旨在列出几种可用的在生产环境中放心使用模块化的方法,希望能帮到诸位后来者这方面的中文资源实在是忒少了。 原文发表在我的博客上。最近捣鼓了一下 ES6 的模块化,分享一些经验 :) Python3 已经发布了九年了,Python 社区却还在用 Python 2...

    KaltZK 评论0 收藏0
  • Javascript 打包工具

    摘要:所以,打包工具就出现了,它可以帮助做这些繁琐的工作。打包工具介绍仅介绍款主流的打包工具,,,,以发布时间为顺序。它定位是模块打包器,而属于构建工具。而且在其他的打包工具在处理非网页文件比如等基本还是需要借助它来实现。 本文当时写在本地,发现换电脑很不是方便,在这里记录下。 前端的打包工具 打包工具可以更好的管理html,css,javascript,使用可以锦上添花,不使用也没关系...

    Sleepy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<