schmackbone
v3.0.0
Published
jQuery-less, Promise-interfaced models based on BackboneJS
Downloads
2,156
Maintainers
Readme
__ __
/\ \ /\ \ __
__ ___\ \ \/'\\ \ \____ ___ ___ __ /\_\ ____
/'__`\ /'___\ \ , < \ \ '__`\ / __`\ /' _ `\ /'__`\ \/\ \ /',__\
SCHM-/\ \ \.\_/\ \__/\ \ \\`\\ \ \ \ \/\ \ \ \/\ \/\ \/\ __/ __ \ \ \/\__, `\
\ \__/.\_\ \____\\ \_\ \_\ \_,__/\ \____/\ \_\ \_\ \____\/\_\_\ \ \/\____/
\/__/\/_/\/____/ \/_/\/_/\/___/ \/___/ \/_/\/_/\/____/\/_/\ \_\ \/___/
\ \____/
\/___/
(_'______________________________________________________________________________'_)
(_.——————————————————————————————————————————————————————————————————————————————._)
Schmackbone.js
Schmackbone is a lighter, modernized fork of the established MV-library Backbone, with the View-logic, jQuery, and Router removed along with its support for legacy browsers. So it's really just an M-library to handle your RESTful API requests.
Its ajax requests are now native and promise-based, and its components are modularized so you can use what you need and tree-shake the rest.
Why?
While creating a Backbone view layer feels a little 2012, its Models and Collections are actually a very light and easy abstraction for interacting with REST APIs. Additionally, its basic Events module make it a cinch to pub/sub your model changes to your actual view layer, for example, your React Components. This allows for some really elegant abstractions without the heaviness of a full-blown state manager like Redux. You can keep your UI-state local and your data-state (the 'state' of the data that is represented in your API).
This is how the resourcerer library employs Schmackbone.
Practical Differences to Backbone
Underscore methods are now opt-in
Backbone automatically adds the bulk of the underscore library methods to its Model/Collection prototypes. But nearly all of them are unnecessary these days. Schmackbone leaves these out by default. However, if you want to add these back, just import the add_underscore_methods
script to your application:
// your-app.js
import 'schmackbone/add_underscore_methods';
(Note that in order to access the add_underscore_methods
script, you'll need a bundler (or other module loading system) that recognizes the exports
property in package.json
. For webpack, this means upgrading to v5.)
Modularized
// before, with webpack shimming via https://github.com/webpack-contrib/imports-loader:
import Backbone from 'backbone';
// after:
import * as Schmackbone from 'schmackbone';
// or
import {Model} from 'schmackbone';
Model and Collection are native Javascript classes
// before:
var MyModel = Model.extend({url: () => '/foo'});
// after:
class MyModel extends Model {
url = () => '/foo'
}
Reserved instance properties now static properties
Due to the way instance properties are instantiated in subclasses (not until after the superclass has been instantiated), many of the reserved instance properties in Schmackbone have been moved to static properties:
// before:
var MyModel = Model.extend({
defaults: {
one: 1,
two: 2
},
cidPrefix: 'm',
idAttribute: '_id',
url: () => '/foo'
});
// after:
class MyModel extends Model {
static defaults = {
one: 1,
two: 2
}
static cidPrefix = 'm'
static idAttribute = '_id'
url() => '/foo'
});
Notes:
- For Models,
defaults
,idAttribute
, andcidPrefix
are static properties.defaults
can optionally be a static function. - For Collections,
model
andcomparator
properties are now static, but if they need to be overridden in an instance, they can do so via theoptions
object in instantiation:
class MyCollection extends Collection {
static comparator = 'created_at'
static model = MyModel
}
const overrideCollection = new MyCollection([], {comparator: 'updated_at', model: MyModel2});
url
(Model/Collection) andurlRoot
(Model) remain instance properties, as they (1) often depend on the instance and (2) are not utilized during instantiation
Requests have a Promise interface
All Schmackbone request methods use window.fetch
under the hood and so now have a Promise interface, instead of accepting jQuery-style success
and error
options.
// before:
todoModel.save({name: 'Giving This Todo A New Name'}, {
success: (model, response, options) => notify('Todo save succeeded!'),
error: (model, response, options) => notify('Todo save failed :/'),
complete: () => saveAttempts = saveAttempts + 1
});
// after
todoModel.save({name: 'Giving This Todo A New Name'})
.then(([model, response, options]) => notify('Todo save succeeded!'))
.catch(([model, response, options]) => notify('Todo save failed :/'))
.then(() => saveAttempts = saveAttempts + 1);
Note a couple important consequences:
- Since Promises can only resolve a single value, the callback parameters are passed via an array that can be destructured.
- All requests must have a
.catch
attached, even if the rejection is swallowed. Omitting one risks an uncaught Promise rejection exception if the request fails. - The
.create
method no longer returns the added model; it returns the promise instead.
qs Dependency
While jQuery was no longer necessary, we could not replace it entirely with native javascript: we added query string stringifying functionality via the small-footprint qs library.
setAjaxPrefilter
Schmackbone offers one hook into its fetch requests: setAjaxPrefilter
. It allows you to alter the
options object
passed to window.fetch
before any requests are made. Use this hook to pass custom headers like auth headers, or a custom
global error handler:
import {setAjaxPrefilter} from 'schmackbone';
// usage:
// @param {object} options object
// @return {object} modified options object
const ajaxPrefilter = (options={}) => ({
...options,
// if you want to default all api requests to json
contentType: 'application/json',
error: (response) => {
if (response.status === 401) {
// refresh auth token logic
} else if (response.status === 429) {
// do some rate-limiting retry logic
}
return options.error(response);
},
headers: {
...options.headers,
Authorization: `Bearer ${localStorage.getItem('super-secret-auth-token')}`
}
});
setAjaxPrefilter(ajaxPrefilter);
By default, the ajaxPrefilter
function is set to the identity function.
Misc
- Note that Schmackbone uses ES2015 in its source and does no transpiling—including
import
/export
(Local babel configuration is for testing, only). Unlike Backbone, whose legacy monolith used UMD syntax and could be used directly in the browser, Schmackbone can be used only in modern browsers via thetype="module"
script MIME-type or via a bundler like webpack that handles module transforms.
This means that if you're not babelifying your node_modules
folder, you'll need to make an exception for this package, ie:
// webpack.config.js
module: {
rules: [{
test: /\.jsx?$/,
exclude: /node_modules\/(?!(schmackbone))/,
use: {loader: 'babel-loader?cacheDirectory=true'}
}]
}
- For Backbone-related information, see the website and especially its annotated source page.