@change-org/sandcastle

Sandcastle is a node.js server for local development that uses dynamic Webpack configs to allow you to test your React components in a browser in isolation.

Usage no npm install needed!

<script type="module">
  import changeOrgSandcastle from 'https://cdn.skypack.dev/@change-org/sandcastle';
</script>

README

Sandcastle (Alpha)

Sandcastle is a node.js server for local development that uses dynamic Webpack configs to allow you to test your React components in a browser in isolation.

How it works

The short version

// sandcastle.js
const startSandcastle = require('path/to/sandcastle');
startSandcastle({ webpackConfig: require('./webpack.config.js) });
// in your shell
node sandcastle.js

To test components/MyComponent.jsx, simply load /components/MyComponent.jsx on the Sandcastle server. For example, for a Sandcastle server running on localhost with port 3141 (the default port), you would load http://localhost:3141/components/MyComponent.jsx in your browser.

The longer version

When you load /components/MyComponent.jsx, the Sandbox server creates an instance of the Webpack compiler that serves compiled JS using webpack-dev-middlware. Any requests for webpack assets from this page will connect to this same middleware so that content like hot module reloading data, source maps, dynamically-loaded chunks, and anything else served by webpack will re-use the same webpack compiler and middleware.

The file browser

Sandcastle mounts a file browser page at / so that you can quickly search and open your files. This page will automatically be opened in your browser at Sandcastle startup if you set the openBrowser config to true.

Sandcastle components and module resolution

Sandcastle will look in three places for the component you're testing. A __sandcastle__ folder in the same directory as your component can be used to set up props and any other dependencies for testing your component.

  1. First, it looks for __sandcastle__/${componentFilename} in the component's directory.
  2. If that file doesn't exist, it will look for __sandcastle__/${componentFilenameWithJsxExtension} in the component's directory.
  3. If neither of those files exist, it will load the component from the path provided in the Sandcastle URL.

For example, if you're loading the TypeScript component components/MyTypescriptComponent.tsx, it will look for component files in this order.

  1. components/__sandcastle__/MyTypescriptComponent.tsx
  2. components/__sandcastle__/MyTypescriptComponent.jsx
  3. components/MyTypescriptComponent.tsx

This allows you to create a Sandcastle component that sets your component up for testing in the same language as the component or as a jsx file (or use the original component if your component does not require setup).

Environment

Sandcastle can use an Environment component to set up depenencies common to all components you'll be testing. This is useful for setting up React context providers so that you can use context consumers in the components you're testing. The props for your Environment component are set up using an environment setup function (except for children, which depends on the component and layout being rendered). The path to the Environment component module is set with the environment config option.

Environment setup

You can define a module that's used to set up the props for your Environment component. This module exports a function that will be run once when initially loading the sandbox page to create the environment props. It can be an asynchronous function if you need to do any asynchronous work to initialize the environment such as HTTP requests. The path to the environment setup module is set with the setupEnvironment config option.

Layouts

When initializing Sandcastle, you can provide a layouts object that maps layout names to file paths containing layout modules.

A layout module should export a React component with ony a children prop.

To use a layout in Sandcastle, use the layout query param to select a layout name. If no layout query param is present, the layout under the default key will be used. If no layouts are defined, the component will be renderd as-is on the page.

It may be useful to create a layout that centers a component for more pleasant development, and another with no margins or padding for components that take up the full page.

You may find it useful to use query params inside of layouts for more options (e.g. a background color).

API

startSandcastle(sandcastleOptions)

Starts the sandcastle server.

Example usage (with default options shown):

const startSandcastle = require('@change/sandcastle');

startSandcastle({
  webpackConfig: undefined,
  openBrowser: false,
  port: 3141,
  webpackPath: '__sandcastle_webpack',
  hotMiddlewarePath: '__webpack_hmr',
  sandcastleComponentFolder: '__sandcastle__',
  environment: undefined,
  setupEnvironment: undefined,
  bootstrap: undefined,
  webpackDefine: {},
  layouts: {},
});

sandcastleOptions

  • webpackConfig (required): The webpack config object to extend when creating Webpack middleware.
  • openBrowser (optional): Whether to open the Sandcastle homepage when running the server. Default: false.
  • port (optional): the port to run the sandcastle server on. Default: 3141.
  • rootDirectory (optional): The base path under which to look for files. Default: the current working directory of the sandcastle process.
  • fileBrowserGlobPatterns (optional): The glob patterns to use when listing and searching files in the file browser. You can use this to restrict file extensions or ignore certain directories (perhaps test files). Default: ['!(node_modules)/**'] (all files except those in node_modules).
  • before (optional): A function that takes Sandcastle's express app as an argument and is run before all sandcastle routes. Can be used to add custom middleware or routes. Default: undefined.
  • apiPath (optional): The path under which to serve the client REST api. Default: '__api'.
  • webpackPath (optional): The path under which to serve webpack content on the Sandcastle server. Default: '__sandcastle_webpack'.
  • hotMiddlewarePath (optional): The path under the webpackPath under which to receive hot reload events. Default: '__webpack_hmr'.
  • sandcastleComponentFolder (optional): If you want to use a directory name other than __sandcastle__ for storing Sandcastle components, you can specify an alternative name here. Default: '__sandcastle__'.
  • environment (optional): Path to a module exporting an environment component.
  • setupEnvironment (optional): Path to a module exporting a function that creates the props for the environment component.
  • bootstrap (optional): Path to a module run before all other code on the Sandcastle client. Can be used to set up polyfills and other global dependencies.
  • webpackDefine (optional): Config object for Webpack's DefinePlugin for inserting data in to the client bundle. Can be useful to make server data (such as configuration) available on the client for use in layouts or environment files. Default: {}.
  • layouts (optional): Map of layout names to paths to their files. Default: {}.

The following options can be passed as a function to allow a change in configuration depending on the path of the file loaded. The function will be called with the relative path to the component file requested.

  • webpackConfig
  • environment
  • setupEnvironment
  • bootstrap
  • webpackDefine

Developing

See DEVELOPMING.md