npm package: @shopify/loom-plugin-build-library
Description: loom consumer plugin for building libraries
Author: Shopify Inc.
Latest version: 1.0.3

# `@shopify/loom-plugin-build-library`

This package is your entrypoint into building libraries with loom. It configures a core series of tools for building libraries:

- Rollup for bundling (using [`@shopify/loom-plugin-rollup`](https://github.com/Shopify/loom/tree/main/packages/loom-plugin-rollup))
- Babel for single file transforms (using [`@shopify/loom-plugin-babel`](https://github.com/Shopify/loom/tree/main/packages/loom-plugin-babel))
- Jest for testing (using [`@shopify/loom-plugin-jest`](https://github.com/Shopify/loom/tree/main/packages/loom-plugin-jest))
- Typescript for type checking (using [`@shopify/loom-plugin-typescript`](https://github.com/Shopify/loom/tree/main/packages/loom-plugin-typescript))

This package handles JavaScript and TypeScript files only. If you need support for other file types, such as styles, images and graphql files then check out [`@shopify/loom-plugin-build-library-extended`](https://github.com/Shopify/loom/tree/main/packages/loom-plugin-build-library-extended).

Ite exposes two plugins - `buildLibrary` and `buildLibraryWorkspace` that should be added to your loom plugins.

## Installation

```sh
$ yarn add @shopify/loom-plugin-build-library --dev
```

## Usage

Add `buildLibrary` and `buildLibraryWorkspace` to your loom plugins.

By default all build types - `commonjs`, `esmodules` and `esnext` are disabled. You should enable the builds that that you need by setting those options to `true`, along with specifying the required `targets` option based upon what browsers / node versions you wish to support.

```js
import {createPackage} from '@shopify/loom';
import {
  buildLibrary,
  buildLibraryWorkspace,
} from '@shopify/loom-plugin-build-library';

export default createPackage((pkg) => {
  pkg.entry({root: './src/index.js'});
  pkg.use(
    buildLibrary({
      // Required. A browserslist string for specifying your target output.
      // Use browser targets (e.g. `'defaults'`) if your package targets the browser,
      // node targets (e.g. `'node 12.22'`) if your package targets node
      // or both (e.g.`'defaults, node 12.22'`) if your package targets both
      targets: 'defaults, node 12.22',
      // Optional. Defaults to false. Defines if commonjs outputs should be generated.
      commonjs: false,
      // Optional. Defaults to false. Defines if esmodules outputs should be generated.
      esmodules: false,
      // Optional. Defaults to false. Defines if esnext outputs should be generated.
      esnext: false,
      // Optional. Defaults to true. Defines if entrypoints should be written at
      // the root of the repository. You can disable this if you have a single
      // entrypoint or if your package uses the `exports` key in package.json
      rootEntrypoints: true,
      // Optional. Defaults to 'node'. Defines if the jest environment should be 'node' or 'jsdom'.
      jestTestEnvironment: 'node',
    }),
    buildLibraryWorkspace(),
  );
});
```

### Targets

`buildLibrary` has single required option - `targets` - which is a [browserslist](https://github.com/browserslist/browserslist) string that controls the level of transpilation that is required.

What targets you specify shall depend upon the type of package you are creating.

- If you are targeting a node-only package you can use `'maintained node versions'` or an explicit minimum version number like `'node 12.22.0'`.
- If you are targeting browsers only you can specify browserlist defaults like `'defaults'`, recent versions like `last 3 chrome versions`, or use the [`@shopify/browserslist-config`](https://github.com/Shopify/web-configs/tree/main/packages/browserslist-config) preset like `'extends @shopify/browserslist-config'`.
- If you are targeting both node and browser usage use a comma separated string to join both of the above: `'extends @shopify/browserslist-config, node 12.22.0'`.

You can see what environments these values correspond to by running e.g. `npx browserslist 'defaults'`. If you find these values unclear or are concerned that changes to them may be non-obvious, you can use explicit version numbers instead.

### Outputs

This plugin exposes 3 ouptput options. They are all disabled by default and you must opt into enabling them.

- `commmonjs` controls creating commonjs output in the `build/cjs` folder. This uses `require()` and `exports =` to handle imports and exports, in the format supported by node. You should enable this format and set it as your `main` entrypoint (see below) when creating packages that are ran in node.
- `esmodules` controls creating esmodule output in the `build/esm` folder. This uses `import` and `export` to handle imports and exports, in the format handled by modern bundlers and node 12.22+. You should enable this format and set it as your `module` entrypoint when creating packages that are meant to be bundled for use in the browser.
- `esnext` controls creating "esnext" output in the `build/esnext` folder. This uses `import` and `export` to handle imports and exports and forces the compile target to be the latest chrome version, instead of whatever was specified in the `targets` option. Setting this as the `esnext` entrypoint will allow apps configured using sewing-kit to use this build to produce a minimally compiled version of the library as part of its application build in the event that the app targets a smaller browserlist than what you specified in your library's `targets`. The consuming app's webpack config must treat this package being as not external - i.e. it must be processed by the consuming app's webpack config.

### Entrypoints

By default, entrypoint files are written to the root of your package that correspond the package's `entry`s defined in its config file. This is to support packages with multiple entrypoints in a world where package.json's [`exports`](https://nodejs.org/api/packages.html#packages_package_entry_points) support is not pervasive as Webpack 4, TypeScript and Jest support is currently absent at time of writing (September 2021).

#### Single entrypoint

When creating a package with a single entrypoint, you can set `rootEntrypoints: false` to not write any root entrypoints, and point fields in your package.json to the contents of the build folder.

Given a `loom.config.js` file that contains:

```js
export default createPackage((pkg) => {
  pkg.entry({root: './src/index.js'});
  pkg.use(
    buildLibrary({
      targets: 'defaults, node 12.22',
      commonjs: true,
      esmodules: true,
      esnext: true,
      rootEntrypoints: false,
    });
  );
});
```

In the `package.json` add the following `main` (for commonjs output), `module` (for esmodules output), `esnext` (for esnext output) and `types` (for TypeScript types) keys. You can omit a given key if you are not generating a particular output type.

```js
{
  "main": "build/cjs/index.js" /* commonjs output */,
  "module": "build/esm/index.mjs" /* esmodules output */,
  "esnext": "build/esnext/index.esnext" /* esnext output */,
  "types": "build/ts/index.d.ts" /* typescript output - this path depends upon your tsconfig.json */
}
```

#### Multiple entrypoints

If you have multiple entrypoints then you should leave `rootEntrypoints: true` to generate entrypoints at the root. This is a slightly flawed approach as only commonjs content for the additional entrypoints is supported, but is the best that we can do give current support.

Given a `loom.config.js` file that contains:

```js
export default createPackage((pkg) => {
  pkg.entry({root: './src/index.js'});
  pkg.entry({root: './src/second-entry.js', name: 'second-entry'});
  pkg.use(
    buildLibrary({
    targets: 'defaults, node 12.22',
    commonjs: true,
    esmodules: true,
    esnext: true,
    rootEntrypoints: true,
    });
  );
});
```

Then this shall write sets of outputs for each entrypoint at the root of your package:

- `index.js` that reexports content from `build/cjs/index.js`
- `index.mjs` that reexports content from `build/esm/index.mjs`
- `index.esnext` that reexports content from `build/esnext/index.esnext`
- `second-entry.js` that reexports content from `build/cjs/second-entry.js`
- `second-entry.mjs` that reexports content from `build/esm/second-entry.mjs`
- `second-entry.esnext` that reexports content from `build/esnext/second-entry.esnext`

In the `package.json` add the following `main` (for commonjs output), `module` (for esmodules output), `esnext` (for esnext output) and `types`(for TypeScript types) keys. You can omit a given key if you are not generating a particular output type. If you specify a `files` array, remember to add these root entrypoints to it.

```js
{
  "main": "index.js" /* commonjs output */,
  "module": "index.mjs" /* esmodules output */,
  "esnext": "index.esnext" /* esnext output */,
  "types": "build/ts/index.d.ts" /* typescript output - this path depends upon your tsconfig.json */,
  "typesVersions": {
    "*": {
      /* typescript types for the second-entry entrypoint */
      "second-entry": ["./build/ts/second-entry.d.ts"]
    }
  }
}
```

If a consuming app imports `your-package/second-entry` then it shall load `second-entry.js` from the root of your package.

### Overriding Babel config

We provide an initial babel config that supports typescript and react, using `@shopify/babel-plugin`. If you need to adjust this config, you can call the `babel()` plugin after `buildLibrary` to override the defaults.

```js
import {createPackage} from '@shopify/loom';
import {
  buildLibrary,
  buildLibraryWorkspace,
  babel,
} from '@shopify/loom-plugin-build-library';

export default createPackage((pkg) => {
  pkg.entry({root: './src/index.js'});
  pkg.use(
    buildLibrary({targets: 'node 12.22.0', commonjs: true}),
    buildLibraryWorkspace(),
    // Override initial babel options.
    // Return a new object, instead of mutating the argument object.
    babel({
      config(babelConfig) {
        return {
          ...babelConfig,
          plugins: [...(babelConfig.plugins || []), 'my-custom-babel-plugin'],
        };
      },
    }),
  );
});
```

## Examples

### Configuring a nodejs-only package

When targeting nodejs only, create commonjs output and target a node version.

```js
import {createPackage} from '@shopify/loom';
import {
  buildLibrary,
  buildLibraryWorkspace,
} from '@shopify/loom-plugin-build-library';

export default createPackage((pkg) => {
  pkg.entry({root: './src/index.js'});
  pkg.use(
    buildLibrary({targets: 'node 12.22.0', commonjs: true}),
    buildLibraryWorkspace(),
  );
});
```

### Configuring a nodejs and browser package for use in sewing-kit powered apps

When targeting nodejs and the browser, create commonjs, esmodules and esnext output and target a node version, and extend from Shopify's browserlist config.

```js
import {createPackage} from '@shopify/loom';
import {
  buildLibrary,
  buildLibraryWorkspace,
} from '@shopify/loom-plugin-build-library';

export default createPackage((pkg) => {
  pkg.entry({root: './src/index.js'});
  pkg.use(
    buildLibrary({
      targets: 'extends @shopify/browserslist-config, node 12.22.0',
      commonjs: true,
      esmodules: true,
      esnext: true,
    }),
    buildLibraryWorkspace(),
  );
});
```

## Linting

This package does not offer any explicit opinion on linting configuration. This is because we want to keep the cadences for updating linting configs and build tooling separate. Combining them means attempting to update build tooling but first having to make a load of otherwise unrelated linter suggested changes, and we've found that really annoying in the past.

We recommend using the following:

- [`@shopify/loom-plugin-eslint`](https://github.com/Shopify/loom/tree/main/packages/loom-plugin-eslint) to run `eslint` with the [`@shopify/eslint-plugin`](https://github.com/Shopify/web-configs/tree/main/packages/eslint-plugin) config.
- [`@shopify/loom-plugin-stylelint`](https://github.com/Shopify/loom/tree/main/packages/loom-plugin-stylelint) to run `stylelint` with the [`@shopify/stylelint-plugin`](https://github.com/Shopify/web-configs/tree/main/packages/stylelint-plugin) config.
- [`@shopify/loom-plugin-prettier`](https://github.com/Shopify/loom/tree/main/packages/loom-plugin-prettier) to run `prettier` with the [`@shopify/prettier-config`](https://github.com/Shopify/web-configs/tree/main/packages/prettier-config) config.