@instawork/design-system

The design system for Instawork's web apps

Usage no npm install needed!

<script type="module">
  import instaworkDesignSystem from 'https://cdn.skypack.dev/@instawork/design-system';
</script>

README

Instawork Design System

The Instawork Design System includes a version of Bootstrap 4.3 customized to match the Instawork UI style guide.

It also includes several reusable UI components implemented as jQuery plugins.

yarn install @instawork/design-system 

Prerequisites

Package consumer

  • jQuery ~3.3.1
  • Luxon ^1.26.0 (time-related components only)

Authoring / Development

Development

To run the demo locally:

nvm install
yarn
yarn start

This will start an HTTP server on port 8086. Visit http://localhost:8086/ in your browser to load the demo site.

Make changes

To make changes to the design system, first start the dev server:

yarn start

CSS / SCSS

For updating CSS styles, the main files to modify are in the scss directory:

  • _custom-variables.scss: Overrides for Bootstrap's variables. To see the full list of variables, see node_modules/bootstrap/scss_variables.scss. If you copy any of these to _custom-variables.scss and modify them, be sure to remove !default from your version.

  • _custom-styles.scss: Add SASS rules here to override Bootstrap's behaviors or add new behaviors.

  • design-system.scss: Add or remove components to the final Bootstrap build here. Avoid creating any implementations directly in this file - it is only to be used for combining style implementations from other files.

jQuery Component Plugins

Instawork's jQuery Component plugins can be found in ./components.

Testing

design-system uses the following stack to provide testing functionality:

  • Karma - Test runner (loads the code, browsers, etc)
  • Mocha - Test framework (define specs and tests with describe, it, etc)
  • Chai - BDD assertion library (expect(...))
  • Istanbul - Test coverage reports
Authoring tests
  • Create a .spec.ts file adjacent to the file you intend to test. For example, tests for code a file named my-awesome.component.ts would go in a file named my-awesome.component.spec.ts
  • Use Mocha's describe(...) to group your tests by method / property name, or logical categorization
  • When defining a test, the text passed to it(...) should form a descriptive sentence. Avoid using redundant words like "should" (e.g it('returns a string') instead of it('should return a string'))
  • Use Chai's expect assertion style. expect automatically included as a global variable for tests.
  • See @instawork/testing for more information on DOM-based testing
Running tests
  • From your terminal, run yarn test:watch - this will start the test server, and automatically run all tests. Tests will be re-run with every code change.
  • To run the test suite once (without watching for changes), run yarn test
  • To debug tests, open http://localhost:9876/debug.html and use the browser's Dev Tools
  • Coverage reports are generated automatically and can be found at .coverage/html/index.html

Note: By default, tests run in a "headless" instance of Chrome. To use other browsers, navigate to http://localhost:9876. To run other browsers automatically, or in "headless" mode, install the appropriate Karma browser launcher plugin.

Tests involving focus state

When testing code involving focus state, be aware that tests may behave differently when running in an interactive browser compared to a headless browser, particularly if you have DevTools open.

This snippet can be used in a before block to ensure non-interactive tests correctly receive focus:

$('<input>').appendTo('body').focus().remove();

For interactive test runs, be sure to give the body of the test document focus before refreshing / re-running your tests. Using the DevTools panel takes the focus away from the test document.

Demo pages

When creating or making significant changes to components or style features, please update or add a section to the corresponding demo page in ./demo. The demo pages are written in the PugJS template language.

Webpack's dev server will automatically refresh or update CSS in the demo pages as you make changes.

Before Submitting a PR

  1. If updating JavaScript / jQuery-based components, ensure there are unit tests covering your changes
  2. Ensure all changes are included in the demo artifact files by running yarn build
  3. Bump version in ./package.json (see Semantic Versioning below)
  4. Update CHANGELOG.md with a summary of your changes

Semantic Versioning

Like most packages in the NodeJS / JavaScript ecosystem, this package uses Semantic Versioning as its versioning scheme. A quick overview of what this means is:

  • The version consists of three parts - major, minor, patch, separated by dots (e.g. 1.2.3)
  • The version component getting incremented is determined by the nature of public facing changes being published
  • "Major" versions are incremented for incompatible or breaking changes
  • "Minor" versions are incremented for new features or other changes that are backwards compatible with the currently published version
  • "Patch" versions are incremented for backwards compatible bug fixes
  • Pre-release labels can be suffixed to the version as an extension - this project uses the -pre.# label for pre-releases (e.g. 3.0.0-pre.0)

Use the yarn next_semver script to automatically determine the next version based on the desired increment:

# show the next major, minor, patch, or pre-release version
yarn next_semver major       # 3.0.0       -> 4.0.0
yarn next_semver minor       # 3.0.0       -> 3.1.0
yarn next_semver patch       # 3.0.0       -> 3.0.1
yarn next_semver prerelease  # 3.0.0-pre.0 -> 3.0.0-pre.1

# show the version for starting a prerelease
yarn next_semver major --prerelease  # 3.0.0 -> 4.0.0-pre.0

# add the --write flag to the above commands to also update package.json
yarn next_semver minor --write 
yarn next_semver minor --prerelease --write
yarn next_semver prerelease --write 

Publishing

Publishing new versions of @instawork/design-system is currently done manually from a developer's machine.

Publishing prerequisites

In order to publish packages to the @instawork organization, you must be logged in using Instawork's shared npmjs.org account. See "Tools and Shared Accounts" in Confluence for authentication information.

  1. Run npm adduser --scope @instawork (note: npm, not yarn)
  2. Enter the username, password, and email from Confluence when prompted
  3. Verify that you see the following message:

    Logged in as (username) to scope @instawork on https://registry.npmjs.org/.

If you get an error or see a different registry URL, check for stray configuration settings by running npm config list.

Publish the package

Stable releases should only be published from master after a PR is approved and merged. Pre-releases can be published at any time from any branch.

To publish the @instawork/design-system package.

  1. Run yarn build:design-system.

  2. At the command line, switch to the dist/design-system directory

  3. Run yarn publish --access public from the dist/design-system directory

  4. When prompted for a version, hit enter - the version should already have been incremented as part of your PR (see Before Submitting a PR above) or for a pre-release

  5. The postpublish script should automatically give the release a distribution tag, which ensures that anyone installing the package using npm install or yarn install without a specific version will only ever get a stable release, and not a pre-release

    • Pre-releases (from a branch other than master) use the dev dist-tag.
    • Stable releases (from master) use the latest dist-tag.
  6. Verify the distribution tag in the console output of the publish command: Pre-releases:

                 [3/4] Publishing...
                 $ ../../scripts/dist_tag
    this line -> +dev: @instawork/design-system@3.0.0-pre.3
                 success Published.
    

    Stable releases:

                 [3/4] Publishing...
                 $ ../../scripts/dist_tag
    this line -> +latest: @instawork/design-system@3.0.0
                 success Published.
    

    If the dist_tag command fails for whatever reason, see Manually adding distribution tags

IMPORTANT: If you are publishing a pre-release, you the release MUST be tagging after publishing! Otherwise, the registry may incorrectly / implicitly treat your pre-release as the "latest", which is used when resolving versions for yarn install / npm install requests that do not include a version.

Note: The version must be changed between releases, as it is not possible overwrite a previously published release. You will see the following error if this is attempted:

Couldn't publish package: "https://registry.npmjs.org/@instawork%2fdesign-system: You cannot publish over the previously published versions: (version here)

Another note: After the publish command succeeds, there may be a brief delay before you can install the newly released version. If you see a message like this:

Couldn't find any versions for "@instawork/design-system" that matches "(your recently published version)"  

...then grab a drink of water, stand up and stretch for a moment, and then try again.

Manually adding distribution tags
  • When publishing from master:
npm dist-tag add @instawork/design-system@VERSION latest  

Example:

npm dist-tag add @instawork/design-system@1.0.0 latest  
  • When publishing a pre-release:
npm dist-tag add @instawork/design-system@VERSION dev  

Example:

npm dist-tag add @instawork/design-system@1.0.0-pre.1 dev  

Note: Use npm here and not yarn - yarn errors when trying to add tags

Removing previously published releases

NPM allows releases to be removed within 72 hours of publishing. This can be done using the npm unpublish command:

npm unpublish @instawork/design-system@3.0.0-pre.3

Be sure the correct version is specified, as the command will appear to succeed given a non-existent version.

Testing changes in another application

Before publishing a new release, it is recommended to check the changes in a client application.

Local testing

It is possible to test changes to the package locally without publishing a new version to yarn / npm using the yarn link command:

  1. Run yarn build:design-system
  2. In the dist/design-system directory, run yarn link
  3. In the project root of the test client application, run yarn link @instawork/design-system

This will create a symlink between the client application's node_modules directory for the @instawork/design-system package, and the dist/design-system directory in the design-system project.

After making changes, you will need to run yarn build:design-system again to update the build artifacts, but you should not need to repeat the yarn link commands.

Running yarn (yarn install) in the client project will restore the real package from the npm repository. To test the local package again, re-run yarn link @instawork/design-system.

Pre-releases

A pre-release will allow you to test a release as it will be used in the real world by actually publishing it to the npm registry, allowing it to be installed by anyone on any machine.

To create a pre-release:

Build commands

The commands use a convention such that the shorter commands run all commands that use their name as a prefix. So for example, yarn build:design-system will run all commands that start with build:design-system:.

  • yarn build: Builds both the @instawork/design-system and @instawork/testing packages
  • yarn build:design-system: Builds all bundles and files required to publish @instawork/design-system
  • yarn build:design-system:lib: Builds the ES module files that go into the /lib subdirectory
  • yarn build:design-system:lib:ts: Compiles TypeScript source to individual JS files in /lib, generates type definition files (.d.ts)
  • yarn build:design-system:lib:scss: Generates empty stand-in JS files for each SCSS file (see Package structure below for details)
  • yarn build:design-system:lib:templates: Compiles PUG templates to ES modules (see Package structure below for details)
  • yarn build:design-system:web: Compiles SCSS to CSS, builds browser-ready JavaScript bundles
  • yarn build:design-system:cp: Copies other required files like package.json, README, and CHANGELOG
  • yarn build:testing: Compiles and collects all files required to publish the @instawork/testing package
  • yarn build:testing:ts: Compiles TypeScript source to individual JS files, generates type definition files (.d.ts)
  • yarn build:testing:cp: Copies other required files like package.json, README, and CHANGELOG
Package structure

@instawork/design-system

  • ./components.js - browser-ready bundle for jQuery plugin components
  • ./components.css - Styles supporting jQuery plugin components
  • ./design-system.css - Themed Bootstrap styles
  • ./marketing.css - Specialized styles for the marketing website
  • ./select2.css - Styles supporting our customized select2 implementation
  • ./fonts - font file assets
  • ./lib - Contains ES module and type declaration files for jQuery plugin components
ES Modules in ./lib

These files are provided to better support using the jQuery plugin components and their respective classes as dependencies in a downstream application. They are intended to be referenced by a bundler like Webpack, or even directly by the TypeScript compiler (as a dependency). They are published as ES modules to support Tree Shaking.

Since several of the components use a PUG templates and/or define their own styles via SCSS, additional processing is done to ensure that these aspects do not break downstream apps or force them to implement their own SCSS or PUG template processing:

PUG Templates

PUG templates are compiled to ES modules that provide the compiled HTML content as their default export. Since they are saved to the same path as the .pug source file, except with .js extension (e.g. time-input.component.pug.js), they will automatically be picked up by the downstream application's bundler or compiler, regardless of it configuring support for PUG. Since this is done without modifying either the source TypeScript file or its compiled ES module output, it does not negatively the accuracy of the source maps.

SCSS styles

A similar approach is used for SCSS styles - though instead of compiling the SCSS, an empty JavaScript file is created, effectively resulting in a noop when the downstream app loads the file. This requires that the downstream app uses a separate method to load the bundled components.css file at runtime.