Tridion.js: The Good, the Bad, and the Ugly of Node.js-powered Tridion implementations

Tridion.js: The good, the bad, and the ugly of Node.js Tridion implementations

Tahzoo + Tridion + Node

Tahzoo has done two Tridion implementations that involved node.js in a significant way.

One implementation was Web 8.5, DXA 1.6, and Node had a serious role to play in the front-end

Another implementation was Web 8.1, DXA 1.6, and Node had a serious role to play in the back-end.

We've also seen another implementation where Node was both back-end and front-end.

Our Goals

I want to tell you a story about our experiences. It's easier to remember a story than a series of facts.

Learn some things that can drive good implementation decisions:

What's new and great with node

How Node integrates with other technolgies in the Tridion ecosystem

How to use Node to solve Tridion problems

JavaScripting in 2018

A TL;DR History of Node

Node was a solution to a concurrency problem

Ryan Dahl created node.js to solve a concurrency problem1. He wanted the server to send messages without the client needing to request them every time.

It only seemed sensible to borrow the V8 engine that the Google team made for Chrome. A JavaScript runtime compiler is the perfect solution to an asynchronicity problem.

Node's power is in the "Event Loop". Everything happens inside of Node is a reaction to an event that's managed within that event loop2. The event loop is what allows Node to handle I/O on a single thread.

Node is a nine-year-old

  • Node got a name in March of 2009, which was six months to the day after the first release of Chrome.3
  • NPM showed up 7 months later4.
  • Express.js' first version came a year after Node.
  • In 2018 alone NPM has over 700,000 packages; the largest package registry in the world5

Node is young but it's growing up fast.

Netflix, PayPal, Uber, and Walmart didn't start using Node because they're hipsters. They started using Node because it solved the problem of how to get a single thread to handle thousands of concurrent requests.

We must defeat the hippies hipsters

Image of a hipster talking about node

JavaScripting in 2018

ES6++

ES6 / ECMAScript 20151

  • const, let block-scoped variables
  • ()=>{}Arrow functions; "lambdas", with added bonus of inheriting this
  • class, super, extends, staticClasses; Syntactic sugar for being object-oriented
  • import, export Modules; The ability to import and export JavaScript files
  • `Hello ${planet}` Template Literals (strings that allow multilines and variable interpolation)
  • Promise() Promises; a way to guarantee asynchronous functions always resolve
  • const { mammalia } = Chordata Destructuring; it's better than mammalia = Chordata.mammalia
  • function isVenomous (isAustralian = false){} Default parameters FTW

ES7 / ECMAScript 20162

  • ['mammals', 'reptiles'].includes('mammals') A better way to find things in an array
  • 3**2; // 9 An exponents operator
hipster who thinks ES6 is too mainstream

ES8 / ECMAScript 20173

  • 'foo'.padStart(4); 'foo'.padEnd('4') String padding.
  • Object.entries(mammalia)Get enumerable parts of an object as an array
  • Object.values(mammalia)Get the values from properties as an array
  • async/await A cleaner way to deal with promises*
                async function animalsOnBoat() {
                    const  canEat = await ajaxGetAnimals('kosher');
                    const  cannotEat = await ajaxGetAnimals('unkosher');
    
                    return canEat.length + cannotEat.length;
                }
* May require a try/catch

JavaScripting in 2018

App Development with Node

Node in the front-end

Front-end Developers aren't using Node that much, but they live by its package manager, NPM. Front end developers are relying on a plethora of NPM modules for delivering software.

Grunt
Configuration based task running.
Popular in 4-year-old repositories and SDL.
Gulp
Stream-based task running.
Popular with our parents.
Webpack
Module Bundler
The hippest way to build something for the browser.
NPM Scripts
Scripts written in a package.json to be executed by command line
The neo-hipster way to run builds and tasks
Babel
JavaScript transpiler. Guarantees your ES6++ works in all browsers
ESLint
"linting" utility that evaluates code for formatting and consistency.
Angular
TypeScript-powered MVW framework with 2-way data-binding
Not hipster because Google backs it.
React
State-based, data-binding, shadow-DOM diffing, MV* templating framework library
Most hipster because Facebook backs it.
Vue
State-based, data-binding, MVVM framework
Punk. Created by a Google employee who wanted Angular to be like React.
Jade
Templating framework made for people who wanted HTML to be more like Python.

It's bad, guys. Really bad.

Node in the back-end

With 700,000 NPM packages, it's hard to say what goes into a Node-based web application.

Rollup
Module Bundler known for "tree shaking" (removing unused dependencies). Used for generation NPM modules. Popular with people that don't want an NPM module to have the density of a dying star.
TypeScript
Type checking for JavaScript. Popular with people afraid of JavaScript.
Bluebird
Promise Library. Extremely popular before Node got native promises.
Express
Web Application Framework. Robust and feature rich, with built-in templating options.
Koa
Web application framework. The lightweight, "spiritual successor" to Express.
GraphQL
Query language and runtime for data. Hipster, because Facebook built it. Not hipster, because SDL uses it.
Lodash
Utility library that makes it easier to deal with data. Becoming less popular because of ES6++

It's not as bad here. But it ain't great.

Testing in Node

Test Frameworks (food)
Mocha
Jasmine
Cucumber
Assertion Frameworks
Chai
Jest
Browser emulators (supernatural)
Nightwatch
Phantom
Casper

Apparently developers get scared and hungry when it's time for testing

Creating Apps

React Native
It's React, but builds native mobile apps. Compiles to Objective-C or Java.
Electron
Builds cross-platform desktop apps. (Visual Studio Code and Slack are both Electron apps)
NativeScript
Builds native mobile apps from Angular, Vue, TypeScript, or vanilla JavaScript.

Probably the most intriguing aspect of the Node ecosystem is that it's now possible to generate Native applications from HTML, CSS, and JavaScript

The Ugly Side of Node

When Other Frameworks Try to Node

Story time

  • It's a B2B2C Project; our client is providing websites to about 700 of their clients
  • Each "end-client" may have multiple sites with the same brand, but a different purpose (main, blog, etc)
  • We'd have upwards of 1,000 content authors
  • End-client need to have some control over the brand
  • Need to offer user authentication
  • Need to offer some "dynamic content"
  • Going to be hosted on AWS

An Architecture that we tried

  • DXA would become a Rest API
  • The front-end would be a .net core + vue.js
  • Authentication would be handled by a .net core application that talked to audience manager endpoints

Motivations

We didn't want one huge app; three small apps would be easier to scale

Platform independence; only the content api had to be on windows

Application independence; we could run our web app against dev, QA, or production content-services

We had a small team with disparate skills; our front-end dev didn't want to write C# or razor, and our back-end dev didn't want to write front-end

.NET core with node

  • Node is still single-threaded; we were worried about CPU usage
  • .net core offers this groovy feature called Node Services1
  • Node Services allows .net core to run JavaScript in a Node environment

The main reason that folks might want to try Node Services is for the packages. There's a lot more in NPM than there is in Nuget

_layout.cshtml

@model App.ViewModels.SpaViewModel
<body class="@Html.Raw(Model.HtmlClasses)">
    @if (Model.XpmEnable) {
        @Html.Raw(Model.PageXpmHtml);
    }
    @RenderBody();
    @RenderSection("topscripts", required: false)
    <script src="/dist/main.min.js"></script>
    @RenderSection("scripts", required: false)
</body>
    

Any ol' page view:

@model App.SpaViewModel
<div class="my-app" asp-prerender-module="wwwroot/main.js" asp-prerender-data="Model" asp-></div>
    

The Ugly side of Integrating Node with .net core

Confusing views

We had views of identical name in two places, for two languages.

  • In our setup, we had to create a .cshtml page for each page template.
  • We still had to create Vue versions of our page templates

Node could crash the .net core app

Node, still being single-threaded, could bring the app to a screeching halt under the right conditions. This is because .net core is expecting something to be returned from the Node Service.

This means a while loop that never terminated would lock up .net core. Logic inside of .vue files was safe as the framework had plenty of logic for handling errors. But logic not used by the templating framework had to be extremely well tested.

The Ugly Side of Node

Node as a Build Utility

Building with Webpack

Webpack's value as a build utility is in the dependency graph. It starts from the command line and moves to the entry point defined in the config file. It finds every single dependency and "bundles" all of these dependencies together. The final product is a single "bundle" that is ready to be executed by the browser

But what if the browser isn't executing your code?

Debugging Webpack

It's easy to debug a webpack bundle in the browser. Because the browser has debugging tools.

At worst, you may need to modify a webpack config to add map files or prevent obfuscation/minification.

When Webpack bundles an entire SPA, and then turns it over to .net core's Node Services, you lose the ability to debug.

All we had for debugging was console.log(). We could set breakpoints on the .net parts of the implementation, but not on the JavaScript!

Stateless-state-based SPAs

JavaScript frameworks like React and Vue are state-based. That means they store data as a "state" and then the DOM is rendered and re-rendered based on that "state".

Our "state" was the page model that we'd retrieved from our Content API.

.net core was passing a model into the Node Services, and it was getting HTML rendered by Vue back.

Since we were getting fully-rendered HTML, we lost out on an important feature: replenishing "state" (called hydration) after page load

The advantage of getting fully-rendered HTML, though, was that XPM worked flawlessly. Because it wasn't really a SPA.

What We Learned with Node.js as a part of our front-end Architecture

  • Separating the architecture into multiple applications was a good idea
  • .net core wasn't a bad idea as an agility layer within our front-end application
  • The way that .net core evaluated JavaScript revealed a weak point in our architecture
  • Not being able to debug a SPA in any runtime is a serious bummer
  • We lost out on some of the cool flexibility of Vue, like being able to hydrate state in the browser
  • What we'd do differently

    • Unit-testing in the non-vue JavaScript
    • Consider a pure node.js front-end; it'd give us the ability to debug the templating, and also properly hydrate state

    The Bad Side of Node

    Node as a web Server

    Story Time

    • It's a B2B project; our client has a few websites available in dozens of languages
    • Client has multiple sites in multiple languages with same brand, different layouts
    • Client's main sites were already using DXA
    • Client has content that is displayed in screens in on-site facilities; not strictly "web content"
    • The content stored in the CMS will have variables… that get resolved outside of our applications…

    An Architecture that we tried

    • We were already using DXA for the main sites, so we'd keep using DXA TBBs in Tridion
    • We would build a Rest API in Node.js so that these custom displays could have a Restful API
    • We would build component templates in DXA so that content authors could use XPM and get a "preview" of what their edits would look like on a physical device

    Motivations

    • We needed an endpoint
    • We didn't want to build something custom with the DXA web app
    • The client was already using Node.js for other applications

    Building a Rest API with node

    This is kind of node's bread and butter here. Node loves it some Rest APIs

    Our Rest API was more of a façade for the OData microservices

    so what's the problem

    So what's the problem?

    There was a complexity that we left… complex

    • The thing on-site that was consuming this data was also Node.js
    • It needed to stay on-site for an important reason. The content had variables in it that needed to be interpolated
    This is the content that was coming out of our API:
    "SomeData" :{
        "content" : "{{Product_name}} is hipper than a scarf in July."
    }
                

    That variable was determined by hardware on-site based on environmental factors. So the display application was retrieving text get/genericText, then getting the environment variable {ProductId: 123}, and hitting the Rest API a second time to get the content of that productId.

    Every piece of content with an environment variable needed 2 Rest calls. Just to print out Avocado toast is hipper than a scarf in July.

    The Bad Side of Node

    Node…and Node

    Meme about damned dirty hipsters

    Another phase of work, and more Node

    We had a new phase of work. This time a section of the existing site would be redone. This client's front-end team decided to use React as a templating engine to generate static HTML.

    So the front-end team needed a Rest API

    So our small API that was built for a handful of displays ended up being an API for a web application. We ended up modelling our JSON in JavaScript manually, and missing core features that DXA offers (like link resolving).

    The Good Side of Node

    Web Sockets

    WTHaWS (What the heck are web sockets)

    A web socket is a persistent connection between the client and the server.1

    What's cool about web sockets is that they're event-driven

    A Server-side example
    const io = require('socket.io')(server);
    
    io.on('connection', socket => {
        socket.on('regionUpdate', (evt) => {
            socket.emit('regionsUpdated', evt.data.regions);
        });
    });
            
    A client-side example
    const socket = io.connect();
        
    socket.on('regionUpdate', (evt) => {
        const { regions } = evt.data;
    
        updateRegions(regions);
    });
                

    But what can we do with it

    • Chat Applications are the obvious thing
    • Real time user messaging (emergency messages)
    • Smarter Smart Target: track cursor movements, scroll behaviors, send and receive new claims on-page, without a refresh
    • Real-time MVC?

    What could a real-time MVC framework look like?

    Wrap up

    What have we learned

    • JavaScript is getting better
    • Node was meant to solve I/O problems and deal with async issues
    • There's An NPM Package For That®
    • Mixing Node with .net core is a cool idea so long as you're keeping an eye on thread usage and trying to be asynchronous
    • Unit Testing is not a discipline yet in JavaScript, despite the tools available
    • You can over-Node. Don't use Node until you have a plan
    • WebSockets are frickin' sweet
    • Electron, React Native, and NativeScript are the future

    Thank You

    Special thanks go to:

    • Brandon Bernard
    • Sarah Braumiller
    • Peter Beemster

    Give me a buzz sometime