JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 443
  • Score
    100M100P100Q94539F
  • License MIT

Conditioner - Frizz Free, Environment-aware, JavaScript Modules

Package Exports

  • conditioner-core

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (conditioner-core) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Conditioner

Declaratively link JavaScript modules to your elements and mount them based on contextual parameters like viewport size and element visibilty.

Example

Mount a component (like a Date Picker, Section Toggler or Carrousel), but only do it on wide viewports.

<h2 data-module="/ui/component.js"
    data-context="@media (min-width:30em)"> ... </h2>

If the viewport is resized or rotated and suddenly it's smaller than 30em conditioner will automatically unmount the component.

Features

  • Progressive Enhancement!
  • Responsive Design!
  • Declarative way to bind logic to elements, why this is good
  • No dependencies and small footprint (~1KB gzipped)
  • Compatible with ES import(), AMD require() and WebPack
  • Easily extend with plugins

Installation

Install with npm:

npm i conditioner-core --save

Using a CDN:

<script src="https://unpkg.com/conditioner-core/umd/conditioner-core.js"></script>

Setup

Using Conditioner on the global scope:

<script src="umd/conditioner-core.js"></script>
<script>

// mount modules!
conditioner.hydrate( document.documentElement );

</script>

Using Conditioner in ES6 modules:

import * as conditioner from 'conditioner-core';

// use es6 dynamic import
conditioner.setPlugin({
    moduleGetConstructor: (module) => module.default,
    moduleImport: (name) => import(name)
});

// mount modules!
conditioner.hydrate( document.documentElement );

Using Conditioner with dynamic imports:

import('conditioner-core/index.js').then(conditioner => {
    
    // use es6 imports
    conditioner.setPlugin({
        moduleGetConstructor: (module) => module.default,
        moduleImport: (name) => import(name)
    });

    // mount modules!
    conditioner.hydrate( document.documentElement );
});

Using Conditioner in an AMD module:

require(['umd/conditioner-core'], function(conditioner) {

    // setup AMD require
    conditioner.setPlugin({
        moduleImport: function(name) {
            return new Promise(function(resolve) {
                require([name], function(module) {
                    resolve(module);
                });
            });
        }
    });

    // mount modules!
    conditioner.hydrate( document.documentElement );
});

A collection of boilerplates to get you started with various project setups:

API

Public Methods

Inspired by React and Babel, Conditioner has a tiny but extensible API.

Method Description
hydrate(element) Mount modules found in the subtree of the passed element, returns an array of bound module objects.
addPlugin(plugin) Add a plugin to Conditioner to extend its core functionality.

Bound Module

Bound modules are returned by the hydrate method. Each bound module object wraps a module. It exposes a set of properties, methods and callbacks to interact with the module and its element.

Property / Method Description
alias Name found in dataset.module.
name Module path after name has been passed through moduleSetName.
element The element the module is bound to.
mount() Method to manually mount the module.
unmount() Method to manually unmount the module.
onmount(boundModule) Callback that runs when the module has been mounted. Scoped to element.
onmounterror(error, boundModule) Callback that runs when an error occurs during the mount process. Scoped to element.
onunmount(boundModule) Callback that runs when the module has been unmounted. Scoped to element.

Plugins

Adding a plugin can be done with the addPlugin method, the method expects a plugin definition object.

Plugins can be used to override internal methods or add custom monitors to Conditioner.

You can link your plugin to the following hooks:

Hook Description
moduleSelector(context) Selects all elements with modules within the given context and returns a NodeList.
moduleGetContext(element) Returns context requirements for the module. By default returns the element.dataset.context attribute.
moduleImport(name) Imports the module with the given name, should return a Promise.
moduleGetConstructor(module) Gets the constructor method from the module object, by default expects it to be defined on module.default or a default export.
moduleGetDestructor(moduleExports) Gets the destructor method from the module constructor return value, by default expects a single function.
moduleSetConstructorArguments(name, element) Use to alter the arguments supplied to the module constructor, expects an array as return value.
moduleGetName(element) Called to get the name of the module, by default retrieves the element.dataset.module value.
moduleSetName(name) Called when the module name has been retrieved just before setting it.
moduleWillMount(boundModule) Called before the module is mounted.
moduleDidMount(boundModule) Called after the module is mounted.
moduleWillUnmount(boundModule) Called before the module is unmounted.
moduleDidUnmount(boundModule) Called after the module is unmounted.
moduleDidCatch(error, boundModule) Called when module import throws an error.
monitor A collection of registered monitors. See monitor setup instructions below.

Let's setup a basic plugin.

Instead of writing each module with extension we want to leave out the extension and add it automatically.

We'll use the moduleSetName hook to achieve this:

conditioner.addPlugin({
    moduleSetName: name => name + '.js'
});

Next up is a plugin that adds a visible monitor using the IntersectionObserver API. Each monitor can be used in a context query by prefixing the name with an @. Monitor plugins should mimic the MediaQueryList API. For Conditioner this means that each monitor should expose a matches property and an addListener method.

conditioner.addPlugin({
    
    // we setup a monitor plugin
    monitor: {
        name: 'visible',
        create:(context, element) => { 

            // setup our api
            const api = {
                matches: false,
                addListener (change) {
                    
                    new IntersectionObserver(entries => {
                        api.matches = entries.pop().isIntersecting == (context === 'true');
                        change(api.matches);
                    }).observe(element);

                }
            };

            // done!
            return api;
        }
    }

});

Now we can use our new visible monitor like this:

<div data-module="/ui/component.js" data-context="@visible true"></div>

We can combine it with the media monitor by adding an and statement.

<div data-module="/ui/component.js" data-context="@media (max-width:30em) and @visible true"></div>

Version History

2.0.0

  • Total rewrite of Conditioner, resulting in an ES6 codebase and a smaller and more plugin oriented API.

1.2.3

  • Replaced this with window to fix Browserify root problems

1.2.0

  • Fixed unload handler not called
  • Renamed .on method to addConditionMonitor and .is method to matchesCondition
  • Added .removeConditionMonitor
  • Fixed problem where .is/matchesCondition method did not clean up Promise
  • Removed global and multiline flags from quick regex test issue 94

1.1.0

  • The supported property has been added which is used to determine if a module can be loaded at all
  • Improved getModule method API
  • Constructor now set when extending a module
  • Performance optimisations

1.0.1

1.0.0

  • Bind multiple modules to the same DOM node.
  • New was statement to make tests sticky element:{was visible}.
  • Alternative more human readable option format data-options=“map.zoom:10, map.type:terrain”.
  • Support for other AMD loaders, if you follow AMD specs you should be fine.
  • Browserify support, for conditional loading you'll still need an AMD loader though.
  • Separate loading state attribute for binding CSS loading animations.
  • Configure the paths and attributes Conditioner uses.
  • getModule and getModules methods to access moduleControllers more directly.
  • New is and on methods for manually testing conditions once or continually.
  • destroy method to destroy previously initialised nodes.
  • Writing your own monitors is now a lot easier.
  • Fixes and small improvements.

Read the 1.0.0 closed issue list for a complete overview.

License

MIT