Skip to content

How to integrate component into your build system

Thomas Schlage edited this page Apr 23, 2018 · 8 revisions

This description should help developers who just want to integrate our component into one of the popular build tools.

Webpack

React

  1. npm i -D git+ssh://git@github.com:fabric-design/scss.git
  2. npm i -D git+ssh://git@github.com:fabric-design/components.git
  3. To load the scss styles directly from your node_modules you can use the following webpack configuration:
    {
      test: /\.scss$/,
      include: /node_modules/,
      use: [{
        loader: 'style-loader',
      }, {
        loader: 'css-loader',
      }, {
        loader: 'sass-loader',
      }, {
        loader: 'string-replace-loader',
        query: {
          search: '([.]{2}\/){2}([node_modules]*)\/', // eslint-disable-line no-useless-escape
          replace: '~',
          flags: 'g',
        },
      }],
    },

We use the string-replace-loader to replace relative import paths to a sass-loader compatible imports with the ~. It basically changes all relative scss imports from

@import "../../node_modules/fabric-scss/utils/variables";

to

@import "~fabric-scss/utils/variables";

Afterwards you can use any Fabric component (e.g. WSHeader):

import React from 'react';
import { WSHeader } from 'fabric-components/dist/commonjs/index';
import 'fabric-components/dist/commonjs/ws-header/ws-header.scss';

export default class HomePage extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
  render() {
    return (
      <div>
        <WSHeader title="Test title" />
        <h1>
          Here we go!
        </h1>
       </div>
    );
  }
}

JSPM

To use this components package along with jspm you have to first install all dependencies.

# If you want to use preact
jspm install react=npm:preact react-dom=npm:preact-compat
# If you want to use react
jspm install npm:react npm:react-dom 

jspm install github:fabric-design/scss
jspm install github:fabric-design/components

Now you'll be able to import the components:

import {WSDropdown} from 'fabric-components'

new WSDropdown()

Aurelia

To import and use the single components in an Aurelia application we have to wrap the React components in an Aurelia ViewModel without view. This can be done by copying the following code to </path/you/want>/components/index.js

import {noView, customElement, bindable} from 'aurelia-templating';
import {decorators} from 'aurelia-metadata';
import {React, render} from 'styleguide-web-components/imports';

/**
 * Configure the aurelia loader to use handle urls with !component
 * @param config {FrameworkConfiguration}
 */
export function configure(config) {
    const loader = config.aurelia.loader;
    loader.addPlugin('styleguide-component', {
        fetch(address) {
            return loader.loadModule(address)
                .then(getComponents);
        }
    });
}

/**
 * Extract the components from the loaded module
 * @param module {Object} Object containing all exported properties
 * @returns {Object}
 */
function getComponents(module) {
    return Object.keys(module).reduce((elements, name) => {
        if (typeof module[name] === 'function') {
            const elementName = camelToKebab(name);
            elements[elementName] = wrapComponent(module[name], elementName);
        }
        return elements;
    }, {});
}

/**
 * Converts camel case to kebab case
 * @param str {String}
 * @returns {string}
 */
function camelToKebab(str) {
    // Matches all places where a two upper case chars followed by a lower case char are and split them with an hyphen
    return str.replace(/([a-zA-Z])([A-Z][a-z])/g, (match, before, after) => {
        return `${before.toLowerCase()}-${after.toLowerCase()}`;
    }).toLowerCase();
}

/**
 * Wrap the React components into an ViewModel with bound attributes for the defined PropTypes
 * @param component {Object}
 * @param elementName {string}
 */
function wrapComponent(component, elementName) {
    let bindableProps = [];
    if (component.propTypes) {
        bindableProps = Object.keys(component.propTypes).map(prop => bindable({
            name: prop,
            attribute: camelToKebab(prop),
            changeHandler: 'render',
            defaultBindingMode: 1
        }));
    }
    return decorators(
        noView(),
        customElement(elementName),
        bindable({name: 'props', attribute: 'props', changeHandler: 'render', defaultBindingMode: 1}),
        ...bindableProps
    ).on(createWrapperClass(component));
}

/**
 * Create a wrapper class for the component
 * @param component {Object}
 * @returns {WrapperClass}
 */
function createWrapperClass(component) {
    return class WrapperClass {
        static inject = [Element];

        constructor(element) {
            this.element = element;
        }

        bind() {
            this.render();
        }

        render() {
            const props = this.props || {};
            // Copy bound properties because Object.assign doesn't work deep
            for (const prop in this) {
                if (this[prop] !== undefined && typeof this[prop] !== 'function') {
                    props[prop] = this[prop];
                }
            }
            render(
                React.createElement(component, props),
                this.element
            );
        }
    };
}

Now define the styleguide components as global resources in your main.js to access them globally.

aurelia.use
  .standardConfiguration()
  .globalResources([
    'styleguide-web-components!component'
  ])

And now just use them! The attributes are bound to the React properties which are defined in the propTypes.

<ws-date-picker value.bind="date" change.delegate="setDate($event.detail.date)" />