4 марта 2015 в 07:25

DuoCode: транслируем C# в JavaScript

Есть такой язык программирования, который называется C#. И есть очень много разработчиков, которым он очень нравится. А ещё есть такой язык программирования, который называется JavaScript. Как-то так сложилось, что он нравится далеко не всем C#-разработчикам. А теперь представьте ситуацию: есть заядлый C#-разработчик. Он очень любит C#, все-все проекты на нём пишет. Но судьба распорядилась так, что ему понадобилось написать клиентское веб-приложение. Знаете, такое, чтобы пользователю не нужно было себе ничего скачивать и устанавливать, чтобы он мог просто открыть любой браузер в любой операционной системе на любом устройстве — а приложение уже там. И вот тут у нашего лирического героя возникла проблема: вроде бы JavaScript идеально подходит для этой задачи, но вот писать на нём отчего-то не очень хочется. К счастью, в современном мире существует много языков, которые транслируются в JavaScript (всякие TypeScript, CoffeScript и тысячи других). Но наш разработчик оказался очень упрямым: он упорно не хочет изменять своему любимому C# с «вражескими» технологиями.

К счастью для него, счастливое будущее уже практически наступило. Есть такой проект, который называется DuoCode. Он умеет транслировать C#-код в JavaScript. Пока он в состоянии beta, но у него уже весьма неплохо получается: поддерживаются нововведения C# 6.0, Generic-типы, Reflection, структуры и LINQ, а отлаживать итоговый JavaScript можно на исходном C#. Давайте посмотрим внимательнее, что же представляет из себя продукт.



Hello DuoCode

Понять происходящее проще всего на примерах. Начнём с классического *Hello world*. Итак, имеем замечательный C#-код:

// Original C# code
using System;
using DuoCode.Dom;
using static DuoCode.Dom.Global; // C# 6.0 'using static' syntax

namespace HelloDuoCode
{
  static class Program
  {
    public class Greeter
    {
      private readonly HTMLElement element;
      private readonly HTMLElement span;
      private int timerToken;

      public Greeter(HTMLElement el)
      {
        element = el;
        span = document.createElement("span");
        element.appendChild(span);
        Tick();
      }

      public void Start()
      {
        timerToken = window.setInterval((Action)Tick, 500);
      }

      public void Stop()
      {
        window.clearTimeout(timerToken);
      }

      private void Tick()
      {
        span.innerHTML = string.Format("The time is: {0}", DateTime.Now);
      }
    }

    static void Run()
    {
      System.Console.WriteLine("Hello DuoCode");

      var el = document.getElementById("content");
      var greeter = new Greeter(el);
      greeter.Start();
    }
  }
}

Лёгким движением руки он превращается в JavaScript:

// JavaScript code generated by DuoCode
var HelloDuoCode = this.HelloDuoCode || {};
var $d = DuoCode.Runtime;
HelloDuoCode.Program = $d.declare("HelloDuoCode.Program", System.Object, 0, $asm, function($t, $p) {
    $t.Run = function Program_Run() {
        System.Console.WriteLine$10("Hello DuoCode");

        var el = document.getElementById("content");
        var greeter = new HelloDuoCode.Program.Greeter.ctor(el);
        greeter.Start();
    };
});
HelloDuoCode.Program.Greeter = $d.declare("Greeter", System.Object, 0, HelloDuoCode.Program, function($t, $p) {
    $t.$ator = function() {
        this.element = null;
        this.span = null;
        this.timerToken = 0;
    };
    $t.ctor = function Greeter(el) {
        $t.$baseType.ctor.call(this);
        this.element = el;
        this.span = document.createElement("span");
        this.element.appendChild(this.span);
        this.Tick();
    };
    $t.ctor.prototype = $p;
    $p.Start = function Greeter_Start() {
        this.timerToken = window.setInterval($d.delegate(this.Tick, this), 500);
    };
    $p.Stop = function Greeter_Stop() {
        window.clearTimeout(this.timerToken);
    };
    $p.Tick = function Greeter_Tick() {
        this.span.innerHTML = String.Format("The time is: {0}", $d.array(System.Object, [System.DateTime().get_Now()])); // try to put a breakpoint here
    };
});

Выглядит это примерно так:



Подскажу, на что стоит обратить внимание:
  • Поддерживается синтаксис using static из C# 6.0.
  • Можно легко работать с консолью, которая отображается внизу вашего приложения.
  • Можно работать с DOM-элементами
  • Работает таймер
Даже этот простой пример уже радует. Но подобное приложение и на самом JavaScript не так сложно написать. Давайте посмотрим примеры поинтереснее.

Крестики-нолики

В дистрибутив входит пример написания замечательной HTML-игры, написанной на чистом C#:



Код игры включает enum-ы и индексаторы:

public enum Player
{
    None = 0,
    X = 1,
    O = -1
}

public sealed class Board
{
    public static Player Other(Player player)
    {
        return (Player)(-(int)player);
    }

    private readonly Player[] Squares;

    public readonly int Count;

    public Player this[int position]
    {
        get
        {
            return Squares[position];
        }
    }

    public Board() // empty board
    {
        //Squares = new Player[9];
        Squares = new Player[] { Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None };
    }

    private Board(Board board, Player player, int position) :
      this()
    {
        Array.Copy(board.Squares, Squares, 9);
        Squares[position] = player;

        Count = board.Count + 1;
    }

    public bool Full { get { return Count == 9; } }

    public Board Move(Player player, int position)
    {
        if (position < 0 ||
            position >= 9 ||
            Squares[position] != Player.None)
        {
            throw new Exception("Illegal move");
        }

        return new Board(this, player, position);
    }

    public Player GetWinner()
    {
        if (Count < 5)
            return Player.None;

        Player result;
        bool winning =
          IsWinning(0, 1, 2, out result) ||
          IsWinning(3, 4, 5, out result) ||
          IsWinning(6, 7, 8, out result) ||
          IsWinning(0, 3, 6, out result) ||
          IsWinning(1, 4, 7, out result) ||
          IsWinning(2, 5, 8, out result) ||
          IsWinning(0, 4, 8, out result) ||
          IsWinning(2, 4, 6, out result);

        return result;
    }

    private bool IsWinning(int p0, int p1, int p2, out Player player)
    {
        int count = (int)Squares[p0] + (int)Squares[p1] + (int)Squares[p2];
        player = count == 3 ? Player.X : count == -3 ? Player.O : Player.None;
        return player != Player.None;
    }
}

Обратите внимание, как ловно удаётся управляться с DOM-элементами:

public static void Main(string[] args)
{
  for (var i = 0; i < 9; i++)
  {
    Dom.HTMLInputElement checkbox = GetCheckbox(i);
    checkbox.checked_ = false;
    checkbox.indeterminate = true;
    checkbox.disabled = false;
    checkbox.onclick = OnClick;
  }

  if (new Random().Next(2) == 0)
    ComputerPlay();

  UpdateStatus();
}

private static dynamic OnClick(Dom.MouseEvent e)
{
  int position = int.Parse(((Dom.HTMLInputElement)e.target).id[1].ToString());

  try
  {
    board = board.Move(Player.X, position);
  }
  catch
  {
    Dom.Global.window.alert("Illegal move");
    return null;
  }

  Dom.HTMLInputElement checkbox = GetCheckbox(position);
  checkbox.disabled = true;
  checkbox.checked_ = true;

  if (!board.Full)
    ComputerPlay();

  UpdateStatus();

  return null;
}

private static Dom.HTMLInputElement GetCheckbox(int index)
{
  string name = "a" + index.ToString();
  Dom.HTMLInputElement checkbox = Dom.Global.document.getElementById(name).As<Dom.HTMLInputElement>();
  return checkbox;
}

WebGL

Хотите работать с WebGL? Нет проблем! Берём C#-код:

using DuoCode.Dom;
using System;

namespace WebGL
{
  using GL = WebGLRenderingContext;

  internal static class Utils
  {
    public static WebGLRenderingContext CreateWebGL(HTMLCanvasElement canvas)
    {
      WebGLRenderingContext result = null;
      string[] names = { "webgl", "experimental-webgl", "webkit-3d", "moz-webgl" };
      foreach (string name in names)
      {
        try
        {
          result = canvas.getContext(name);
        }
        catch { }
        if (result != null)
          break;
      }
      return result;
    }

    public static WebGLShader CreateShaderFromScriptElement(WebGLRenderingContext gl, string scriptId)
    {
      var shaderScript = (HTMLScriptElement)Global.document.getElementById(scriptId);

      if (shaderScript == null)
        throw new Exception("unknown script element " + scriptId);

      string shaderSource = shaderScript.text;

      // Now figure out what type of shader script we have, based on its MIME type
      int shaderType = (shaderScript.type == "x-shader/x-fragment") ? GL.FRAGMENT_SHADER :
                       (shaderScript.type == "x-shader/x-vertex")   ? GL.VERTEX_SHADER   : 0;
      if (shaderType == 0)
        throw new Exception("unknown shader type");

      WebGLShader shader = gl.createShader(shaderType);
      gl.shaderSource(shader, shaderSource);

      // Compile the shader program
      gl.compileShader(shader);

      // See if it compiled successfully
      if (!gl.getShaderParameter(shader, GL.COMPILE_STATUS))
      {
        // Something went wrong during compilation; get the error
        var errorInfo = gl.getShaderInfoLog(shader);
        gl.deleteShader(shader);
        throw new Exception("error compiling shader '" + shader + "': " + errorInfo);
      }
      return shader;
    }

    public static WebGLProgram CreateShaderProgram(WebGLRenderingContext gl, WebGLShader fragmentShader, WebGLShader vertexShader)
    {
      var shaderProgram = gl.createProgram();
      gl.attachShader(shaderProgram, vertexShader);
      gl.attachShader(shaderProgram, fragmentShader);
      gl.linkProgram(shaderProgram);

      bool linkStatus = gl.getProgramParameter(shaderProgram, GL.LINK_STATUS);
      if (!linkStatus)
        throw new Exception("failed to link shader");
      return shaderProgram;
    }

    public static WebGLTexture LoadTexture(WebGLRenderingContext gl, string resourceName)
    {
      var result = gl.createTexture();
      var imageElement = Properties.Resources.duocode.Image;
      imageElement.onload = new Func<Event, dynamic>((e) =>
      {
        UploadTexture(gl, result, imageElement);
        return true;
      });

      return result;
    }

    public static void UploadTexture(WebGLRenderingContext gl, WebGLTexture texture, HTMLImageElement imageElement)
    {
      gl.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, GL.ONE);
      gl.bindTexture(GL.TEXTURE_2D, texture);
      gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, imageElement);
      gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
      gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_NEAREST);
      gl.generateMipmap(GL.TEXTURE_2D);
      gl.bindTexture(GL.TEXTURE_2D, null);
    }

    public static float DegToRad(float degrees)
    {
      return (float)(degrees * System.Math.PI / 180);
    }
  }
}

И применяем к нему DuoCode-магию:

WebGL.Utils = $d.declare("WebGL.Utils", System.Object, 0, $asm, function($t, $p) {
    $t.CreateWebGL = function Utils_CreateWebGL(canvas) {
        var result = null;
        var names = $d.array(String, ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]);
        for (var $i = 0, $length = names.length; $i != $length; $i++) {
            var name = names[$i];
            try {
                result = canvas.getContext(name);
            }
            catch ($e) {}

            if (result != null)
                break;
        }
        return result;
    };
    $t.CreateShaderFromScriptElement = function Utils_CreateShaderFromScriptElement(gl, scriptId) {
        var shaderScript = $d.cast(document.getElementById(scriptId), HTMLScriptElement);

        if (shaderScript == null)
            throw new System.Exception.ctor$1("unknown script element " + scriptId);

        var shaderSource = shaderScript.text;

        // Now figure out what type of shader script we have, based on its MIME type
        var shaderType = (shaderScript.type == "x-shader/x-fragment") ? 35632 /* WebGLRenderingContext.FRAGMENT_SHADER */ : (shaderScript.type == "x-shader/x-vertex") ? 35633 /* WebGLRenderingContext.VERTEX_SHADER */ : 0;
        if (shaderType == 0)
            throw new System.Exception.ctor$1("unknown shader type");

        var shader = gl.createShader(shaderType);
        gl.shaderSource(shader, shaderSource);

        // Compile the shader program
        gl.compileShader(shader);

        // See if it compiled successfully
        if (!gl.getShaderParameter(shader, 35713 /* WebGLRenderingContext.COMPILE_STATUS */)) {
            // Something went wrong during compilation; get the error
            var errorInfo = gl.getShaderInfoLog(shader);
            gl.deleteShader(shader);
            throw new System.Exception.ctor$1("error compiling shader '" + $d.toString(shader) + "': " + errorInfo);
        }
        return shader;
    };
    $t.CreateShaderProgram = function Utils_CreateShaderProgram(gl, fragmentShader, vertexShader) {
        var shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        var linkStatus = gl.getProgramParameter(shaderProgram, 35714 /* WebGLRenderingContext.LINK_STATUS */);
        if (!linkStatus)
            throw new System.Exception.ctor$1("failed to link shader");
        return shaderProgram;
    };
    $t.LoadTexture = function Utils_LoadTexture(gl, resourceName) {
        var result = gl.createTexture();
        var imageElement = WebGL.Properties.Resources().get_duocode().Image;
        imageElement.onload = $d.delegate(function(e) {
            WebGL.Utils.UploadTexture(gl, result, imageElement);
            return true;
        }, this);

        return result;
    };
    $t.UploadTexture = function Utils_UploadTexture(gl, texture, imageElement) {
        gl.pixelStorei(37440 /* WebGLRenderingContext.UNPACK_FLIP_Y_WEBGL */, 1 /* WebGLRenderingContext.ONE */);
        gl.bindTexture(3553 /* WebGLRenderingContext.TEXTURE_2D */, texture);
        gl.texImage2D(3553 /* WebGLRenderingContext.TEXTURE_2D */, 0, 6408 /* WebGLRenderingContext.RGBA */, 6408 /* WebGLRenderingContext.RGBA */, 5121 /* WebGLRenderingContext.UNSIGNED_BYTE */, imageElement);
        gl.texParameteri(3553 /* WebGLRenderingContext.TEXTURE_2D */, 10240 /* WebGLRenderingContext.TEXTURE_MAG_FILTER */, 9729 /* WebGLRenderingContext.LINEAR */);
        gl.texParameteri(3553 /* WebGLRenderingContext.TEXTURE_2D */, 10241 /* WebGLRenderingContext.TEXTURE_MIN_FILTER */, 9985 /* WebGLRenderingContext.LINEAR_MIPMAP_NEAREST */);
        gl.generateMipmap(3553 /* WebGLRenderingContext.TEXTURE_2D */);
        gl.bindTexture(3553 /* WebGLRenderingContext.TEXTURE_2D */, null);
    };
    $t.DegToRad = function Utils_DegToRad(degrees) {
        return (degrees * 3.14159265358979 /* Math.PI */ / 180);
    };
});

Вы можете самостоятельно потыкать демку на официальном сайте. Выглядит это примерно так:



RayTracer

И это не предел! Один из примеров включает полноценный RayTracer (с векторной математикой, работой с цветом и освещением, камерой и поверхностями — всё на чистом C#):



Отладка

Звучит невероятно, но отлаживать это чудо можно прямо в браузере. C#-исходники прилагаются:



На текущий момент отладка возможна в VS 2015, IE, Chrome и Firefox.

Ещё пара примеров

При трансляции из C# в JavaScript одним из самых больных вопросов являются структуры. Сегодня DuoCode поддерживает только неизменяемые структуры, но для хорошего проекта этого должно хватить (как мы знаем, мутабельных структур следует избегать).

C#:

public struct Point
{
    public readonly static Point Zero = new Point(0, 0);

    public readonly int X;
    public readonly int Y;

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

JavaScript:

HelloDuoCode.Program.Point = $d.declare("Point", null, 62, HelloDuoCode.Program, function($t, $p) {
    $t.cctor = function() {
        $t.Zero = new HelloDuoCode.Program.Point.ctor$1(0, 0);
    };
    $t.ctor = function Point() {
        this.X = 0;
        this.Y = 0;
    };
    $t.ctor.prototype = $p;
    $t.ctor$1 = function Point(x, y) {
        this.X = x;
        this.Y = y;
    };
    $t.ctor$1.prototype = $p;
});

Лично меня особенно радует, что есть полноценная поддержка LINQ:

C#:

public static IEnumerable<int> Foo()
{
    return Enumerable.Range(0, 10).Where(x => x % 2 == 0).Select(x => x * 3);
}

JavaScript:

$t.Foo = function Program_Foo() {
    return System.Linq.Enumerable.Select(System.Int32, System.Int32, System.Linq.Enumerable.Where(System.Int32, 
        System.Linq.Enumerable.Range(0, 10), $d.delegate(function(x) {
            return x % 2 == 0;
        }, this)), $d.delegate(function(x) {
        return x * 3;
    }, this));
};

Мелкие радости вроде Generic, params, nullable, перегрузка методов, значения по умолчанию также идут в комплекте:

C#:

public class Foo<T> where T : IComparable<T>
{
    public void Bar(int? x, T y, string z = "value")
    {
        System.Console.WriteLine((x ?? -1) + y.ToString() + z);
    }
    public void Bar(string z, params object[] args)
    {
    }
}
// Main
new Foo<int>().Bar(null, 2);

JavaScript:

HelloDuoCode.Program.Foo$1 = $d.declare("Foo`1", System.Object, 256, HelloDuoCode.Program, function($t, $p, T) {
    $t.ctor = function Foo$1() {
        $t.$baseType.ctor.call(this);
    };
    $t.ctor.prototype = $p;
    $p.Bar$1 = function Foo$1_Bar(x, y, z) {
        System.Console.WriteLine$10($d.toString(($d.ncl(x, -1))) + y.ToString() + z);
    };
    $p.Bar = function Foo$1_Bar(z, args) {};
}, [$d.declareTP("T")]);
// Main
new (HelloDuoCode.Program.Foo$1(System.Int32).ctor)().Bar$1(null, 2, "value");

Заключение

Напомню, что DuoCode пока находится в состоянии beta, но уже на сегодняшний день список фич приятно радует глаз:



Разработка идёт достаточно быстро, постоянно выходят обновления с новыми возможностями. Будем надеяться, что мы уже буквально в паре шагов от того светлого будущего, когда можно будет писать действительно сложные клиентские веб-приложения на C#, используя всю мощь языка и сопутствующих инструментов разработки.
Автор: @DreamWalker
Enterra
рейтинг 37,47
Компания прекратила активность на сайте
Похожие публикации

Комментарии (54)

  • +1
    Простейшие примеры выглядят как-то неоправданно раздуто. На чистом C# и на чистом JS было бы гораздо компактнее.

    • +1
      на чистом JS было бы гораздо компактнее

      Ну, с этим никто не спорит, у подобного подхода есть своя цена. «Раздутость» кода связана с поддержкой всех C#-штук, которые мы должны уметь делать. К счастью, сгенерированный JavaScript нам не нужен для дальнейшей работы — разрабатывать и отлаживать можно на C#.
      • +1
        А что делать, когда есть большой кусок кода / библиотека написанная на JS. Как реализовано взаимодействие C#<->JS?

        Можно ли из C# дернуть JS методы, а из JS потом вызвать C#-колбек?
        • 0
          Не уверен, не изучал вопрос. Попробуйте написать авторам библиотеки.
        • 0
          Через dynamic поди
        • 0
          Если вас интересует серверный JS, то есть библиотека edge, которая позволяет делать интероп между кодом на C# и JS без всякой трансляции.

          Если же говорить именно про Duocode, то тут всё странно. В официальном FAQ есть только про вызовы C# из JS, а для всего остального они вроде бы написали врапперы. Всегда можно, конечно, посмотреть, как они делают эти врапперы, но есть более прямолинейный подход. Вы можете в своём коде при инициализации получить объект со списком лямбда-функций. Когда JS вызывает ваш код на C#, он передаёт в него ссылки на все необходимые функции, и вы их потом вызываете.

          Но я бы не стал использовать Duocode, пока не появится серьёзных портов чего-нибудь с C# в браузер. Все существующие трансляторы (Netjs, IL2JS) так и не были доведены до конца вследствие технических сложностей такой реализации.
  • +5
    «А теперь представьте ситуацию: есть заядлый C#-разработчик. Он очень любит C#, все-все проекты на нём пишет. Но судьба распорядилась так, что ему понадобилось написать клиентское веб-приложение.»

    И он берёт TypeScript и радуется жизни. Или не берёт, но почему?
    • 0
      TypeScript — отличная штука. Но предыдущее детище Хейлсберга всё-таки радует намного больше в плане синтаксиса и инструментария.
    • +2
      (Disclaimer: мне нравится C#, как язык.)

      Потому что заядлые C#-разработчики такие заядлые, видимо. Многие просто не хотят ничего учить.
      • +1
        Тут не в обучении дело, TypeScript достаточно простой. Но C# всё-равно значительно обгоняет его в плане возможностей.
        • +1
          JS-то еще проще.
          значительно обгоняет его в плане возможностей

          Я в принципе понимаю, что вы хотите сказать, но вообще-то JS обладает полнотой по Тьюрингу:) Единственное, чего мне действительно не хватает — это атрибутов.
          • +1
            Ну, это не аргумент, Brainfuck тоже полный по тьюрингу. В C# есть мощный reflection, богатые возможности по работе с ООП, клёвый LINQ, куча синтаксического сахара (особенно с C# 6). Да, я знаю, что в JS есть куча библиотек, которые всё это пытаются эмулировать, но функционал всё равно не тот.
            Плюс, не стоит забывать про инструментарий. Да, статическая типизация в TypeScript помогает, но с помощью VS2015+ReSharper9 я могу за 5 минут выполнить такой рефакторинг большого проекта, на который в любом другом трансляторе в JS у меня бы ушёл минимум день. Да, JS проще, маленькие проекты на нём быстро поднимаются. Но если у вас сотни тысяч строк бизнес-логики, то поддерживать такой проект на JavaScript — не самая лёгкая задача.
            • +4
              Ну, это не аргумент. Вместо LINQ у нас map/reduce и карринг, ООП у нас тоже есть, сахар — дело вкуса. Reflection'а не хватает, тут согласен. Рефакторинг я делаю в WebStorm точно с такой же скоростью.

              Но если у вас сотни тысяч строк бизнес-логики, то поддерживать такой проект на JavaScript — не самая лёгкая задача.

              Я что-то сомневаюсь в больших проектах на компилируемых в JS языках. Во-первых, из-за поддержки всех тех фич все это будет неимоверно весить и тормозить, во-вторых, рано или поздно придется дебажить в каком-нибудь IE9, в котором поддержки source map нету и не будет. В-третьих, этого GWT-подобного добра сколько угодно, и ни один большой проект на них не написан, почему-то.

              Набросать что-то на скорую руку, если JS ты не знаешь, а надо срочно — запросто. А делать что-то большое, не зная досконально нижележащих технологий — рискованно.
              • 0
                Ок, а такой сценарий: у меня уже есть C#-проект, я хочу портировать его в веб. Silverlight не оправдал ожиданий, а других альтернатив-то и нету.
                • +2
                  В таком сценарии возражений не имею.
            • +3
              Но если у вас сотни тысяч строк бизнес-логики, то поддерживать такой проект на JavaScript — не самая лёгкая задача.


              Тут не от языка программирования зависит, а от разработчиков. Я и на C# видел достаточно проектов, даже не очень больших, которые сопровождать невозможно.
              На JavaScript можно писать большие проекты и сопровождать их можно легко, если они хорошо написаны, все как и на большинстве других языков.
              • 0
                Ок, Catberry намекает на то, что если руки растут из того места, то и правда можно. Увы, таких разработчиков не так уж и много. А C# просто не даст совершить тех ошибок, которые среднестатистический Js-разработчик делает каждый день (это не камень в сторону Js, просто говнокодеров в последнее время слишком много развелось). Плюс, повторю мысли из соседних комментариев о пользе DuoCode-подхода:
                • Более богатый синтаксис и возможности платформы (тот же reflection). Ну не может тут Js составить достойную конкуренцию, не его это судьба.
                • Портирование существующих C#-проектов на сторону клиента

                А по поводу больших проектов: как бы не развивался Js в наши дни, я не верю, что возможности рефакторинга сравнятся с C#+VS2015+R#. Плюс, если проект хорошо написан, то большая часть проблем уходит сразу. А если он плохо написан? Мне доводилось делать рефакторинг огромного количества говнокода на C#, инструментарий сделал эту работу не такой сложной. А если бы это был проект с нестрогой динамической типизацией, то я бы скорее плюнул и написал бы с нуля и нормально.
                • +1
                  Согласен, C#+VS2015+R# намного лучше рефакторит чем тот же WebStorm. Проекты попадаются разные и если это большой и плохо написанный проект на JS, то да – легче начать с нуля, чем разгребать то что есть.

                  А reflection лично мне за всю разработку на JS особо не понадобился. Средства языка JavaScript позволяют и так получить много информации об объекте или функции: его конструктор, имена своих полей, имена полей предка, исходник метода методом .toString(), можно сгенерировать в райнтайме код или тип (прототип или конструктор). В общем у динамических языков тоже есть тут свои преимущества.

                  Конкретно про DuoCode – только рад что появилось такое решение, много кому оно облегчит жизнь.
                  • 0
                    А reflection лично мне за всю разработку на JS особо не понадобился.

                    Это больше вопрос привычек и парадигмы мышления. Когда много и хорошо решаешь какие-то задачи с помощью какого-то инструмента, то сразу замечаешь, когда инструмент у тебя отобрали. Например, я как функциональщиной увлёкся, начал иногда в C# ощущать небольшой дискомфорт (LINQ вывозит, но не всегда).
                    Как по мне — основные преимущества динамических языков проявляются в небольших проектах. Например, если мне нужно что-то небольшое написать, то я порой с C# переключаюсь на Python. А в большом проекте с динамическими фишками нужно быть аккуратным. Если нет достаточно опыта и квалификации, то можно крайне легко усложнить себе дальнейшую жизнь.
                • 0
                  это не камень в сторону Js, просто говнокодеров в последнее время слишком много развелось


                  Я полгода как ушел с большого проекта на шарпе. Если человеку важна только скорость, а не качество, если он не знает основ, если он, в конце концов, кое-какер — то его не спасет то, что он шарпер.
  • +1
    Чем это отличается от Script#?
    • 0
      Script# (он всё ещё живой?) и другие подобные поделки имеют очень ограниченный функционал, ибо распарсить C# самим и выполнить нормальную трасляцию всех фич (а потом ещё и дорабатывать продукт под новые версии C#) — крайне сложная задача. DuoCode использует Roslyn для получения синтаксического дерева, что убивает основную сложность. Больше нет ограничений и багов по работе с самим C# — остаётся только аккуратно сгенерировать JavaScript по синтаксическому дереву.
    • 0
      Он уже мерт, да и в нем нет поддержки даже .NET 4.0, не говоря уже о более современных версиях.
  • +2
    SharpLang — на входе откомпиленые в MSIL сборки, на выходе LLVM-представление, которое можно скармливать emscripten. Почему-то есть мнение, что оно со временем будет по поддержке фич и совместимости ощутимо лучше любых средств, занимающихся перегоном шарпового AST в JS.
    • 0
      1. Проект хороший, но пока немного сыроватый. Очень надеюсь, что в скором времени ребята допилят его до хорошего уровня.
      2. MSIL->LLVM — это прикольно, но если начинать работать сразу с IL, то не будем ли мы лишены полезной для трансляции информации? Выхлоп DuoCode местами пока немного пугает, но он вполне читаем: легко можно разобраться что и где делается. Не думаю, что из IL (а особенно после трансляции в LLVM) можно вытащить код такого же уровня читаемости.
      • +1
        По идее к MSIL прилагается pdb с нужной инфой, а дальше уже включаются штатные средства LLVM для поддержки работы с отладочной информацией. То есть в итоге мы таки получим .map-файлы нужные отладчику.
        • 0
          Ну, хорошо, если так. Будем следить за развитием проекта.
      • 0
        Трансляция LLVM в asm.js не предполагает читаемости, поскольку asm.js сделан не для людей. У таких модулей всегда есть исходный код (в данном случае C#), и есть чёрный ящик на JS с известным нам API.

        Мне кажется, вам просто более привычен подход TypeScript «давайте сделаем выдачу читаемой». Я как раз никогда не мог понять, зачем это нужно. Вы не могли бы объяснить?
        • 0
          Зачем делать результат работы typescript читаемым? Чтобы скомпилированный код можно было нормально отладить когда map файл не доступен или не позволяет понять суть происходящего.
          • –1
            Хм, вопросов стало только больше.

            Если source map не доступен, нужно сделать его доступным. У разработчика есть возможность, у остальных нет необходимости.

            Если транслятор статически типизированного языка не даёт гарантий относительно качества target кода, то транслятору место на свалке истории. Даже в C/С++ с их чудесами при наличии undefined behaviour в коде никогда не нужно отлаживать ассемблер. Если такие ситуации возникают, то вместо отладки транслированного кода нужно создавать тикеты вот здесь.
            • –1
              Source map не доступен в IE, например, и тут от разработчика ничего не зависит.
              • –1
                Да доступен уже почти год как.

                Если говорить про более старые версии IE, то необходимость отлаживать исходный код сугубо из-под IE может быть связана только с тем, что не использованы fallback/shim библиотеки для работы с некоторым функционалом с плохой кросс-браузерностью. Разработчиков таких библиотек, конечно, уже ничего не спасёт, но я пока не слышал ни об одной библиотеке в духе jQuery или es5-shim, написанной на TypeScript.

                К сожалению, от разработчика всегда всё зависит.
                • –1
                  Странно, еще в декабре соурс-мапы не сработали у меня. В любом случае, старые IE еще какое-то время никуда не денутся.
                  fallback/shim существует не для всего, и то, что есть, может быть с багами, которые нужно отловить.
  • 0
    Есть такой вопрос, зачем все это? Ведь даже микрософт не сделал такого транслятора, а создал TypeScript.
    • 0
      TypeScript появился в 2012, когда Roslyn находился в весьма зачаточном состоянии, а вести удобную разработку Js-прилоежний уже хотелось.
      Вопрос про политику Microsoft заслуживает отдельного обсуждения. Я думаю так: TypeScript — универсальная штука, которую можно использовать для любых Js-проектов. А DuoCode — нишевое решение, оно подойдёт не всем (зато в своей нише может очень хорошо пригодится).
    • +1
      Хочется странного. Вон, Nemerl'овцы всё туда же.
  • +5
    Выбрать в качестве разработки язык C#, чтобы потом внутри писать document.getElementById… Это какое-то извращение. C# прекрасный язык, но имхо инструмент нужно выбирать под задачу.
    • 0
      А с другой стороны хочется удобный инструмент. Плюс, отписывался выше: что если нужно портировать существующую C#-логику на сторону клиента?
      • +1
        У нас сейчас как раз проект, подразумевающий переписывание клиента с Silverlight на HTML5. Автоматическая конвертация, как впрочем и ручное построчное переписывание, превращает приложение в "лет ми спик фром май харт". Так что в долгосрочной перспективе, если подразумевается хоть какое-нибудь развитие и поддержка, лучше использовать «родные» вебовские подходы.
        • 0
          У нас одна из редакций Grapholite на Silverlight написана. Огромное количество C#-кода, в котором возможности рефлексии и биндинг-магии используются на полную. Сколько не смотрели в сторону HTML5, не хватает духу попробовать подобный функционал повторить на HTML5.
          • 0
            Возможно, подойдет AtScript.
    • 0
      В C# cтатическая типизация, всё же.
      • 0
        C# в этом плане, к счастью, не уникален :)
        • –1
          Я бы даже сказал, что он в этом плане довольно плох, и я бы предпочёл куда более строго типизированный PureScript. Но это уже намного лучше, чем типизация JS с её сплошным WAT.
          • 0
            Что значит, «более строго типизирован»? С# действительно где-то делает недостаточно строгие проверки, или вы имеете в виду, что хотелось бы больше гибкости, вроде зависимых типов и т.д.?
            • 0
              Зависимых типов хотелось бы, конечно. Настоящих, не path-dependent.
  • 0
    Интересно, можно ли будет скомпилить сам Roslyn под JavaScript? Давно уже есть одна идея, может как раз получится ее реализовать.
    • 0
      Идея крайне увлекательная, но сложная. Если получится, то будет мега-круто.
    • 0
      Благодаря существованию трансляторов CIL -> JS скомпилить можно. А вот насколько оно будет юзабельно…
      • 0
        Пытался это сделать с помощью jsil.org, сыпались ошибки. Но тогда не стал особо заморачиваться.
    • 0
      Не думаю, что сейчас есть хоть один транслятор, способный на это. У всех существующих есть ошибка в реализации разных моментов языка, от встроенных массивов размерности больше 1 до полного отсутствия атрибутов и рефлексии. Крупные проекты обычно хоть что-нибудь из этого используют, а поэтому не транслируются.
  • 0
    Раньше занимался задачей написания универсального C# кода под .NET и JavaScript. В итоге удалось создать общую графическую библиотеку, использующую функции GDI+ под .NET и функции canvas из html5 под JavaScript. Реализовывал тогда это с помощью Script#. Думаю, с помощью данной либы получилось бы реализовать все более изящно, без многочисленного количества костылей.
  • 0
    Попробовал установить, но оказалось, что данные проект пока что находится в закрытой бете. Будем ждать…
  • 0
    Нашел недавно WootzJs, тоже построенный на Roslyn. Ну и еще есть трансляторы, построенные на NRefactory (SaltarelleCompiler). Интересно, зачем их так много?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка