This set of scripts builds and watches JS projects using popular transpiler and bundler APIs.


2.0.13 years ago4 years agoMinified + gzip package size for @density/structure in KB


Dependency Status Package Version License

Structure is a modular build system for frontend projects. It's built to be flexible, transparent, and to supress lock-in to any one technology (we're looking at you, Webpack). There is out of the box support for a number of transpilers (typescript, babel), a number of bundlers (webpack, browserify), and a css post-processor (sass).

Why not use Webpack to do all of this?

  • Flexibility. Configuring Webpack to support a custom stack can be clunky and complex.
  • Transparency. When you buy into the "Webpack way", you end up using tons of plugins that are really opaque. Because you don't really know what transforms your code goes through, it's hard to optimise your bundle easily.
  • Troubleshootability. Due to the above, it's difficult to develop and troubleshoot the development server.

Getting Started

Structure can be installed on its own, or with create-react-app.


For new React applications, structure can be configured as the react-scripts package (does not include a testing framework):

create-react-app --scripts-version @density/structure my-app


  1. Install structure (npm i -S @density/structure)
  2. Create a structure.js script. Here's an example (more details found in contributing):
// structure.js
const structure = require('@density/structure');

// Start live server

  // Configure modules for assets, styles, transpiler, and bundler
  assets: structure.assets('./src', './build'),
  styles: structure.sass('./src/index.css', './src/**/*.css', './build/app.css'),
  transpiler: structure.typescript('./src/**/*.js', './tmp', { jsx: 2 }),
  bundler: structure.webpack('./tmp/index.js', './build/app.js'),

  // These are defaults but any live-server options can go here
  serverOptions: {
    root: './build',
    file: 'index.html'
  1. Run the script to get a live-reloading dev server: node structure.js.

  2. BONUS: add a start script in your package.json file that runs the build script: "start": "node structure.js"

An end to end example:

$ # Set up a tiny project
$ mkdir src/
$ echo "console.log('Hello');" > src/index.js
$ echo "body { color: red; }" > src/index.css
$ cat <<EOF > src/index.html
   <link rel="stylesheet" href="/app.css" />
   <script src="/app.js"></script>
$ # Build the project
$ node structure.js
* Assets ready!
* Styles ready!
* Full transpile done!
* Bundle ready!
* Serving "./build" at

Transpiler/bundler Build System

Structure has scripts to set up and run each step in the build process. Right now it uses the TypeScript compiler API to transpile and watch, and Webpack's API to bundle. The reason for using these specific APIs directly is that we get faster compile times by keeping the compilers in memory. Alternate configurations utilize Babel and Browserify for transpiling and bundling, respectively.

NodeJS Scripts

Subfolders in this project are for purpose-built scripts, like build and start. Let's look at each of those:


structure.build is pretty straightforward. It imports the parts used to compile everything and runs a full transpile and bundle with CSS and assets.

This script runs the final ES5 output through UglifyJS by default.


structure.start is more complicated. We want incremental or "fast" compilation when we're developing, and a live reload function. The transpiler.transpile() function can be passed a filename to fast-transpile individual files on each save.

With the TypeScript transpiler, each source file is immediately transpiled on every save for a quick refresh, before the full program is typechecked. This is because we're not sure if it is possible to incrementally update the program representation that TypeScript works with internally, and checking the whole program again takes a few extra seconds.

The logic on every TS change is this:

  1. Run the added or updated file through the transpiler right away and write the transpiled output. This uses a persistent reference to a "language service" to process and emit the new file.

  2. Call run or whatever on the bundler instance in memory. This has an entry point main.js and walks the file system to get the rest of the bundle.

  3. In the bundler callback, we can now force the dev server to refresh. The live-server instance is actually monkey-patched at the end of the start script. It has a .change() method and doesn't actually watch files (so we can be sure everything is done before the reload happens).

  4. Finally queue up a "slow" full typecheck + transpile after a one-second delay. This will get us comprehensive error checking in the console, so a few seconds after the browser reloads, any compile-time errors will show up in the console. It makes a brand-new "program" instance every time that processes all files in the project.

The result is we get full typechecking on every change, and fast reload for all valid changes (a very fast reload if sourcemaps are disabled).

Babel & Browserify

Babel and Browserify are not usable out of the box. To enable, add the necessary dependencies to package.json:

  "babel-core": "^6.26.3",
  "babel-preset-es2015": "^6.24.1",
  "babel-preset-react": "^6.24.1",
  "browserify": "^16.2.2",

Internals & Contributing

There's much more detail in CONTRIBUTING.md.

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.