🌎 Zero-config build tool to create packages with Rollup and TypeScript (supports JavaScript too).
🌏 Rollpkg creates esm
, cjs
and umd
builds for development and production, and fully supports tree shaking.
🌍 Default configs are provided for TypeScript, Prettier, ESLint, and Jest for a complete zero decision setup.
For an example package see rollpkg-example-package
: package repo, and built and published code.
Used by: react-interactive
, detect-it
, event-from
, react-router-hash-link
, fscreen
, and others.
Setup ⚡️ package.json
⚡️ Build options ⚡️ Default configs ⚡️ 🚫 TS type pollution ⚡️ FAQ ⚡️ Compare to TSdx
rollpkg
Initialize with git
and npm
. Note that the docs use npm
, but it works just as well with yarn
.
mkdir <package-name>
cd <package-name>
git init
npm init
rollpkg
and typescript
TypeScript is a peerDependency
of Rollpkg, and Rollpkg will use the version of TS that you install for its builds.
npm install --save-dev rollpkg typescript
main
, module
, types
, and sideEffects
fields to package.json
Rollpkg uses a convention over configuration approach, so the values in package.json
need to be exactly as listed below, just fill in your <package-name>
and you’re good to go. Note that for scoped packages where "name": "@scope/<package-name>"
, use <scope-package-name>
for the main
and module
fields.
{
"name": "<package-name>",
...
"main": "dist/<package-name>.cjs.js",
"module": "dist/<package-name>.esm.js",
"types": "dist/index.d.ts",
"sideEffects": false | true,
...
}
Note about
sideEffects
: most packages should set"sideEffects": false
to fully enable tree shaking. A side effect is code that effects the global space when the script is run even if theimport
is never used, for example a polyfill that automatically polyfills a feature when the script is run would setsideEffects: true
. For more info see the Webpack docs (note that Rollpkg doesn't support an array of filenames containing side effects like Webpack).
build
, watch
and prepublishOnly
scripts to package.json
"scripts": {
"build": "rollpkg build",
"watch": "rollpkg watch",
"prepublishOnly": "npm run build"
}
tsconfig.json
fileIt is recommended to extend the Rollpkg tsconfig
and add your own options after extending it (however extending the Rollpkg tsconfig
is not a requirement).
// tsconfig.json
{
"extends": "rollpkg/configs/tsconfig.json"
}
Note: you can specify a custom path or name for your
tsconfig
using the--tsconfig
command line option.
index.ts
or index.tsx
entry file in the src
folderThis entry file is required by Rollpkg and it is the only file that has to be TypeScript (the rest of your source files can be JavaScript if you'd like). Note that you can write your entire code in index.ts
or index.tsx
if you only need one file.
package-name
├─node_modules
├─src
│ ├─index.ts | index.tsx
│ └─additional source files
├─.gitignore
├─package-lock.json
├─package.json
├─README.md
└─tsconfig.json
dist
to .gitignore
Rollpkg creates a dist
folder for the build files, and this shouldn't be checked into version control.
# .gitignore file
node_modules
dist
files
array with dist
to package.json
This lets npm
know to include the dist
folder when you publish your package.
"files": [
"dist"
]
npm version patch | minor | major
npm publish
No complex options to understand or insignificant decisions to make, just a sensible convention for building packages with Rollup and TypeScript. This is what you get with Rollpkg:
esm
and CommonJS cjs
builds into the dist
folder (umd
builds can be added with the --addUmdBuild
cli option).esm
build supports tree shaking and is ready to be used in development and production by modern bundlers (e.g. Webpack).cjs
build comes with both development and production versions, and will automatically select the appropriate version when it's used.if (process.env.NODE_ENV !== 'production') {...}
or if (__DEV__) {...}
is removed.*.d.ts
type filespackage.json
This includes the optional Rollpkg default configs. Also see rollpkg-example-package
for a fully set up example package.
{
"name": "<package-name>",
"version": "0.0.0",
"description": "Some awesome package",
"main": "dist/<package-name>.cjs.js",
"module": "dist/<package-name>.esm.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"scripts": {
"build": "rollpkg build",
"watch": "rollpkg watch",
"prepublishOnly": "npm run lint && npm test && npm run build",
"lint": "eslint src",
"test": "jest",
"test:watch": "jest --watchAll",
"coverage": "npx live-server coverage/lcov-report"
},
"files": ["dist"],
"devDependencies": {
"rollpkg": "^0.5.5",
"typescript": "^4.2.3"
},
"prettier": "rollpkg/configs/prettier.json",
"eslintConfig": {
"extends": ["./node_modules/rollpkg/configs/eslint"]
},
"jest": {
"preset": "rollpkg"
}
}
Rollpkg uses the TypeScript compiler to transform your code to ES2018
(default) and Rollup to create esm
, cjs
and umd
builds. The TypeScript compiler uses your tsconfig.json
with a few added defaults to prevent global type pollution, create source maps, and generate *.d.ts
type files.
rollpkg build
creates esm
, cjs
and umd
builds for both development and production.rollpkg watch
is lightning quick and always exits 0
so you can chain npm scripts.sideEffects: false
in package.json
fully enables tree shaking.ES5
, ES2018
, etc) in your tsconfig
if needed. The Rollpkg default is ES2018
.src
folder to npm
.rollpkg build
rollpkg build
rollpkg build
command. It creates esm
and cjs
builds with source maps and *.d.ts
type files.rollpkg build --tsconfig ./path/to/tsconfig.build.json
tsconfig
if needed. This option also works in watch mode with rollpkg watch
. By default Rollpkg will look in the root of your project for a tsconfig.json
file.rollpkg build --addUmdBuild
rollpkg build
only creates esm
and cjs
builds, use this option to also create umd
builds.rollpkg build --noStats
rollpkg build
will calculate Bundlephobia stats after each build, use this option skip the stats calculation.esm
build<package-name>.esm.js
esm
build is ready to be used by modern bundlers (e.g. Webpack) for both development and production and fully supports tree shaking. When creating production builds the bundler will minify the code and remove any code that is gated by process.env.NODE_ENV !== 'production'
.cjs
build<package-name>.cjs.js
cjs
entry file that selects the development or production cjs
build based on process.env.NODE_ENV
, you can view what this file looks like here.<package-name>.cjs.development.js
<package-name>.cjs.production.js
cjs
build creates separate versions for development and production, as well as an entry file that selects the appropriate version.umd
build--addUmdBuild
cli option to create umd
builds.<package-name>.umd.development.js
<package-name>.umd.production.js
The umd
builds are ready to be used directly in the browser from the Unpkg CDN, just add the script to index.html
:
<!-- in development use -->
<script src="https://unpkg.com/<pacakge-name>/dist/<pacakge-name>.umd.development.js"></script>
<!-- in production use -->
<script src="https://unpkg.com/<pacakge-name>/dist/<pacakge-name>.umd.production.js"></script>
window
as the PascalCase version of your <package-name>
. For example, if your package name is rollpkg-example-package
, then it will be available on the window
as RollpkgExamplePackage
.umd
build is bundled with your package dependencies
included, but with your package peerDependencies
listed as external globals, which are assumed to be available on the window
as the PascalCase version of their <package-name>
. For example, if your package has react
as a peer dependency, then Rollpkg assumes it will be available on the window
as React
, which is true if React is also loaded from the CDN.umd
build depends on and what they will be available on the window
as by adding a umdGlobalDependencies
object to your package.json
. The object needs to be in the form of { "package-name": "GlobalName" }
, for example "umdGlobalDependencies": { "react-dom": "ReactDOM" }
. If umdGlobalDependencies
is specified in your package.json
, then Rollpkg will use that instead of the peerDependencies
list for external globals.rollpkg watch
esm
build so rebuilds are lightning quick.ctrl c
to exit watch mode.0
(non-error state) so you can chain commands in npm scripts, for example rollpkg watch && npm run ...
(if watch mode didn't exit 0
, then npm run ...
would never run).tsconfig
using rollpkg watch --tsconfig ./path/to/tsconfig.build.json
sideEffects: boolean
"sideEffects": false
to fully enable tree shaking. A side effect is code that effects the global space when the script is run even if the import
is never used, for example a polyfill that automatically polyfills a feature when the script is run would set sideEffects: true
. For more info see the Webpack docs (note that Rollpkg doesn't support an array of filenames containing side effects like Webpack).sideEffects: false
enables the following tree shaking optimizations:
moduleSideEffects
, propertyReadSideEffects
, tryCatchDeoptimization
, and unknownGlobalSideEffects
all set to false
(note that tree shaking is still enabled with sideEffects: true
, just a milder version of it is used).Dev mode code is code that will only run in development and will be removed from production builds. You can use process.env.NODE_ENV
or __DEV__
to gate dev mode code and Rollpkg will remove it from production builds:
if (process.env.NODE_ENV !== 'production') {
// dev mode code here
}
if (__DEV__) {
// dev mode code here
}
Note that __DEV__
is shorthand for process.env.NODE_ENV !== 'production'
and Rollpkg will transform __DEV__
into process.env.NODE_ENV !== 'production'
before proceeding to create development and production builds.
tsconfig
Note that for most projects extending the Rollpkg
tsconfig
is all that's required and you can ignore this section.
The Rollpkg default is to compile your code to ES2018
which is supported by all modern browsers. If you need to support legacy browsers, then you probably want to compile your code to ES5
.
To control how your code is compiled and what JS APIs are available at runtime use the target
and lib
options in your tsconfig
. The target
option specifies the ECMAScript version that your code is compiled to (ES5
, ES2018
, etc). The lib
option specifies the JS APIs that will be available at runtime, which is needed for using JS APIs that can't be compiled to the specified target
. For example, array.includes
and the Promise
API cannot be compiled to ES5
but you may find it necessary to use them in your code (note that all JS APIs in your code will need to be available in the browser, either supported natively or provided by a polyfill).
For example, let's say your target
is ES5
and you need to use the Promise
API, your tsconfig.json
would look like this:
Note that when using the
lib
option you need to specify all available JS APIs, including the baseES5
APIs andDOM
APIs.
// example tsconfig.json to use ES5 and the Promise API
{
"extends": "rollpkg/configs/tsconfig.json",
"compilerOptions": {
"target": "ES5",
"lib": ["DOM", "ES5", "ES2015.Promise"]
}
}
Rollpkg provides sensible defaults for common configs that can be used for a complete zero decision setup. You can also add you own overrides to the defaults if needed. Default configs are provided for TypeScript, Prettier, ESLint, and Jest (the configs are setup to work with TypeScript, JavaScript, and React). Use of these configs is optional and while they include support for React, using React is not a requirement (they work just fine without React).
It is recommended to extend the Rollpkg tsconfig
and add your own options after extending it (however extending the Rollpkg tsconfig
is not a requirement).
// tsconfig.json
{
"extends": "rollpkg/configs/tsconfig.json",
// add your own options, etc...
}
If you want to use Prettier (recommended) you can extend the config provided by Rollpkg. There is no need to install Prettier as it is included with Rollpkg (alternatively if you need to use a specific version of Prettier, you can install it and that version will be used). In package.json
add:
"prettier": "rollpkg/configs/prettier.json"
You may also want to set up a pre-commit hook using pre-commit
or husky
, and lint-staged
so any changes are auto-formatted before being committed. See the rollpkg-example-package
for an example pre-commit hook setup, as well as the Prettier docs for Git hooks.
If you want to use ESLint (recommended) you can extend the config provided by Rollpkg. It includes support for TypeScript, JavaScript, React, Prettier, and Jest. The provided ESLint config mostly just extends the recommended defaults for each plugin. There is no need to install ESLint or specific plugins as they are included with Rollpkg (alternatively if you need to use a specific version of ESLint or plugin, you can install it and that version will be used). In package.json
add:
Note that the path includes
./node_modules/...
, this is because in order for ESLint to resolveextends
it requires either a path to the config, or for the config to be published in its own package namedeslint-config-...
, which may happen at some point, but for now it will remain a part of Rollpkg for easy development.
"eslintConfig": {
"extends": ["./node_modules/rollpkg/configs/eslint"]
}
It is also recommended to add a lint
script to package.json
(the eslint src
command tells ESLint to lint the src
folder). As well as add npm run lint
to the prepublishOnly
script so your code is linted before publishing.
"scripts": {
...
"prepublishOnly": "npm run lint && npm test && npm run build",
"lint": "eslint src"
}
If you want to use Jest (recommended) you can use the preset provided by Rollpkg. The preset uses ts-jest
for a seamless and fully typed checked TypeScript testing experience. There is no need to install Jest as it is included with Rollpkg (alternatively if you need to use a specific version of Jest, you can install it and that version will be used). In package.json
add:
"jest": {
"preset": "rollpkg"
}
It is also recommended to add test
, test:watch
and coverage
scripts to package.json
(the coverage
script will open the coverage report in your browser). As well as add npm test
to the prepublishOnly
script so tests are run before publishing.
"scripts": {
...
"prepublishOnly": "npm run lint && npm test && npm run build",
"lint": "eslint src",
"test": "jest",
"test:watch": "jest --watchAll",
"coverage": "npx live-server coverage/lcov-report"
}
The Rollpkg Jest config will automatically generate a code coverage report when Jest is run and save it in the coverage
folder, which shouldn't be checked into version control, so also add coverage
to .gitignore
.
# .gitignore
node_modules
dist
coverage
TypeScript's default behavior is to include all of the types in your node_modules/@types
folder as part of the global scope. This allows you to use things like process.env.NODE_ENV
and Jest's test(...)
or expect(...)
without causing a type error. However, it also adds a significant amount of global type pollution that you might not realize is there. This pollution can make it seem like you are writing type safe code that safely compiles to your target
ECMAScript version, but in reality you are using APIs that won't be available at runtime. For example, your node_modules/@types
folder probably includes node
's types (they are required by Jest and others), so the TypeScript compiler thinks your code will have access to all of Node's APIs at runtime. If your compilation target
is set to ES5
, using APIs like array.includes
, Map
, Set
or Promise
won't produce a TypeScript error despite the fact that none of these can be compiled to ES5
🤦♀️ (the TypeScript compiler assumes your code will have access to these APIs at runtime).
TypeScript does provide a types
compiler option for you to explicitly specify which packages are included in the global scope (note that these will be in addition to any import
s in your code, for example if you import * as React from 'react'
then react
's types will always be included). However, failing to include types that are needed in development but won't be available at runtime (e.g. Node's process.env.NODE_ENV
and Jest's test
) will cause a TypeScript error in development 🤦♂️ Also you can only specify entire packages so it is not possible to only include the process.env
type but exclude the rest of Node's types.
Ideally TypeScript would allow overrides based on file types like ESLint does, but until that happens the most widely used solution is multiple tsconfig
s (e.g. tsconfig.build.json
, tsconfig.test.json
, etc) that include or exclude specific files and types (or alternatively ignoring the issue and accepting the global type pollution). Note that VS Code (and other editors) can only use one tsconfig.json
per file tree section to provide type feedback in the UI, so when using multiple tsconfig
s it is typical to have a tsconfig.json
file that doesn't restrict global types or files so you can benefit from UI based type feedback without unwanted type errors in the UI. That is, even with multiple tsconfig
s, global type pollution is unavoidable in the editor UI.
tsconfig
that you can extend in your tsconfig.json
doesn't restrict the types available in the global scope to provide a smooth editor experience (VS Code uses your tsconfig.json
for type checking).rollpkg build
or rollpkg watch
only files that are part of the build are type checked, and node_modules/@types
that are not imported are excluded to prevent global type pollution (your tsconfig.json
is used for the build, but with a types: []
override). Also, process.env.NODE_ENV
is stubbed so it won't cause an error before it is transformed during the build process.rollpkg build
or rollpkg watch
that doesn't show up in your editor or when running npx tsc
(which uses your tsconfig.json
). This usually happens because you are using a JS API that is not included in your tsconfig.json
's lib
option, but is included in the global scope via node_modules/@types
(e.g. array.includes
, Promise
, etc). The solution is to explicitly add this JS API to your tsconfig.json
's lib
option so Rollpkg will include it in the build process. For how to do this see the setting target ECMAScript version section.rollpkg build
section for details.src/index.ts
, the rest of your files can written in JavaScript. This is because Rollpkg uses the TypeScript complier to compile both TypeScript and JavaScript files.ES2018
(or your target ECMAScript version), this avoids the limitations of using TypeScript with Babel which means your code is fully type checked all the way through the build process. Also, by not using Babel the tsconfig.json
becomes the single source of truth for how your code is compiled and eliminates the complexity and confusion caused by having both a tsconfig.json
and a babel.config.json
.React.createElement(...)
API regardless, which makes the build process more complex.Some background: I started creating npm packages in 2016, and I've mostly used my own build setup. Recently I started looking for a build tool that would provide a convention over configuration approach to reduce the number of decisions I needed to make. I used Microbundle for a bit, and experimented with TSdx, but neither was a good fit, so I created Rollpkg (this is essentially the origin story behind all of my open source projects, I just want the thing to exist so I can use it in another project 😆).
Without taking anything away from TSdx (I think it's generally a solid tool that gets a lot of things right), here are some areas where Rollpkg takes a different approach (as of TSdx v0.14.1):
peerDependency
vs regular dependency
peerDependency
and will use whatever version you have installed to compile your code.dependency
and will only use the TS version that it comes with.tsconfig.json
: extend a default config vs static setup
tsconfig
that you can extend and add overrides to if needed.tsconfig.json
for you when you create a new project and writes the configuration to that file.tsconfig
options change, all you have to do is upgrade Rollpkg to use the latest best practices/convention, while with TSdx you're on your own to manage/update your tsconfig
in each project.tsconfig
is recommended but not required, so you're free to fully manage your tsconfig
if you want.peerDependencies
vs all dependencies
as required globals
dependencies
as part of the UMD build and leaves only your peerDependencies
as required globals.dependencies
and peerDependencies
as required globals in the UMD build.peerDependency
plus some regular dependencies
, Rollpkg will bundle the regular dependencies
as part of the UMD build and only React
will be required to be available on the window
. Compared to TSdx which will require React
plus all of your dependencies
to be available on the window
(some of which may not have an available UMD build that can be loaded from a CDN in a <script>
tag).rollpkg build
.package.json
when you create a new project (adds a Size Limit config, npm script, and size-limit
as a dependency), but doesn't include any package size stats as part of the tsdx build
command.0
vs non-0
rollpkg watch
always exits 0
, including when you use ctrl c
to exit watch mode, so you can chain npm scripts.tsdx watch
exits non-0
.npm run devSetup && rollpkg watch && npm run devCleanup
, but with TSdx if you do npm run devSetup && tsdx watch && npm run devCleanup
the npm run devCleanup
command will never run.