您的位置:首页 > 其它

使用 Web API 作为动态 TypeScript 编译器运行环境

2012-10-19 16:12 411 查看
转自:http://www.oschina.net/question/12_72796?from=20121014

使用WebAPI作为动态TypeScript编译器运行环境,因为你不需要对Typescript进行预编译。

已经有很多社区的文章在介绍Typescript这个新的语言,这是理所当然的-因为它解决了很多JavaScript的问题,尽管处于起步阶段,却已经显示了巨大的潜力。

Typescript要求你必须预编译来生成JavaScript代码,现在也可以直接在浏览器上动态编译,但你必须引用Typescript.js这个JS编译器,这个编译器有250Kb大小,这可能是一颗很难咽下的药丸。

好在,通过ASP.NET的瑞士军刀——WebAPI,我们可以实现对Typescript动态编译为Javascript。

思路

为了不用再每次修改Typescript后都要手工编译,我们将透过WebAPI来为我们完成这项工作,使用的是定制的MediaTypeFormatter.

你所需要做的就是通过一个特定的预先配置好的WebAPI路由/控制器来引用这个JS编译器脚本(Typescript.js),然后让WebAPI管道通过
MediaTypeFormatter来完成这项重任务。

路由和控制器

我们在HTML中引用编译后的js文件如下:

1
<
script

type
=
"text/javascript"

src
=
"/dynamic/scripts/demo.js"
></
script
>
为了实现这个目的,新建一个典型的MVC4,WebAPI项目。

需要一个定制的路由和一个简单的控制器:

1
config.Routes.MapHttpRoute(

2
name:
"DynamicScripts"
,
3
routeTemplate:
"dynamic/{controller}/{name}.{ext}"
,
4
defaults:
new
{name=RouteParameter.Optional,ext=RouteParameter.Optional},
5
constraints:
new
{controller=
"Scripts"
}

6
);
1
public
class
ScriptsController:ApiController

2
{
3
public

string
Get(
string

name)
4
{

5
return

name;
6
}

7
}
这个路由可以让我们传递一个name参数和一个扩展(用于匹配所需的filename.js),然后控制器简单的将文件对应的请求重定向到formatter中。

插件

为了让上述思路可行,我们需要命令行的Typescript编译器,可从
官方网站上获取,选择中间那个(Plugins),推荐VisualStudio2012,但2010也没关系,我们只关心命令行工具而已。

一旦安装成功,你会找到一个名为TCS.exe的可执行文件,默认位于C:\ProgramFiles(x86)\MicrosoftSDKs\TypeScript\0.8.0.0\.将所有编译器文件拷贝到解决方案目录下的TS文件夹。

格式化器/编译器

注意下面的代码应根据你特定的需求进行调整(包括持久化机制、错误处理等等):

01
public
class
TypeScriptMediaTypeFormatter:MediaTypeFormatter
02
{
03
private

static
readonly

ObjectCacheCache=MemoryCache.Default;
04
05
public

TypeScriptMediaTypeFormatter()
06
{
07
this
.AddUriPathExtensionMapping(
"js"
,
"text/html"
);
08
}
09
10
public

override
void

SetDefaultContentHeaders(Typetype,System.Net.Http.Headers.HttpContentHeadersheaders,System.Net.Http.Headers.MediaTypeHeaderValuemediaType)
11
{
12
headers.ContentType=
new
MediaTypeHeaderValue(
"application/javascript"
);
13
}
14
15
public

override
bool

CanReadType(Typetype)
16
{
17
return

false
;
18
}
19
20
public

override
bool

CanWriteType(Typetype)
21
{
22
return

type==
typeof
(
string
);
23
}
24
25
public

override
TaskWriteToStreamAsync(Typetype,
object
value,StreamwriteStream,HttpContentcontent,TransportContexttransportContext){
26
//TODO
27
}

28
}
在我们开始写数据到流前,所有代码的执行都是很耗资源的,注意我们设置了一些默认值,我们添加了UriPathExtensionMapping这样格式化器就可以处理所有.js请求。所有的输出的内容content-type将是
application/javascript,告诉浏览器这些都是js文件。也支持请求text/html,某些浏览器可能会这样。

我们只支持序列化(单向的格式化,非反序列化),而且只接受字符串。

WriteToStreamAsync

MediaTypeFormatter方法根据如下流程将数据写到流中:

文件名(Typescript文件)检查TS文件是否存在
检查缓存,如果相应的文件已存在则使用MD5checksum来确保文件没有改动
如果没改动则直接从缓存中返回内容
如果改动了,或者缓存文件不存在则使用tcs.exe编译并返回JS
将第5步生成的JS和MD5checksum内容保存到缓存中以便继续使用

01
public
override
TaskWriteToStreamAsync(Typetype,

object
value,StreamwriteStream,HttpContentcontent,TransportContexttransportContext)
02
{
03
varserverPath=HttpContext.Current.Server.MapPath(
"~/tsc"
);
04
varfilepath=Path.Combine(serverPath,value.ToString()+
".ts"
);

05
varjsfilepath=Path.Combine(serverPath,value.ToString()+
".js"
);

06
07
vartcs=
new
TaskCompletionSource<
object
>();
08
09
if

(File.Exists(filepath))
10
{

11
string

cachedItem=CheckCache(filepath,value
as

string
);
12
13
if

(cachedItem!=
null
)
14
{
15
using

(varwriter=
new

StreamWriter(writeStream))
16
{
17
writer.Write(cachedItem);
18
}
19
}
20
else
21
{
22
vartypescriptCompiler=
new
ProcessStartInfo
23
{
24
UseShellExecute=
false
,

25
RedirectStandardError=
true
,

26
FileName=Path.Combine(serverPath,
"tsc.exe"
),

27
Arguments=
string
.Format(
"\"{0}\""
,filepath)
28
};
29
30
varprocess=Process.Start(typescriptCompiler);
31
varresult=process.StandardError.ReadToEnd();
32
process.WaitForExit();
33
34
if

(
string
.IsNullOrEmpty(result))
35
{
36
using

(varfilestream=
new

FileStream(jsfilepath,FileMode.Open,FileAccess.Read))
37
{
38
filestream.CopyTo(writeStream);
39
varfileinfo=
new
Dictionary<
string
,
string
>();

40
fileinfo.Add(
"md5"
,ComputeMD5(filepath));
41
42
using

(varreader=
new

StreamReader(filestream))
43
{
44
filestream.Position=0;
45
varfilecontent=reader.ReadToEnd();
46
fileinfo.Add(
"content"
,filecontent);
47
}
48
49
Cache.Set(value
as
string
,fileinfo,DateTime.Now.AddDays(30));
50
}
51
}
52
else
53
{
54
throw

new
InvalidOperationException(
"Compilererror:"

+result);
55
}
56
}
57
}

58
59
tcs.SetResult(
null
);
60
return

tcs.Task;
61
}
62
63
private
string
CheckCache(
string

filepath,
string

filename)
64
{
65
varmd5=ComputeMD5(filepath);
66
varitemFromCache=Cache.Get(filename)
as
Dictionary<
string
,
string
>;

67
68
if

(itemFromCache!=
null
)
69
{

70
if

(itemFromCache[
"md5"
]==md5)
71
{
72
return

itemFromCache[
"content"
];
73
}
74
}

75
return

null
;
76
}
77
78
private
string
ComputeMD5(
string

filename)
79
{
80
varsb=
new
StringBuilder();
81
using

(varfile=
new

FileStream(filename,FileMode.Open,FileAccess.Read))

82
{

83
varmd5=
new
MD5CryptoServiceProvider();
84
varbytes=md5.ComputeHash(file);
85
86
for

(
int

i=0;i<bytes.Length;i++)
87
{
88
sb.Append(bytes[i].ToString(
"x2"
));
89
}
90
}

91
92
return

sb.ToString();
93
}
这样做的好处是文件修改时我们只需要编译一次就可以重复使用,这里选用的是直接在内存中缓存,你也可以使用其他方式,例如返回磁盘中的js文件之类的。原则上,我们是通过MD5checksum来确定文件是否修改。

注意这里使用了TCS进程的StandardError属性来判断编译器运行成功运行,如果编译过程中发生任何错误,该属性将会包含详细的错误信息,否则就是空的。

测试

假设有如下的TS文件,名为demo.ts:

1
class

Person{
2
constructor(
public

name){}
3
sing(text){
4
return

this
.name+

"sings"
+text;

5
}

6
}
我在HTML页面中引用如下,使用相同的名称,只是将ts扩展名改为js,这样该请求就会触发控制器调用编译方法:

1
<
script

type
=
"text/javascript"

src
=
"/dynamic/scripts/demo.js"
></
script
>
现在WebAPI将即时编译demo.ts并生成所需的js输出到浏览器,而我们并没有手工去编译它:





而且JS内容是缓存的,以后再次刷新页面无需重新编译ts文件。

我的调用结果:





如果修改了Typescript代码:IfIchangetheTypescriptcodetosomethingelse–i.e.let’smodifythe
singmethod:

1
class

Person{
2
constructor(
public

name){}
3
sing(text){
4
return
this
.name+
"sings"

+text+
"andit'sembarassing."

;
5
}
6
}
我不需要重新编译,只需要刷新页面,因为MD5checksum不匹配,因此TS会自动重新编译并生成新的JS:





总结

我们前面提到的,Typescript可使用纯JavaScript来编译生成JavaScript,但因为JS编译器本身有250Kb大小,因此采用了这种方法来避免编译器的加载变得让人无法接受。

当然,如果你使用的是其他的Web开发技术,也可以参考这个思路来实现。

英文原文,OSCHINA原创翻译
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐