Skip to content

Standard Custom Web Components using lit-html(render, html) for templating

License

Notifications You must be signed in to change notification settings

ulsmith/custom-web-component

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Custom Web Component

Forget angular, forget react, forget vue, lets start building raw custom web components!

using lit-html (render, html), an optional templating binder to offer super simple html templating and targetted updates to the dom with this.updateTemplate(), or simple embed your own raw HTML, your choice.

Look in the example folder for some basic concepts and use case, more docs to follow

DEVELOPMENT IN CHROME (native)

    <!-- Polyfill missing methods -->
    <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
    <!-- Native project entrypoint -->
    <script type="module" src="./index.mjs"></script>

PRODUCTION THROUGH BUILDS (compiled with babel)

    <!-- Polyfill missing methods -->
    <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
    <!-- Native project entrypoint -->
    <script type="module" src="./index.mjs"></script>
    <!-- Backup compiled endpoint for browsers that lack module loading -->
    <script nomodule src="./index.js"></script>

The above will give you modular JS loading for those browsers that support it and fallback to compiled build using babel should you wish to support IE11

YOUR ENTRY POINT

import './node_modules/reflect-constructor/reflect-constructor.js'; // IE11 Reflect polyfill
import './node_modules/@webcomponents/custom-elements/custom-elements.min.js'; // custom element class polyfill
import './src/HelloWorldComponent.js'; // your entry component

INSTALLATION

npm install reflect-constructor lit-html custom-web-component --save

BUILDING

// Typical Build script
var fs = require('fs');
var fsx = require('fs-extra');
var replace = require('replace-in-file');
var browserify = require("browserify");
var babelify = require("babelify");

/*************************************************/
/* Build into distributable, production versions */
/*************************************************/

// CUSTOM WEB COMPONENT -- BUILD //
console.log('--------------------------------');
console.log('- Custom Web Component - BUILD -');
console.log('--------------------------------');
console.log('');

// clean up build
console.log('Cleaned Build...');
fsx.remove('./index.js')
.then(() => console.log('Cleaned Build DONE'))
.catch((error) => console.log('Cleaned Build FAILED...', error))

// build src into distributable
.then(() => console.log('Create distributable logic...'))
.then(() => new Promise((resolve, reject) => {
	browserify({ debug: true })
	.transform(babelify.configure({ extensions: [".mjs"] }))
	.require("./index.mjs", { entry: true })
	.bundle()
	.on("error", (err) => reject("Browserify/Babelify compile error: " + err.message))
	.pipe(fs.createWriteStream("./index.js"))
	.on("finish", () => resolve());
}))
.then(() => console.log('Create distributable logic DONE'))
.catch((error) => console.log('Create distributable logic FAILED', error))

.then(() => {
	console.log('');
	console.log('-------');
	console.log('- END -');
	console.log('-------');
	console.log('');
});

COMPILATION (to build the index.js fall back using build.js)

# example in example folder
# build the IE11 fallback
npm run build

Will run the build.js script

SERVE USING EXPRESS

# example in example folder
# serve to localhost:XXXX
npm run serve

PACKAGE SETUP FOR BABEL

{
  "version": "0.0.1",
  "name": "MyApp",
  "description": "My Application",
  "licence": "MIT",
  "author": "Paul Smith (ulsmith)",
  "scripts": {
    "start": "node server.js",
    "serve": "node server.js",
    "build": "node build.js"
  },
  "dependencies": {
    "@webcomponents/custom-elements": "^1.2.1",
    "@webcomponents/webcomponentsjs": "^2.2.1",
    "custom-web-component": "^1.2.0",
    "lit-html": "^0.14.0",
    "reflect-constructor": "^1.0.0"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-plugin-transform-custom-element-classes": "^0.1.0",
    "babel-plugin-transform-es2015-classes": "^6.24.1",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-es2015-loose": "^7.0.0",
    "babel-preset-es2016": "^6.24.1",
    "babel-preset-latest": "^6.24.1",
    "babelify": "^7.3.0",
    "browserify": "^16.2.3",
    "express": "^4.16.4",
    "fs-extra": "^7.0.1",
    "replace-in-file": "^3.4.2"
  },
  "babel": {
    "plugins": [
      "transform-custom-element-classes",
      "transform-es2015-classes"
    ],
    "presets": [
      "latest"
    ]
  },
  "browserify": {
    "transform": [
      [
        "babelify",
        {
          "plugins": [
            "transform-custom-element-classes",
            "transform-es2015-classes"
          ],
          "presets": [
            "latest"
          ]
        }
      ]
    ]
  }
}

YOUR FIRST WEB COMPONENT

import { CustomHTMLElement, html } from "./node_modules/custom-web-component/index.js";

/**
 * HelloWorldComponent
 * A sample Custom HTML Element, to be used in any system that is capable of outputting HTML
 * Build on Web Standards and polyfilled for legacy browsers, using a simple clean lite HTML template rendering called lit-html
 */
class HelloWorldComponent extends CustomHTMLElement {
    
    /**
     * @public constructor()
     * Invoked when instantiation of class happens
     * NOTE: Call super() first!
     * NOTE: Declare local properties here... [this.__private, this._protected, this.public] 
     * NOTE: Declarations and kick starts only... no business logic here!
     */
    constructor() {
        super();

        this.foo = 'FOO!!';
        this.bar;
    }
    
    /**
     * template()
     * Return html TemplateResolver a list of observed properties, that will call propertyChanged() when mutated
     * @return {TemplateResult} Returns a HTML TemplateResult to be used for the basis of the elements DOM structure 
     */
    static template() {
        return html`
            <style>
                /* Style auto encapsulates in shadowDOM or shims for IE */
				:host { display: block; }
                #hello-world-component p { display: block; border: 2px solid red; padding: 20px; color: red; }
            </style>

			<div>
				<p>
					<slot name="main">Hello</slot>
					<br/>
					<br/>
					<strong>FOO:</strong> ${this.foo}
					<br/>
					<strong>BAR:</strong> ${this.bar}
					<br/>
					<br/>
					<slot name="footer">World</slot>
				</p>
			</div>
        `;
    }

    /**
     * @static @get observedProperties()
     * Return a list of observed properties, that will call propertyChanged() when mutated
     * @return {Array} List of properties that will promote the callback to be called on mutation 
     */
    static get observedProperties() { return ['foo', 'bar']; }

    /**
     * @public propertyChanged()
     * Invoked when an observed instantiated property has changed
     * @param {String} property The name of the property that changed 
     * @param {*} oldValue The old value before hte change 
     * @param {*} newValue The new value after the change
     */
    propertyChanged(property, oldValue, newValue) {
        console.log('propertyChanged', property, oldValue, newValue);

        this.updateTemplate();
    }
    
    /**
     * @public propertiesChanged()
     * Invoked when all property changes have taken place for this cycle
     * @param {Array} properties The names of the observed properties
     */
    propertiesChanged(properties) {
        console.log('propertiesChanged', properties);

        this.updateTemplate();
    }

    /**
     * @static @get observedAttributes()
     * Return a list of observed attributes, that will call attributeChanged() when mutated
     * @return {Array} List of attributes that will promote the callback to be called on mutation 
     */
    static get observedAttributes() { return ['bar']; }
    
    /**
     * @public attributeChanged()
     * Invoked when an observed node attribute has changed
     * @param {String} attribute The name of the attribute that changed 
     * @param {*} oldValue The old value before hte change 
     * @param {*} newValue The new value after the change
     */
    attributeChanged(attribute, oldValue, newValue) {
        console.log('attributeChanged', attribute, oldValue, newValue);

        if (attribute === 'bar') this.bar = newValue;

        this.updateTemplate();
    }
    
    /**
     * @public attributesChanged()
     * Invoked when all attribute changes have taken place for this cycle
     * @param {Array} attributes The names of the observed attributes
     */
    attributesChanged(attributes) {
        console.log('attributesChanged', attributes);

        this.updateTemplate();
    }

    /**
     * @public connected()
     * Invoked when node is connected/added to the DOM
     */
    connected() {
        console.log('connected');
    }

    /**
     * @public disconnected()
     * Invoked when node is disconnected/removed from the DOM
     */
    disconnected() {
        console.log('disconnected');
    }

    /**
     * @public templateUpdated()
     * Invoked when the template is updated
     */
    templateUpdated() {
        // this.dom will return you the <div id="hello-world-component"></div> element instance of this specific instance of the web component 
        console.log('Template has been updated via this.update()');
    }
}

customElements.define('hello-world-component', HelloWorldComponent);

use it in your html

<hello-world-component bar="bar!">
    <slot name="main">Hello</slot>
    <slot name="footer">World</slot>
</hello-world-component>

About

Standard Custom Web Components using lit-html(render, html) for templating

Resources

License

Stars

Watchers

Forks

Packages

No packages published