browserify运行原理分析
目前对于前端工程师而言,如果只针对浏览器编写代码,那么很简单,只需要在页面的script脚本中引入所用js就可以了。
但是某些情况下,我们可能需要在服务端也跑一套类似的逻辑代码,考虑如下这些情景(以node作为后端为例):
1.spa的应用,需要同时支持服务端直出页面以及客户端pjax拉取数据渲染,客户端和服务器公用一套渲染模板并执行大部分类似的逻辑。
2.一个通过websocket对战的游戏,客户端和服务端可能需要进行类似的逻辑计算,两套代码分别用于对用户客户端的展示以及服务端实际数值的计算。
这些情况下,很可能希望我们客户端代码的逻辑能够同时无缝运行在服务端。
解决方法1:UMD
一种解决方法是使用UMD的方式,前端使用requirejs,同时兼容nodejs的情况,例如:
(function (window, factory) {
if (typeod exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});
解决方案2:使用browerify,使代码能同时运行于服务端和浏览器端。
什么是browserify?
Browserify 可以让你使用类似于 node 的 require() 的方式来组织浏览器端的 Javascript 代码,通过预编译让前端 Javascript 可以直接使用 Node NPM 安装的一些库。
例如我们可以这样写js,同时运行在服务端和浏览器中:
mo2.js:
exports.write2 = function(){
//write2
}
mo.js:
var t = require("./mo2.js");
exports.write = function(){
t.write2();
}
test.js:
var mo = require("./mo.js");
mo.write();
代码可以完全已node的形式编写。
原理分析:
总体过程其实可以分为以下几个步骤:
阶段1:预编译阶段
1.从入口模块开始,分析代码中require函数的调用
2.生成AST
3.根据AST找到每个模块require的模块名
4.得到每个模块的依赖关系,生成一个依赖字典
5.包装每个模块(传入依赖字典以及自己实现的export和require函数),生成用于执行的js
阶段2:执行阶段
从入口模块开始执行,递归执行所require的模块,得到依赖对象。
具体步骤分析:
1.从入口模块开始,分析代码中require函数的调用
由于浏览器端并没有原生的require函数,所以所有require函数都是需要我们自己实现的。因此第一步我们需要知道一个模块的代码中,哪些地方用了require函数,依赖了什么模块。
browerify实现的原理是为代码文件生成AST,然后根据AST找到require函数依赖的模块。
2.生成AST
文件代码:
var t = require("b");
t.write();
生成的js描述的AST为:
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "t"
},
"init": {
"type": "CallExpression",
"callee": {
"type": "Identifier",
"name": "require"
},
"arguments": [
{
"type": "Literal",
"value": "b",
"raw": "\"b\""
}
]
}
}
],
"kind": "var"
},
{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "Identifier",
"name": "t"
},
"property": {
"type": "Identifier",
"name": "write"
}
},
"arguments": []
}
}
]
}
可以看到我们代码中调用的require函数,对应AST中的对象为上面红字部分。
3.根据AST找到每个模块require的模块名
生成了AST之后,我们下一部就需要根据AST找到require依赖的模块名了。再次看看上面生成的AST对象,要找到require的模块名,实质上就是要:
找到type为callExpression,callee的name为require所对应的第一个argument的value。
关于生成js描述的AST以及解析AST对象,可以参考:
https://github.com/ariya/esprima 代码生成AST
https://github.com/substack/node-detective 从AST中提取reqiure
https://github.com/Constellation/escodegen AST生成代码
4.得到每个模块的依赖关系,生成一个依赖字典
从上面的步骤,我们已经可以获取到每个模块的依赖关系,因此可以生成一个以id为键的模块依赖字典,browerify生成的字典示例如下(根据之前的范例代码生成):
{
1:[
function(require,module,exports){
var t = require("./mo2.js");
exports.write = function(){
document.write("test1");
t.write2();
}
},
{"./mo2.js":2}
],
2:[
function(require,module,exports){
exports.write2 = function(){
document.write("=2=");
}
},
{}
],
3:[
function(require,module,exports){
var mo = require("./mo.js");
mo.write();
},
{"./mo.js":1}
]}
字典记录了拥有那些模块,以及模块各自依赖的模块。
5.包装每个模块(传入依赖字典以及自己实现的export和require函数),生成用于执行的js
拥有了上面的依赖字典之后,我们相当于知道了代码中的依赖关系。为了让代码能执行,最后一步就是实现浏览器中并不支持的export和require。因此我们需要对原有的模块代码进行包装,就像上面的代码那样,外层会传入自己实现的export和require函数。
然而,应该怎样实现export和require呢?
export很简单,我们只要创建一个对象作为该模块的export就可以。
对于require,其实我们已经拥有了依赖字典,所以要做的也很简单了,只需要根据传入的模块名,根据依赖字典找到所依赖的模块函数,然后执行,一直重复下去(递归执行这个过程)。
在browerify生成的js中,会添加以下require的实现代码,并传递给每个模块函数:
(function e(t,n,r){
function s(o,u){
if(!n[o]){
if(!t[o]){
var a=typeof require=="function"&&require;
if(!u&&a)
return a(o,!0);
if(i)
return i(o,!0);
var f=new Error("Cannot find module '"+o+"'");
throw f.code="MODULE_NOT_FOUND",f
}
var l=n[o]={exports:{}};
t[o][0].call(l.exports,function(e){
var n=t[o][1][e];
return s(n?n:e)
},l,l.exports,e,t,n,r)
}
return n[o].exports
}
var i=typeof require=="function"&&require;
for(var o=0;o<r.length;o++)
s(r[o]);
return s
})
我们主要关注的红字部分,其中t是传入的依赖字典(之前提到的那块代码),n是一个空对象,用于保存所有新创建的模块(export对象),对比之前的依赖字典来看就比较清晰了:
首先我们创建module对象(包含一个空对象export),并分别把module和export传入模块函数作为浏览器自己实现的module和export,然后,我们自己实现一个require函数,该函数获取模块名,并递归寻找依赖的模块执行,最后获取到所有被依赖到的模块对象,这个也是browerify生成的js在运行中的整个执行过程。
感谢围观,转载请标明出处:http://www.cnblogs.com/Cson/p/4039144.html
browserify运行原理分析的更多相关文章
- Camel运行原理分析
Camel运行原理分析 以一个简单的例子说明一下camel的运行原理,例子本身很简单,目的就是将一个目录下的文件搬运到另一个文件夹,处理器只是将文件(限于文本文件)的内容打印到控制台,首先代码如下: ...
- tensorflow运行原理分析(源码)
tensorflow运行原理分析(源码) https://pan.baidu.com/s/1GJzQg0QgS93rfsqtIMURSA
- 【Java编程实战】Metasploit_Java后门运行原理分析以及实现源码级免杀与JRE精简化
QQ:3496925334 文章作者:MG1937 CNBLOG博客ID:ALDYS4 未经许可,禁止转载 某日午睡,迷迷糊糊梦到Metasploit里有个Java平台的远控载荷,梦醒后,打开虚拟机, ...
- Java程序运行原理分析
class文件内容 class文件包含Java程序执行的字节码 数据严格按照格式紧凑排列在class文件的二进制流,中间无分割符 文件开头有一个0xcafebabe(16进制)特殊的标志 JVM运行时 ...
- springboot创建,自动装配原理分析,run方法启动
使用IDEA快速创建一个springboot项目 创建Spring Initializr,然后一直下一步下一步直至完成 选择web,表示创建web项目 运行原理分析 我们先来看看pom.xml文件 核 ...
- 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles
老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles poptest是国内唯一一家培养测试开 ...
- 老李推荐:第5章6节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 初始化事件源
老李推荐:第5章6节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 初始化事件源 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试 ...
- 老李推荐:第5章3节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动脚本
老李推荐:第5章3节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动脚本 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...
- 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用
老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用 上一节我们描述了monkey的命令处理入口函数run是如何调用optionP ...
随机推荐
- aws s3文件上传设置accesskey、secretkey、sessiontoken
背景: 最近跟进的项目会封装aws S3资源管理细节,对外提供获取文件上传凭证的API,业务方使用获取到的凭证信息直接请求aws进行文件上传.因此,测试过程需要验证S3文件上传的有效性.aws官网有提 ...
- charles系列破解激活办法(最高charles4.2都可以激活)
// Charles Proxy License // 适用于Charles任意版本的注册码,谁还会想要使用破解版呢. // Charles 4.2目前是最新版,可用. Registered Na ...
- input子系统事件处理层(evdev)的环形缓冲区【转】
在事件处理层(evdev.c)中结构体evdev_client定义了一个环形缓冲区(circular buffer),其原理是用数组的方式实现了一个先进先出的循环队列(circular queue), ...
- CentOS7下用firewall-cmd控制端口与端口转发
1.firewalld 守护进程 2.控制端口/服务 3.伪装IP 4.端口转发 实现目标:服务器A和服务器B都是内网互通的,但是只有服务器A是有外网然后现在做端口转发实现服务器B能使用服务器A的外网 ...
- CSRF 漏洞原理详解及防御方法
跨站请求伪造:攻击者可以劫持其他用户进行的一些请求,利用用户身份进行恶意操作. 例如:请求http://x.com/del.php?id=1 是一个删除ID为1的账号,但是只有管理员才可以操作,如果攻 ...
- Pandas 库中excel的读写方法介绍
首选:https://blog.csdn.net/u014597198/article/details/83104653 其次:https://blog.csdn.net/qq_34377830/ar ...
- 静态库lib调试
1.首先生成lib文件的解决方案编译通过. 2.将最新的lib和头文件在需要调用的exe中,替换掉. 3.复制需要调试的cpp文件到exe解决方案下,并添加现有项. 4.运行无错误后,添加断点即可调试 ...
- Linux 下安装Node.js
安装 node.js 安装包 http://nodejs.org 通过 rz 上传到 CentOS 进行解压 tar -xvf node-v8.0.0-linux-x64.tar.xz 进入到 bin ...
- (转)Python网络爬虫实战:世纪佳缘爬取近6万条数据
又是一年双十一了,不知道从什么时候开始,双十一从“光棍节”变成了“双十一购物狂欢节”,最后一个属于单身狗的节日也成功被攻陷,成为了情侣们送礼物秀恩爱的节日. 翻着安静到死寂的聊天列表,我忽然惊醒,不行 ...
- Git提交新项目
Github或者码云上新建项目 $ git init $ git add * $ git remote add origin https://gitee.com/demo/demo.git $ git ...