NEW: Heap for mobile. Track every interaction, on every platform.

Learn more
skip to content
Loading...
    • The Digital Insights Platform Transform your digital experience
    • How Heap Works A video guide
    • How Heap Compares Heap vs. competitors
    • The Future of Insights A comic book guide
  • Data Insights

    • Session Replay Complete context with a single click
    • Illuminate Data science that pinpoints unknown friction
    • Journeys Visual maps of all user flows

    Data Analysis

    • Segments User cohorts for actionable insights
    • Dashboards Share insights on critical metrics
    • Charts Analyze everything about your users
    • Playbooks Plug-and-play templates and analyses

    Data Foundation

    • Capture Automatic event tracking and apis
    • Mobile Track and analyze your users across devices
    • Enrichment Add context to your data
    • Integrations Connect bi-directionally to other tools

    Data Management

    • Governance Keep data clean and trusted
    • Security & Privacy Security and compliance made simple
    • Infrastructure How we build for scale
    • Heap Connect Send Heap data directly to your warehouse
  • Solutions

    • Funnel Optimization Improve conversion in user flows
    • Product Adoption Maximize adoption across your site
    • User Behavior Understand what your users do
    • Product Led Growth Manage PLG with data

    Industries

    • SaaS Easily improve acquisition, retention, and expansion
    • eCommerce Increase purchases and order value
    • Financial Services Raise share of wallet and LTV

    Heap For Teams

    • Product Teams Optimize product activation, conversion and retention
    • Marketing Teams Optimize acquisition performance and costs
    • Data Teams Optimize behavioral data without code
  • Pricing
  • Support

    • Heap University Video Tutorials
    • Help Center How to use Heap
    • Heap Plays Tactical how-to guides
    • Heap Updates
    • Professional Services

    Resources

    • Blog A community for digital builders
    • Content Library Ebooks, whitepapers, videos, guides
    • Press News from and about Heap
    • Webinars & Events Virtual and live events
    • Careers Join us

    Ecosystem

    • Customer Community Join the conversation
    • Partners Technology and Solutions Partners
    • Developers
    • Customers Over 8,000 successful companies
  • Free TrialRequest Demo
  • Log In
  • Free Trial
  • Request Demo
  • Log In

All Blogs

Engineering

Goodbye CoffeeScript, Hello TypeScript

Jiawei Li
October 7, 20154 min read
  • Facebook
  • Twitter
  • LinkedIn

Web apps are becoming increasingly feature-rich, and the Heap frontend is no different. We expose an interface that lets users organize their data and build custom visualizations. Nearly every interaction changes an underlying model and there are subtle rules around how the UI behaves.

Our previous stack of Backbone and CoffeeScript wasn’t scaling well to a lot of common UI challenges, such as data syncing, subview management, and redrawable views. It was time to rethink how we could both improve maintainability and speed up future feature development.

It all starts with language

We wanted a language that would address 2 main concerns in our codebase:

  1. Code shouldn’t be hard to reason about We had too many implicit object schemas, mutative operations on inputs, and implicit cross-view assumptions. This caused many hard-to-reason about bugs, and sometimes very unexpected behavior in production.

  2. Development should be fast Write code => hopefully encounter runtime error when testing => repeat is inefficient both in speed and accuracy. Many common errors in code shouldn’t require a refresh of a test page and a mechanical series of clicks.

CoffeeScript

CoffeeScript was wildly popular in the early days of Heap and regarded as one of the most mature alternative JS languages. Here’s how it stacks up:

Pros:

  • Minimalistic syntax with lots of syntax sugar

  • Already well-known within team

  • Easy to map output JS back to source CoffeeScript (before source maps)

Cons:

Variable initialization and reassignment are the same

It’s easy to accidentally overwrite a variable from a higher scope as a codebase increases in depth. This is really bad, because it limits our ability to write more complex code. Safely creating a variable requires pressing Ctrl + F and examining the current file to make sure there isn’t a conflict.

Existential operator accessor (?.) is a leaky abstraction for null/undefined

This is often added to ‘make things work’, without understanding why it’s necessary. If we could document in a single place the potential ’emptiness’ of a value, reading related code would be much easier.

Ambiguous syntax

It’s reasonable to assume foo bar and hello world will compile to either:

  • foo(bar) && hello(world)

  • foo(bar && hello(world))

depending on what you’re hoping it’ll compile to. In the words of one teammate:

This is the only language I’ve worked in where I need to compile my code and verify the output to make sure it does what I expect it to.

TypeScript

Given the shortcomings of CoffeeScript, we investigated many alternatives, including ClojureScript, Babel, Elm, and PureScript. TypeScript stood out as a clear winner:

Pros:

  • Built-in type annotations let you document interfaces and enforce their correctness at compile time, cutting down on logical errors

  • Already has several other tools built on it (e.g. Flow type annotations compiling down to TypeScript annotations, Angular 2.0)

  • Clear, maintained development roadmap with rapid releases

  • Community type definitions for adding types to third-party JavaScript

Cons:

  • Not yet 100% parity with ES6 (lagging behind Babel)

  • Some missing type system features: type bounds, half-baked local types, f-bounded polymorphism

  • Community type definitions are unversioned, so it’s difficult to find type annotations for older versions of libraries

Many other languages were disqualified for one or more of these reasons:

  • Overly complex external JavaScript workflow

  • Non-mainstream syntax

  • Small community – integrations may need to be self-written

  • Docs hard to navigate or find

Filling in the gaps

There are a few language features which we heavily missed in TypeScript:

Pattern matching is a common feature in functional languages that drastically reduces type casts, while encouraging consistent return types and discouraging mutation by directly returning an expression. We’ve built a small type-safe version of pattern matching in TypeScript and use it extensively throughout our codebase.

Implicit conversions are a safer form of monkey patching, a popular though frowned upon idiom in JavaScript. These exist in Ruby as refinements, and we can simulate them with a slightly more verbose syntax (inspiration heavily drawn from Scala). Here, we build a generic version of Immutable.js’ withMutations method:

While sometimes more verbose, consistent usage of these patterns leads to a codebase where nearly all bugs are logical errors, and not accidental organizational errors. This makes bugs easier to track down.

Uncaught TypeError: undefined is not a function

We had several bugs in our error tracker that were previously black holes – variables would take on a value of null or undefined, and it’d be nearly impossible to figure out why.

The easy solution: add more guarantees via types.

Options

Options declare whether something is allowed to be null/undefined. They completely solve the CoffeeScript issue of duplicating ?. operators throughout your codebase – you only need to declare the nullability of something once. After that, the compiler will warn you if you err in assuming that something won’t ever be null. This means a more reliable application and serves as living documentation that’ll auto-update as your application grows, decreasing ramp-up time into the codebase.

Since adopting these, we’ve eliminated the entire class of null/undefined errors. This is important because that class is among the hardest to track down, usually because the error occurrence and the error in logic end up far away from each other.

Trys

Trys declare whether something succeeded or failed. They’ve caused us to recognize and react to parsing errors when we try to convert data into client-side models. Parsing nested objects is easier as well, since we can compose parsers for each component of the aggregate object.

Futures

Futures (also known as Promises) declare whether an asynchronous operation succeeded or failed, and let you apply control flow to the result. This is far better than passing callbacks into other functions because logic around onComplete and onFail only needs to be implemented once, instead of for every single asynchronous function. We’ve chosen a more traditional choice in the form of when.js since it’s compliant with the A+ Promises spec, which promises better integration with other libraries.

Monapt is the open-source library we use throughout our codebase. Feel free to stop by gitter chat with any questions!

Language is just the beginning of creating a solid frontend codebase. The structure and organization of views can make an enormous difference as well. In part 2, we’ll explain the evolution of our view architecture, and lessons learned from React and Elm.

Let us know if you have any questions @heap!

If you find this kind of work interesting, we’re hiring! Check out our engineering team and open roles.

Jiawei Li

Was this helpful?
PreviousNext

Related Stories

See All

  • Google Analytics 4

    Product Insights

    Google Analytics 4: What it promises, and what that really means

    April 28, 2022

  • Heap.io

    How to

    The 3 key first steps to improving CRO

    March 29, 2023

  • Heap.io

    Data Stories

    Celebrating H&R Block as the inaugural winner of the Digital Innovator Award

    March 22, 2023

Subscribe

Sign up to stay on top of the latest posts.

Better insights. Faster.

Request Demo
  • Platform
  • Capture
  • Enrichment
  • Integrations
  • Governance
  • Security & Privacy
  • Infrastructure
  • Illuminate
  • Segments
  • Charts
  • Dashboards
  • Playbooks
  • Use Cases
  • Funnel Optimization
  • Product Adoption
  • User Behavior
  • Product Led Growth
  • Customer 360
  • SaaS
  • eCommerce
  • Financial Services
  • Why Heap
  • The Digital Insights Platform
  • How Heap Works
  • How Heap Compares
  • The Future of Insights
  • Resources
  • Blog
  • Content Library
  • Events
  • Topics
  • Heap University
  • Community
  • Professional Services
  • Company
  • About
  • Partners
  • Press
  • Careers
  • Customers
  • Support
  • Request Demo
  • Help Center
  • Contact Us
  • Pricing
  • Social
  • Twitter
  • Facebook
  • LinkedIn
  • YouTube

© 2023 Heap Inc. All Rights Reserved.

  • Legal
  • Privacy Policy
  • Status
  • Trust