Controlling generated config

Overview

Please see

for an overview of the options for curating your import and some possible reasons for doing so.

On this page we will describe how to modify the Interplay config before it is deployed to Interplay - either by:

  • changing how it is generated,
  • or modifying it after it is generated, before it is sent to Interplay
👉

You don't need to modify your component code for Interplay - your components should be the same as you use in your production applications.

Changing CLI settings

If you are using the CLI to generate your config, changing your settings can have a major impact on what is imported to Interplay. Your settings in interplay.config.js control:

  • Components - packages specifies which packages are imported from your repo, and within each package src controls what component index is parsed and ignoreExports specifies any exports to ignore.
  • Presets - presetPaths controls which files to look in for instances of your components and presetRules specifies which component instances are are imported as presets. You can also use data-attributes to tag instances to use as presets - see below.
  • Files - includePaths and deployPaths control which files are deployed to Interplay and whether they are included on the page by default.

For more details about these CLI setting please refer to

Curating Components

Excluding components

There are 2 ways to exclude components from the Interplay config created by the CLI:

1. Ignoring index exports

Each package you import has an index file that is used as the entrypoint for parsing. In your CLI settings you can set a list of exports to ignore in the ignoreExports

Use this approach for exports that still need to be present in your build (for example, if they are used by other components) but you want to avoid having them registered as components in Interplay.

2. Custom component index

Alternatively you can create a custom index file to use for Interplay that only exports the components you want in Interplay.

If you use this same index file for parsing and bundling (i.e. in the src and build package settings in your CLI settings) then both the build and the config will only contain the components in the index.

Your index file must:

  • Export your components individually (as named exports or default)
  • Use the same export names as the build you provide (this will of course be the case if you use same index for parsing and bundling).

After creating your custom index, update the src setting for the relevant package in your CLI settings file to tell the CLI to use this custom index.

Component config structure

Before attempting to modify the component config it is useful to see an example of how it is structured. You can see the config the CLI has created for your components in your .interplay/deploy/code.json file.

"< identifier value >": {
			"id": "< identifier value >",
			"name": "Button
			"description": "Here is the component description",
			"code": {
				"packageName": "reactstrap",
				"exportName": "Button",
				"lib": "reactstrap"
			},
			"path": "Actions/Buttons"
			"props": {
				"variant": {
						"name": "variant",
						"type": "string"
				},
				"size": {
					"name": "size",
					"type": "string",
					"enum": [
						"small",
						"medium",
						"large"
					],
					"default": "medium",
					"ui": {
						"hidden": true
					}
				},
					//...(more props)
			},
			
			"relativePath": "src/Button.js"
		},

Component

name - the component name to display in Interplay

description - the description text displayed in Interplay

code - this block specifies how the Interplay accesses this component in the your build

path - Interplay folder structure for Button (available CLI 2.0.0+)

Component props

type - how Interplay should treat the value set for this prop

  • Currently a single type is supported per prop
  • Current allowed values are string, number, boolean, object, date, array, Node, Event, Component, fnNode, unknown

enum - sets the allowed values for the prop - these will appear as a dropdown in properties panel

ui.hidden - prevent prop displaying in Interplay and plugins (can still edit online in Edit Properties)

Controlling Component Config

1. In your code

The Interplay parser will generate metadata from the structure of your code. For React components you can set:

displayName - to set the name that Interplay will use for your component. If this is not set it will default to the filename containing the component.

class StatefulButton extends React.Component {
	//(StatefulButton implementation)
}
StatefulButton.displayName = "Button"

propTypes (react) or type definitions for your props - we recommend controlling property type information information using prop-types or typescript types definitions in your code. The CLI parser will find this type information and use it to generate Interplay configuration.

e.g. Using proptypes to specify the allowed values for a property, which will appear as dropdown in Interplay props panel

//Button.js
import PropTypes from 'prop-types';

//(Button implementation)

Button.propTypes = {
  variant: PropTypes.oneOf(['success', 'warning', 'error']),
	
//more Button props
}

e.g. Using typescript to specify allowed values

//Button.ts
export type Size = 'small' | 'medium' | 'large'

export interface IButtonProps {
  /** The size of the button */
	size?: Size                                   //using variable

  variant?: 'success' | 'warning' | 'error';    //inline values
	
//more Button props
}
const Button = ({ children, ...props }: IButtonProps) => {
	//Button implementation
}

2. Using JSDoc tags

You can modify the component config generated by adding JSDoc tags to the component definition, or to the props definitions.

Components

description

You can add normal JSDoc comments to your components and their properties and they will be used as the component description in Interplay when discovered by the parser:

/** 
 * Normal JSDoc description will be set on component by Interplay
*/
class Button extends React.Component {

You can set a custom description for Interplay with the @interplaydesc tag

  /** 
   * Default description of Button
	 * @interplaydesc Custom description parsed by Interplay CLI
   * */
  class Button extends React.Component {

path (CLI 2.0.0+)

You can create and control the folder that your component is displayed in by setting the path attribute using the @interplaypath tag. On running the import, the equivalent folders will be created in Interplay if they do not already exist:

/** 
 * Description of Button component
 * @interplaypath Actions/Buttons
*/
class Button extends React.Component {

Props

description

As for components, can set description on props using a normal jsDocs tag on their propTypes in javascript or in type definitions for Typescript...

  /** 
   * The size of Button to display
   * */
  size: PropTypes.string, 

...and you can set a custom description for Interplay with the @interplaydesc tag

  /** 
   * The size of Button to display
	 * @interplaydesc Custom description parsed by Interplay CLI
   * */
  size: PropTypes.string, 

ui.hidden

You can hide component properties by setting the ui.hidden attribute, using the @interplayignore tag (CLI1.x.x) or @interplayhidden (CLI 2.0.0)

/** @interplayignore */
ref: PropTypes.string, 

type (e.g. for Render Props)

There is a shortcut tag @interplaytype available for overriding type for a prop.

We recommend type information for props setting this via propTypes or typescript type definitions rather than JSDocs as outlined above.

There are two cases where you might need to modify the type

  1. You need to override the value the parser finds. e.g. if the type was not parsed, or there are two different types allowed by the code and we need to specify which one to use:
/** 
 * Here is the description for this prop
 * @interplaytype string
 * */
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

  1. Another case where you need to tell the parser the type to use is with renderProps. To the parser, a render prop will look like any other function property. We can tell the parser this is a render prop by assigning a tag with the special value of () => React.ReactNode this will cause the type of 'fnNode' (function node) to be set in the config
/** Description of the render prop
* @interplaytype () => React.ReactNode
*/
  myRenderProp: PropTypes.func.isRequired,

Setting other attributes on both components and props (CLI 2.0.0+)

JSDocs can be also used to modify or set other config attributes by using one or more interplay tags, which use the format @interplay {type} attribute - value

  • type is the type of value you are providing (string, number, boolean, object or array)
  • attribute is the dot path to the attribute you are modifying from where the JSDoc tag is applied
  • - is used as delimiter between attribute and value
  • value is the value to set, parsed based on {type}

Here are some examples setting attributes on the component object

/** 
 * Default description of Button
 * @interplay {string} description - Custom description of Button for Interplay
 * @interplay {string} path - Actions/Buttons
 */
class Button extends React.Component {

These examples are the equivalent of setting the @interplaydesc and @interplaypath tags above.

Here are some examples setting attributes on a prop declaration.

/** Default description of variant prop
 * @interplay {string}  description  - Here is my override variant description
 * @interplay {boolean} ui.hidden    - true
 * @interplay {array}   enum         - ["primary", "secondary", "danger", "warning"]
 * @interplay {number}  default      - 0
 */
  variant: PropTypes.string

3. Programmatically modifying component config

If you are using the CLI, you can intercept the parsing at different points to programmatically modify the generated configuration. This is done by overriding modifier events in your interplay.config.js file.

Please see "Programmatically Modifying Config" below for more details.

Presets

Interplay can parse exported instances of your components from files

to use as presets from Storybook stories in both Component Story Format and the older storiesOf format. Stories can be in either javascript or typescript files.

For best control over your presets in Interplay, we recommend writing your presets in Component Story Format, an open format proposed by the creators of Storybook.

Using Storybook CSF

Component Story Format is an open format proposed by the creators of Storybook and includes provision for adding metadata and including/excluding stories from processing.

Here is a CSF file that exports 2 presets (stories) for the Button component:

//import your components via relative path or package alias so Interplay can find your code
import Button from './Button';
export default {
  title: 'Button'      
}
//export presets individually
export const primaryButton = () => <Button appearance="primary">Primary Button</Button>;
export const secondaryButton = () => <Button appearance="secondary">Secondary Button</Button>;

//The preset name is determined by the named export, or you can optionally set metadata:
primaryButton.story = {
    name: "Primary Button example"
    description: "Here is a description for the primary button example."
}

When writing presets to parse:

  • Use a separate named export for each preset to retain control of the metadata. e.g. the file above has 4 named exports with one Button configuration in each, not 1 export containing 4 Buttons.
  • Import the components used in your presets from their source files using relative paths to help the parser resolve the component.

Selectively including exports

If you are parsing existing storybook files you may want to include or exclude particular stories. Storybook CSF provides a syntax for includes and excludes, via array or regex pattern.

In the preset above we could modify the default export as follows:

export default {
    title: 'Button',
    component: Button,
    includeStories: ['primaryButton', 'secondaryButton']
}

Interplay will obey the includeStories and excludeStories settings that you set here and only import those stories.

For full details of Component Story Format please see the Storybook website

Overriding CSF metadata for Interplay

Alternatively you can override the core CSF metadata with Interplay-specific metadata as follows:

import Button from './Button';
export default {
  title: 'Button',         
  component: Button,
  interplay: {
      includeStories: ['primaryButton'] //interplay-specific
  }
}

export const primaryButton = () => <Button appearance="primary">Primary Button</Button>;

//Overriding CSF metadata for Interplay
primaryButton.story = {
    name: "Primary Button"             //Storybook name
    description: "Primary Button"      //Storybook description
    interplay: { 
        name: "Main Button",           //Interplay name
        description: "The description in Interplay"  //Interplay description
    }
}

Using Data Attributes

You can use data attributes such as data-interplay-name and data-interplay-description tags in your the JSX you parse to generate presets, to control:

  • Which instances in your JSX are imported as presets - to cherry-pick component instances in addition to using presetRules.
  • Metadata on the resulting preset - in this case name and description.

All instances parsed in your presetPaths files that are tagged with one of these data attributes will be imported to Interplay, even if they are not unique or do not match your presetRules. This can be a useful way of selecting particular instances to use as presets in existing files.

import React from 'react';
import { Button, ButtonGroup } from 'reactstrap';
const Example = (props) => {
  return (
    <ButtonGroup data-interplay-name="ButtonGroup Example" data-interplay-description="Basic example with 3 Buttons">
      <Button data-interplay-name="Button Example" data-interplay-description="Default Button">Button</Button>
      <Button>Button</Button>
      <Button>Button</Button>
    </ButtonGroup>
  );
}
export default Example;

Tokens

You can bulk edit tokens online in Interplay in ThemeUI format - please see

The CLI also supports loading design tokens and token groups from a JSON file in your repo.

We will publish documentation of these formats very soon - meanwhile please contact us for help loading these.

Programmatically modifying config

As the CLI parses your code, it builds up a configuration object with the information it has found. At the end of the parsing stage it outputs this information as JSON data to .interplay/stage/config/parsing.json in the interplay folder, ready to send to Interplay.

Sometimes it is useful to modify either the config before it is submitted to Interplay. You can do this using event and modifier functions.

Modifier functions

At various stages during processing, the CLI calls event functions, passing the context so that you can modify it to change the CLI behaviour or parsing results.

You can implement handlers for these events by implementing them in your interplay.config.js file. For example, for the parsingComplete event:

//interplay.config.js
module.exports = {
    events: {
			parsingComplete: (context) => {
					//get the components and examples from the context
			    const { components = {}, examples = {}} = context;

					//make changes to component config or examples (presets) here
			
					//return the context to pass it on to next function in CLI
           return context;
        }
    }
}


The following modifier events and placeholders are currently available. We will be expanding this list as needed to allow more customisation of the CLI's behaviour - please let us know what you need.

//interplay.config.js
module.exports = {
    events: {
			//modify config after the CLI resolves source files, before parsing
			resolveComponentsComplete: (context) => {},
			//modify config after all parsing, before deploy to interplay
			parsingComplete: (context) => { return context;}
    },
    modifiers:{
			//modify the plugins used to transpile
			babelPlugins: (plugins) => { return plugins},
			//modify the webpack config used to bundle
			webpackConfig: (config, webpack) => { return config},
    }
}

Use cases

Setting the folder for each component

We can override the parsingComplete event to modify the component config produced by the parsing.

In this scenario we will set a folder of 'Icons' on selected components:

//interplay.config.js
module.exports = {
    events: {
//override the parsingComplete event
parsingComplete: (context) => {
				//get the component config from the context
        const { components = {} } = context;
						
            Object.keys(components).forEach(componentId => {
                const component = components[componentId] || {};
                const { code } = component;
                if(code.exportName && code.exportName.indexOf('Arrow') > -1){
										//set the component folder
                    component.path = 'Icons/Arrows';
                }
            });

            return context;
        }
    }
}


Parsing additional component source files

Sometimes we may need to instruct the CLI to parse components addition to those defined in your component index. This may be useful for subcomponents not exported from your index (e.g. Dropdown.Item), or where the CLI cannot resolve a source file from your index for some reason.

To do this we effectively need to provide a mapping between the source file to parse and where in the build Interplay can find the component to run (because it is not exported from your index).

We can use the resolveComponentsComplete event to programmatically add the same mapping config the CLI would have generated, as if it had found and resolved the component from the index:

//interplay.config.js
module.exports = {
    events: {
//override the resolveComponentsComplete event
resolveComponentsComplete: (context) => {
			//get the config generated by resolving the index components
      const { components = {} } = context;
						//tell Interplay about another component
            const DropdownItem = {
								//use the same ID format as for your other components
                id: 'PackageName_DropdownItem',
								
							//package build and export to use at runtime
                code: {
                    packageName: '@package/name',
                    exportName: 'Dropdown.Item'//as exported from build
                },

								//component source file and export to parse, relative to cwd
                resolved: {
                    relativePath: 'src/components/Dropdown/DropdownItem.js',
                    exportName: 'default'//as exported from source file
                }
              }

						//add to the components config for CLI to process
	          components[DropdownItem.id] = DropdownItem

            return context;
        }
    },
}


Customising babel plugins

The CLI uses a default set of babel plugins when bundling your code using webpack. In many cases the CLI defaults will be suitable, because we ask you to transpile your source code using your own build process before running the CLI.

In some situations you may need to modify the default set of babel plugins used. You can do this by overriding the babelPlugins modifier function as shown below. The function is called when initialising the CLI.

//interplay.config.js
const removeGraphQlPlugin =require("babel-plugin-remove-graphql-queries").default;
module.exports = {
    modifiers: {
				babelPlugins: (plugins) => {
						//add a plugin to the babel plugins array
            plugins.unshift(removeGraphQlPlugin);
            return plugins;
        },
    }
}

Customising webpack config

The CLI uses webpack with a default configuration to bundle your code for deployment to Interplay.

You can customise the webpack config used by the CLI using the webpackConfig modifier function.

For more details please see Custom Builds section in