@profiscience/knockout-contrib
Metapackage containing all of the goodies from the @profiscience/knockout-contrib monorepo. Everything you need to start building SPAs with KnockoutJS. Some bits are more opinionated than others, use however much/little as makes sense for your project. Publishing everything together as a metapackage is intended to a) reduce typing and b) increase discoverability.
Installation / Usage
First, install the metapackage. This will install the latest version of each package in @profiscience/knockout-contrib.
yarn add @profiscience/knockout-contrib
Then somewhere in your app before you kick off ko.applyBindings()
, add the following boilerplate code...
NOTE: Nothing will be added to the global scope, nor any Knockout registries modified simply by installing/importing this package. You must register any bindings, components, filters, or router middleware/plugins you wish to use. This allows you to use your own loading mechanism, naming schemes, etc. if you so choose, as well as keeping each package pure (exports only, no global scope modification), enabling tree-shake-ability with webpack, rollup, or the esm bundler du jor.
import * as ko from 'knockout'
import 'knockout-punches'
import {
altClickBindingHandler,
ctrlClickBindingHandler,
metaClickBindingHandler,
shiftClickBindingHandler,
draggableBindingHandler,
jqueryBindingHandler,
toggleBindingHandler,
formateDateFilter,
Route,
Router,
createScrollPositionMiddleware,
childrenRoutePlugin,
componentRoutePlugin,
componentsRoutePlugin,
componentInitializerRoutePlugin,
redirectRoutePlugin,
createtitleRoutePlugin,
withRoutePlugin,
} from '@profiscience/knockout-contrib'
ko.bindingHandlers['click.alt'] = altClickBindingHandler
ko.bindingHandlers['click.ctrl'] = ctrlClickBindingHandler
ko.bindingHandlers['click.meta'] = metaClickBindingHandler
ko.bindingHandlers['click.shift'] = shiftClickBindingHandler
ko.bindingHandlers.draggable = draggableBindingHandler
ko.bindingHandlers.jquery = jqueryBindingHandler
ko.bindingHandlers.$ = jqueryBindingHandler
ko.bindingHandlers.toggle = toggleBindingHandler
const _ko: any = ko
_ko.punches.textFilter.enableForBinding('text')
_ko.punches.textFilter.enableForBinding('attr')
_ko.filters['date.format'] = formatDateFilter
Router.use(createScrollPositionMiddleware())
Route.usePlugin(
withRoutePlugin,
redirectRoutePlugin,
componentRoutePlugin,
componentInitializerRoutePlugin,
childrenRoutePlugin,
componentsRoutePlugin,
createtitleRoutePlugin(
(titleSegments: string[]) => `My App | ${titleSegments.join(' > ')}`
)
)
View the documentation for individual packages to see what they do / how to use them.
Using the ESNext build
This assumes you are using webpack. It is likely possible with most other modern bundlers, but that's on you to figure out. If you would like to contribute documentation, PRs are accepted.
The default build is transpiled to ES5 in order to be compatible in older browsers (*cough* IE).
However, if you only need to support modern runtimes, you can use the esnext build. This build has much less transpilation overhead and does not require any additional runtime libraries (e.g. babel-runtime
). This can have a pretty decent impact on the size of your builds when used with babel-minify, and provides for a much nicer debugging experience should you find yourself digging through the stack somewhere in this library; transpiled code can be very difficult to reason about.
This library uses the proposed esnext
package.json field.
Add esnext
to resolve.mainFields
in your webpack config to use this build. Additionally, you'll need to configure an additional rule so that this field is used transitively. See Webpack #6796.
e.g.
module.exports = {
resolve: {
mainFields: [
'esnext',
'module',
'main',
],
},
module: {
rules: [
{
test: path.resolve(__dirname, 'node_modules'),
resolve: {
mainFields: ['esnext', 'es2015', 'module', 'main'],
},
},
],
},
}
TypeScript Caveats
For reasons that are mostly related to my own development comfort, TypeScript is configured to point at the source instead of built definitions. What this comes down to is allowing parallel transpilation, however there is also the very real restriction that TypeScript refuses to emit a declaration for a mixin that uses a private
or protected
property.
That said, using this library with TypeScript requires a few properties to be set in your tsconfig.json
.
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "h",
"allowSyntheticDefaultImports": true,
"lib": [
"dom",
"es5",
"es2015",
"esnext.asynciterable"
]
}
}
You may omit jsx
and jsxFactory
if you are not using any component packages.