build
Library for building production ready bundles for packages written in TypeScript targetting next environments:
- NodeJS
- Bundlers (Webpack, etc.)
- Browsers
Installation
Install @tramvai/build first:
- npm
- Yarn
npm install --save-dev @tramvai/build
yarn add --dev @tramvai/build
Get started
Add necessary fields to package.json:
{
"main": "lib/index.js",
"module": "lib/index.es.js",
"typings": "lib/index.d.ts",
"sideEffects": false,
"files": ["lib"]
}
"main": "lib/index.js"based on that field lib calculates entry point for the build and it will be"src/index.ts"in this case
Create tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "node",
"module": "ESNext",
"target": "ES2015",
"allowJs": true,
"declaration": true,
"sourceMap": true,
"importHelpers": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./lib",
"types": ["node"],
"lib": ["es2015", "es2016", "es2017", "es2018", "dom"]
},
"include": ["./src"],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx"]
}
Add to dependencies library tslib:
- npm
- Yarn
npm install tslib
yarn add tslib
Build package with command tramvai-build:
tramvai-build --preserveModules --forPublish
with flag
--preserveModulestramvai-build wil preserve file structure of library modules for better tree-shaking
with flag
--forPublishtramvai-build replaces some fields inpackage.jsonin order to make built package usable in the end apps, for example"browser"field with object value can be updated`
Explanation
The main purpose for the lib is the effective production build for TypeScript package using rollup, with watch mode support.
Such builds, especially for monorepositories with big number of packages, can take a long time and are not very comfortable to work. Thats why, for the development environment it is preferred to use tsc with project references and incremental build.
Recommended and automatically generated package.json for @tramvai/build allows apps to use packages that were built either with tsc, or with @tramvai/build without any additional steps.
All of the built bundles will contain ES2022 standard code, it is expected that they will be used in Node.js 16+ or will be bundled to ES5 using bundler (Webpack, etc.) with configured transpilation through babel for packages inside node_modules, written in modern JS.
NodeJS bundle in CommonJs format
Modern NodeJS has support for ES modules, but @tramvai/build still generates bundle in ES2022 standard in CommonJS format automatically for better interoperability with existing packages and tools that still do not support ES modules fully. Name of the result bundle is taken from field main in package.json, e.g. lib/index.js.
When bundling package in the app using webpack with option target: 'node' this CommonJS bundle probably will not be used as webpack will prefer to use module field while resolving source code.
It is expected that bundle from field
"main"will be resolved only byNodeJSitself while bundlers will use bundle from field"module"
Bundle for bundlers (Webpack, etc.) in ES modules format
Modern bundlers support ES modules and non-standard field "module" in package.json. @tramvai/build generates bundle in ES2022 standard in ES modules format automatically. Name of the result bundle is calculates from field main in package.json by adding postfix .es e.g. lib/index.es.js.
If build was called with flag --forPublish to package.json will be added new field "module": "lib/index.es.js".
When bundling package in the app through webpack with option target: 'node' bundle from field module will have higher priority over bundle from main.
ES2022code standard is generated as it is expected that bundle from field"module"will be resolved by bundler with configured transpilation throughbabelfor packages insidenode_modules, written in modern JS. This target is fully supported in Node.js 16+ versions.
Bundle for browsers
Modern bundlers support ES modules and non-standard field "browser" in package.json. When field browser in specified in package.json, @tramvai/build will generate bundle in ES2022 standard in ES modules format.
If field browser in package.json is defined as a string then this string determines entry point to browser bundle and its name. E.g. when "browser": "lib/browser.js" entry point will be src/index.ts and bundle will have a name lib/browser.js.
Otherwise, if field browser is defined as an object and build was called with flag --forPublish then name is defined by the field main in package.json with adding postfix .browser e.g. lib/index.browser.js. After that to field browser new property will be added as pointer for bundlers to bundle for the browser, instead of the field module:
{
"browser": {
...,
"./index.es.js": "./index.browser.js"
}
}
Specification for the field browser
ES2022code standard is generated as it is expected that bundle from"browser"field will be resolved by bundler with configured transpilation throughbabelfor packages insidenode_moduleswritten in modern JS to the code according to thebrowserslistconfig.
When building our package in the app with webpack with option target: 'web' bundle from field browser will be prioritized over field module.
Copy static assets
For every build, all of the non JS/TS/JSON files (e.g. CSS, fonts, images) are copied to the output bundle preserving their relative paths (e.g. src/css/style.css -> lib/css/style.css). You can disable such copying by using flag copyStaticAssets:
tramvai-build --copyStaticAssets false
Build and copy migrations
When directory migrations has any code files they are considered as migration files. These files will be compiled to .js and copied to directory __migrations__.
CLI
Single build
tramvai-build
Build in watch mode
tramvai-build --watch
Copy static assets
tramvai-copy
Available flags
tramvai-build --help
JavaScript API
TramvaiBuild
TramvaiBuild is used to configure build process for following usage.
import { TramvaiBuild } from '@tramvai/build';
new TramvaiBuild(options);
Available options:
export type Options = {
sourceDir: string;
copyStaticAssets: boolean;
watchMode?: boolean;
forPublish?: boolean;
preserveModules?: boolean;
only?: 'migrations' | 'tests';
cwd?: string;
};
Build
Method TramvaiBuild.start builds package either single time or in watch mode depending on configuration of TramvaiBuild:
import { TramvaiBuild } from '@tramvai/build';
new TramvaiBuild(options).start();
Copy static files
Method TramvaiBuild.copy copies static assets to the output directory:
import { TramvaiBuild } from '@tramvai/build';
new TramvaiBuild(options).copy();
How to
Build separate bundle for browsers
Let's say we have to entry points. One is for the server - src/server.ts and for the client - src/index.ts. In this case we should set field browser in package.json the next way:
{
"main": "lib/server.js",
"browser": "lib/browser.js"
}
After build for publication we will get next package.json:
{
"main": "lib/server.js",
"browser": "lib/browser.js",
"typings": "lib/server.d.ts",
"module": "lib/server.es.js"
}
Replace specific module for browser bundle
Let's say we have one entry point - src/index.ts and a module src/external.ts we want to replace by src/external.index.ts. In this case we should set field browser in package.json the next way:
{
"main": "lib/index.js",
"browser": {
"./lib/external.js": "./lib/external.browser.js"
}
}
After build for publication we will get next package.json:
{
"main": "lib/index.js",
"browser": {
"./lib/external.js": "./lib/external.browser.js",
"./lib/index.es.js": "./lib/index.browser.js"
},
"typings": "lib/index.d.ts",
"module": "lib/index.es.js"
}
Build all of the packages in monorepo in watch mode
@TODO + link to @tinkoff/fix-ts-references
Import module only under some circumstances or put module to separate chunk
Instead of static imports you can use dynamic import or require. In this case imported module will be build in the separate chunk. Later this chunk can be added by bundler to the generated bundle and if dynamic import was used it will be separate chunk as well after bundlers build, but when using require separate chunk will not be generated.
let func = noop;
if (process.env.NODE_ENV !== 'production') {
func = require('./realFunc').func;
}
export { func };
Use JSON in package
By default in root tsconfig.json option resolveJsonModule is enabled. It is allows to import json-files the same way as usual source code using import, moreover typecheck and tree-shaking will work to json as well when publishing package. To disable ts errors for json imports add to tsconfig.json of the package new entry to field includes:
{
"includes": ["./src", "./src/**/*.json"]
}
Use assets file in the package (e.g. css, svg)
These files are not used in bundle or source code and ts will ignore them. For proper package usage additional setup should be done. Add script tramvai-copy to package.json:
{
"scripts": {
"copy-static-assets": "tramvai-copy"
}
}
This script will copy not related files to source code to the output directory. Copying itself happens either on dependencies install in the repository root or on package publishing. As for some reasons output directory might be deleted it may be needed to rerun tramvai-copy command for package.
Use css-modules
In order to disable typescript errors for css-modules imports add new file typings.d.ts to the src folder with the next content:
declare module '*.css' {
const value: any;
export default value;
}
To copy css while deb-build change next command:
"watch": "tramvai-copy && tsc -w"
Such imports are not compiled. To use it properly you can use @tramvai/cli for building app or any other solution for the css-modules.
When building correctness of imports for the css is not checking so check your package manually before publication.
Build only migrations or tests
If you need to build only migrations or tests files then you can specify option --only with either migrations or tests value.
tramvai-build --only migrations
tramvai-build --only tests