From aee0e504475029868753332c285697e8911459ce Mon Sep 17 00:00:00 2001 From: Tyler Brostrom Date: Mon, 4 Nov 2019 16:27:02 -0600 Subject: [PATCH] add feature, promised components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On picoapp.mount(), each component is now wrapped in a Promise.resolve, which has the following consequences: 1. You can now asynchronously pass components to `picoapp` and `appInstance.add`. 1. Picoapp requires that `Promise` be polyfilled for browsers that don’t support it. 1. `appInstance.mount()` returns a `Promise` that resolves when all components have been successfully resolved and bound to the DOM. --- README.md | 44 ++++++++++++++++++++++++++++++++++++-------- index.js | 22 +++++++++++++++------- test.js | 19 ++++++++++++------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index de5b351..50355e8 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ > [operator](https://github.com/estrattonbailey/operator) – where page > transitions can make conventional JS patterns cumbersome. +**Note**: You’ll need to polyfill `Promise` for browsers that don’t support it. + ## Install ``` npm i picoapp --save @@ -44,7 +46,40 @@ const app = picoapp({ button }) To bind your component to the DOM node, call `mount()`: ```javascript -app.mount() +app.mount() // returns a promise which resolves when all components successfully mount +``` + +## Adding Components +If you need to add components – maybe asynchronously – you can use `add`: +```javascript +app.add({ + modal: component(context => {}) +}) + +app.mount() // bind the newly added component to the DOM +``` + +Additionally, you can pass arbitrary promises that resolve to a component: +```javascript +import { picoapp } from 'picoapp' +import button from './button.js' + +const components = { button } + +// check for native image lazy-loading +if (!'loading' in HTMLImageElement.prototype) { + const lazyImage = import('lazy-image-fallback.mjs').then(module => module.default) + components.push(lazyImage) +} + +const app = picoapp(components) // components.lazyImage is a promise +``` + +```javascript +// lazy-image-fallback.mjs +export default component((node, ctx) => { +… +}) ``` ## State & Events @@ -166,13 +201,6 @@ And then access it from anywhere: app.getState() // { count: 5 } ``` -If you need to add components – maybe asynchronously – you can use `add`: -```javascript -app.add({ - lazyImage: component(context => {}) -}) -``` - If `data-component` isn't your style, or you'd like to use different types of "components", pass your attributes to `mount()`: diff --git a/index.js b/index.js index 39eeafb..cd0275e 100644 --- a/index.js +++ b/index.js @@ -41,6 +41,7 @@ export function picoapp (components = {}, initialState = {}) { return evx.hydrate(data) }, mount (attrs = 'data-component') { + const queue = [] attrs = [].concat(attrs) for (let a = 0; a < attrs.length; a++) { @@ -57,17 +58,24 @@ export function picoapp (components = {}, initialState = {}) { if (comp) { node.removeAttribute(attr) // so can't be bound twice - try { - const instance = comp(node, evx) - isFn(instance.unmount) && cache.push(instance) - } catch (e) { - console.log(`🚨 %cpicoapp - ${modules[m]} failed - ${e.message || e}`, 'color: #E85867') - console.error(e) - } + queue.push( + Promise.resolve(comp) + .then(initFn => { + const instance = initFn(node, evx) + isFn(instance.unmount) && cache.push(instance) + }) + .catch(e => { + console.log(`🚨 %cpicoapp - ${modules[m]} failed - ${e.message || e}`, 'color: #E85867') + console.error(e) + }) + ) } } } } + + // NOTE: Promise.allSettled might be be ideal, but browser support isn't there yet + return Promise.all(queue).then() // so `mount()` can be awaited }, unmount () { for (let i = cache.length - 1; i > -1; i--) { diff --git a/test.js b/test.js index 71e33a5..6387dd0 100644 --- a/test.js +++ b/test.js @@ -42,19 +42,24 @@ test('events', t => { app.emit('a') app.emit('b') }) -test('mount', t => { - t.plan(1) +test('mount', async t => { + t.plan(2) - const app = picoapp({ - foo: component((node, ctx) => { + const createComponent = () => { + return component((node, ctx) => { const u = ctx.on('foo', () => { t.pass() u() // unsub itself }) }) + } + + const app = picoapp({ + foo: createComponent(), + bar: Promise.resolve(createComponent()) // async component }, { bar: true }) - app.mount() + await app.mount() app.emit('foo') @@ -62,7 +67,7 @@ test('mount', t => { app.emit('foo') }) -test('unmount', t => { +test('unmount', async t => { t.plan(4) const app = picoapp({ @@ -75,7 +80,7 @@ test('unmount', t => { }) }) - app.mount() + await app.mount() app.emit('foo')