nodeasp

`NodeAsp`是一套Classic ASP框架,借鉴了NodeJS的模块化思想,让您可以使用全新的理念愉快地书写ASP程序。


License
MIT
Install
npm install nodeasp@0.0.3

Documentation

NodeAsp

NodeAsp是一套Classic ASP框架,借鉴了NodeJS的模块化思想,让您可以使用全新的理念愉快地书写ASP程序。

NodeAsp使用遵循CommonJS规范的require,完全兼容NodeJS模块加载方式,让您可以直接使用NodeJS 50%以上的模块。一切不关乎NodeJS运行环境和ES5-ES6特有对象的模块都能直接使用。如此庞大的模块资源库,这在以往任何ASP框架下都是没有的。

NodeAsp是ASP领域独树一帜的创新性框架,她的出现改变了传统的ASP编写模式,让您只需要会js就可以同时完成前后端开发,并免除了部署NodeJS服务器的繁琐过程。

NodeAsp作为ASP领域的终结者,来了。

主页

NodeAsp:http://nodeasp.com

模块下载:http://nap.webkits.cn

运行环境

这个框架仅限在WIN平台IIS上运行。通常普通的ASP虚拟主机即可运行本框架。

简单示例

default.asp

<!--#include file="NodeAsp.asp" -->

<%
    require('./index.js');
%>

index.js

var http = require('http');

http.createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
});

使用方法

第一步:下载NodeAsp。

第二步:引用NodeAsp。

<!--#include file="NodeAsp.min.asp" -->

第三步:使用NodeAsp。

<%
var version = process.version;
Response.Write(version);
%>

全局对象

这些对象在所有模块中都是可用的。有些对象实际上并非在全局作用域内,而仅仅是在模块作用域内——这种情况在以下文档中会特别指出。

global

在浏览器中,顶级作用域就是全局作用域。这就是说,在浏览器中,如果当前是在全局作用域内,var something将会声明一个全局变量。在NodeAsp中则不同。顶级作用域并非全局作用域,在NodeAsp模块里的var something只属于那个模块。

process

输出与运行环境有关的一些信息。

在NodeAsp中,process存在的主要目的是为了兼容某些NodeJS模块,通常不会使用得到。

console

用于打印标准输出和标准错误。

详见下面的控制台章节。

Buffer

我们引入了buffer模块来兼容NodeJS的Buffer,请注意必须在node_modules下包含buffer模块才能使用Buffer。

如果您忘记在node_modules下放入buffer模块,这并不会影响程序的正常运行,只有当您使用Buffer时,才会抛出错误。

require

引入模块。与NodeJS有点不一样的是,因为IIS只能直接执行ASP文件而非JS文件,所以require也可以用于在ASP代码中require一个模块。类似于在命令行中执行node test.js。

如果想知道调用require()方法加载模块时的真实文件路径,可以使用require.resolve()方法来得到。

详见下面的模块章节。

__filename

当前所执行代码文件的文件路径。这是该代码文件经过解析后的绝对路径。

例如:执行 C:\websites\nodeasp\index.asp

// module.js
Response.Write(__filename);
// C:\websites\nodeasp\module.js

// index.asp
require('./module');
Response.Write(__filename);
// C:\websites\nodeasp\index.asp

__dirname

当前执行脚本所在目录的目录名。

module

当前模块的引用。特别地,module.exports和exports指向同一个对象。module实际上并非全局的而是各个模块本地的。

详见下面的模块章节。

exports

module.exports对象的引用,该对象被当前模块的所有实例所共享,通过require()可访问该对象。 何时使用exports以及何时使用module.exports的详情可参见模块系统文档。 exports实际上并非全局的而是各个模块本地的。

详见下面的模块章节。

定时器

定时器函数一共包括以下四个。由于ASP是单线程的,所以以下函数实际上是不兼容的。

setTimeout(cb, ms)

clearTimeout(t)

setInterval(cb, ms)

clearInterval(t)

控制台

为了更好的调试ASP程序,我们实现了类似NodeJS/Chrome浏览器的命令行调试工具。下载地址:https://github.com/Spikef/NodeAsp-Console

NOTE: 需要IIS7.5和.NET4才能运行,其它环境未测试。

使用指南

第一步:注册COM组件

以管理员权限打开CMD,使用REGASM命令注册component\Terminal.dll,其中REGASM位于C:\Windows\Microsoft.NET\Framework\v4.0.30319(具体位置与版本号有关)。

C:\Windows\Microsoft.NET\Framework\v4.0.30319\REGASM D:\component\Terminal.dll /codebase

第二步:修改注册表,解决 ASP 0177 : 8000ffff 错误

对于32位系统,找到如下注册表位置:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_IGNORE_ZONES_INITIALIZATION_FAILURE_KB945701

对于64位系统,找到如下注册表位置:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_IGNORE_ZONES_INITIALIZATION_FAILURE_KB945701

选择或者新建项FEATURE_IGNORE_ZONES_INITIALIZATION_FAILURE_KB945701,然后新建DWORD值:

名称:w3wp.exe 值:1

第三步:启动Console

双击Console.exe,打开NodeAsp调试命令行。

第四步:调试

在default.asp中输入以下代码,然后通过浏览器访问default.asp。接下来,你就可以在Console.exe中看到结果了。

var a = {name: "nodeasp", value: true}
// 在console中将输出
//{
//    name: "nodeasp",
//    value: true
//}

模块方法

console.log()

向Console输出,使用默认颜色。

var a = {name: "NodeAsp", version: "0.0.1"};
console.log(a);

console.info()

向Console输出,使用绿色。

console.error()

向Console输出,使用红色。特别地,如果输出的是Error对象,将显示完整的错误信息。

console.warn()

向Console输出,使用黄色。

console.time(label)

使用label指定名称,开始一个计时器,用于计算操作所需要花费的时间。

console.timeEnd(label)

输出某项操作所需要消耗的时间。

Example:

    console.time('100-elements');
    for (var i = 0; i < 100; i++) {
      ;
    }
    console.timeEnd('100-elements');
    // prints 100-elements: 262ms

命令

在控制台中可以输入以下命令。

  • cls/clear:清空调试信息,不可恢复。

  • about:显示关于信息。

  • copylast:复制上一条输出信息。

  • copyall:复制所有输出信息。

模块

NodeAsp有一个跟NodeJS几乎一致的模块加载系统,这样可以保证NodeAsp可以直接使用大量NodeJS的模块。

NodeAsp的核心几乎不包含任何开发网站需要功能,所有功能都是通过模块来扩展的。可以通过NodeAsp的模块中心或者NPM来寻找您所需要的功能模块。

在NodeAsp中,文件和模块是一一对应的。下面是示例foo.js加载同一目录下的circle.js。

foo.js的内容

var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
+ circle.area(4));

circle.js的内容:

var PI = Math.PI;
exports.area = function (r) {
    return PI * r * r;
};
exports.circumference = function (r) {
    return 2 * PI * r;
};

circle.js模块输出了area()和circumference()两个函数。要输出某个对象,把它加到exports这个特殊对象下即可。

注意,exports是module.exports的一个引用,只是为了用起来方便。当你想输出的是例如构造函数这样的单个项目,那么需要使用module.exports。

// 正确输出构造函数
module.exports = MyConstructor;

模块内的本地变量是私有的。在这个例子中,PI这个变量就是circle.js私有的。

循环

考虑这样一种情形:

a.js

console.log('a starting');
exports.done = false;
var b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

b.js

console.log('b starting');
exports.done = false;
var a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

main.js

console.log('main starting');
var a = require('./a.js');
var b = require('./b.js');
console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

首先main.js加载a.js,接着a.js又去加载b.js。这时,b.js会尝试去加载a.js。为了防止无限的循环,a.js会返回一个unfinished copy给b.js。然后b.js就会停止加载,并将其exports对象返回给a.js模块。

这样main.js就把这两个模块都加载完成了。这段程序的输出如下:

main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true

跟NodeJS一样,通常循环依赖模块并不会导致死循环,但是如果此时直接在模块加载时执行其它模块的方法,会提示找不到对应的方法,所以应该避开这种情况。

// a.js
var b = require('./b.js');
    exports.add = function(m, n) {
    console.info(m + n);
};

// b.js
var a = require('./a');
var m = 101, n = 102;
exports.result = function() {
    a.add(m, n);        // 此处没有问题
};
a.add(m, n);            // 此处会报错,找不到a.add方法

文件模块

如果按文件名没有查找到,那么NodeAsp会添加.js.json后缀名,再尝试加载。

.js会被解析为Javascript纯文本文件,.json会被解析为JSON格式的纯文本文件。

模块以'/'为前缀,则表示绝对路径。例如,require('/home/marco/foo.js') ,加载的是/home/marco/foo.js这个文件。

模块以'./'为前缀,则路径是相对于调用require()的文件。 也就是说,circle.js必须和foo.js在同一目录下,require('./circle')才能找到。

当没有以'/'或者'./'来指向一个文件时,这个模块是从node_modules文件夹加载的。

如果指定的路径不存在,require()会抛出一个错误。

NOTE: 考虑到IIS主机上,.js文件可以直接通过浏览器访问,所以如果不希望泄露源码,您也可以使用任意文件后缀名。

从node_modules文件夹中加载

如果require()中的模块名不是一个本地模块,也没有以'/', '../', 或是 './'开头,那么node会从当前模块的父目录开始,尝试在它的/node_modules文件夹里加载相应模块。

如果没有找到,那么就再向上移动到父目录,直到到达顶层目录位置。

例如,如果位于'/home/ry/projects/foo.js'的文件调用了require('bar.js'),那么node查找的位置依次为:

  • /home/ry/projects/node_modules/bar.js

  • /home/ry/node_modules/bar.js

  • /home/node_modules/bar.js

  • /node_modules/bar.js

模块包

可以把程序和库放到一个单独的文件夹里,并提供单一入口来指向它。有三种方法,使一个文件夹可以作为require()的参数来加载。

首先是在文件夹的根目录创建一个叫做package.json的文件,它需要指定一个main模块。下面是一个package.json文件的示例。

{
    "name" : "some-library",
    "main" : "./lib/some-library.js"
}

示例中这个文件,如果是放在./some-library目录下面,那么require('./some-library')就将会去加载./some-library/lib/some-library.js。

如果目录里没有package.json这个文件,那么node就会尝试去加载这个路径下的index.js。

缓存

模块在第一次加载后会被缓存。这意味着(类似其他缓存)每次调用require('foo')的时候都会返回同一个对象,当然,必须是每次都解析到同一个文件。

多次调用 require(foo) 未必会导致模块中的代码执行多次. 这是一个重要的功能. 借助这个功能, 可以返回部分完成的对象; 这样, 传递依赖也能被加载, 即使它们可能导致循环依赖。

如果你希望一个模块多次执行,那么就输出一个函数,然后调用这个函数。

模块的缓存是依赖于解析后的文件名。由于随着调用的位置不同,可能解析到不同的文件(比如需从node_modules文件夹加载的情况),所以,如果解析到其他文件时,就不能保证require('foo')总是会返回确切的同一对象。

模块对象

在每一个模块中,变量 module 是一个代表当前模块的对象的引用。 特别地,module.exports 可以通过全局模块对象 exports 获取到。 module 不是事实上的全局对象,而更像是每个模块内部的。

module.exports

module.exports对象是通过模块系统产生的。因此,只需要将要导出的对象赋值给module.exports。例如,我们也可以使用下面的方法来写circle.js,这是完全等效的。

// circle.js
var PI = Math.PI;
var circle = {};
circle.area = function (r) {
    return PI * r * r;
};
circle.circumference = function (r) {
    return 2 * PI * r;
};
module.exports = circle;

module.id

用于区别模块的标识符。通常是完全解析后的文件名。

module.filename

模块完全解析后的文件名。

module.parent

引入这个模块的模块。

查找流程

当使用require()引用一个模块时,是按照如下流程根据表达式来查找目标模块的。

require(X) from module at path Y
1. If X begins with './' or '/' or '../'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
2. LOAD_NODE_MODULES(X, dirname(Y))
3. THROW "not found"

LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text.  STOP
2. If X.js is a file, load X.js as JavaScript text.  STOP
3. If X.json is a file, parse X.json to a JavaScript Object.  STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
a. Parse X/package.json, and look for "main" field.
b. let M = X + (json main field)
c. LOAD_AS_FILE(M)
2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP

LOAD_NODE_MODULES(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. LOAD_AS_FILE(DIR/X)
b. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
a. if PARTS[I] = "node_modules" CONTINUE
c. DIR = path join(PARTS[0 .. I] + "node_modules")
b. DIRS = DIRS + DIR
c. let I = I - 1
5. return DIRS

内置模块

内置模块与NodeJS一样,也必需先require才能使用。

assert

该模块用于编写程序的单元测试用例,通过require('assert')调用。直接移植自NodeJS。

events

事件处理模块,直接移植自NodeJS。

fs

文件操作模块,兼容绝大部分NodeJS文件操作模块的同步操作API方法。

http

HTTP请求与处理模块。移植并修改自NodeJS,绝大部分兼容。

path

本模块包含一套用于处理和转换文件路径的工具集。几乎所有的方法只做字符串变换, 不会调用文件系统检查路径是否有效。

移植并修改自NodeJS,几乎完全兼容。

punycode

编码和解码URL,完全兼容NodeJS。

auerystring

处理URL查询字符串,完全兼容NodeJS。

url

该模块包含用以 URL 解析的实用函数。 使用 require('url') 来调用该模块。

完全兼容NodeJS。

util

辅助方法模块,兼容4.0以下版本的NodeJS。

编译NodeAsp源码

编译NodeAsp源码需要安装node环境,同时需要全局安装uglifyJS。

在命令行中执行node build即可,编译好的文件位于bundle目录中。

C:\Disk\projects\NodeAsp>node build
-----------------------
# build nodeAsp success
+ build/NodeAsp.min.asp
@ 2016-03-01 13:46:04
-----------------------

License

MIT