使用 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。
你所需要做的就是通过一个特定的预先配置好的WebAPI路由/控制器来引用这个JS编译器脚本(Typescript.js),然后让WebAPI管道通过
MediaTypeFormatter来完成这项重任务。
为了实现这个目的,新建一个典型的MVC4,WebAPI项目。
需要一个定制的路由和一个简单的控制器:
这个路由可以让我们传递一个name参数和一个扩展(用于匹配所需的filename.js),然后控制器简单的将文件对应的请求重定向到formatter中。
插件
为了让上述思路可行,我们需要命令行的Typescript编译器,可从
官方网站上获取,选择中间那个(Plugins),推荐VisualStudio2012,但2010也没关系,我们只关心命令行工具而已。
一旦安装成功,你会找到一个名为TCS.exe的可执行文件,默认位于C:\ProgramFiles(x86)\MicrosoftSDKs\TypeScript\0.8.0.0\.将所有编译器文件拷贝到解决方案目录下的TS文件夹。
在我们开始写数据到流前,所有代码的执行都是很耗资源的,注意我们设置了一些默认值,我们添加了UriPathExtensionMapping这样格式化器就可以处理所有.js请求。所有的输出的内容content-type将是
application/javascript,告诉浏览器这些都是js文件。也支持请求text/html,某些浏览器可能会这样。
我们只支持序列化(单向的格式化,非反序列化),而且只接受字符串。
文件名(Typescript文件)检查TS文件是否存在
检查缓存,如果相应的文件已存在则使用MD5checksum来确保文件没有改动
如果没改动则直接从缓存中返回内容
如果改动了,或者缓存文件不存在则使用tcs.exe编译并返回JS
将第5步生成的JS和MD5checksum内容保存到缓存中以便继续使用
这样做的好处是文件修改时我们只需要编译一次就可以重复使用,这里选用的是直接在内存中缓存,你也可以使用其他方式,例如返回磁盘中的js文件之类的。原则上,我们是通过MD5checksum来确定文件是否修改。
注意这里使用了TCS进程的StandardError属性来判断编译器运行成功运行,如果编译过程中发生任何错误,该属性将会包含详细的错误信息,否则就是空的。
我在HTML页面中引用如下,使用相同的名称,只是将ts扩展名改为js,这样该请求就会触发控制器调用编译方法:
现在WebAPI将即时编译demo.ts并生成所需的js输出到浏览器,而我们并没有手工去编译它:
而且JS内容是缓存的,以后再次刷新页面无需重新编译ts文件。
我的调用结果:
如果修改了Typescript代码:IfIchangetheTypescriptcodetosomethingelse–i.e.let’smodifythe
singmethod:
我不需要重新编译,只需要刷新页面,因为MD5checksum不匹配,因此TS会自动重新编译并生成新的JS:
当然,如果你使用的是其他的Web开发技术,也可以参考这个思路来实现。
英文原文,OSCHINA原创翻译
使用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 > |
需要一个定制的路由和一个简单的控制器:
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 | } |
插件
为了让上述思路可行,我们需要命令行的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 | } |
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
|
87 | { |
88 | sb.Append(bytes[i].ToString( "x2" )); |
89 | } |
90 | } |
91 |
92 | return sb.ToString(); |
93 | } |
注意这里使用了TCS进程的StandardError属性来判断编译器运行成功运行,如果编译过程中发生任何错误,该属性将会包含详细的错误信息,否则就是空的。
测试
假设有如下的TS文件,名为demo.ts:1 | class
|
2 | constructor( public name){} |
3 | sing(text){ |
4 | return this .name+ "sings" +text; |
5 | } |
6 | } |
1 | < script type = "text/javascript" src = "/dynamic/scripts/demo.js" ></ script > |
而且JS内容是缓存的,以后再次刷新页面无需重新编译ts文件。
我的调用结果:
如果修改了Typescript代码:IfIchangetheTypescriptcodetosomethingelse–i.e.let’smodifythe
singmethod:
1 | class
|
2 | constructor( public name){} |
3 | sing(text){ |
4 | return this .name+ "sings" +text+ "andit'sembarassing." ; |
5 | } |
6 | } |
总结
我们前面提到的,Typescript可使用纯JavaScript来编译生成JavaScript,但因为JS编译器本身有250Kb大小,因此采用了这种方法来避免编译器的加载变得让人无法接受。当然,如果你使用的是其他的Web开发技术,也可以参考这个思路来实现。
相关文章推荐
- jplayer中动态添加列表曲目(js提取request中的list数据作为js参数使用)
- 动态sql语句基本语法(字段名,表名,数据库名之类作为变量时,必须用动态SQL如ALTER TABLE中使用程序传递的参数)
- 使用Ceph集群作为Kubernetes的动态分配持久化存储
- 转:使用::std::vector作为管理动态数组的优先选择
- 使用::std::vector作为管理动态数组的优先选择(转载)
- 使用 OWIN 作为 ASP.NET Web API 的宿主
- openjweb快速开发平台中使用Groovy动态语言作为规则引擎解决方案
- 使用委托作为参数进行动态调用
- Druid作为“数据库连接池”的使用以及数据库的动态切换
- 使用::std::vector作为管理动态数组的优先选择
- openjweb快速开发平台中使用Groovy动态语言作为规则引擎解决方案
- Jasper+ireport动态报表学习(二)使用数据库作为数据源进行报表绘制
- 使用::std::vector作为管理动态数组的优先选择
- 使用::std::vector作为管理动态数组的优先选择
- oracle 不能是用变量来作为列名和表名 ,但使用动态sql可以;
- 动态sql语句基本语法(字段名,表名,数据库名之类作为变量时,必须用动态SQL如ALTER TABLE中使用程序传递的参数)
- 在 ASP.NET Web API 中,使用 命名空间(namespace) 来作为路由的参数
- Glide-使用动态图作为正在加载占位图
- 动态sql语句基本语法(字段名,表名,数据库名之类作为变量时,必须用动态SQL如ALTER TABLE中使用程序传递的参数)
- 动态sql语句基本语法(字段名,表名,数据库名之类作为变量时,必须用动态SQL如ALTER TABLE中使用程序传递的参数)