tachyon-package-builder1 v32.0.0
Tachyon Package Builder
Builds TypeScript packages into module and dist/bundle output with accompanying types, while also ensuring that code will run in both browser and node contexts while being fully tree-shakeable for webpack/parcel/etc builders to minimize client-side bundle size.
Install
yarn add -D tachyon-package-builderCLI Usage
The base command is simply:
build-packageIf everything is properly configured (as listed below), this should just work and you'll be ready to publish your package!
Flags
There are a couple of flags you can use to modify the build behavior:
- default option (technically
--pkgDir/-pfor obscure use-cases) This defines the directory of the package to be built. Useful for CI or automation situations in which you might want to run the builder from outside of the package directory. Since this is the default argument you should not normally need the flag:
build-package path/to/package--watch/-wThis will cause the builder to run in watch mode, in which case it will incrementally rebuild any change files, greatly reducing subsequent build times. Useful for when you developing the package while it is linked to another application or in a monorepo.--notify/-nThis will give you an OS notification when the build is done. Useful for large projects with longer build-times.--serverOnly/-SThis will eliminate the module build from the output, for when you know that the package will never be run in the browser. See config notes below regarding this build method.--webpackOnly/-WThis is a special mode that will skip TypeScript as well and only run webpack, outputting only a single bundle file. This is only for very specific use-cases like transpiling a vendored dependency and should not be generally used.--help/-hThis shows the available options.
Node Usage
You can use this package inside another JavaScript file by importing it.
const buildPackage = require('tachyon-package-builder');
...
// Set the config values you want. Note that usage in node requires providing
// the pkgDir parameter.
const opts = { help, notify, pkgDir, serverOnly, watch, webpackOnly };
buildPackage(opts);How it Works
This package works by combining TypeScript, Babel, and Webpack to generate the appropriate build artifacts. It uses TypeScript only to strip types and emit .d.ts files, leaving the actual JS transpilation to Babel in order to be able to share @babel/runtime with community packages (instead of getting tied to ts.lib). Webpack is left to do the bundling for the dist output.
Package Configuration
As a result of the above pipeline, there are a few requirements with regards to package structure:
tsconfig.build.jsonin the root of the package. This will probably extend atsconfig.json, and can be useful to exclude things like tests, mocks, and set-up scripts from the actual package distribution contents (using the top-levelexcludeskey). There are a few mandatorycompilerOptionssettings (you can change the rest as needed):
"compilerOptions": {
"declaration": true,
"declarationDir": "types",
"declarationMap": true,
"jsx": "preserve",
"module": "esnext",
"moduleResolution": "node",
"target": "esnext",
"outDir": "esnext"
}Note that by default, tsc will still emit output even if there are type errors
in the code. This is relevant for rebuilds within a --watch setting, because
after the first successful build, tsc will continue to emit transpiled JS
(which will be picked up by babel and webpack) regardless of any type errors;
the type errors will still be shown in the console. This can be useful for
exploratory coding and similar use-cases (and is the recommended configuration
for good developer experience), but you can opt-out of this behavior by adding
the noEmitOnError option to the compilerOptions. Normal (non-watch) builds
will always fail on type errors with or without this flag.
babel.config.jsin the root of the package. In order to ensure that ES module are transpiled for the dist build but left intact for the module build, there is a base configuration you will need (you can add transforms as needed):
module.exports = (api) => {
api.cache.using(() => process.env.NODE_ENV);
return {
presets: [
['@babel/preset-env', {
loose: true,
modules: !api.env('module') && 'auto',
}],
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: [
'@babel/plugin-transform-runtime',
],
};
};webpack.config.jsin the root of the package. We'll be re-using the Babel config here via babel-loader, and here is the resulting minimal configuration you will need:
const { resolve } = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const { name: library } = require('./package.json');
module.exports = function(env = {}) {
return {
mode: 'production',
entry: resolve(__dirname, 'esnext', 'index'),
resolve: {
extensions: ['.js', '.jsx'],
},
externals: [nodeExternals(nodeExternalsOpts)],
module: {
rules: [
{
test: /\.jsx?$/,
use: 'babel-loader',
},
],
},
output: {
filename: 'index.js',
path: resolve(__dirname, 'dist'),
libraryTarget: 'commonjs2',
library,
},
};
};If you are only supporting server builds, then you should add target: "node"
to your config.
srcdirectory should contain all code for packaging. As mentioned above, to prevent any unwanted files insrcfrom being included in the final distributable package, use thetsconfigexcludekey to keep files from entering the build process.package.jsonwill need a few entries after this is all done, making your package ready for publishing/distribution:
"main": "dist/index.js",
"module": "module/index.js",
"types": "types/index.d.ts",
"sideEffects": false,
"files": [
"dist/",
"module/",
"types/"
],
"browserslist": [
...
],Use the browserslist values to
control which features you transpile and which you allow through (consumed by
Babel's preset-env).
If you are only supporting server builds, then you should omit the module key.
It is also common to include:
"scripts": {
"build": "build-package",
"build:watch": "build-package -w",
}2 years ago