TypeScript And The Enterprise

TypeScript, the nearly decade-old Microsoft project, increases in popular by the day. What is it?

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

It offers the many benefits of JavaScript with the safety of types. The ThoughtWorks Radar explains the hype:

Large-scale projects benefit most from the type safety. Our developers favor its minimal configuration management, well-integrated IDE support and its ability to refactor code safely and gradually adopt types.

ThoughtWorks marked TypeScript as “adopt” in early 2019. Here’s a few reasons why, when compared to JavaScript, the technology is a good choice today:

  • Prioritizes reliable software: TypeScript alleviates common JavaScript runtime bugs by encouraging developers to handle errors at build time, reinforcing a program’s correctness and ensuring users encounter less crashes. Strong typing permits sweeping refactors and rewrites with safety.
  • Quality developer tooling: IDE features like code completion, symbol lookup, and inline compiler errors are a huge productivity boost, and TypeScript’s robust type system makes them possible. TypeScript’s language server is so good that it can partially provide these features to plain JavaScript in Visual Studio Code.
  • Less testing required: A typed language largely eliminates common unit and integration tests around parameters and return values. This also benefits the runtime: typings remove the need for libraries like prop-types, resulting in smaller bundles.
  • Bright future: TypeScript quickly adopts the latest ECMAScript specification with powerful typing primitives. This allows developers to use tomorrow’s language features today, and ship code that runs in any JavaScript environment.

The Enterprise

Engineers at large companies choose to author their web user interfaces, services, and native applications in JavaScript. Adopting TypeScript poses unique advantages at enterprise scale.

Experienced and junior engineers alike want to use TypeScript: senior practitioners recognize its value, and newbies know of its increasing importance in the industry. Adopting this upcoming technology and training your company’s engineers on its use can be a big motivator in hiring and skill retention.

Applications written in TypeScript tend to be less prone to bugs, but there’s some additional benefits for large teams:

  • Quicker on-boarding: Engineering resources shift drastically depending on the business’s needs: new teams form and existing teams change focus to ensure priority projects ship. TypeScript’s types serve as additional documentation on internal application state, data schemas, and data transformations of the program; types can help answer the ”why” questions that newcomers often have. Coupled with type safety, it helps newcomers confidently ship code.
  • Accelerated development: Time to market is incredibly important at the enterprise level, and TypeScript can be a competitive advantage, as my team observed with Walmart’s Baby Registry.
  • Manage technical debt: Enterprise software often contains feature-rich, long-lived code bases, a recipe that makes continued development increasingly difficult. TypeScript especially shines in large programs, where its safety mechanisms can guarantee new code doesn’t break the system and internal refactors don’t break public-facing interfaces. The act of adding types reveals similarities between disparate pieces of code, which can lead to useful refactors.

TypeScript can foster cross-team collaboration, an important yet often fleeting goal at companies with hundreds of engineers. This begins with a joint effort in typing the company’s microservices and APIs in reusable packages, and continues to community rewrites of shared code bases. A typed language also presents a more inclusive code base for practitioners of traditional enterprise languages (Java and C++): I’ve witnessed these engineers willingly contribute to TypeScript code bases.

Means of Adoption

TypeScript supports incremental on-boarding of projects, with increasing improvements in safety and developer ergonomics at each step. This favors teams adopting the technology at their own pace. A rough outline of how this looks:

  1. Installing and configuring the compiler
  2. Porting JavaScript files to TypeScript files, adding type annotations
  3. Opting into stricter checks
  4. Progressively removing loose type definitions

TypeScript adoption becomes easier for application teams if the shared code layer (UI components, service clients, utility libraries, etc.) offer support via rich types: this ensures developers get all the advantages of building on a typed ecosystem without the additional burden of handling these types in each code base. Rewriting shared code in TypeScript is preferred: it ensures accurate types for consumers and will guaranteed surface a host of bugs in the common layer.

Embracing TypeScript at enterprise companies comes with a cost: refactoring existing JavaScript to TypeScript in code bases is non-trivial, and it takes time for engineers to learn a new tool. However, the advantages of more reliable software for users, tooling that engineers enjoy, faster on-boarding, improved handling of large code bases, and the potential to build a cross-team community are worth it.

Enthusiastic about TypeScript? Great news: we’re hiring in Portland, Oregon. Check out our job listings on careers.walmart.com or shoot me an email for more details.

Setting up the Shell for Android Development

Developing for Android through Android Studio can be cumbersome at times, especially if you just want to build your application and test it on an emulator. These common development tasks can be a little faster by running commands in a terminal!

First, you'll want to set an environment variable that points to the Android SDK installed by Android Studio. I've called it ANDROID_HOME:

export ANDROID_HOME="$HOME/Library/Android/sdk"

Next, add the SDK's tools to your PATH:

export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH"

This adds useful Android binaries (adb, emulator, and other tools).

Launching Android Studio can be slow, especially if you just want to start an existing emulator. This helpful shell function uses select to provide an interactive shell menu for launching an emulator from any existing avd:

start_emulator() {
  select AVD in $(emulator -list-avds); do
    if [ -n "$AVD" ]; then
      emulator -avd "$AVD" $@
      break
    fi
  done
}

Android Studio's build/run functionality can be slow, especially for large applications. After building your app from the command line (./gradlew assembleWhatever) you can install the resulting .apk file in a running emulator:

install_apk() {
  select APK in $(find . -type f -name "*.apk"); do
    if [ -n "$APK" ]; then
      adb install -r "$APK"
      break
    fi
  done
}

Launching Baby Registry on Walmart.com

Walmart Labs launched a new baby registry experience at the beginning of April. I was part of the engineering team that rewrote the web application and services. Walmart doesn’t ship new customer-facing products frequently; this presents an interesting case study into how a large corporation creates something new.

Engineering efforts began in late 2018: a new team of mobile, service and web folks transitioned from well-staffed legacy applications or joined specifically for the project. I found myself looking for web work after deciding to leave the core mobile team (more on that later), and registry sounded like a fun return to my old JavaScript stack.

Services

The MVP’s customer entrypoint centered around an interactive chat that translated users’ preferences to a fully stocked registry. This also (hopefully) makes registry creation a bit more fun for users. Services borrowed an engineer for the initial work, a generic chat service with canned questions using GraphQL and Apollo Server via hapi. Our team quickly found the codebase inflexible as requirements shifted. Chat composed a small portion of the experience: we also needed several queries and mutations to power the clients’ views.

Schema-Driven

One of our fantastic iOS engineers introduced us to the concept of “backend-for-frontend,” where a single service collates responses from multiple APIs. Clients only request data from backend-for-frontend, improving network overhead and caching. Fortunately, iOS, Android and web maintain similar user interfaces, so all clients could rely on the same queries and mutations to power their experiences. We crafted a schema that sensibly mapped services’ responses for the clients.

Separately, I decided that our original chat design didn’t meet our needs. A more flexible workflow presented itself:

+----------+
| Question |---> Answer
+----------+       |
     ^             |
     |             |
     +-------------+

The previous answers are passed into the next question, which is a function that returns an answer. This allows for question branching and the possibility of a more dynamic series of responses.

TypeScript

I wanted to explore TypeScript due to its recent praise. TypeScript was new to many folks on the team, including me. After a week of learning the basics, I found myself more productive writing TypeScript compared to JavaScript: refactors were a breeze (especially with VS Code’s language integration), unit tests required less assertions due to type safety, and adding features felt natural.

Web Application

The web team started development in November 2018 using React, redux, and Walmart’s homegrown framework, electrode. We lost our senior engineer to another team early on, so I assisted with code reviews while working on services. After the services team gained another staff engineer, I transitioned over to web full time.

More TypeScript

Buoyed by the benefits of TypeScript on services, we migrated our web codebase to TypeScript. The front-end has more complexity due to the large DOM and React API surface areas. We finally fully migrated to TypeScript in January – not without a handful of any safety escapes. This unlocked massive development speedup over the following weeks.

We opted for a minimal GraphQL client to avoid the overhead of Apollo’s client. The service relied on gql2ts for build-time response alignment with the schema. This assisted our web client development as we were able to use the same schema-derived types. For example:

type Registry {
  metadata: RegistryMetadata!
  items: [RegistryItem!]!
}
type RegistryMetadata {
  date: String!
  gender: RegistryGender!
  title: String!
  # ...
}
type RegistryItem {
  id: ID!
  name: String!
  price: Float!
  quantity: Int
  # ...
}

This translates to these TypeScript interfaces:

export interface IRegistry {
  metadata: IRegistryMetadata
  items: IRegistryItem[]
}
export interface IRegistryMetadata {
  date: string
  gender: RegistryGender
  title: string
  // ...
}
export interface IRegistryItem {
  id: string
  name: string
  price: number
  quantity: number | null
  // ...
}

The web client only requires fields necessary for its UI. Using RegistryItem as an example, we only need name and price, so we only request those fields:

import { IRegistry, IRegistryItem } from 'my-types'

export type RegistryFields = Omit<IRegistry, 'items'> & {
  items: Pick<IRegistryItem, 'name' | 'price'>
}

export const registryFields = `{
  metadata: {
    date
    gender
    title
    # ...
  }
  items {
    name
    price
  }
}`

The RegistryField type is paired with the registryFields schema over a genericized fetch-based wrapper, and RegistryField is used in a redux reducer’s state. The alignment of types-to-request is a bit fragile: changes require modifying both RegistryFields and registryFields. It does, however, presents huge benefits: the client now aligns correctly with the API, and changes result in a breaking build instead of runtime errors.

A Web of Problems

Product and design focused on mobile applications, leaving web as an afterthought. Our design department had a high turnover rate: at least three different designers owned visuals throughout the project, leaving us with incomplete mockups and no UX specification. While we had a wealth of web development expertise, most were new to the many challenges of web development at Walmart Labs. The result? Engineer was behind big time as a strict launch deadline approached.

My lovely engineering manager’s continued efforts to warn leadership of the problems eventually paid off when we received reinforcements, including an old MRN cohort, Chris. We re-prioritized the backlog for a bare bones MVP, and began hacking.

At the end of Februrary we lacked functional pages for any part of the experience. Chris and I worked evenings and met for weekend coffees and code. Through the web engineering team’s tireless efforts we delivered: everyone made personal sacrifices (several ~60 hour weeks on my part) to ship this thing. Despite discovering new requirements, changing features’ designs, encountering serious ADA compliance issues, and finding a handful of major bugs, we were ready to ship by the deadline.

When we finally ramped up to 100% it was a relief. Everything worked! The redesign has already increased new baby registry creation significantly, and we received some great press.

Project Recap

What did we accomplish? What did we learn?

  • Sweating details at the expense of a foundation guarantees problems: Initial efforts focused on the entrypoint to the experience when we should have built up base functionality and integrations. This hurt us towards the end as we encountered several missing pieces.
  • Waterfall doesn’t work: Departments functioned in isolation instead of working as a team. We realized this post-launch during retro meetings with product, research, design and engineering. Waterfall inhibits design from ensuring their solutions work and prevents researchers from validating the product addresses customers’ needs, ultimately burdening engineering to hit inflexible deadlines. This promotes burnout, which we don’t want.
  • TypeScript was essential: We wouldn’t have shipped on time without TypeScript’s safety, which protected us from bugs throughout development. In fact, it saved us so much time that we shipped a major feature originally cut from the MVP’s scope. We also had the benefit of strongly typed GraphQL schema coupled with TypeScript definitions: wiring up the client to the API was seamless. “I’ve never seen anything like it. It just worked!” exclaimed Chris.
  • Firsts for Walmart.com: As far as I know, we shipped the first customer-facing web application written in TypeScript. It was one of the first to be powered by a GraphQL service.

What Next?

The web team must repay some post-MVP technical debt (increasing test coverage, improving analytics). We’ll listen to our users’ feedback through data collection and research to hone the product into something that increasingly helps customers save money and live better.

Aside from user-facing features, we discovered some thorny areas within Walmart Labs’ developer ecosystem, including underwhelming service documentation and subpar developer tooling. I personally find this energizing: we can adopt aspects of startups (agility, use of existing open source solutions) and improve our tooling and frameworks, which will result in happier engineers, healther inter-sourcing, faster feature delivery and bug fixes, and ultimately better user experiences. We can also impart our technology findings to the others: good initial technology choices (TypeScript, GraphQL) made us successful, and I’d like to encourage their use throughout the greater organization.

Sound exciting? Good news: we’re hiring in Portland, Oregon. Check out our job listings on careers.walmart.com or shoot me an email for more details.

Gulpifying Jekyll

It’s important to use the right tools for the job. Investing in good infrastructure and setup reduces future fiddling and makes actual work happen faster.

This post is a walkthrough for setting up a Gulp-driven Jekyll site. Jekyll is a static site generator, which turns organized posts and pages written in Markdown into a bunch of static HTML files. It’s makes a site extremely fast for visitors because there’s no server-side scripting, database, etc. Gulp is an efficient JavaScript task runner.

I use Jekyll to power Beer Review. At some point I fired up generator-jekyllrb, a Yeoman generator, to compile Sass, minify code, and optimize images within a Grunt-based workflow. It served me well, but builds run slowly, and the setup has tasks that I don’t need. Plus, I’ve grown to prefer the code-over-configuration approach of Gulp.

Set Up

Let’s get ready. Here’s what you’ll need:

  • Ruby (already installed)
  • Jekyll (2.5.3 as of this writing): gem install jekyll
  • Libsass: brew install libsass
  • Node and NPM: brew install node
  • Gulp: npm install -g gulp

(Commands assume you’re on a Mac and have Homebrew installed.)

First, we need a Jekyll project to work on. Use an existing one or make a new one by running jekyll new [project_directory], replacing [project_directory] with your desired directory name.

Now, we need a starter gulpfile.js and package.json. I really enjoy the generator-gulp-webapp: it’s maintained by the Yeoman team, and it always has the latest and greatest stuff. We’ll borrow their files.

Here’s how retrieve this generator’s files (alternatively, download them here):

  1. Install Yeoman: npm install -g yo
  2. Install the generator: npm install -g generator-gulp-webapp
  3. Make a new temporary directory and cd into it
  4. Use Yeoman to generate a new project: yo gulp-webapp
  5. Follow the prompts
  6. Stop Yeoman from installing dependencies

We’re ready to set up the Jekyll project directory. Move all Jekyll-assocated files and directories into a new directory named app (to follow the Yeoman standard). A couple exceptions: keep the configuration file (_config.yml), any READMEs, Gemfile, etc. in the project root. It should look something like this:

app
    _includes
    _layouts
    _posts
    _sass
    css
    feed.xml
    index.html
_config.yml

Now, move gulpfile.js and package.json from the temporary gulp-webapp directory into your Jekyll project directory. You can optionally move the dotfiles (.jshintrc, .editorconfig, etc.) if you want their functionality. It directory structure should now look like this:

app
    _includes
    _layouts
    _posts
    _sass
    css
    feed.xml
    index.html
.editorconfig
.jshintrc
_config.yml
gulpfile.js
package.json

Once everything’s in place, run npm install in the Jekyll directory to install the project’s dependencies.

Styles

First up is the project’s stylesheet (assuming Sass):

  1. Go into your root Sass file (app/css/main.scss in a default Jekyll install) and remove the front matter (everthing between and including the ---s). This is necessary for running Sass through Jekyll, but it chokes up regular Sass.
  2. Ensure the styles task in the gulpfile.js points to the right file:

    -  return gulp.src('app/styles/main.scss')
    +  return gulp.src('app/css/main.scss')
    
  3. Add the app/sass_ directory to Sass's includes:

    -       includePaths: ['.'],
    +       includePaths: ['.', 'app/_sass'],
    

Building Jekyll

Jekyll needs to know about the new directory structure. In keeping with Yeoman standards, the site’s code lives in app and the built and bundled code lives in dest. Jekyll doesn’t need worry about images or styles, so let’s set the appropriate excludes. Add these to your _config.yml file:

source: "app"
destination: "dist"
exclude: ["img", "css", "_sass", "js"]
keep_files: ["img", "css", "js"]

We’ll need a way to fire up Jekyll through Gulp. gulp-shell works perfectly for this. Install it: npm install gulp-shell --save-dev. Then, add a jekyll task to the gulpfile.js:

gulp.task('jekyll', function () {
  return gulp.src('_config.yml')
    .pipe($.shell([
      'jekyll build --config <%= file.path %>'
    ]))
    .pipe(reload({stream: true}));
});

Wire up the new task in your gulpfile.js:

- gulp.task('html', ['styles'], function () {
+ gulp.task('html', ['styles', 'jekyll'], function () {
- gulp.task('html', ['styles'], function () {
+ gulp.task('serve', ['styles', 'jekyll'], function () {

You’ll also want to change your gulp watches:

    gulp.watch([
-     'app/*.html',
-     'app/scripts/**/*.js',
+     'app/js/**/*.js',
-     'app/images/**/*',
+     'app/img/**/*',
-     '.tmp/fonts/**/*'
    ]).on('change', reload);

+   gulp.watch('app/**/*.{md,markdown,html}', ['jekyll']);

This will ensure BrowserSync reloads the server when your Jekyll files change.

Other Changes

Make sure your feed.xml isn’t overridden in the extras task:

  return gulp.src([
    'app/*.*',
+   '!app/feed.xml',
    '!app/*.html'
  ], {

I also dropped the fonts task and any references to Bootstrap, which comes from the generator-gulp-webapp by default.

A note on Base URL

Beer Review operates from a base url (/beer-review/). Unfortunately, because of the new setup, Jekyll’s baseurl configuration can’t be relied upon. I found a workaround that involves string replacing. It isn’t the prettiest, but it works.

npm install gulp-replace --save-dev

Add this to the html task:

+   var baseurl = 'beer-review'
+   var htmlPattern = /(href|src)(=["|']?\/)([^\/])/gi;
+   var htmlReplacement = '$1$2' + baseurl + '/$3';
+   var cssPattern = /(url\(['|"]?\/)([^\/])/gi;
+   var cssReplacement = '$1' + baseurl + '/$2';

      .pipe($.if('*.html', $.minifyHtml({conditionals: true, loose: true})))
+     .pipe($.if('*.html', $.replace(htmlPattern, htmlReplacement)))
+     .pipe($.if('*.css', $.replace(cssPattern, cssReplacement)))
      .pipe(gulp.dest('dist'));

Replace baseurl with your desired path.

That’s that! See the complete gulpfile.js here. Happy building!