Fix for Markdown rendering for SSR

There’s a problem with ngx-markdown package – it doesn’t work out of the box with server side rendering, because the loader uses HttpClient to load the markdown from a file, but on the server side the relative path makes no sense (as there’s no base URL), so it actually fails to render on the server. Here’s the fix: https://github.com/laas-sh/laas-sh-app/pull/6/files

We provide a custom loader on the server side in app.server.module.ts:

//...
export class HttpClientForMarkdownSsr extends HttpClient {
  get(url: string): Observable<any> {
    return of(readFileSync(`${process.cwd()}/dist/browser${url}`, 'utf8'));
  }
}

@NgModule({
  imports: [
    //...
    MarkdownModule.forRoot({ loader: { provide: HttpClient, useClass: HttpClientForMarkdownSsr } })
  ]
  //...
});

This loader just reads the file with readFileSync on the server. Problem solved!

Cheers,
Andrew

Bonus step 3.5: routing animations

Let’s have a little fun with animations: https://github.com/laas-sh/laas-sh-app/pull/5/files.

We are going to add an animation on every route change. To do that, we attach an animation to the router-outlet wrapper in app.component.html:

<app-header></app-header>

<div class="content" [@pageChangeAnimation]="miscService.page$ | async"><router-outlet></router-outlet></div>

<app-footer></app-footer>

We are introducing a new observable in MiscService called page$ – it will emit every time the page changes. This is what it looks like in misc.service.ts:

  page$: Observable<Page> = this.router.events.pipe(
    filter(event => event instanceof NavigationEnd),
    map(() => this.router.routerState.snapshot.url.slice(1) as Page)
  );

We define Page as an enum in page.enum.ts:

export enum Page {
  PRIVACY_POLICY = 'privacy-policy',
  TERMS_OF_SERVICE = 'terms-of-service',
}

The values are relative URLs, that’s why we can simply do this.router.routerState.snapshot.url.slice(1) as Page conversion.

That’s all we need to trigger the animation: when page changes, we have a new value emitted by the observable. The same approach works for any sort of animation – as long as we have an observable, we can use it to trigger an Angular animation. The animation itself is defined in app.component.ts:

@Component({
  // ...
  animations: [
    trigger('pageChangeAnimation', [
      transition('* => *', [
        group([
          query(
            ':enter',
            style({
              position: 'absolute',
              transform: 'translateX(100%) rotate3d(1, 1, 1, -15deg) scale(1.5)',
              opacity: 0.5
            }),
            {
              optional: true
            }
          ),
          query(
            ':leave',
            [
              style({ position: 'absolute', 'z-index': -1 }),
              animate(
                '500ms cubic-bezier(0.0, 0.0, 0.2, 1)',
                style({
                  opacity: 0,
                  transform: 'rotate3d(0, 0, 1, 15deg) translateX(-50%) translateY(75%) scale(0.5)',
                  'transform-origin': 'top left'
                })
              )
            ],
            { optional: true }
          ),
          query(':enter', animate('250ms cubic-bezier(0.0, 0.0, 0.2, 1)', style('*')), { optional: true })
        ])
      ])
    ])
  ]
})

The cool thing about Angular animations is that we have access to both “previous” and “next” page – so when user switches from Terms of Use to Privacy Policy, we can access the previous page (Terms of Use) with :leave selector and the next page (Privacy Policy) with :enter query. We can even group the two animations together by using group. We could have different animations depending which page is the “previous” and which page is the “next” one, but we are going to have the same animation for all transitions; this is defined in transition: '* => *'means “from any value to any value”.

We have 3 instructions grouped together, let’s dissect them one by one.

First instruction is the initial state for the new page:

          query(
            ':enter',
            style({
              position: 'absolute',
              transform: 'translateX(100%) rotate3d(1, 1, 1, -15deg) scale(1.5)',
              opacity: 0.5
            }),
            {
              optional: true
            }
          ),

We are setting the new page to be outside of the visible area on the right with translateX(100%), give it a little rotation with rotate3d(1, 1, 1, -15deg) and scale it 150% with scale(1.5). We also set the opacity to 50% so the new page will fly in from the right, while scaling down to normal size, rotating and becoming opaque. The option optional: true is necessary because :enter query will only work when pages are changed and would fail on initial page rendering.

The second instruction is for the old page to fly away:

          query(
            ':leave',
            [
              style({ position: 'absolute', 'z-index': -1 }),
              animate(
                '500ms cubic-bezier(0.0, 0.0, 0.2, 1)',
                style({
                  opacity: 0,
                  transform: 'rotate3d(0, 0, 1, 15deg) translateX(-50%) translateY(75%) scale(0.5)',
                  'transform-origin': 'top left'
                })
              )
            ],
            { optional: true }
          ),

This goes in 2 steps defined in the array: first step to set style to { position: 'absolute', 'z-index': -1 } – we need this so the old page does not affect the layout. Without this step, the new page renders underneath the old one, because both exist at the time of transition, and that doesn’t look good at all. We set z-index to -1 because we want the old page to appear underneath the new one. The second instruction is to fly the old page to the left while rotating it and scaling it down.

And then finally, we reset styles for our new page with the 3rd instruction:

query(':enter', animate('250ms cubic-bezier(0.0, 0.0, 0.2, 1)', style('*')), { optional: true })

Here’s how this looks like when we increase the animation times by 3 seconds:

And here’s what it looks like with short animation times:

Pretty cool, right? 🙂

We also add ngx-markdown npm package in this PR to render pages Privacy Policy and Terms of Service – the markdown is generated by the lovely tool Privacy Policy Generator. The home page is just custom HTML, both in English and Russian – please check it out, hopefully the purpose of LaaS becomes more clear now.

Step 3: i18n, multi-lingual support

Fun fact: i18n stands for internationalization and can you guess what 18 means? Hint: it’s the same meaning as 11 has in a11y.

We are going to support English, because everybody speaks it, and Russian, because I speak it. Let’s dive in: https://github.com/laas-sh/laas-sh-app/pull/3/files (deployed to https://03-multilingual.laas.sh).

We’ll need to render pages in correct language in SSR based on a cookie, so we make some changes to support cookies server-side in server.ts:

//...
import * as cookieParser from 'cookie-parser';
//...
app.use(cookieParser());
//...
app.engine('html', (_, options, callback) =>
  ngExpressEngine({
    bootstrap: AppServerModuleNgFactory,
    providers: [
      provideModuleMap(LAZY_MODULE_MAP),
      {
        provide: 'REQUEST',
        useValue: options.req
      },
      {
        provide: 'RESPONSE',
        useValue: options.req.res
      }
    ]
  })(_, options, callback)
);

Just following instructions from @ngx-utils/cookies npm package here. What this does is it attaches parsed cookies to the request object by using cookie-parser and then injects both request and response Express objects to Angular DI container, so the cookies can be used in the app during server side rendering.

There are a few changes app.module.ts to enable multi-lingual support in the app:

// AoT requires an exported function for factories
export function httpLoaderFactory(httpClient: HttpClient) {
  return new TranslateHttpLoader(httpClient);
}

@NgModule({
//...    
  imports: [
//...    
    PrebootModule.withConfig({ appRoot: 'app-root' }),
    BrowserCookiesModule.forRoot(),
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: httpLoaderFactory,
        deps: [HttpClient]
      }
    })
  ],
  providers: [
    TranslateStore,
    TranslateService,
    {
      provide: APP_INITIALIZER,
      useFactory: (appInitializerService: AppInitializerService) => () => appInitializerService.init(),
      deps: [AppInitializerService],
      multi: true
    }
  ],
//...
})

Here, we add preboot – a neat npm module that helps with transition between server side rendered HTML and fully bootstrapped app – it’s a nice addition to any app with SSR (should have been part of the previous PR, but oh well). Once server side rendered HTML is delivered to the user, the browser will display the page and then begin bootstrapping the SPA code. So there’s a period of time when user can interact with the HTML already, but our Javascript is not bootstrapped yet, so we will lose these user interactions and leave the user frustrated. Preboot solves this problem by buffering the interactions and then replaying them once the app is ready to process user-initiated events.

For internationalization we are using ngx-translate – it’s a much easier solution than the one explained in the official Angular docs; the official one doesn’t actually support Russian correctly – there’s no ru-RU locale for example (USA sanctions?), and the other ru-* locales didn’t work for me. On the other hand, ngx-translate is very simple and free of problems. On the browser side, we are using TranslateHttpLoader to load translation files over HTTP, note how TranslateModule is bootstrapped for DI. We want to preload the translation before the app is considered bootstrapped – Angular provides APP_INITIALIZER mechanism just for that and we are going to add appInitializerService.init()to the bootstrapping process: the function provided for useFactory just needs to return a Promise so Angular can wait until that’s resolved before the app is considered bootstrapped.

On the server side, TranslateModule is bootstrapped in app.server.module.ts:

// AoT requires an exported function for factories
export function serverLoaderFactory(injector: Injector) {
  const request = injector.get('REQUEST');
  const language = getLanguageFromString(request.cookies[Cookie.LANGUAGE]);
  return new TranslateServerLoader(language);
}
//...
@NgModule({
  imports: [
//...      
    ServerCookiesModule.forRoot(),
//...
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: serverLoaderFactory,
        deps: [Injector]
      }
    }),

TranslateServerLoader is a simple service to load translation by language from the file system on the server side, it’s defined in translate.server.loader.ts:

import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { Language } from '@laas/shared';
const fs = require('fs');

export class TranslateServerLoader implements TranslateLoader {
  language: Language;

  constructor(language: Language) {
    this.language = language;
  }

  getTranslation(): Observable<any> {
    return new Observable(observer => {
      observer.next(JSON.parse(fs.readFileSync(`./dist/browser/assets/i18n/${this.language}.json`, 'utf8')));
      observer.complete();
    });
  }
}

The actual translation is performed by translate pipe; here’s how it looks like in footer.component.html:

        {{ footerButton.caption | translate }}
<!-- ... -->
      <a target="_blank" mat-button href="https://blog.laas.sh">{{ 'Blog' | translate }}</a>
<!-- ... -->
      <mat-select [value]="languageService.currentLanguage$ | async" (valueChange)="onLanguageChange($event)">
        <mat-option *ngFor="let language of languages" [value]="language"> {{ languageNames[language] }} </mat-option>
      </mat-select>

If you pull this branch (git checkout 03-multilingual) and npm start, you can see how this works in your browser at http://localhost:4200/:

We can also validate that server side rendering reflects selected language by parsing the cookies; to do that, run with SSR (npm run build:ssr && npm run serve:ssr and open http://localhost:4000), select Russian language in the dropdown, then refresh the page and view source, you’ll notice that page is rendered correctly, with the Russian translations:

Log In button in the header.component.html is translated the same way:

    <button *ngIf="!(authService.user$ | async)" mat-raised-button (click)="authService.login()">
      {{ 'Log In' | translate }}
    </button>

All language-related functionality is encapsulated in language.service.ts:

import { Injectable } from '@angular/core';
import { getLanguageFromString, Cookie, Language } from '@laas/shared';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { CookiesService } from '@ngx-utils/cookies';

@Injectable({
  providedIn: 'root'
})
export class LanguageService {
  private _currentLanguage$ = new BehaviorSubject<Language>(undefined);
  currentLanguage$ = this._currentLanguage$.asObservable();

  constructor(public translateService: TranslateService, private cookiesService: CookiesService) {}

  init(): Promise<any> {
    const defaultLanguage = getLanguageFromString(this.translateService.getBrowserLang());
    this.translateService.setDefaultLang(defaultLanguage);
    return this._setCurrentLanguage(
      getLanguageFromString(this.cookiesService.get(Cookie.LANGUAGE), defaultLanguage)
    ).toPromise();
  }

  setCurrentLanguage(language: Language) {
    this._setCurrentLanguage(language);
    this.cookiesService.put(Cookie.LANGUAGE, language);
  }

  private _setCurrentLanguage(language: Language): Observable<any> {
    this._currentLanguage$.next(language);
    return this.translateService.use(language);
  }
}

Here, we expose the currentLanguage$ observable, init() method returns a Promise used in APP_INITIALIZER bootstrapping process, and setCurrentLanguage is used to switch language (when user makes a selection in the footer dropdown). We also deal with cookies here. Note how language will default to translateService.getBrowserLang() if no cookie is set.

Translation files are found under apps/frontend/src/assets/i18n/. English “translation” in en.json is actually an empty object at this point:

{}

The reason this object is empty is that when translation key is not found, it’s used as is, so we don’t have to translate “Blog” for example (to English that is), as it’s the key for other languages and can be used as is. When we need to translate bigger blocks of text, we can give them names – same idea as variable names, – and then provide “translation” from the variable name (key) to English as well as other languages.

Russian translations are found in ru.json:

{
  "Log In": "Вход для пользователей",
  "Privacy Policy": "Политика конфиденциальности",
  "Terms of Service": "Условия обслуживания",
  "Blog": "Блог"
}

Even though we only deal with one cookie at this point, we’re introducing enum Cookie with a single key – it’s always a good idea to list things like cookies in an enum, so that we can quickly see which cookies we work with by looking at the file cookie.enum.ts:

export enum Cookie {
  LANGUAGE = 'language'
}

For the languages, we extend language.enum.ts with language names – these are never translated and are used in the footer as is, each language name specified both in English and in the native language to make sure the users are never confused:

export enum Language {
  ENGLISH = 'en',
  RUSSIAN = 'ru'
}

export const languages = Object.values(Language);

export const languageNames = {
  [Language.ENGLISH]: 'English',
  [Language.RUSSIAN]: 'Russian (Русский)'
};

export const getLanguageFromString = (languageString: string, defaultLanguage = Language.ENGLISH): Language => {
  if (languages.includes(languageString)) {
    return languageString as Language;
  }
  return defaultLanguage;
};

I hope you enjoyed this tutorial, and found it helpful. We’re getting ready for world domination, so i18n is a must. And, oh! – btw, – 18 stands for the number of omitted characters in the word internationalization (same as 11 stands for the number of skipped letters in accessibility) 🙂

Cheers,
Andrew

Step 2: SSR, Server-Side Rendering

Our second PR adds SSR: https://github.com/laas-sh/laas-sh-app/pull/2/files (deployed to https://02-ssr.laas.sh).

SSR is pretty much obligatory for any SPA (single page application) – without the Server-Side Rendering, the search engines don’t see your app content, only an empty shell. If you checkout the first branch 01-initial-setup, run npm start and then open page source in your browser, you should see this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>LaaS: Lightning as a Service</title>
    <base href="/" />

    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <link
      rel="apple-touch-icon"
      sizes="180x180"
      href="/assets/favicon/apple-touch-icon.png"
    />
    <link
      rel="icon"
      type="image/png"
      sizes="32x32"
      href="/assets/favicon/favicon-32x32.png"
    />
    <link
      rel="icon"
      type="image/png"
      sizes="16x16"
      href="/assets/favicon/favicon-16x16.png"
    />
    <link rel="manifest" href="/assets/favicon/site.webmanifest" />
    <link
      rel="mask-icon"
      href="/assets/favicon/safari-pinned-tab.svg"
      color="#5bbad5"
    />
    <meta name="msapplication-TileColor" content="#da532c" />
    <meta name="theme-color" content="#ffffff" />
  </head>
  <body>
    <app-root></app-root>
  <script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="es2015-polyfills.js" nomodule></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript" src="styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script></body>
</html>

This is not search engine friendly as it doesn’t contain any searchable text, only code. The text will get rendered by Javascript as the app bootstraps – this is good enough for our users, but not good enough for web crawling bots. This problem is fixed with SSR. Switch to 02-ssr branch, run npm run build:ssr, then npm run serve:ssr and you should see fully rendered HTML with all the text, so it can be crawled and indexed by Google and other search engines. We don’t have much text on the home page, but you can search for it to validate this is true:

This is achieved by adding Angular Universal – please refer to the official tutorial, it’s pretty good. Universal basically adds a new build target in angular.json:

        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist/server",
            "main": "apps/frontend/src/main.server.ts",
            "tsConfig": "apps/frontend/tsconfig.server.json"
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "apps/frontend/src/environments/environment.ts",
                  "with": "apps/frontend/src/environments/environment.prod.ts"
                }
              ]
            }
          }
        }

This new target builds the app for the server. The app is then served by a simple Express app found in the server.ts – this code is created automatically by installing Universal. Things we need to do manually are very few.

We also added a cute little “beta” sign to header.component.html:

There’s not much else in this PR, it’s mostly auto-generated code. We also renamed LoginService to AuthService here and introduced npm run tests command that runs in sequence all linting, then all unit tests with Jest and then all end to end tests with Cypress – we don’t have a lot of tests yet, but they all pass 🙂

Coming up next: multi-lingual support.

Cheers,
Andrew

Step 1: Initial Setup

We begin here, with our first PR: https://github.com/laas-sh/laas-sh-app/pull/1/files.

To follow along, run these commands one by one:

git clone git@github.com:laas-sh/laas-sh-app.git
cd laas-sh-app/
git checkout 01-initial-setup
npm i
npm start

Then open http://localhost:4200/ in your browser, you should see this:

The app doesn’t do much at this point, but there are quite a few things we bootstrap here.

First of all, it’s an Nx monorepo already, with a single Angular 7 app called “frontend” inside. Nx is great, it allows us to develop both frontend and backend in a single repository, which allows for easy typing information and functionality sharing. It also helps us to set up Jest and Cypress, and has a few more “Angular CLI power-ups” to improve development experience.

We add Prettier right away in our initial setup, because prettier is just so good. No need to worry about individual code style preferences, no holywars around spaces and brackets. The bigger the team, the more benefit you get from using Prettier. We customize it just a bit in .prettierrc, otherwise it’s just the default configuration:

{
  "printWidth": 120,
  "singleQuote": true
}

We add a few routes in app-routing.module.ts:

const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    component: HomePageComponent,
  },
  {
    path: Page.PRIVACY_POLICY,
    component: PrivacyPolicyPageComponent
  },
  {
    path: Page.TERMS_OF_SERVICE,
    component: TermsOfServicePageComponent
  },
  {
    path: '**',
    redirectTo: ''
  }
];

Note how we are using an enum for Page – it’s always helpful for readability to make enums where a fixed set of pre-defined values needs to be handled. We start with only 3 pages: Home (default), Privacy Policy and Terms Of Service – this is a fixed set of pre-defined values, so a perfect candidate for an enum. It’s a great imrovement over a free-form string, because it provides type safety and documentation – by simply looking at the enum, we know what pages we have in the app, this way we are building our DSL, domain-specific language.

Page enum values are relative page urls – by encapsulating this information in the enum we are also following the DRY principle – arguably, the most important principle of software development. If we want to change the url in the future, we won’t have to touch multiple files, the actual url is only found in a single place, right here in page.enum.ts:

export enum Page {
  PRIVACY_POLICY = 'privacy-policy',
  TERMS_OF_SERVICE = 'terms-of-service',
}

We are building a Bitcoin app, Bitcoin is borderless, so we will need multi-lingual support. We add a dropdown for language selection in our footer.component.html:

      <mat-select [value]="'English'">
        <mat-option
          *ngFor="let language of ['English', 'Russian']"
          [value]="language"
        >
          {{ language }}
        </mat-option>
      </mat-select>

It’s a fake right now, but we will implement this soon in an upcoming Pull Request. Note that we are using Material Design in this project, it’s a natural choice for an Angular app that works great on mobile as well as the web.

There’s a bit of interactivity already going on in the header:

This is again a fake, but not a complete fake: we introduce LoginService that exposes login and logout methods as well as Observable user$. Note how all of our components use ChangeDetectionStrategy.OnPush – there’s no reason to use default change detection strategy, using observables with async pipe is straightforward, and not using it is very expensive in terms of app performance, plus it’s harder to reason about. When everything is streamed asynchronously via observables, things are simple and fast. Here’s what LoginService looks like:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { User } from '@laas/shared';

@Injectable({
  providedIn: 'root'
})
export class LoginService {
  private _user$ = new BehaviorSubject<User>(undefined);
  get user$() {
    return this._user$.asObservable();
  }

  login() {
    this._user$.next({
      avatar: {
        small: {
          url: '/assets/favicon/favicon-32x32.png'
        }
      },
      firstName: 'Vasya',
      lastName: 'Pupkin',
      username: 'VPupkin'
    });
  }

  logout() {
    this._user$.next(null);
  }
}

This is a nice little pattern we’re gonna use a lot – a service that exposes observables and methods to manipulate the observables. The view components consume the observables by using async pipe. The actual state is encapsulated in the service with a private BehaviorSubject, exposed publicly using _privateSubject.asObservable(). This pattern is called a Facade (when describing public API), and a Service with a Subject (when describing the internal implementation). Using NgRx is not an improvement over Service with a Subject in my experience, as it leads to more complex code that is harder to maintain and extend – but, by using Facade we can switch to using NgRx without having to modify any of the components, because from the point of view of the components, they will still consume the same API: call a method to affect the state and just subscribe to observables to get state changes streamed to you. The inside of the service can dispatch an action and expose an observable made from selectors if you choose to use NgRx, it wouldn’t matter on the outside, the public API of the Facade stays the same.

Here’s another place where we’re using this pattern – MiscService:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { MediaObserver } from '@angular/flex-layout';

@Injectable({
  providedIn: 'root'
})
export class MiscService {
  private _isMobile$ = new BehaviorSubject<boolean>(undefined);
  get isMobile$() {
    return this._isMobile$.asObservable();
  }

  constructor(public mediaObserver: MediaObserver) {
    this.mediaObserver.asObservable().subscribe(mediaChanges => {
      const isMobile = !!mediaChanges.find(
        mediaChange => mediaChange.mqAlias === 'lt-md'
      );
      if (this._isMobile$.value !== isMobile) {
        this._isMobile$.next(isMobile);
      }
    });
  }
}

We are using Angular flex-layout here to expose isMobile$ observable – as user changes window size, we’re going to stream true or false through this observable to indicate whether or not we’re on mobile phone sized screen. This is going to be useful for responsive design. Note how header layout changes for different page width:

This is enabled by isMobile$ observable in header.component.html:

    <button
      mat-icon-button
      *ngIf="(miscService.isMobile$ | async) && (loginService.user$ | async)"
    >
      <mat-icon *ngIf="true">menu</mat-icon>
      <mat-icon *ngIf="false">close</mat-icon>
    </button>

We also introduce shared library, with a couple enums and interfaces, for example User interface in user.interface.ts:

export interface User {
  avatar: {
    small: Image;
  }
  firstName: string;
  lastName: string;
  username: string;
}

Conclusion

We have set up a monorepo created with Nx, with a single Angular 7 app, one shared library, basic routing, responsive design based on Angular flex-layout and a fake login service to simulate authentication state. We set some code quality standards by installing Prettier, setting up TSLint and insisting on ChangeDetectionStrategy.OnPush. We are getting ready for multi-lingual support, for unit testing with Jest and end to end testing with Cypress. It’s all broad strokes just now, but the outline of the project hopefully begins to emerge.

Reach out to me with any questions and suggestions: @ALeschinsky on Twitter is probably the best way to contact me.

Cheers,
Andrew

Show me how you move

This blog is going to be very opinionated. I’ve been writing software professionally for almost 30 years now, and before that I was competing on a global level as a school kid (representing Ukraine in a World Olympiad in Informatics back in 1993), so I have a lot of wisdom to share (and not too much modesty), so welcome if you want to learn.

Before we get to LaaS code, I wanted to talk about moving through the code. This aspect is easy to overlook and I don’t see a lot of articles about it anywhere, which is a shame. Programming is similar to video games in a way – you have a map of the world in your mind, you remember where everything’s at, and to be efficient you have to be able to move around with ease. Ideally with grace even, with no unnecessary efforts, very quickly and without stumbling. Look how professional Starcraft player fingers dance on the keyboard:

When playing to win, you don’t have time to think about where the individual fingers go, you need to think about resources, armies, your long-term strategy – the fingers just do the work following your intentions, subconsciously.

For software engineering, the first step in terms of moving gracefully is of course to touch type – if you have to look down to find the keys with your eyes, do yourself a favour and learn to type without looking ASAP. Human brain has a limited capacity, and if you’re thinking about individual keys you are not thinking about program architecture, algorithm branches, edge cases and other things you could be thinking about – those brain “CPU cycles” are wasted.

Pretty much everyone touch types nowadays, which is great. But there are at least 2 more techniques that are just as crucial for being productive as touch typing – only contrary to touch typing I see most of programmers not using them. It’s like watching someone play Doom for the first time – instead of having fun and killing demons they’ll just bump into walls helplessly.

Go to Declaration and Back

First of these 2 techniques is simply going back. Most programmers use “Go to declaration” feature already – it’s just too good, even the beginners use it. But it only gives you a fraction of value if you don’t have a shortcut to go back. Sometimes I just can’t help but notice how people literally look lost after “going in” (e.g. going to declaration) a couple of times, and have to use editor tabs, or global search, or look for the file in the project directory tree to return… and if it’s the same file, they scroll… such a waste of time and focus! This in turn makes you afraid to go to declaration often, because getting lost sucks.

But it’s a problem that’s very easy to fix: just go back by using “Back” feature (duh!). It’s incredible how underused this feature is. Assign a shortcut to it that you like (I use Ctrl+Backspace), and use it all the time. Never go back by searching or clicking around – the previous location is always one keystroke away. In Webstorm, go to Settings, Keymap and search for Back to assign a custom hot key:

This changes the workflow. You see a function that you don’t quite remember what it’s doing? Ctrl+click it – or better yet, press your keyboard shortcut to go in (keyboard is way more efficient than mouse or god forbid touch pad), – take a look, and go back with Ctrl+Backspace or whatever your preferred key combination is. It takes a second, and you don’t lose your train of thought this way. Since it’s so cheap to do, you can jump to declaration multiple times in a row – you see that function, and there’s another function, and then there’s an interface you don’t quite remember what the shape of it is – just go in the second, the third time, take a look, and come back quickly to where you started. No mental effort required, the fingers just follow your intention and you don’t lose your train of thought.

This is so simple, and such a great boost to productivity, I really cannot emphasise this enough. By doing this all the time while developing, you get to know the code in a very different manner, it becomes like a big hyper-linked web of things, you start to feel how the project is made of interconnected objects and pathways between them. Very much like in a video game, you build a mind map of the world you’re in – and you move through it gracefully, without giving a second thought to the mechanics of fingering. And if you don’t feel the game, you’re gonna lose to someone who does every single time, they will just run circles around you.

Bookmarks

This technique serves the same purpose as going to declaration and back: to prevent getting lost in the code. When working on a piece of code for a long time, just bookmark it with Ctrl+1 – this way it’s always just one keystroke away. See a method and want to jump through the usages? Just press your shortcut for Find Usages, scroll through the list with Next occurrence and Previous occurrence (obviously, using keyboard shortcuts), and wander around the code with ease and confidence – the piece you’re working on at the moment is always just one keystroke away, so you are never really lost.

Bookmarks remind me of Starcraft groups a lot – not only the actual hot keys are the same (Ctrl + number), but the feeling of control it gives you is very similar. In Starcraft, you have multiple groups within your army that you can switch between and control independently, and in programming, you have multiple pieces of code you’re focusing on at the same time. It could be, for instance, a unit test and the code under test (I use Ctrl+0 for the test I’m currently working on, and Ctrl+1 for the code under test), or controller and template, or one main function and a couple auxiliary functions, or a service and interface definitions and some dependencies – there are up to 10 bookmarks you can have, which is more than I personally can handle, I don’t really use more than 3. But I pretty much always have at least one bookmark – being able to quickly go back and switch between 2-3 places in the code is just invaluable in my opinion.

Conclusion

There are more important techniques, of course, but these 2 – Go to Declaration and Back and Bookmarks, – I find to be the most undervalued and underused. If you’re not using these IDE features yet, give them a try, I guarantee you will increase productivity this way (be a happier developer and make more money, yeah?) Good luck, reach out to me with questions and suggestions; feedback is always appreciated – and stay tuned for more good stuff soon 🙂

LaaS: Lightning as a Service

We are finally ready to announce the project we have been working on for the last year!

It’s a fully Open-Source project, but it’s also a for-profit business, with a team of paid professionals working on the code. Our goal is to build a thriving community around it, and hopefully make some money as well, sort of like WordPress, MySQL and many other successful Open Source projects do.

The project is called LaaS and it’s going to live at https://laas.sh. There’s nothing there just yet, but we have most of the code developed by now. Next step is we are going to start releasing it in a series of step by step tutorials on how we built it from scratch – this way it’s also going to grow into a free educational resource on Angular, Nx, Docker, btcd+lnd, socket.io, jest, cypress and more. We’re going to really polish it out, spend time on things like multi-lingual support, 100% unit test coverage, thorough end to end tests. Bitcoin is cool and different, and we are also trying something cool and different here – it’s not a standard business model, it’s an investment into the future, into the community, at least that’s what we’re aiming at.

The name LaaS is a play on words: there are SaaS, PaaS and IaaS – Software as a Service, Platform as a Service and Infrastructure as a Service, – and now there’s LaaS – Lightning as a Service. It’s a service for people who don’t want to run a full Bitcoin node and a Lightning Network node, manage channels, worry about incoming and outgoing liquidity, backups – they can just use Lightning as a Service, via the UI or the API. The .sh domain suits us perfectly: laas.sh looks like a name of an executable Linux shell script, which is really cute, we’re really happy with it.

LaaS is a monorepo built with modern frameworks and tools, see our README.md on GitHub for more details. It consists of two parts:

The first part is the API: it’s a backend application built on top of a dockerized stack that includes btcd, lnd, PostgreSQL and ELK Stack. We will publish detailed step by step installation instructions for everything you need to run LaaS API as a self hosted service for your own projects on your own servers and dev laptops. You can just use it yourself or resell it and charge your users a fee, it’s fully yours if you are running it, you can modify it as you like (we hope you’ll submit a Pull Request if it’s something useful for the others in the community, deal?), we’re releasing everything under the MIT License. There will be tutorials and (probably) video lessons on how to set up enterprise grade deployment pipeline on bare metal from scratch, with GitLab running tests and deploying into different environments, with Portainer giving you access to all your containers, with logs in Kibana, with automatic backups and monitoring tools.

Bitcoin is all about low time preference (if you don’t understand what that means, please read The Bitcoin Standard immediately! I’ll wait) – so prepare for a long ride. Although we have most of the code developed by now, we want to really polish it out before open-sourcing it on GitHub. We have very strict due dates in our roadmap; it’s actually the same due date on all of the milestones: once it’s ready. We are actually going to open-source the API last, we’ll start with the second part first:

The second part is the app: it’s a simple hosted Bitcoin wallet where registered users can receive and send Bitcoin, on-chain or via Lightning Network. There is a QR-code scanner, a simple report on transactions, a PWA with push notifications for mobile and tablets, all the essentials that every Bitcoin-based project needs. We want you to build on top of it – come up with ideas, build cool things, go nuts! Use LaaS as a starting point – just clone it from GitHub and you are ready to work on your features in minutes.

As we build our app in a series of tutorials, each step is going to be pushed to <step#> branch in git and deployed to https://<step#>.laas.sh. For example, the first URL will look like https://01-initial-setup.laas.sh. The latest step will also be found in master branch and deployed to https://laas.sh. Once we’re done building out everything with stubs, we will connect the app to LaaS API exposed at https://api.laas.sh – the API is not going to be open-sourced at this point yet, but it will be made available to everyone via the app. Every registered user gets a hosted wallet, and also an API key. It’s going to be a paid API, and that’s how we’re going to make money – hopefully it’s gonna be enough to cover the web hosting, but if everything goes according to the plan, it will cover our lambos as well (evil laughter). Once this is done, we have a working business, with the app part fully open-sourced. The final milestone is open-sourcing the API part, which we will approach in the same manner, as a series of tutorials where we share the lessons learned during the first year of development.

It’s just a roadmap at the moment, but we are pretty much production ready, so stay tuned for regular updates. Please do contact me with any questions, suggestions and offers to help, I’m @ALeschinsky on Twitter. You’ll meet the other team members on GitHub, most of the blogging here I’ll probably do myself. One area we’d love immediate help with is translation to other languages – we are going to start with English and Russian, please reach out to me if you want to help translating LaaS into your language. Bitcoin is borderless, Open Source Software is borderless, and LaaS aspires to be borderless as well, so all languages are welcome.

Wish us luck! 🙂

Cheers,
Andrew