Integrating Your Own Components

Requirements

This tutorial is about integrating your own repository of React.js components.

Merge requires the following technology set-up and code standards:

Framework – components created with React.js version ^16.0.0.

Browser - UXPin Merge works best in Chrome browser

Bundler – webpack (version ^4.6.0) config joining styles with components.

Directory structure:

  • single component per directory.
  • filename of the component defines the component name.

File structure:

Export - component files have to contain export default providing the component.

Dialect - we support the following JS dialects:

  • JavaScript (properties defined with PropTypes)
  • Flow (properties defined with types)
  • TypeScript (properties defined with interfaces)

 Prerequisites:

  • Your code has to follow the requirements listed above.
  • You're familiar with Javascript development and related tools.
  • You and your team have been invited to the UXPin Merge.

How much time will it take to go through this guide?

  • Setting up the environment and rendering the first component shouldn't take more than 30 minutes.
  • A complete integration of a complex Design System can take anything from 2 hours to 3 – 4 days. 

There are 2 kinds of Integration with Merge:

Clean integration – Merge works directly with your production code. No Wrappers or code changes are necessary.

Benefits:

  • Well integrated and simple workflow connecting designers and engineers.
  • Effortless maintenance (no extra work is needed to update the components in UXPin).
  • Fast integration

Flaws:

  • Requires strict following of Merge technical and code standard requirements.
  • Doesn't allow to modify the code of components for the benefit of designers using UXPin.

Wrapped integration – Merge works with Wrappers for your components. Wrappers, typically are HOC components.

Benefits:

  • Flexible integration (less problems with following Merge technical and code standard requirements).
  • Ability to modify coded components to meet the requirements of designers (e.g. creating controlled checkboxes).

Flaws:

  • Complicates the process of code maintenance and may require manual updates of code of components.
  • Integration may take more time (you have to create a Wrapper for every component).

Clean integration

We recommend trying the Wrapped Integration first and only if it doesn't work or changes in code are impossible to pursue, try going with Clean Integration.

Step-by-step guide for a Clean Integration

1. Install the UXPin Merge CLI tool in your project:

npm install @uxpin/merge-cli --save-dev

2. Create a uxpin.config.js file and place it at the root directory. Take a look at this example of a config. If you'd like to learn more, follow our dedicated guide: Config File. To make debugging easier, start by adding just one component in the config file and keep adding them one by one. Your initial config file will look like this:

module.exports = {
  components: {
    categories: [
      {
        name: 'General',
        include: [
          'src/Button/Button.js',
        ]
      }
    ],
    wrapper: 'src/Wrapper/UXPinWrapper.js',
    webpackConfig: 'webpack.config.js',
  },
  name: 'New Design System'
};

3. If your webpack config is complex, consider creating a specialwebpack config for Merge. It may simplify the debugging process. Merge needs webpack to bundle JS, CSS and import any assets that you need to import (icons, font files, etc.). Below you can see the most basic webpack config for a Design System using:

  • CSS in JS (for example – emotion)
  • Icons passed as inline SVGs to a React component
const path = require('path');
 
module.exports = {
    entry: ['./src/index.js'],
    output: {
      path: path.resolve(__dirname, 'build'),
      filename: 'bundle.js',
      publicPath: '/',
    },
    resolve: {
      modules: [__dirname, 'node_modules'],
      extensions: ['*', '.js', '.jsx'],
    },
    devtool: 'source-map',
    module: {
      rules: [
        {
          test: /\.svg$/,
          exclude: /node_modules/,
          loader: 'svg-react-loader',
        },
        {
          test: /\.js$/,
          exclude: /node_modules/,
          enforce: 'pre',
          loader: 'eslint-loader',
          options: {
            failOnWarning: false,
            failOnError: true,
          },
        },
        {
          loader: 'babel-loader',
          test: /\.js$/,
          exclude: /node_modules/,
        },
        {
          enforce: 'pre',
          test: /\.js$/,
          loader: 'source-map-loader',
        },
      ],
    },
};

To learn more about webpack configs for Merge, take a look at our dedicated guide: Webpack Configuration.

4. If you need to provide themes or any other kind of Wrapper for your components, create a HOC Wrapper component and place it in a separate directory anywhere in your repository. Here's an example of a Wrapper Component with a theme provider:

import React from 'react';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
 
const theme = createMuiTheme({
  palette: {
    primary: { main: indigo[500] },
    secondary: { main: teal[100]},
  },
  typography: { useNextVariants: true },
});
 
export default function UXPinWrapper({ children }) {
  
  return <MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>;
}

To learn more about Wrapper components, follow our Wrapper Component tutorial.

5. If your first component is ready, run the following command in the terminal:

uxpin-merge --disable-tunneling

Integration with wrappers for components

Step-by-step guide for integration with wrappers for components

1. You have two options to start with:

  • Create new directories for Wrapper Components inside of your existing repository. For example: ./src/components/Button/Merge/Button/Button.js
  • Create a completely new repository to keep your wrappers 

The options above do not affect the rest of the guide, but they will affect your internal processes of managing and releasing your components, so consider carefully what you want to do.

2. Once your repository is ready to welcome Wrapper Components, install Merge CLI tool as a project dependency:

npm install @uxpin/merge-cli --save-dev

3. Create your first Wrapper. A typical Wrapper would be a HOC React.js component that imports and returns your original component. For example:

import React from 'react';
import PropTypes from 'prop-types';
import { Button as ButtonM } from '../../Button';
 
function Button(props) {
   return (
      <ButtonM ...props>{props.children}</ButtonM>
  );
}
 
Button.propTypes = {
   children: PropTypes.node,
   size: PropTypes.oneOf(['small', 'large', 'medium'])
}
 
export default Button;

Merge requires a unified naming of the parent directory and the exported component. Since this name shows up in the UXPin Editor and the UXPin spec mode, make sure that the name of the exported component matches the name of the original component. However, to avoid name conflict, import your original component under a different name with a suffix. (ButtonM in the example above).

4. Create a uxpin.config.js file and place it at the root directory. Take a look at this example of a config. If you'd like to learn more, follow our dedicated guide: UXPin Merge Config File.

module.exports = {
  components: {
    categories: [
      {
        name: 'General',
        include: [
          'src/Button/Merge/Button/Button.js',
        ]
      }
    ],
    wrapper: 'src/Wrapper/UXPinWrapper.js',
    webpackConfig: 'webpack.config.js',
  },
  name: 'New Design System'
};

5. If your webpack config is complex, consider creating a special webpack config for Merge. it may simplify the debugging process. Merge needs webpack to bundle JS, CSS and import any assets that you need to import (icons, font files, etc.). Below you can see the most basic webpack config for a Design System using:

  • CSS in JS (for example – emotion)
  • Icons passed as inline SVGs to a React component
const path = require('path');
 
module.exports = {
    entry: ['./src/index.js'],
    output: {
      path: path.resolve(__dirname, 'build'),
      filename: 'bundle.js',
      publicPath: '/',
    },
    resolve: {
      modules: [__dirname, 'node_modules'],
      extensions: ['*', '.js', '.jsx'],
    },
    devtool: 'source-map',
    module: {
      rules: [
        {
          test: /\.svg$/,
          exclude: /node_modules/,
          loader: 'svg-react-loader',
        },
        {
          test: /\.js$/,
          exclude: /node_modules/,
          enforce: 'pre',
          loader: 'eslint-loader',
          options: {
            failOnWarning: false,
            failOnError: true,
          },
        },
        {
          loader: 'babel-loader',
          test: /\.js$/,
          exclude: /node_modules/,
        },
        {
          enforce: 'pre',
          test: /\.js$/,
          loader: 'source-map-loader',
        },
      ],
    },
};

To learn more about webpack configs for Merge, take a look at our dedicated guide -> Webpack Configuration for Merge and browse repositories of test integrations: Examples & Test Integrations.

6. If you need to provide themes or any other kind of wrapper for your components, create a HOC wrapper component and place it in a separate directory anywhere in your repository. Here's an example of a Wrapper Component with a theme provider:

import React from 'react';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
 
const theme = createMuiTheme({
  palette: {
    primary: { main: indigo[500] },
    secondary: { main: teal[100]},
  },
  typography: { useNextVariants: true },
});
 
export default function UXPinWrapper({ children }) {
  
  return <MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>;
}

To learn more about Wrapper components, follow our guide Wrapper Component.

7. If your first component is ready run the following command in the terminal:

uxpin-merge --disable-tunneling

The recommended way to send your React.js components to your production UXPin account is via integration with Continuous integration (Circle CITravis...). 

To start the integration:

  • Go to your UXPin account,
  • Enter UXPin design editor,
  • Create a new library,
  • Select the option "Import react.js components"
  • copy the authentication token (do not share it with anyone and do not place it in any files checked into Git repository. This token gives direct access to the library on your account)

Once you got the Auth Token, go to your CI app, start a project tracking your Design System repository (if you haven't yet) and add a new environment variable UXPIN_AUTH_TOKEN with the value set to your Auth token that you copied from your UXPin account.

In the config of your CI server, add a new step and run the following command (adjust attributes where necessary): 

./node_modules/.bin/uxpin-merge push --webpack-config ./webpack.config.js --wrapper ./src/Wrapper/UXPinWrapper.js

The simplest config file (for CircleCI) looks like this:

jobs:
  build:
    docker:
      - image: circleci/node:10.15-stretch-browsers
    working_directory: ~/project/
    steps:
      - checkout
      - run:
          name: "Install dependencies"
          command: npm install
      - run:
          name: 'Push to UXPin'
          command: ./node_modules/.bin/uxpin-merge push --webpack-config ./webpack.config.js --wrapper ./src/Wrapper/UXPinWrapper.js

If you want to learn more about CI integrations, take a look at our dedicated guide -> CI Servers.

An alternative to CI Server integration is a one time push. However, this option should be used only if you know that you won't be planning frequent updates of components. Open your terminal and type:

./node_modules/.bin/uxpin-merge push --webpack-config ./webpack.config.js --wrapper ./src/Wrapper/UXPinWrapper.js --token "YOUR_AUTH_TOKEN"

Do not save your Auth_Token in any files checked to the repository.

Possible Issues & Troubleshooting

  1. Some styles appear broken – your styles may interfere with UXPin CSS, or UXPin can interfere with your styles, so your styles need to be locally scoped to avoid conflicting with UXPin CSS.
  2. My components are distributed as individual NPM packages or I'm using typescript – consider writing Wrappers for every component (HOC) and importing your components / system as a dependency. Example.