Skip to main content

Building and testing

@flecks/create-fleck sets up an environment that makes building, testing, and distributing your flecks easy.

Mixins

Bootstrap

Your fleck may implement a build/flecks.bootstrap.js script. If one exists, it will be loaded as a bootstrap fleck during the bootstrap phase. This includes the boostrap phase that occurs when the fleck itself is compiled.

Less nice things

The bootstrap fleck is executed by Node.JS as-is as CommonJS and is not subject to any compilation or preprocessing. You must use e.g.

exports.hooks = {};

and not

const exports hooks = {};

In addition to exporting hooks, a bootstrap fleck may also export:

Dependencies

exports.dependencies = ['@some/fleck'];

Dependent flecks will automatically be added to the build manifest when this fleck is present.

Fostering dependency

If you run flecks add @some/fleck in your fleck root, @some/fleck will be added to your package.json and your exports.dependencies will be automatically updated to include @some/fleck.

Stubs

Some code isn't careful when it comes to things like accessing window. flecks can stub out problem modules on any platform:

exports.stubs = {
server: ['@pixi'],
};

This is effectively the Node.JS require() version of Webpack's {'alias': false} functionality.

package.json and entry points

flecks automatically uses the files key in your package.json to determine the entry points of your fleck. Entry points are automatically discovered from the src directory of your fleck.

Think of your files key as a form of Subpath exports with less hassle. If your package.json's files key looks like this:

  "files": [
"client.js",
"index.js",
"something-else.js"
],

Other code could import any of those paths (e.g. @your/fleck/client, @your/fleck, @your/fleck/something-else), while the actual paths would be, respectively, @your/fleck/src/client, @your/fleck/src, @your/fleck/src/something-else. Smooth!

Who cares? Everything works without it!

Who cares about files or exporting or whatever? I created a fleck and everything Just Works™. This seems like extra busywork for no reason!

Sharing is caring

The reason we take care of the files key in our package.json is because this is how we make sure we can publish working flecks to npm! this tooling is directed toward making it easier and frictionless to share code.

Processing package.json

flecks augments your source package.json during the build process and outputs a built package.json to dist.

If you generate a fleck using @flecks/create-fleck, its files key will look like this:

  "files": [
"index.js"
],

flecks automatically adds some paths to the files key of your built package.json:

  • build directory
  • src directory (if sources exist)
  • sourcemaps for each entry point (e.g. index.js -> index.js.map)
  • test directory (if tests exist)
Hook that one, too

@flecks/fleck invokes a hook @flecks/fleck.packageJson which exposes this for any other fleck to process package.json when building a fleck.

For instance, @flecks/web implements this hook. @flecks/web will automatically output CSS, fonts, and other frontend assets to the assets directory in your build output. If any of these frontend assets exist, @flecks/web will automatically add the assets directory to the files key of your built package.json. You nor anyone using your fleck will have to think about it!

Normal distribution

What this means is that when we publish a fleck we don't publish the root directory, we publish the dist output directory.

webpack build configuration

build/fleck.webpack.config.js

flecks provides a default webpack configuration for your fleck. This may not be what you want, so you may override it:

build/fleck.webpack.config.js
const flecksWebpackDefaults = require('@flecks/fleck/build/fleck.webpack.config');
module.exports = async (env, argv, flecks) => {
const config = await flecksWebpackDefaults(env, argv, flecks);
// ...
return config;
};

You don't actually have to extend the configuration like that, you could return a new configuration entirely! That's just an illustration of how you can override the defaults.

That being said, the defaults (including the automatic entry point stuff above and so much more) will absolutely make your life easier.

Testing

flecks uses Mocha and Chai to run your tests. When you create a fleck, it includes a run script test. If you run it in your new empty fleck, you will see the output:

No fleck tests found.

No tests exist by default. Let's look at some example code and tests to understand how it works.

Example source

packages/example/src/index.js
export const add2 = (n) => n + 2;

export const add3 = (n) => n + 3;

Example test

packages/example/test/add2.js
import {expect} from 'chai';

const {add2} = require('@testing/unit');

it('can add two to a number', () => {
expect(add2(2)).to.equal(5);
});
note

We intentionally made an error so we can see what a failed test looks like.

If we run the following command from within packages/example:

npm run test

We would see the following output:

  1) can add two to a number

0 passing (11ms)
1 failing

1) can add two to a number:

AssertionError: expected 4 to equal 5
+ expected - actual

-4
+5

It catches the error! If we fixed it:

packages/example/test/add2.js
import {expect} from 'chai';

const {add2} = require('@testing/unit');

it('can add two to a number', () => {
expect(add2(2)).to.equal(4);
});

Try again:

npm run test
  ✓ can add two to a number

1 passing (4ms)

Everything's good!

The test task has more options like -w that support test watching and automatic running on change. For an exhaustive list of options, see the CLI documentation

Linting

flecks automatically includes support for ESLint through a lint run script. flecks configures a lot of default rules including a lightly-tweaked version of eslint-config-airbnb as well as overrides for special circumstances: for instance, loosening some rules for the test directory.

build/default.eslint.config.js

You may want to completely change these defaults and you can do so by creating a file build/default.eslint.config.js:

build/default.eslint.config.js
const flecksEslintDefaults = require('@flecks/build/build/default.eslint.config');
module.exports = async (flecks) => ({
extends: [
await flecksEslintDefaults(flecks),
],
// ...
});

Again, you don't actually have to extend the configuration like that, you could return your own!

Wait a minute, is that asynchronous ESLint configuration?

ESLint has made *async noises* lately, but as of this writing, the actually-working version requires you to write synchronous configuration files. So how does flecks do it? Honestly? Don't ask... We interface with torturous APIs so you don't have to!