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
structured.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 callpreinitialize()
,
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
(meaningthis
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 likeBackbone.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
interface
s to customize options passed to views.
Our version of the types are opt-in, requiring the following in
tsconfig.json
:
{
"compilerOptions": {
"paths": {
"Backbone": [
"node_modules/@beanbag/spina/lib/@types/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()
, andrenderInto()
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 replacesrender()
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.