choo-lazy-view
Advanced tools
Comparing version 0.3.0-1 to 1.0.0
105
index.js
@@ -1,75 +0,56 @@ | ||
var assert = require('assert') | ||
var Component = require('nanocomponent') | ||
module.exports = lazy | ||
module.exports.view = view | ||
module.exports.store = store | ||
module.exports = LazyView | ||
function LazyView (id, state, emitter, view) { | ||
Component.call(this, id) | ||
function lazy () { | ||
if (arguments.length === 3) return store.apply(undefined, arguments) | ||
else return view.apply(undefined, arguments) | ||
} | ||
LazyView.create = function (fn, loader) { | ||
assert(typeof fn === 'function', 'choo-lazy-view: fn should be a function') | ||
fn = promisify(fn) | ||
function view (load, loader) { | ||
var init = promisify(load) | ||
var Self = this | ||
var id = 'view-' + Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) | ||
return function proxy (state, emit) { | ||
if (proxy.render) return proxy.render.call(this, state, emit) | ||
return function (state, emit) { | ||
var cached = state.cache(Self, id) | ||
var p = init().then(function (render) { | ||
// asynchronously render view to account for nested prefetches | ||
if (typeof window === 'undefined') render(state, emit) | ||
proxy.render = render | ||
return render | ||
}) | ||
if (!cached.view) { | ||
emit('choo-lazy-view:fetch', id) | ||
var promise = fn(function (err, view) { | ||
if (err) return emit('choo-lazy-view:error', err) | ||
emit('choo-lazy-view:done', id, view) | ||
cached.view = view | ||
emit('render') | ||
}) | ||
emit('lazy:load', p) | ||
if (typeof loader === 'function') return loader(state, emit) | ||
var prefetch = state.prefetch || state._experimental_prefetch | ||
if (prefetch) { | ||
promise = promise.then(function (view) { | ||
// account for view issuing more prefetches | ||
var res = cached.render(state, emit) | ||
// defer to nested prefetches issued by above render | ||
var nested = prefetch.filter(function (p) { return p !== promise }) | ||
return Promise.all(nested).then(function () { return res }) | ||
}) | ||
prefetch.push(promise) | ||
return promise | ||
} | ||
// assuming app has been provided initialState by server side render | ||
var selector = state.selector | ||
if (typeof window === 'undefined') { | ||
// eslint-disable-next-line no-new-wrappers | ||
var str = new String('<' + selector + '></' + selector + '>') | ||
str.__encoded = true | ||
return str | ||
} | ||
if (cached.view) return cached.render(state, emit) | ||
if (typeof loader === 'function') return loader(state, emit) | ||
if (loader) return loader | ||
assert(typeof window !== 'undefined', 'choo-lazy-view: loader is required when not using prefetch') | ||
if (typeof Self.selector === 'string') return document.querySelector(Self.selector) | ||
if (Self.selector instanceof window.Element) return Self.selector | ||
assert.fail('choo-lazy-view: could not mount loader') | ||
if (typeof selector === 'string') return document.querySelector(selector) | ||
if (selector instanceof window.Element) return selector | ||
throw new Error('choo-lazy-view: loader or server side generated initialState required') | ||
} | ||
} | ||
LazyView.mount = function (app, selector) { | ||
this.selector = selector | ||
return app.mount(selector) | ||
function store (state, emitter, app) { | ||
state.selector = state.selector || app.selector | ||
emitter.on('lazy:load', function (p) { | ||
var prefetch = state.prefetch || state._experimental_prefetch | ||
if (prefetch) prefetch.push(p) | ||
p.then( | ||
emitter.emit.bind(emitter, 'lazy:success'), | ||
emitter.emit.bind(emitter, 'lazy:error') | ||
).then(emitter.emit.bind(emitter, 'render')) | ||
}) | ||
} | ||
LazyView.prototype = Object.create(Component.prototype) | ||
LazyView.prototype.constructor = LazyView | ||
LazyView.prototype.update = function () { | ||
return true | ||
} | ||
LazyView.prototype.createElement = function (state, emit) { | ||
assert(this.view, 'choo-lazy-view: cannot render without view') | ||
return this.view(state, emit) | ||
} | ||
// wrap callback function with promise | ||
// fn -> fn | ||
function promisify (fn) { | ||
return function (cb) { | ||
return function () { | ||
return new Promise(function (resolve, reject) { | ||
@@ -81,10 +62,4 @@ var res = fn(function (err, value) { | ||
if (res instanceof Promise) return res.then(resolve, reject) | ||
}).then(done.bind(null, null), done) | ||
function done (err, res) { | ||
if (typeof cb === 'function') cb(err, res) | ||
if (err) throw err | ||
else return res | ||
} | ||
}) | ||
} | ||
} |
{ | ||
"name": "choo-lazy-view", | ||
"version": "0.3.0-1", | ||
"version": "1.0.0", | ||
"description": "Lazily fetch view when needed", | ||
@@ -9,7 +9,2 @@ "main": "index.js", | ||
}, | ||
"browserify": { | ||
"transform": [ | ||
"unassertify" | ||
] | ||
}, | ||
"repository": { | ||
@@ -23,3 +18,4 @@ "type": "git", | ||
"bankai", | ||
"jalla" | ||
"jalla", | ||
"lazy" | ||
], | ||
@@ -35,7 +31,3 @@ "author": "Carl Törnqvist <carl@tornqv.ist>", | ||
"standard": "^11.0.1" | ||
}, | ||
"dependencies": { | ||
"nanocomponent": "^6.5.2", | ||
"unassertify": "^2.1.1" | ||
} | ||
} |
129
README.md
@@ -9,10 +9,7 @@ # choo-lazy-view | ||
Lazily load views as the router invokes them. Works great with dynamic import or | ||
[split-require][split-require] but should work with any software with a promise | ||
or callback interface. | ||
[split-require][split-require] but should work with any promise or callback API. | ||
## Usage | ||
```javascript | ||
var splitRequire = require('split-require') | ||
var LazyView = require('choo-lazy-view') | ||
var html = require('choo/html') | ||
var lazy = require('choo-lazy-view') | ||
var choo = require('choo') | ||
@@ -22,42 +19,29 @@ | ||
app.route('/', main) | ||
app.use(lazy) | ||
app.route('/my-page', lazy(() => import('./views/my-page'))) | ||
// using dynamic import | ||
app.route('/some-page', LazyView.create(() => import('./a'))) | ||
// or using split-require | ||
app.route('/another-page', LazyView.create((cb) => splitRequire('./b', cb))) | ||
module.exports = LazyView.mount(app, 'body') | ||
function main () { | ||
return html` | ||
<body class="Home"> | ||
<h1>home</h1> | ||
<a href="/a">a</a><br> | ||
<a href="/b">b</a> | ||
</body> | ||
` | ||
} | ||
module.exports = app.mount('body') | ||
``` | ||
## API | ||
The module exposes a [Nanocomponent][nanocomponent] class with two static | ||
methods which are used for wrapping your views and `choo.mount`. | ||
### `app.use(lazy)` | ||
Hook up lazy view manager to the app. The lazy view store detects [jalla][jalla] | ||
and [bankai][bankai] prefetch (_experimental_prefetch) behaviour so that server | ||
side rendering works just as you'd expect. | ||
### `LazyView.create(callback, loader?)` | ||
### `lazy(callback, loader?)` | ||
Accepts a callback and an optional loader view. The callback will be invoked | ||
when the returned function is called upon by the router. The callback, in turn, | ||
should load the required view and relay it's response (or error) back to the | ||
caller. This can be done either with a `Promise` or with the supplied callback. | ||
when the view is called upon by the router. The callback, in turn, should load | ||
the required view and relay it's response (or error) back to the caller. This | ||
can be done either with a `Promise` or with a callback. | ||
```javascript | ||
// using promise | ||
app.route('/my-page', LazyView.create(function () { | ||
return fetchViewPromise() | ||
})) | ||
app.route('/my-page', lazy(() => import('./views/my-page'))) | ||
// using the callback | ||
app.route('/another-page', LazyView.create(function (callback) { | ||
fetchViewCallback(callback) | ||
// using a callback | ||
app.route('/another-page', lazy(function (callback) { | ||
fetchView(function (err, view) { | ||
callback(err, view) | ||
}) | ||
})) | ||
@@ -67,8 +51,7 @@ ``` | ||
The second argument is optional and should be a function or a DOM node which | ||
will be displayed while loading. By default, the node used to mount the | ||
application in the DOM is used as loader (meaning the view remains unchanged | ||
while loading). | ||
will be displayed while loading. By default, the node used to mount the app in | ||
the DOM is used as loader (meaning the view remains unchanged while loading). | ||
```javascript | ||
app.route('/a', LazyView.create( | ||
app.route('/a', lazy( | ||
() => import('./my-view'), | ||
@@ -79,61 +62,27 @@ (state, emit) => html`<body>Loading view…</body>` | ||
### `LazyView.mount(app, selector)` | ||
Wrapper function for `app.mount` which stores the selector internally to use as | ||
fallback loader while fetching views. | ||
During server side render, the store will expose the selector used by | ||
`app.mount` on the app state and use that as the fallback loader view. If you | ||
are not doing server side rendering and exposing the server side rendered state | ||
as `initialState` on the client, a loader view is required. *Note: jalla does | ||
this automatically for you.* | ||
```diff | ||
- module.exports = app.mount('body') | ||
+ module.exports = LazyView.mount(app, 'body') | ||
``` | ||
### Extending LazyView | ||
You may extend on the LazyView component to add a shared framework wrapper, e.g. | ||
a header and footer. | ||
```javascript | ||
// components/view/index.js | ||
var html = require('choo/html') | ||
var LazyView = require('choo-lazy-view') | ||
var Header = require('../header') | ||
var Footer = require('../footer') | ||
module.exports = class View extends LazyView { | ||
createElement (state, emit) { | ||
return html` | ||
<body> | ||
${state.cache(Header, 'header').render()} | ||
${super.createElement(state, emit)} | ||
${state.cache(Footer, 'footer').render()} | ||
</body> | ||
` | ||
} | ||
} | ||
``` | ||
```javascript | ||
// index.js | ||
var choo = require('choo') | ||
var View = require('./components/view') | ||
var app = choo() | ||
app.route('/', View.create(() => import('./views/home'))) | ||
module.exports = View.mount(app, 'body') | ||
``` | ||
### Events | ||
Events are namespaced under `choo-lazy-view` and emitted when loading views. | ||
Events are namespaced under `lazy-view` and emitted when loading views. | ||
#### `choo-lazy-view:fetch` | ||
When fetching a view. | ||
#### `lazy-view:load(promise)` | ||
When fetching a view. The argument `promise` resolves to the loaded view. | ||
#### `choo-lazy-view:done` | ||
When the view has been fetched and is about to rerender. | ||
#### `lazy-view:success(view)` | ||
When the view has been fetched, before the app will rerender. The argument | ||
`view` is the resolved view. | ||
#### `choo-lazy-view:error` | ||
#### `lazy-view:error(err)` | ||
When the view fails to load. | ||
## License | ||
MIT | ||
[choo]: https://github.com/choojs/choo | ||
[nanocomponent]: https://github.com/choojs/nanocomponent | ||
[jalla]: https://github.com/jallajs/jalla | ||
[bankai]: https://github.com/choojs/bankai | ||
[split-require]: https://github.com/goto-bus-stop/split-require | ||
@@ -140,0 +89,0 @@ |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
0
6240
56
95
- Removednanocomponent@^6.5.2
- Removedunassertify@^2.1.1
- Removedacorn@5.7.47.4.1(transitive)
- Removedamdefine@1.0.1(transitive)
- Removedcall-bind@1.0.8(transitive)
- Removedcall-bind-apply-helpers@1.0.1(transitive)
- Removedcall-bound@1.0.3(transitive)
- Removedcall-matcher@2.0.0(transitive)
- Removedconvert-source-map@1.9.0(transitive)
- Removeddeep-equal@1.1.2(transitive)
- Removeddeep-is@0.1.4(transitive)
- Removeddefine-data-property@1.1.4(transitive)
- Removeddefine-properties@1.2.1(transitive)
- Removeddom-walk@0.1.2(transitive)
- Removeddunder-proto@1.0.1(transitive)
- Removedes-define-property@1.0.1(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedes-object-atoms@1.1.1(transitive)
- Removedescodegen@1.14.3(transitive)
- Removedesprima@4.0.1(transitive)
- Removedespurify@2.1.1(transitive)
- Removedestraverse@4.3.0(transitive)
- Removedesutils@2.0.3(transitive)
- Removedfast-levenshtein@2.0.6(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedfunctions-have-names@1.2.3(transitive)
- Removedget-intrinsic@1.2.7(transitive)
- Removedget-proto@1.0.1(transitive)
- Removedglobal@4.4.0(transitive)
- Removedgopd@1.2.0(transitive)
- Removedhas-property-descriptors@1.0.2(transitive)
- Removedhas-symbols@1.1.0(transitive)
- Removedhas-tostringtag@1.0.2(transitive)
- Removedhasown@2.0.2(transitive)
- Removedis-arguments@1.2.0(transitive)
- Removedis-date-object@1.1.0(transitive)
- Removedis-regex@1.2.1(transitive)
- Removedlevn@0.3.0(transitive)
- Removedmath-intrinsics@1.1.0(transitive)
- Removedmin-document@2.19.0(transitive)
- Removedmulti-stage-sourcemap@0.2.1(transitive)
- Removednanoassert@1.1.02.0.0(transitive)
- Removednanocomponent@6.6.0(transitive)
- Removednanomorph@5.4.3(transitive)
- Removednanoscheduler@1.0.3(transitive)
- Removednanotiming@7.3.1(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedobject-is@1.1.6(transitive)
- Removedobject-keys@1.1.1(transitive)
- Removedon-load@4.0.2(transitive)
- Removedoptionator@0.8.3(transitive)
- Removedprelude-ls@1.1.2(transitive)
- Removedprocess@0.11.10(transitive)
- Removedregexp.prototype.flags@1.5.4(transitive)
- Removedset-function-length@1.2.2(transitive)
- Removedset-function-name@2.0.2(transitive)
- Removedsource-map@0.1.430.6.1(transitive)
- Removedthrough@2.3.8(transitive)
- Removedtype-check@0.3.2(transitive)
- Removedunassert@1.6.0(transitive)
- Removedunassertify@2.1.1(transitive)
- Removedword-wrap@1.2.5(transitive)