Add Spina, a modern TypeScript/ES6-supported specialization of Backbone.

Review Request #12788 — Created Jan. 19, 2023 and submitted — Latest diff uploaded




We make heavy use of Backbone.js, but Backbone has little support for
modern tooling for JavaScript, such as TypeScript or ES6 classes. The
former is available through a community-driven typing package, and the
latter sort of works but requires some tradeoffs in how classes are

Those tradeoffs are due to a conflict in the way that Backbone
initializes classes and the way ES6 classes are initialized. Backbone's
base objects (Model, View, etc.) will call preinitialize(),
initialize(), and other methods on the class being constructed, which
is pretty reasonable. However, ES6 class construction is not reasonable.
Each class in the hierarchy must call the parent class's constructor
first, and until that finishes, the class does not have a prototype
(meaning this does not work). That prevents parent classes from
calling or accessing properties on a derived class.

We've addressed much of this, and are introducing the result as a new
library called Spina.

Spina provides some tooling for delayed class initialization, through
the use of an intermediary class wrapper that's meant to sit in front of
a base class like Backbone.Model and through decorators that decorate
each chain in a hierarchy. These help control the initialization order,
ensuring classes are set up correctly.

We have Spina versions of most of the Backbone classes:

  • Spina.BaseCollection
  • Spina.BaseModel
  • Spina.BaseRouter
  • Spina.BaseView
  • Spina.Collection
  • Spina.Router

Unlike Backbone's versions, our base classes are truly base classes, and
cannot be instantiated directly. Subclasses are required.

Every subclass in a hierarchy must be decorated with the @spina
decorator. This sets up front-end wrapper class that triggers the proper
initialization order and then passes an instance of the actual class.
For most classes (those inheriting directly from Spina.BaseModel, for
example), there's minimal waste. For deeper hierarchies, there will be
small constructor-only classes that end up in the prototype chain.

This also bundles a fork of @types/backbone along with it. This
customizes types in the following ways:

  • Avoids some of the hard-coded method-only types found in places like
    Backbone.Model.defaults. While that may have been generally
    necessary for Backbone, it was ultimately a hack and it's not
    something we'll be needing to deal with as we move to Spina.

  • Adds support for interfaces to customize options passed to views.

Our version of the types are opt-in, requiring the following in

  "compilerOptions": {
    "paths": {
      "Backbone": [

Spina does not embed Backbone, so consumers will need to ensure that's
loaded before loading Spina.

And finally, there are improvements to views:

  • New show(), hide(), and renderInto() methods simplify some
    common patterns in our codebase.

  • A modelEvents attribute makes it easy to connect model events to the
    view on the first render.

  • A new onRender() method replaces render() without the
    return this requirement, simplifying code.

  • A new onInitialRender() method allows for logic to execute only on
    the first render.

The render behavior was discussed and debated separately, but I've
ultimately added it because we do use this pattern a fair amount (and
has been a pet peeve of mine for years), and the state logic is required
anyway in order to support model events.

Successfully built this and ran through a series of tests in Review Board,
involving new base classes and various levels of subclasses.

Verified that initialization orders and resulting prototype states were
always correct.

Verified that the types for the various attributes formerly typed as methods
can now be methods or values.

This is NOT 100% tested at this point. There's a lot of real-world
testing required still.