# @keeex/tslib


Helper for the build process of typescript libraries.

## Quick setup

- install this library as a dev dependency (`npm install @keeex/tslib`)
- put all your TypeScript source in `/src`
- have all your links/scripts that reference compiled JS look into `/lib`
- put all tests either in files named `*.test.ts` alongside the rest of the code or in `/src/tests`
  (you can mix, to have test-specific modules there)
- if it's not a library but a final "service" or "tool" kind of project add
  `"tslib":{"noDeclarations": false}` to "package.json"
- configure `build` script in "package.json" to run `tslib build` (also have that somewhere in
  `prepack` if applicable)
- configure `test` script in "package.json" to run `tslib test`
- you can use `npx tsnode <script>` to run a TypeScript script directly

## Configuration

This project uses the following configurations from the "tslib" section in the caller's project's
`package.json`.

The section is not mandatory; if missing, default values are used.

```JavaScript
{
  "buildDir": "lib",
  "bundleConfig": {
    "bundlename": {
      "entryPoint": "bundle.ts"
    }
  },
  "webDir": "web",
  // Deprecated
  "subpackageDir": "subpackages",
  "excludeFromBuild": [],
  "exports": {},
  "jsx": "react-native",
  "noDeclarations": false,
  "srcDir": "src",
  "testPattern": "mixed",
  "types": ["node", "mocha"],
  "typesDir": "types",
  "typedoc": true,
  "buildWeb": false,
  "knip": true,
  "noMinify": true,
  "prettier": {
    "disabled": false,
    "excludeFiles": [
      "package.json",
      "npm-shrinkwrap.json",
      "package-lock.json",
      "tsconfig.json",
      "tsconfig-build.json",
      "tsconfig-loader.json",
      "tsconfig-tsloader.json",
      "babel.web.json",
      "knip.json"
    ],
    "excludeDirectories": [
      "android",
      "apidoc",
      "coverage",
      "dist",
      "gen",
      "generated",
      "ios",
      "lib",
      "src/gen",
      "test_snapshots",
      "web"
    ],
    "suffixes": [
      ".md",
      ".js",
      ".jsx",
      ".cjs",
      ".mjs",
      ".ts",
      ".tsx",
      ".mts",
      ".cts",
      ".json",
      ".css",
      ".scss",
      ".less",
      ".html",
      ".yaml",
    ]
  },
  "noSwc": false,
  "webapps": [],
  // Deprecated
  "entrypoints": [],
  "webDependency": [],
}
```

### `buildDir`

Directory into which the output will be put. Avoid sharing this directory with other resources, as a
full build will erase the whole directory.

### `bundleConfig`

Build autonomous JavaScript bundle file (usually for browser direct loading).
See the section about bundles below.

### `webDir`

Directory for the Babel output. See "Browser Build" below.

### `excludeFromBuild`

Do not build files that match any of the globs provided here. The paths are relative to `srcDir`.

### `exports`

Generate the `exports` property in the `package.json` file. This is needed for libraries or project
that could be imported as packages. It is disabled by default. The full configuration object is:

```javascript
{
  "exports": {
    "enabled": false,
    "exports": ["**/*.ts"],
    "private": [],
    "base": {},
    "legacy": false,
    "noAutoMain": false,
  }
}
```

The properties have the following meanings:

- `enabled`: set to true to enable the `tslib exports` command, as well as checks with `tslib check`
- `exports`: glob of files to include; if nothing is provided, all files in `src` are considered
- `private`: glob of files to exclude
- `base`: initial value for the `exports` property, to set extra properties that are not
  automatically set by the `tslib exports` command
- `legacy`: gives access to `./lib/*` (and `./web/*` if applicable) in addition to the root exports.
- `noAutoMain`: do not extract the default export from `main` and/or `browser`.

This auto-generation will make all applicable path from `./lib/*` (and `./web/*` if applicable)
available at the root of the library. As an example:

- `./src/something.ts` will be exported as `<package>/something.js`
- `./src/services/other.ts` will be exported as `<package>/services/other.js`

It will also automatically generate the `.` entry from `main` and `browser`, if present.

### `jsx`

Option to pass to the react attribute in `tsconfig.json`. An empty string means the value is unset.
Valid values are the same as those accepted by TypeScript ("preserve", "react", "react-native",
"react-jsx", "react-jsxdev"). Usual value when JavaScript files are then babelized is "react-native"
to leave JSX untouched.

### `noDeclarations`

Do not generate the type declaration files in the output. Usually needed for libraries; when
building a non-library project, setting this to true prevent type declaration files from being
emitted.

### `srcDir`

Directory containing the TypeScript source of the project.

### `testPattern`

How tests are organised. The possible options are:

- "none": no test
- "mixed": in file named `*.test.ts`
- "testDir": all tests are in `${srcDir}/tests/`

Currently, this library only supports using mocha-drive tests.

### `types`

Represent the list of types packages imported into TypeScript.
The default behavior of TypeScript is to import packages from `@types/*` only when they are referenced in actual imports.
However, some packages are never references directly (for example, `@types/node`).
These packages needs to be added to the `types` property of TypeScript config.

The `tslib` project generate this property the following way:

- If nothing is specified, it defaults to setting `node` and, if `testPattern` is not `none`, also use `mocha`.
- If a value is specified in the `tslib` section of the `package.json` file, it is appended to the above defaults.
- If for some reasons you want to disable the default behavior, you can manually specify `-node` or `-mocha` to explicitly remove them from the generated config.

Note that the two defaults are only added if they are present in the project (in `devDependencies`).
If your project does not use `node` at all, and `@types/node` is not installed, it will not be referenced to begin with.

### `typesDir`

Additional typing files can be put in this directory. If configured and present, this directory will
be added appropriately in `tsconfig.json`.

### `buildWeb`

Automatically build the browser version with the `build` command.

### `webDependency`

Deprecated in v2.x, will be removed in v3.x.

List of dependencies that have a "web" version. These packages will see their import renamed from
`<package>/lib/*` to `<package>/web/*` in the browser build.

All imports from the KeeeX organizations (currently only "@keeex") should be automatically detected
and added when the configuration files are generated.

### `entrypoints`

Deprecated in v2.x, will be removed in v3.x.

List of entrypoints for custom build. An entrypoint is a single file used as the source of the whole
dependency tree; only files imported from that entrypoints (and it's dependencies, recursively) will
be built when it is invoked. Entrypoint configuration are done using an object whose key is the name
of the entrypoint, and the value is an object with the following keys:

- `path`: relative path to the entrypoint from the source directory
- `forbiddenImports`: an optional array of strings that will be parsed as regex. Any imported file
  that matches one of these is considered "forbidden" and will raise an error, preventing the build
  from happening.
- `disableWeb`: if web builds are available for this project, entrypoints will be built against
  them. Use this flag to disable it.
- `license`: path to a different license file for this entrypoint. If omitted uses `LICENSE`
- `subpackage`: optional; if present allows building a "subpackage"; merging the output of an
  entrypoint build with a custom `package.json` file to create an autonomous package based on the
  same source tree. See the "Subpackages" section for more details.

### `knip`

Enabled by default. Run `knip`, a CLI tool that detect export/import issues. When run manually you
can add the `production` argument to only check production files (see below). The configuration used
is very basic and consider all files in the source directoy to be part of the project. To mark an
export as public, use the jsdoc tag `@public`. It can also be useful to mark internal exports as
`@internal`, although it will still report them if unused for now.

The configuration entry for knip can either be a boolean to enable/disable it, or an object with the
following properties (analogous to the base knip config):

- `entry`
- `internal`
- `ignore`
- `ignoreBinaries`
- `ignoreDependencies`
- `noAuto`
- `rules`

`entry` and `internal` are merged in the knip config `entry`. `entry` are marked as "production"
(using a `!`), while `internal` are left as-is. Both are relative to the configured `src` directory.
Note that test files are automatically marked as "non production".
The `duplicates` rule, filtering items exported multiple times, is set as a warning (it will be
displayed but won't prevent the check from being ok).
This is often used for deprecation/renaming without breaking compatibility.

`entry` is optional.
If not provided, it will be filled with detected paths from the `bin` and `exports` property of the
project's `package.json`.
This is knip's default behavior.

Some files will be automatically added to `entry` and `ignore` as needed.
This behavior can be disabled by setting `noAuto` to true.
It involves the following:

- all files at the root of the project matching `/config(-.*)?\.js` and `secrets(-.*)?\.js` will be
  ignored
- files matching `/ecosystem\.config\.c?js` will be ignored
- anything in `/resources(-.*)?` will be ignored
- anything matching `/scripts/.*\.[cm]?js` will be added to entries
- anything matching `/src/**/db/migrations/*.ts` will be added to entries unless there is also a
  file matching `/src/**/db/migrations/index.ts`.

### `noMinify`

By default, built files are NOT minified using terser. Disabling this behavior can be useful for
debugging and deployment in private servers. It is possible to temporarily enable/disable
minification, disregarding this setting, by setting the environment variable `TSLIB_MINIFY` to "1"
or "0".

### `prettier`

Control the behavior of the `tslib pretty` command (and `tslib check`). In addition to the provided
excluded directories, `node_modules` is _always_ excluded. There is an historical `noPretty`
property that can be used instead, but it is legacy. Instead, set `{"prettier": {"disabled": true}}`

### `noSwc`

Disable using "swc" as a faster interpreter with ts-node.

### `typedoc`

By default, `tslib` will produce typedoc configuration in the `tsconfig-build.json` file. All files
will be used; however, anything marked private or internal will be ignored. Files excluded from
build (test files, etc.) will also be ignored.

Generating typedoc config can be disabled by setting the `typedoc` property to `false`. The default
output directory is `apidoc`.

The property can be left empty or set to true to use the defaults. It can also be set using these
fields:

```JavaScript
{
  "entryPoints": ["path1.ts", "path2.ts"],
  "outputDir": "apidoc",
  "missingImports": true,
  "index": "README.md",
  "sourceLinkTemplate": "https://some.site/{gitRevision}/{path}#{line}"
}
```

The `entryPoints` property, if defined, limit the entries that will be immediately visible in the
documentation. If not provided, all files in `srcDir` are used as entry points. All paths are
relative to `srcDir` and can include glob patterns.

The `missingImports` property can be used to force using the `typedoc-plugin-missing-exports`. If
not specified, the plugin is used when no entrypoints are provided. Otherwise, it can be forced on
or off.

The `index` proprty indicates a markdown file to use as content for the index page of the generated
doc.

The `sourceLinkTemplate` is optional and is used to create link to source files. Typedoc supports
automatic linking to github if the repository uses that, but for other cases a custom link can be
provided.

### `webapps`

Provides one (or more) configuration to build a webapp.
Basically, runs entrypoints through Babel and Webpack.

The property is an array of objects with this interface:

```javascript
interface WebappConfig {
  entryPoint: Arrayable<string>;
  /** Defaults to `dist/` */
  outputDirectory?: string;
  webpack?: WebpackOptions;
  worker: Arrayable<string>;
}
```

It is possible to customize the behavior of webpack, but for most basic purposes it is enough to
only provide `entryPoint` and/or `worker`.

## Changes in the project's directory

All TypeScript-related commands will update (if needed) the `tsconfig.json` file in the project's
root. This file will hold the configuration needed for editors/tools to handle all the source code
of the project (source and tests). It also does not produce output.

The actual file used to build the project will be named `tsconfig-build.json`. It will contains
directives to exclude test files, if applicable, and to actually produce outputs.

It is safe to commit these files with the project; they are not expected to change unless there is
an update to `@keeex/tslib`.

It will also create a file named `.tsbuildinfo` used to cache changes during watch mode. This file
should be ignored.

If web build are enabled, or triggered manually, a `babel.web.json` file will be created.

When building entrypoints, the output directory is cleaned and a subdirectory with the entrypoint
name is created to contain the generated files. In addition, a dot file named after the entrypoint
can be generated at the root of the project.

## Usage

### Create TypeScript config files

The config files are created/updated automatically with each build commands. If you want to create
them by hand without calling for a build you can call

```shell
npx tslib init [auto]
```

If the `auto` argument is passed to init, missing dependencies are installed automatically.

### Building the library

You can trigger a full build of the library by calling

```shell
npx tslib build
```

It will clear the target directory and compile all source files.

### Watch build

The watch feature of npx can be triggered using

```shell
npx tslib watch
```

Note that this is for convenience only; it is the same as running `npx tsc -w` on the build config.

### Running the tests

Assuming tests are configured, you can run them using

```shell
npx tslib test
# without c8
npx tslib rawtest
```

This will run all tests using ts-node, mocha and c8. Usual configuration for each of these is
applicable. Source directory will be automatically set for c8. The `NODE_ENV` environment variable
will be set to "test".

Tests requires the following dependencies to be available:

- `mocha`
- `c8` (except for `rawtest`)
- `ts-node`
- `@swc/core`

Any extra arguments provided will be passed as-is to mocha (before the specifications).

#### Running tests for Sonarqube/reporting

An additional target called `reporting` is available:

```shell
npx tslib reporting
```

This will configure both c8 and mocha to output their reports into appropriate files (lcov reporter
for coverage and `xunit.xml` for mocha).

This requires the `mocha-sonarqube-reporter` package to be installed as a dev dependency in addition
to the other dependencies required for running tests.

### Building API documentation

API Documentation is built using Typedoc. It requires the following packages to be installed:

- `typedoc`
- `typedoc-plugin-missing-exports`
- `typedoc-plugin-rename-defaults`

For convenience a `typedoc` command is provided which makes sure the configuration is generated
before calling `typedoc`.

### Building all documentation

For convenience, a command to build both Typedoc is available: `doc`.

### Cleaning up generated content

The generated content (build output, cache and configuration) can be cleaned using:

```shell
npx tslib clean
npx tslib clean all
```

The first form will remove all build output and cache files. If "all" is provided, the generated
configuration files are also cleaned.

### Checking import/export using knip

You can use this command to generate knip configuration and run it:

```shell
npx tslib knip
```

### Checking whole project

For convenience a `check` command was added that will run the following sequence:

- generate/update config files
- check local imports in `package.json` (can be skipped with the flag `--loose`)
- check the TypeScript build
- check the source using `eslint`
- check the import/export using `knip` in production mode
- check prettification

If any of these fails, the process will exit with an error code of 1.

```shell
npx tslib check
```

### Running prettier

Although it is advised to configure your editor to run prettier when saving files, it is possible to
run it on the whole project by using the `npx tslib prettier` command. This will apply prettier to
all JS, TS, and other applicable formats. It will exclude `node_modules` as well as hidden
directories and the basic generated configuration files from tslib. It will also skip the "gen" and
"src/gen" directories.

It will also exclude everything in `.gitignore`.

### Browser build

You can use the following command to run a "browser" build:

```shell
npx tslib web
```

This will create the same output as a normal build, but it will be placed in the `webDir` directory
(default `/web`) and is transpiled with Babel. To do so you need to install the following packages
first:

- `@babel/cli`
- `@babel/core`
- `@babel/preset-env`
- `babel-plugin-import-redirect`

Calling `npx tslib web` will manually build the web version. To enable it automatically using
`npx tslib build` set `webBuild` to `true`.

### Building subpackages (new)

Subpackages are NPM packages that rely on some of the same source code from the "main" project, but
can be built as a separate package with a different name.
Such package keep the same version number by default.

Some of the features of subpackages:

- allow automatic check that some files are _not_ packaged in the subpackage
- different exports list
- trimmed dependencies
- different license/readme/other files
- still support web/lib build, with automatic exports

To define a subpackage, create a file named `/pkg/<name>.json` with the following content:

```typescript
{
  // Inherited from main package
  author?: string;
  // Inherited from main package
  contributors?: Contributors;
  /** List of strings for dependencies to always include from main project */
  dependencies?: Array<string>;
  // Inherited from main package
  description?: string;
  /** Mapping: "exported name"→"real file path" (use .js extensions) */
  exports?: Record<string, string>;
  // Inherited from main package
  homepage?: string;
  // Inherited from main package
  license?: string;
  /** Default export, if present */
  main?: string;
  /** Minify the output */
  minify?: boolean;
  /** Name of the subpackage, mandatory */
  name: string;
  subpackage?: {
    /** Include /lib */
    buildLib?: boolean;
    /** Include /web */
    buildWeb?: boolean;
    /** Files to copy and bundle in subpackage. Mapping "subpackage root" → "project root" */
    extraFiles?: Record<string, string>;
    /** List of globs that will stop the subpackage build process if they are present in output */
    forbiddenImports?: Array<string>;
    /** Do not include type declaration files */
    noDeclarations?: boolean;
  };
  // Inherited from main package
  type?: string;
}
```

This is a lightweight version of a project config; appropriate web/lib versions will be generated
accordingly.

Generating the content of a subpackage is done with the command:

```shell
npx tslib pkg <name>
```

### Building bundles

Bundles, single-file JavaScript that can be embedded more easily (in browser, or using `pkg`) can be
built using the `bundleConfigs` config property.
It is an object whose keys are the bundle name, and the value is the config.
All properties beside `entryPoint` are optional.

- `chunked`: if true, allows building a chunked bundle, that will only load some parts on demand.
  This is unlikely to work with `pkg` but can be useful for browsers.
- `entryPoint`: path to the entrypoint (relative to `src/`)
- `libraryName`: if the bundle should export an object in the global scope, this is the name of that
  object
- `output`: specify the output path; defaults to `bundle/dist/<name>.js`
- `resolveVoid`: list of package imports that are resolved to "false"
- `lib`: if true, skip using Babel for transpiling toward a more supported version of JavaScript

To build a single bundle:

```raw
npx tslib bundle <name>
```

Omit the name to build all configured bundles.

### Building from entrypoints

Deprecated in v2.x, will be removed in v3.x.

Even if entrypoints are configured, they are not built by default, since their usual use cases
requires further action with the output and can conflict with full build. To build from an
entrypoint, run the command `entrypoint`:

```shell
npx tslib entrypoint <name> [<1 to output dot dependency graph>]
```

The first argument can be the string "all", in which case all entrypoints will be built
sequentially.

If a second argument is provided, a dot graph file is produced named after the entrypoint. The
output of an entrypoint generation is found in `<output dir>/<entrypoint name>`.

### Subpackages (old)

Deprecated in v2.x, will be removed in v3.x.

To build a subpackage, run the following command:

```shell
npx tslib subpackage [<name>]
```

If no name is provided, all configured subpackages are built. The configuration of a subpackage is
done by adding the `subpackage` property to an entrypoint. The subpackages will be built in the
directory pointed by the `subpackageDir` configuration entry. That property must be an object with
the following properties:

- `name`: the name of the subpackage (as seen by NPM)
- `packageOverride`: properties to change in the output `package.json`
- `extraFiles`: key/value mapping to copy files from the project's root into the subpackage
- `noDoc`: set to true to disable typedoc generation (it won't be generated if disabled for the
  whole project anyway)
- `readme`: path to a readme file for this subpackage, relative to the project's root

The output `package.json` takes the following properties from the base project's `package.json`
file:

- `version`
- `description`
- `type`
- `author`
- `contributors`
- `license`
- `dependencies`
- `files`

It is possible to add new properties, change their values, and remove them by setting
`packageOverride`. Any value present in this property will replace any other value, and a `null`
value in the override results in the property being removed from the output `package.json`.

Extra files can be copied from the project's root by providing a mapping into `extraFiles`.

Subpackages are not minified; they do embed a minification script that is run on `npm pack`.

#### Dependencies handling

Unless an override is present, the `dependencies` property is copied from the original
`package.json` file but dependencies that are not present in the entrypoint output will be removed.

## Run TypeScript files directly

You can run TypeScript files directly using `npx tsnode <script path>`. Contrary to `ts-node`, this
accepts files with the suffix `.ts`.

## License files

Licensing terms are added to built file.
They are either taken from the files directly (if they contain an `SPDX-License-Identifier`) or from
the `LICENSE` file.

## Under the hood

The project basically wraps three steps:

- creating the two tsc config files according to presets and project's settings
- clean the target directory, run `tsc` and put the license in output files
- run mocha with appropriate flags to use c8 and ts-node

Not the hardest thing in the world, but repeated in every projects.

Entrypoints leverage the ability of the TypeScript compiler to only grab dependencies, added with
custom code to actually make sure some files are excluded from the build.
