sewing_kit Build status

Zero configuration, high performance front end development at organization scale.

sewing_kit facilitates legacy Rails integration using ERB tags. For a more complete modern stack with performance-as-a-feature, consider quilt_rails. For details of the sewing-kit node package's configuration and usage, see the sewing-kit README.

Quick Start

Create a Rails project using dev init then:

Install Sewing Kits

# Add Ruby/Node dependencies
bundle add sewing_kit
yarn add @shopify/sewing-kit

# Optional - add Polaris
yarn add @shopify/polaris react react-dom

yarn
dev up

Add JavaScript

sewing_kit looks for JavaScript in app/ui/index.js. The code in index.js (and any imported JS/CSS) will be built into a main bundle.

The main bundle is imported into erb files using Rails helpers:

<%= sewing_kit_link_tag *sewing_kit_assets('main', extension: 'css') %>
<%= sewing_kit_script_tag *sewing_kit_assets('main') %>

Note: CSS <link> tags appear only in production; in development, CSS is embedded within the main.js bundle.

Minimal Project Layout

├── Gemfile (must contain "gem 'sewing_kit")
├── package.json (must specify '@shopify/sewing-kit' as a 'devDependency')
│
└── app
   └── ui
   │   └─- index.js
   └── views
       └─- layouts
           └─- application.html.erb (must link to JS / CSS using sewing_kit_script_tag / sewing_kit_link_tag

Rails, Polaris, and React Layout

A typical Polaris app will use React to render views and components. The following layout shows best practice locations for:

  • Global SCSS settings
  • App sections (roughly analogous to Rails routes)
  • Components
  • Co-located CSS modules
  • Co-located unit tests
└── app
    └── ui
        ├─- index.js (renders React into the DOM)
        ├── styles (optional)
        │   └── settings.scss (global vars and @polaris overrides)
        │
        └── tests (optional)
        │   └── each-test.ts
        │   └── setup.ts
        └── components (optional)
            ├── App
            │   ├── index.js
            │   ├── App.js
            │   └── tests
            │       └── App.test.js
            │
            ├-─ MyComponent
            │   ├-─ index.js
            │   ├-─ MyComponent.js
            │   ├── MyComponent.scss (optional; component-scoped CSS styles, mixins, etc)
            │   └── tests
            │       └── MyComponent.test.js
            │
            └── sections (optional; container views that compose presentation components into UI blocks)
                └── Home
                    ├-─ index.js
                    └── Home.js

React Boilerplate

  • Create a React app in app/ui/App.js (example)
  • In an erb view, add a placeholder for React content (example)
  • In index.js, render a React component into the placeholder element (example)
  • Use sewing_kit_script_tag/sewing_kit_link_tag helpers to link erb/js (example)

Testing the front end

For fast tests with consistent results, test front-end components using Jest instead of Rails integration tests.

Use sewing-kit test to run all .test.tsx files in the app/ui directory. Jest is used as a test runner, with customization available via its sewing-kit plugin.

Testing React

For testing React applications we provide and support @shopify/react-testing.

Example

Given a component MyComponent.tsx

// app/components/MyComponent/MyComponent.tsx
export function MyComponent({name}: {name: string}) {
  return <div>Hello, {name}!</div>;
}

A test would be written using Jest and @shopify/react-testing's mount feature.

// app/components/MyComponent/tests/MyComponent.test.tsx
import {MyComponent} from '../MyComponent';

describe('MyComponent', () => {
  it('greets the given named person', () => {
    const wrapper = mount(<MyComponent name="Kokusho" />);

    // toContainReactText is a custom matcher provided by @shopify/react-testing/matchers
    expect(wrapper).toContainReactText('Hello, Kokusho');
  });
});

Test setup files

By default, the jest plugin will look for test setup files under /app/ui/tests.

setup can be used to add any custom polyfills needed for the testing environment.

// app/ui/tests/setup.ts

import 'isomorphic-fetch';
import 'raf/polyfill';
import {URL, URLSearchParams} from 'url';

(global as any).URL = URL;
(global as any).URLSearchParams = URLSearchParams;

each-test can be used for any logic that needs to run for each individual test suite. Any setup logic that needs to happen with jest globals in scope, such as importing custom matchers, should also be done here.

// app/ui/tests/each-test.ts

// we cannot import these in `setup` because `expect` will not be defined
import '@shopify/react-testing/matchers';

beforeAll(() => {
  console.log('I will run before every test suite');
});

beforeEach(() => {
  console.log('I will run before every test case');
});

afterEach(() => {
  console.log('I will run after every test case');
});

afterAll(() => {
  console.log('I will run after every test suite');
});

For more complete documentation of the jest plugin see it's documentation.

Rails tests

Building JavaScript assets for Rails tests adds friction to test driven development, but some projects rely on tests that intermingle erb / JavaScript behaviour. To accommodate different test styles, sewing_kit offers two test modes.

:return_no_assets - sewing_kit helpers return empty arrays (default)

As noted above, the Web Foundation team highly recommends sewing-kit test for front end testing. Projects following this recommendation can use sewing_kit's default behaviour for controller, integration tests, and e2e tests. Note that no sewing_kit content will appear in pages with this mode enabled.

The default behaviour is equivalent to this configuration:

# config/initializers/sewing_kit.rb
SewingKit.configure do |config|
  config.test_manifest_mode = :return_no_asets
end

Use precompiled assets

If end-to-end tests are unavoidable:

  • Add extra steps to your test commands and CI pipelines that run sewing-kit build --mode test before test startup
  • Configure sewing_kit's test_manifest_mode to use precompiled assets:
# config/initializers/sewing_kit.rb
SewingKit.configure do |config|
  config.test_manifest_mode = :use_precompiled_assets
end

If your tests require production assets, precompile using sewing-kit build --mode production instead.

FAQ

Which version of sewing-kit can I use?

Assume that the sewingkit gem's latest minor version requires _at least the same minor version of the sewing-kit package.

If sewing-kit makes a breaking change, this gem's minor version will be bumped to match the required sewing-kit version.

How can I fix production builds that are failing due to missing devDependencies?

By moving everything into package.json#dependencies. This is necessary because Rails 5.2 prunes devDependencies during asset compilation.

How can I test a production verison of my changes?

Ideally, by deploying to a staging environment. If that is not possible, a production-like local development experience is available via:

NODE_ENV=production SK_SIMULATE_PRODUCTION=1 bundle exec rake assets:precompile
NODE_ENV=production SK_SIMULATE_PRODUCTION=1 dev run

Note:

  • Code changes will not be automatically recompiled in this state
  • SK_SIMULATE_PRODUCTION does not make Rails itself run in production mode
  • The most accurate way to gauge performance of the production version of an application is via a staging environment

After verifying production behaviour, run bundle exec rake assets:clobber to get back to development mode.

My project is using sprockets-commoner, can I use sewing_kit too?

No. sprockets-commoner uses an outdated Babel version, and is no longer compatible with sewing-kit.

sewing-kit's logs are too noisy. How can I see less of them?

sewing-kit defaults to using the same log_level as Rails' built in logger. It can be customized this using a configuration block:

SewingKit.configure do |config|
 config.log_level = :warn # may be `:inherit`, `:debug`, `:info`, `:warn`, or `:error`
end

How can I set breakpoints for server rendered apps?

Start your Rails server with SK_DEBUG set as an environment variable, and configure your Node debugger to listen on port 5858.

SK_DEBUG=1 bundle exec rails start