fwebc.js

Toy framework for learning web components
git clone git://git.finwo.net/lib/fwebc.js
Log | Files | Refs | README

commit cd2dccaf5ca4d354a7cea69682999c210a1c87c6
parent ec59b356dd842e90703061e2b69c9540d2379fbe
Author: finwo <finwo@pm.me>
Date:   Wed, 28 Oct 2020 16:50:39 +0100

First version

Diffstat:
AREADME.md | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aindex.js | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackage.json | 3++-
Atest/assets/fwebc.js | 2++
Atest/assets/style.css | 0
Atest/index.html | 20++++++++++++++++++++
Atest/partial/my-app.tag | 8++++++++
7 files changed, 193 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md @@ -0,0 +1,78 @@ +# fwebc + +Simple web component framework to teach myself about web components + +## Install + +This package is intended for direct use in the browser, although wrappers like +browserify and/or webpack *should* work. + +```js +npm install --save fwebc +``` + +```html +<script src="https://unpkg.com/fwebc"></script> +``` + +Upon load, fwebc returns within `module.exports` if support for that is +detected, else it attaches itself to `window.fwebc`. + +## Usage + +fwebc makes use of `window.customElements`, so initialization is not needed, +only configuration. + +### fwebc.cfg( configuration? ) + +For now, only the default file extension and the base uri (location to your +templates) are configurable. + +```js +fwebc.cfg({ + ext : 'tag', + base: '/partial', +}); +``` + +### fwebc.install( callback:fn &lt;component&gt; ) + +Adds a callback to the creation process of an element. The shadow root (a.k.a. +component) is given as the first -and only- argument to the callback. + +This functionality allows you to add mixins or other features to components. + +### fwebc.uninstall( callback ) + +When given the exact same callback as you installed, removes it from the calls +to perform upon component creation. + +### fwebc.register(name, source) + +Registers a new component, based on the source string you give. The name must +include a hyphen `-` because fwebc is built directly on top of +`window.customElements`. + +**CAUTION**: this method will **NOT** throw an error if a name has already been +registered. + +### fwebc.load(name) + +Loads the template identified by the given name, surrounded by the configured +`base` and `ext`, and registers it. + +## Templates + +Loaded templates are attached to elements as shadow roots, with the addition of +all top-layer script tags being executed having the shadow root as their `this` +variable. + +Data binding is not included by default, but is easily added by including a +module like [rivets](https://www.npmjs.com/package/rivets) and installing it +like a plugin as follows: + +```js +fwebc.install(component => { + rivets.bind(component, component); +}); +``` diff --git a/index.js b/index.js @@ -0,0 +1,83 @@ +;(factory => { + if (('object' === typeof module) && ('exports' in module)) { + module.exports = factory({ + fetch: require('node-fetch'), + }); + } else if ('object' === typeof window) { + window.fwebc = factory({ + fetch: window.fetch, + }); + } +})(({fetch}) => { + const fwebc = {}; + const plugins = []; + const config = { + ext: 'tag', + base: '/partial', + }; + + // Override configs + fwebc.cfg = cfg => { + Object.assign(config, cfg); + }; + + // Install a plugin + fwebc.install = callback => { + if ('function' !== typeof callback) return; + plugins.push(callback); + }; + + // Remove a plugin + fwebc.uninstall = callback => { + const idx = plugins.indexOf(callback); + if (!~idx) return; + plugins.splice(idx, 1); + }; + + // Register a component + fwebc.register = (name, source) => { + if (window.customElements.get(name)) return; + window.customElements.define(name, class extends HTMLElement { + constructor() { + super(); + + // Build shadowroot + let template = document.createElement('template'); + template.innerHTML = source; + if (template.content.firstChild instanceof HTMLTemplateElement) { + template = template.content.firstChild; + } + const shadow = this.attachShadow({ mode: 'open' }); + shadow.appendChild(template.content); + + // Run plugins + for (const plugin of plugins) { + plugin(shadow); + } + + // Run component code + (new Function([...shadow.children] + .filter(el => el instanceof HTMLScriptElement) + .map(el => el.innerHTML) + .join('') + )).call(shadow); + + // Load dependencies + if (shadow.dependencies) { + dependencies.forEach(fwebc.load); + } + } + }); + }; + + // Load a component + fwebc.load = name => { + fetch(`${config.base}/${name}.${config.ext}`) + .then(res => res.text()) + .then(source => { + fwebc.register(name, source); + }); + }; + + return fwebc; +}); diff --git a/package.json b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\"", + "postpublish": "PACKAGE_VERSION=$(cat package.json | grep \\\"version\\\" | head -1 | awk -F: '{print $2}' | sed 's/[\",]//g' | tr -d '[[:space:]]') && npm deprecate \"fwebc@<${PACKAGE_VERSION}\" \"Rolling release, please update to ${PACKAGE_VERSION}\"" }, "repository": { "type": "git", diff --git a/test/assets/fwebc.js b/test/assets/fwebc.js @@ -0,0 +1 @@ +../../index.js +\ No newline at end of file diff --git a/test/assets/style.css b/test/assets/style.css diff --git a/test/index.html b/test/index.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf8"> + <link rel="stylesheet" href="/assets/style.css"> + </head> + <body> + <my-app></my-app> + <script src="/assets/fwebc.js"></script> + <script> + (async () => { + fwebc.cfg({ + base: '/partial', + ext : 'tag', + }); + fwebc.load('my-app'); + })(); + </script> + </body> +</html> diff --git a/test/partial/my-app.tag b/test/partial/my-app.tag @@ -0,0 +1,8 @@ +<template> + <h2>Bird is the word!</h2> + <style> + h2 { + color: #555; + } + </style> +</template>