Script#

Script# brings the C# developer experience (programming and tooling) to Javascript/Ajax world. This post shares a project for enabling script authoring via C#...

Script3#将C#开发者的经验(编程和工具)带到Javascrip/AJAX世界。本文分享一个能用C#编写脚本的项目。

Script# brings the C# developer experience (programming and tooling) to Javascript/Ajax world. Yep, I am finally publicly sharing a spare time project I've been working on the side in an off and on manner for some time now.

 

A Better Scripting Environment

一个更好的脚本编写环境

The fundamental goal was to improve the state of the art in scripting. This goes way beyond the usual things that come to mind at first thought: compile-time checking, and intellisense or statement completion. I do think these are super-useful, and would be valuable in and of themselves. As we worked on Atlas we were also thinking of a great tools experience, and we explored different approaches and technologies for achieving this. Script# is a prototype of one such exploration. It attempts to address some programming environment requirements:

一个基本的目标是改进脚本编写艺术的状态。首先浮现在脑海中的是:编译期检查、智能或自动补全。我认为这些都是超有用的,本身也是非常有意义的。随着我们使用Atlas开发时,我们有大量的工具经验,我们也探索了不同的方式和技术来取得这个。Script#是一个这样的探索原型。它试图确定一些环境要求:
  • A clean language with the natural constructs. Today with script, you can go a long ways in simulating OOP (classes, interfaces, inheritance etc.), but the simulations are thrown at the face of the developer. Various C# constructs such as properties, events, and syntax for defining inheritance go a long way in making the code readable. Similarly modifier keywords like public vs. protected vs. internal, sealed vs. virtual etc. help define a better object model. All of this can be brought into the scripting world.
  • 一个带有自然构造的干净语言。当今,你编写脚本可以使用许多模拟OOP(类、接口、继承等),但这些模拟被扔到了开发者面前。各种C#结构,如属性、事件和定义继承的语法,使得代码更具可读性。相似的,像public, private, internal, sealed, virtual等修饰符可以帮助定义更好的对象模型。所有这些也可以带进脚本世界。
  • Easier refactoring and exploration. Script development could benefit immensely from the refactoring, and class browsing support already present in the IDE and in tools such as .NET Reflector. Both lend themselves to having a more manageable code base over time.
  • 更容易的重构和浏览。脚本开发能大大得益于重构、已呈现在IDE中的类浏览支持、诸如.NET重构器工具。这些都能使得代码更易于管理。
  • Ability to generate documentation. Again doc-comments from C# and the existing infrastructure could be leveraged here.
  • 能够生成文档。来自于C#的文档注释和存在的基础结构也能保留。
  • Ability to customize the script code easily. For example, I'll show debug vs. release and minimization below. The same idea could apply to building a version of the script that had more error checking or logging built in when you do need to run diagnostics. The approach could also be used to include instrumentation for the purposes of profiling, measuring code coverage, etc. Another interesting aspect of this project is that it will be able to generate script catering to multiple script profiles such as the current Javascript language, as well as Javascript 2 when it appears.
  • 能够更容易的自定义脚本代码。例如,我下面将显示debug/release和最小化。相同的思想能应用到创建一个有更多错误检查脚本的版本。这种方法也能用来包含工具用以分析、测量代码覆盖等。此项目另一个有趣的地方是它将能生成脚本以迎合多种脚本版本,例如当前的Javascript语言和将出现的Javascript2

 

Demonstration

范例


I'll use the Ajax version of the timeless Hello World scenario to illustrate the model. Imagine a simple page with textbox, button and label:

我将使用AJAX版本永远的Hello World例子来证明此模型。想象一个带有textbox, button和label的简单页面:
<input type="text" id="nameTextBox" />
<input type="button" id="okButton" value="OK" />
<span id="helloLabel"></span>

Now I want to write the script for this page to add some UI logic. I'll create HelloWorld.cs with the following code to handle the button click, extract the name entered into the textbox, make a request using XMLHttp and finally display the result in a label.

现在我想要写脚本来为此页加一些UI逻辑。我将创建HelloWorld.cs,用下面代码来handle按钮点击,提取textbox中输入的名字,使用XMLHttp发送一个请求和最终显示在label中。
using System;
using ScriptFX;
using ScriptFX.UI;
 
namespace HelloWorld {
 
    public class HelloWorldScriptlet : IScriptlet {
 
        private Button _okButton;
        private TextBox _nameTextBox;
        private Label _helloLabel;
 
        private XMLHttpRequest _request;
 
        public void Start() {
            _okButton = new Button(Document.GetElementById("okButton"));
            _nameTextBox = new TextBox(Document.GetElementById("nameTextBox"));
            _helloLabel = new Label(Document.GetElementById("helloLabel"));
 
            _okButton.Click += new EventHandler(OnOKButtonClick);
        }
 
        private void OnOKButtonClick(object sender, EventArgs e) {
            Callback completedCallback = new Callback(this.OnRequestComplete);
 
            _request = new XMLHttpRequest();
            _request.Onreadystatechange = Delegate.Unwrap(completedCallback);
            _request.Open("GET", "Hello.axd?name=" + _nameTextBox.Text, /* async */ true);
            _request.Send(null);
        }
 
        private void OnRequestComplete() {
            if (_request.ReadyState == 4) {
                _request.Onreadystatechange = null;
 
                string greeting = _request.ResponseText;
                _helloLabel.Text = greeting;
            }
        }
    }
}

Basically this shows a couple of super-simple class HelloWorldScriptlet class. It's a pleasure to write it in C#. Now its time for some Script# magic.

基本上这显示一对超简单类HelloWorldScriplet。用C#写真是愉悦。现在该是时候接触一些Script魔力了。
ssc /ref:sscorlib.dll /ref:Script.ScriptFX.Core.dll /debug /out:HelloWorld.js HelloWorld.cs

Running the Script# compiler (ssc.exe) generates HelloWorld.js which looks like this:

运行Script#编译器(ssc.exe)生成HelloWorld.js,它看上去是这样的:

Type.registerNamespace('HelloWorld');
 
////////////////////////////////////////////////////////////////////////////////
// HelloWorld.HelloWorldScriptlet
 
HelloWorld.HelloWorldScriptlet = function Scenarios_HelloWorldScriptlet() {
}
HelloWorld.HelloWorldScriptlet.prototype = {
    _okButton: null,
    _nameTextBox: null,
    _helloLabel: null,
    _request: null,
    
    start: function Scenarios_HelloWorldScriptlet$start() {
        this._okButton = new ScriptFX.UI.Button(document.getElementById('okButton'));
        this._nameTextBox = new ScriptFX.UI.TextBox(document.getElementById('nameTextBox'));
        this._helloLabel = new ScriptFX.UI.Label(document.getElementById('helloLabel'));
        this._okButton.add_click(new Delegate(this, this._onOKButtonClick));
    },
    
    _onOKButtonClick: function Scenarios_HelloWorldScriptlet$_onOKButtonClick(sender, e) {
        var completedCallback = new Delegate(this, this._onRequestComplete);
        this._request = new XMLHttpRequest();
        this._request.onreadystatechange = Delegate.unwrap(completedCallback);
        this._request.open('GET', 'Hello.axd?name=' + this._nameTextBox.get_text(), true);
        this._request.send(null);
    },
    
    _onRequestComplete: function Scenarios_HelloWorldScriptlet$_onRequestComplete() {
        if (this._request.readyState == 4) {
            this._request.onreadystatechange = null;
            var greeting = this._request.responseText;
            this._helloLabel.set_text(greeting);
        }
    }
}
 
HelloWorld.HelloWorldScriptlet.registerClass('HelloWorld.HelloWorldScriptlet', null, ScriptFX.IScriptlet);

Notice how the class is converted into its Javascript equivalent: a function, and methods on its prototype. Further, notice how this automatically generates calls to register the function as a class. The compiler provides a nice natural model for declaring the inheritance hierarchy. Other things to call out are conversion of of C#-based event and property accesses with Javascript simulations. Essentially, let the compiler do the hard work, so you don't have to mentally work against an OOP simulation. Finally, since the compiler was passed the /debug flag, it provides a name to each method (anonymous methods aren't friendly in the debugger)... again this happens automagically. There is a lot more to show that a simple sample doesn't get across in terms of the capabilities of the conversion.

注意类是如何转化成它对等的Javascript: 一个函数和附着在prototype上的方法。更进一步,注意怎样自动生成调用来注册那个函数成为一个类。编译器提供一个友好的自然模型来声明继承层次。另外的事情是通过Javascript模拟来访问基于C#的事件和属性。基本上,让编译器做困难的工作,因此你不必依赖手工模拟OOP。最后,因为编译器被传入了/debug标示,它提供给每个方法(匿名方法是对编译器不友好的)一个名字...这也是自动发生的。


The generated code can now be used in the page via a regular <script> tag.

<script type="text/javascript" src="sscorlib.js"></script>
<script type="text/javascript" src="ssfxcore.js"></script>
<script type="text/javascript" src="HelloWorld.js"></script>
<script type="text/javascript">
   ScriptFX.Application.Current.run(new HelloWorld.HelloWorldScriptlet());
</script>
The Script# compiler can optionally generate an assembly containing the .js file as a resource. An associated server control can then use the ASP.NET WebResources feature to include the scriptlet into the page in a simpler manner.
ssc /ref:sscorlib.dll /ref:Script.ScriptFX.Core.dll /debug /assembly:HelloWorld.dll /out:HelloWorld.js HelloWorld.cs
 
<nStuff:Scriptlet runat="server"
    ScriptAssembly="HelloWorld" ScriptletType="HelloWorld.HelloWorldScriptlet" />
 
The compiler has the notion of debug and release builds.
ssc /ref:sscorlib.dll /ref:Script.ScriptFX.Core.dll /minimize /out:HelloWorld.js HelloWorld.cs

Running the compiler without /debug and with /minimize produces a release build as shown below (I've intentionally added some line breaks for display purposes):

运行编译器不带/debug和/minimize产生一个release生成,如下所示:
Type.registerNamespace('HelloWorld');
HelloWorld.HelloWorldScriptlet=function(){}
HelloWorld.HelloWorldScriptlet.prototype={$0:null,$1:null,$2:null,$3:null,
start:function(){
this.$0=new ScriptFX.UI.Button(document.getElementById('okButton'));
this.$1=new ScriptFX.UI.TextBox(document.getElementById('nameTextBox'));
this.$2=new ScriptFX.UI.Label(document.getElementById('helloLabel'));
this.$0.add_click(new Delegate(this,this.$4));},
$4:function(sender,e){
var $2=new Delegate(this,this.$5);
this.$3=new XMLHttpRequest();
this.$3.onreadystatechange=Delegate.unwrap($2);
this.$3.open('GET','Hello.axd?name='+this.$1.get_text(),true);
this.$3.send(null);},
$5:function(){
if(this.$3.readyState==4){
this.$3.onreadystatechange=null;
var $0=this.$3.responseText;
this.$2.set_text($0);}}}
HelloWorld.HelloWorldScriptlet.registerClass('HelloWorld.HelloWorldScriptlet',null,ScriptFX.IScriptlet);

As you'll notice all insignificant whitespace has been trimmed out. Furthermore, there is minimization of identifiers (which are amongst the biggest contributors to script size) with identifiers named as $0, $1 etc. What is super exciting is that the Script# compiler can use all the information present in C# source code to allow it to maximize the minimization. This includes information about which members are internal/private vs. which ones are public. For example, the _okButton member of the class is now $0 and the OnRequestComplete method is $5.

如你所注意到的,所有不重要的空白都被清理掉。而且,有一个使用最小化标示符,例如$0, $1等。超级令人兴奋的是Script#编译器能使用所有呈现在C#源代码中的信息来允许你来最大化此minization. 这包含信息关于哪个成员是internal/private,那些是public。例如,_okButton类成员现在是$0和OnRequestComplete方法是$5.


Beyond generation of script in a smarter way, Script# helps improve the script authoring model within the IDE. Despite programming the DOM for several months now, I still need a browser window constantly open to browse MSDN reference documentation on the DOM. With Script# here is what I get:

除了Javascript代码智能生成外,Script#帮助改进IDE中脚本编写模型。尽管编程DOM有几个月了,但是我仍然需要一个浏览窗口来浏览关于DOM的MSDN参考文档。在这里我用Script#提示:



Specific things to notice are the intellisense and statement completion popups and tooltips offered by the C# editor, as it reflects on the sscorlib assembly referenced by this project.
Essentially, I've introduced the notion of a Script# class library project, and associated msbuild targets file and task for integrating the experience into Visual Studio 2005. Building builds both a .dll assembly, and an associated .js file, that you can now deploy with your web project.
I have put together a 10 min video of this scenario. This is an experiment in itself with me putting out a video to get some concepts out (so feedback appreciated on this as well).

How does it work?
Essentially the Script# compiler is a C# compiler that generates Javascript instead of IL. A key driving goal of the design is to produce readable Javascript that you may have authored yourself, and would be ok deploying into real apps. Hence the translation works at the C# level, as opposed to converting from IL to script, and the converter doesn't add any levels of abstraction itself.
There are a set of C# things that don't make sense (eg. lock, unsafe etc.) and set of things I don't support yet (like generics). The converter does however provide support for key C# patterns such as foreach, delegates, and things like Debug conditionals. The associated class library (sscorlib, equivalent of mscorlib) is also tuned to Javascript. It doesn't contain the same stuff as mscorlib does. The approach I have taken is not to create a converter that converts an arbitrary existing application written in C#, and convert it to Javascript with something like a winforms abstraction layer. Instead, like Atlas, the idea behind Script# is oriented at providing an engineering approach and superior environment to developing applications using HTML/CSS and Javascript in a more productive, scalable and maintainable manner.
There is a small set of Javascript specific things (specifically closures and more functional style programming) that aren't supported in the conversion. I think there are equivalent alternatives, but I'd be interested in feedback nonetheless. The implementation does allow you to hand-code some Javascript and expose it to the C# code in the form of assembly metadata. This provides a nice out for incorporating something that exists already, or something that needs more hand tuning. I'll go into this some in a later post.

Some historical context and downloadable bits...
A number of people have wondered if our OOP-based type system in Atlas was a precursor to enabling C#-based development in the browser, and I'll answer to that directly now. The idea of using C# to author script emerged at almost the same time we started actively writing script, and I missed everything I took for granted from C#. In December, while on vacation, I decided to finally get down to writing a compiler that could enable this.
Since then, the work has proceeded to a point where it can be used to generate useful script. I was planning on posting an announcement just last week. It's very coincidental that the folks at Google have been thinking along similar lines. I guess I should have posted this stuff a few days back :-) Regardless, I have an initial release ready... You can download the Script# compiler along with the sample. Keep in mind it is raw in quite the literal sense (and if you do try it, make sure your code compiles as valid C# first). I'll need some more time to get it to a crisper state, providing better integration into Web projects, and simplifying some key C# scenarios. This will happen over time as this is a spare time prototype. In the meantime, the download will give you a sense of the experience. I'm definitely open to feedback, and look forward to comments.

Scripting Challenges and Future Vision
Recently, I posted my wish list for the browser and scripting world, and I wanted to speak to things relating and coming together from an overall vision perspective. At the end of the day, the end result of translation is still script. There are still some unsolved challenges in terms of a limited runtime environment in the browser, cross-browser differences etc. Furthermore translation-based models add a wrinkle to the debugging experience. Script# provides a tool to incrementally improve script authoring in the short term. We want to provide great tools and frameworks that target native scripting as it exists today with things like Atlas. Longer term, the real solution for rich Web applications is a combination of WPF/E (as shown at MIX06), and a cross-platform CLR that can be hosted in the browser allowing direct authoring and execution of managed code and fully benefiting from the model.

Posted on 2008-03-05 00:49  Chio  阅读(381)  评论(0)    收藏  举报
©2008 Suprasoft Component Intelligence Inc., All right reserved.