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#, используя всю мощь языка и сопутствующих инструментов разработки.
    Enterra 35,60
    Компания
    Поделиться публикацией
    Похожие публикации
    Комментарии 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
                                  В 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). Интересно, зачем их так много?

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

                                            Самое читаемое