Bridge 1.11 Released

We’re happy to announce the immediate availability of Bridge.NET 1.11.

This release is a compilation of the generous contributions from the Bridge community and represents a huge amount of work by the Bridge team.

Please download or install using NuGet.

Install-Package Bridge

For a full list of closed issues, please review in the GitHub Bridge repository, but let’s work our way through highlighting some of the new Features.

Local NuGet Packaging

In previous releases our NuGet packaging process was located in an external project, which made building from source an overly complicated process for community members. With 1.11 we greatly simplified the process.

switch-to-release-modeTo generate the NuGet packages, now all that is required is to build in Release mode. A .nupkg file will be added to the /bin/Release/ folder of each Bridge project, along with the standard .dll, .pdb and .xml files.

Once the .nupkg file is created, within the NuGet Package Manager add a Local Source configuration. In addition to searching for updated packages remotely at NuGet.org, the NuGet Package Manager will search a local folder. Once the local package source is configured, the process of updating to a new release of Bridge happens exactly the same as if we published a new package to NuGet.org. The NuGet Package Manager will inform you that a new release is available, then you just accept the update.

Be sure to read Hosting Your Own Nuget Feeds for much more information about configuring a local package source.

This new NuGet packaging has simplified our build process, and we hope you find as much value in it as we have.

Updated README

bridge-readmeBefore 1.11 we didn’t have much of a README, so during this release we improved the document to provide a quick overview of the project and some basic samples. It still needs help, and we promise to revisit regularly to refine the instructions, although for now this is a step in the right direction.

Want to contribute to Bridge but do not feel comfortable modifying the source code? Submitting a change to revise the README is a great place to get your feet wet.

Our CONTRIBUTING document should help step you through contributing a Pull Request, and Scott Hanselman has a great blog post too, see Get involved in Open Source today. There are several excellent articles on GitHub as well.

Convert Class

Bridge was missing support for the C# Convert class, but no more, thanks to community member @AndreyChechel from Russia. In the process, Andrey has also closed our oldest open Issue (#14).

Andrey single handily took on this task and provided an implementation, as well as integrating more than 1800 Unit Tests associated to the functionality. Now Convert functionality is supported and the code is constantly run through our Testing project to ensure stability.

Within Bridge, casting from one type to another has always been possible, but previously if you were porting code from another project into a Bridge project, missing the Convert class required making modifications to your original source. That transition should now be seamless and converting from one base datatype to another is available in C# and JavaScript.

The following samples demonstrate basic conversion of types using Convert and their equivalents compiled into JavaScript.

Example (Live)

C#
var flag = Convert.ToBoolean(“true”);

int val = Convert.ToInt32(“99”); 
JS
var flag = Bridge.Convert.toBoolean(“true”);

var val = Bridge.Convert.toInt32(“99”); 

New InitPosition Options

We’ve added new InitPosition.Top and InitPosition.Bottom Enum options which can help configure the [Init] Attribute.

These new position options will emit the body of a method directly to the absolute top or absolute bottom of the generated .js file. The following sample demonstrates.

Example (Live)

C#
using Bridge;
using Bridge.Html5;

namespace Demo
{
    public class App
    {
        [Init(InitPosition.Top)]
        public static void Top()
        {
            Console.Log(“Absolute Top”);
        }

        [Init(InitPosition.Bottom)]
        public static void Bottom()
        {
            Console.Log(“Absolute Bottom”);
        }
    }
} 
JS
console.log(“Absolute Top”);

(function (globals) {
    “use strict”;

    Bridge.define(‘Demo.App’, {
        statics: {

        }
    });

    Bridge.init();
})(this);

console.log(“Absolute Bottom”);
 
 
 
 
  

Please take note of how just the method body content is compiled and added to the top or bottom of the output. The Top and Bottom methods themselves are not defined in the output. Only the code within the body is used.

This has obvious implications within your C# code in that these methods should not be called, since they will not exist in the output. If these methods are called within your C#, Bridge will step in during compilation and throw the following error:

Error: This method cannot be invoked directly

To avoid the compilation error, just remove the call to the [Init] marked method.

A common scenario where this new InitPosition functionality may come in handy are writing custom comments or basic startup code to the top of the output.

Example (Live)

C#
using Bridge;
using Bridge.Html5;

namespace Demo
{
    public class App
    {
        [Init(InitPosition.Top)]
        public static void Top()
        {
            /*
                A custom comment
            */

            Console.Log(“start”);
        }
    }
} 
JS
/*
    A custom comment
*/

console.log(“start”);

(function (globals) {
    “use strict”;

    Bridge.define(‘Demo.App’);

    Bridge.init();
})(this);
 
 
 
 
  

Please see the Attribute Reference knowledge base guide for more information on InitPostion options.

Added Touch Events

touch-testSupport for Touch events have been added to Bridge 1.11. GitHub Issue #974.

Big thanks to community member @TinkerWorX for contributing the initial implementation.

We created a simple demo to test functionality. Give the sample a try by viewing on any touch enabled device. Source code for the Touch demo is available in our Demos repo on GitHub.

System.Diagnostics Namespace

In order to assist with the development and debugging experience while using Bridge, support has been added for several features of the System.Diagnostics Namespace and other related classes, including the following:

  • Conditional Attribute (docs)
  • Debug Class, including .Assert() and .WriteLine() (docs)
  • Stopwatch Class, including .Start(), .Stop(), and .Restart() (docs)
  • Contracts.Contract Class (docs)

Conditional Attribute

A Conditional Attribute can be added to methods to instruct the Bridge Compiler to conditionally remove calls to the method if not building in the configured mode. A Condition Attribute creates conditional compilation methods.

For example, if your project is compiling in Debug mode, methods marked with the [Conditional("DEBUG")] attribute will be emitted to the output file during Debug compilation only. If a method is marked with [Conditional("DEBUG")], then build in Release mode, the method calls will be ignored by the Compiler.

switch-to-release-modeSwitching between Debug and Release build modes is easily accomplished by choosing a new build option from the dropdown configuration within the Visual Studio toolbar.

The following sample demonstrates a scenario using the Conditional Attribute.

Example (.cs)

C#Use of Conditional Attribute
using Bridge.Html5;
using System.Diagnostics;

namespace Demo
{
    public class App
    {
        public static void Main()
        {
            // Only call in DEBUG build mode
            App.Check();
        }

        [Conditional(“DEBUG”)]
        private static void Check()
        {
            Console.Log(“FAIL”);
        }
    }
} 

The Conditional Attribute is applied to line #14 above, and the expected output will vary depending on the build mode of either Debug or Release. The compiled output from both modes are demonstrated next.

Example (.js)

JSOutput in Debug mode
(function (globals) {
    “use strict”;

    Bridge.define(‘Demo.App’, {
        statics: {
            main: function () {
                // Only call in DEBUG build mode
                Bridge.get(Demo.App).check();
            },
            check: function () {
                console.log(“FAIL”);
            }
        }
    });

    Bridge.init();
})(this); 
JSOutput in Release mode
(function (globals) {
    “use strict”;

    Bridge.define(‘Demo.App’, {
        statics: {
            main: function () {
                // Only call in DEBUG build mode
            },
            check: function () {
                console.log(“FAIL”);
            }
        }
    });

    Bridge.init();
})(this);
  

Notice how the call to the .check() function is missing from the Release mode output.

If the Conditional Attribute is ignored, because the condition is not met, only the calling location is ignored, not the method definition. The method definition will still exist in the compiled output. This still enables triggering the condition within an external Assembly and calling the methods without runtime errors being thrown.

Debug.Assert Method

debug-assert-promptAdding calls to Debug.Assert() within your app will check for a condition, and if false, the specified message will be displayed along with a confirmation asking to jump into the browsers debugger instance.

Debug.Assert statements only emit to the compiled output during Debug builds and do not appear in the compiled Release mode output.

C#Basic Assert statement
var someCondition = false;
Debug.Assert(someCondition, “Condition Failed”); 
JSOutput only in Debug mode
var someCondition = false;
Bridge.Debug.assert(someCondition, “Condition Failed”); 

Stopwatch Class

The Stopwatch class provides methods and properties useful for measuring elapsed time. A Stopwatch is often used for benchmarking code performance while searching for application bottlenecks.

In the following sample, we instantiate a new Stopwatch class, start the Stopwatch, then for demonstration purposes we force the thread to sleep for 1250 milliseconds. The Thread.Sleep method simulates a long running process.

After the thread is released, the Stopwatch is stopped and a message is written to the Console.

Example (Live)

C#Basic Assert statement
using Bridge.Html5;
using System;
using System.Diagnostics;

namespace Demo
{
    public class App
    {
        [Ready]
        public static void Main()
        {
            // Create new stopwatch.
            Stopwatch stopwatch = new Stopwatch();

            // Begin timing.
            stopwatch.Start();

            Thread.Sleep(1250);

            stopwatch.Stop();

            // Write result.
            Console.WriteLine(“Time elapsed: {0}”, stopwatch.Elapsed);
        }
    }

    public static class Thread
    {
        /// <summary>
        /// Simulate a Long Running Task with Window.SetTimeout
        /// </summary>
        /// <param name=”milliseconds”>The number of milliseconds to sleep</param>
        public static void Sleep(int milliseconds)
        {
            var start = new Date().GetTime();

            for (var i = 0; i < 1e7; i++)
            {
                if ((new Date().GetTime() – start) > milliseconds)
                {
                    break;
                }
            }
        }
    }
}
 
 
 
 
 
 
  
JSOutput only in Debug mode
(function (globals) {
    “use strict”;

    Bridge.define(‘Demo.App’, {
        statics: {
            config: {
                init: function () {
                    Bridge.ready(this.main);
                }
            },
            main: function () {
                // Create new stopwatch.
                var stopwatch = new Bridge.Stopwatch();

                // Begin timing.
                stopwatch.start();

                Bridge.get(Demo.Thread).sleep(1250);

                stopwatch.stop();

                // Write result.
                console.log(Bridge.String.format(“Time elapsed: {0}”, stopwatch.timeSpan()));
            }
        }
    });

    Bridge.define(‘Demo.Thread’, {
        statics: {
            /**
             * Simulate a Long Running Task with Window.SetTimeout
             *
             * @static
             * @public
             * @this Demo.Thread
             * @memberof Demo.Thread
             * @param   {number}    milliseconds    The number of milliseconds to sleep
             * @return  {void}
             */
            sleep: function (milliseconds) {
                var start = new Date().getTime();

                for (var i = 0; i < 10000000.0; i++) {
                    if ((new Date().getTime() – start) > milliseconds) {
                        break;
                    }
                }
            }
        }
    });

    Bridge.init();
})(this); 

The Stopwatch class is a powerful tool for performing diagnostics and benchmarking of your application code. Now with support in Bridge, we’re also ensuring easy backwards compatibility with existing C# code to simplify reuse.

Code Contracts

Want to learn more about Code Contracts? The best introduction we’ve found was provided on Kevin Hazzard’s Developer Journey blog. See Code Contracts Part 1 – Introduction.

Code Contracts requires installation of the Code Contracts for .NET Visual Studio extension.

Lifted Anonymous Functions

In what can only be described as an epic feature request, long time Bridge Community member @ProductiveRage (forums, website, twitter) posted an idea that could potentially lead to significant performance gains in how anonymous functions are compiled within Bridge.

The idea was incubated out work being done on his port of React JS (nuget) and the associated Immutable Objects (nuget) library for Bridge.NET.

Let’s start with a C# sample to demonstrate the scenario. In the following sample we create a simple string Array and then run two chained LINQ queries against the Array filtering the results.

C#Simple String Array With LINQ Query
using Bridge.Html5;
using System.Collections;
using System.Linq;

namespace Demo
{
    public class App
    {
        [Ready]
        public static void Main()
        {
            string[] values = { “One”, “Two”, “Three”, “Four”, “Five” };

            IEnumerable query = values
                .Where(n => n.Contains(“e”))
                .OrderBy(n => n.Length);
        }
    }
} 

After building your project, the body of Main() will compile to the following JavaScript.

JS
var values = [“One”, “Two”, “Three”, “Four”, “Five”];

var query = Bridge.Linq.Enumerable.from(values).where(function (n) {
    return Bridge.String.contains(n,”e”);
}).orderBy(function (n) {
    return n.length;
}); 

Notice how an anonymous JavaScript function is passed into the where() call. This technique works well, although there is a cost in that these dynamic nested functions have to be created with each call of the parent function. In this sample the function is only called once, and the function is only created once, but imagine a scenario where the function is called a thousand times or even a million times. If you call the function a million times, the anonymous function is created a million times. Not good.

Eventually the garbage collector would help us (maybe) clear the unused memory, although this performance flaw in the app screams for optimization. This is exactly where Bridge can offer value.

To quote @ProductiveRage:

The short version is that nested functions in JavaScript have a cost as they are created on-the-fly every single time that the parent function is executed.

Want to learn more and really dig into the weeds on this topic? The following video tutorial and blog post is a fantastic introduction to the issues with nesting of JavaScript functions.

Another excellent talk if you’re interested in JavaScript memory management and performance tuning is The Joys of Static Memory Javascript. #permatters

Ideally the LINQ nested functions from our original sample would be automatically lifted into their own named definitions, thereby only creating once. The parent functions would then call the already lifted function instead of the dynamic anonymous function. The potential gains for processor and memory intensive programs such video gaming applications could be significant.

Unlike other types of values, functions typically don’t change; a function is created to perform a specific task. So it doesn’t make much sense to waste CPU cycles recreating a somewhat static value over and over again. – Jeremy McPeak

The goal was how to implement lifted anonymous functions in Bridge AND not require a change in the users original C# code. Ideally this feature would be a true compiler level optimization and would be transparent to a user of Bridge after upgrading.

We had the goal, but now the tricky part of lifting these functions within the compiler.

After a few iterations and feedback from the community, we found the sweet spot. The following JavaScript samples demonstrate the compiler output before and after this change was implemented.

Example (Live)

JSBefore Bridge 1.11
Bridge.define(‘Demo.App’, {
    statics: {
        config: {
            init: function () {
                Bridge.ready(this.main);
            }
        },
        main: function () {
            var values = [“One”, “Two”, “Three”, “Four”, “Five”];

            var query = Bridge.Linq.Enumerable.from(values)
                .where(function (n) {
                    return Bridge.String.contains(n,”e”);
                }).orderBy(function (n) {
                    return n.length;
                });
        }
    }
});
 
 
 
 
 
 
 
 
 
  
JSAfter Bridge 1.11
Bridge.define(‘Demo.App’, {
    statics: {
        config: {
            init: function () {
                Bridge.ready(this.main);
            }
        },
        main: function () {
            var values = [“One”, “Two”, “Three”, “Four”, “Five”];

            var query = Bridge.Linq.Enumerable.from(values)
                .where($_.Demo.App.f1)
                .orderBy($_.Demo.App.f2);
        }
    }
});

var $_ = {};

Bridge.ns(“Demo.App”, $_)

Bridge.apply($_.Demo.App, {
    f1: function (n) {
        return Bridge.String.contains(n,”e”);
    },
    f2: function (n) {
        return n.length;
    }
}); 

With the new output, the Bridge compiler was able to correctly determine that the anonymous LINQ methods could be lifted, moved them into new named private functions, then referenced the new private functions in their original parent callers. This is all done completely transparently and requires no modifications to the original application C# source, or burden on the end user developer.

This lifted function scenario may be the single best demonstration of what we’re trying to achieve here with Bridge. Having a layer of abstraction can yield big wins. Even very minor refinements made to the compilation process are spread across your apps and all the other apps being developed on Bridge.NET. As a developer, you didn’t need to do anything, yet you automagically pick up all these amazing feature and performance gains. All completely transparent to the end user developer.

Summary

There was a lot packed into v1.11 and much more than we can cover in this post. Take a quick look through the list of Closed Issues for a detailed overview of fixes and features.

We want to extend a huge thanks to the Bridge.NET community for contributing code, ideas and energy to the project. Thanks!

Thanks again to @ProductiveRage for going above and beyond while thinking up those edge cases. Be sure to check out the Bridge related blog posts at ProductiveRage.com.

One Comment

Comments are closed.