GitHub - privatenumber/pkgroll: 📦 🍣 Zero-config JS bundler for ESM, CommonJS, an...
source link: https://github.com/privatenumber/pkgroll
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
pkgroll
Write your code in ESM & TypeScript and bundle it to get ESM, CommonJS, and type declaration outputs with a single command!
Features
- Zero config Configuration automatically inferred from
package.json
- ESM/CJS Detection Uses
.mjs
/.cjs
extension orpackage.json#type
to infer module type - Export & Import maps Uses export and import maps to configure endpoints and aliases
- TypeScript friendly Transform TS and emit
.d.ts
files (Supports.cts
/.mts
too!) - Node.js ESM ⇄ CJS friendly Preserves named exports from ESM to CJS
- Fast Transformations powered by esbuild
Install
npm install --save-dev pkgroll
Quick setup
-
Setup your project with source files in
src
and output indist
(configurable). -
Specify entry files in
package.json
.These configurations are read by Node.js to determine how to import the package. Pkgroll leverages the same configuration to determine how to build the package.
{ "name": "my-package", // Set "module" or "commonjs" (https://nodejs.org/api/packages.html#type) // "type": "module", // Define the output files "main": "./dist/file.js", "module": "./dist/file.mjs", "types": "./dist/file.d.ts", // Define output files for Node.js export maps (https://nodejs.org/api/packages.html#exports) "exports": { "require": "./dist/file.js", "import": "./dist/file.mjs", "types": "./dist/file.d.ts" }, // bin files will be compiled to be executable with the Node.js hashbang "bin": "./dist/cli.js", // (Optional) Add a build script referencing `pkgroll` "scripts": { "build": "pkgroll" } // ... }
Paths that start with
./dist/
are automatically mapped to files in the./src/
directory. -
Package roll!
npm run build # or npx pkgroll
Usage
Entry-points
Pkgroll parses package entry-points from package.json
by reading properties main
, module
, types
, and exports
.
The paths in ./dist
are mapped to paths in ./src
(configurable with --src
and --dist
flags) to determine bundle entry-points.
Output formats
Pkgroll detects the format for each entry-point based on the file extension or the package.json
property it's placed in, using the same lookup logic as Node.js.
package.json
property
Output format
main
Auto-detect
module
ESM
Note: This unofficial property is not supported by Node.js and is mainly used by bundlers.
types
TypeScript declaration
exports
Auto-detect
exports.require
CommonJS
exports.import
Auto-detect
exports.types
TypeScript declaration
bin
Auto-detect
Also patched to be executable with the Node.js hashbang.
Auto-detect infers the type by extension or package.json#type
:
Extension Output format
.cjs
CommonJS
.mjs
ECMAScript Modules
.js
Determined by package.json#type
, defaulting to CommonJS
Dependency bundling & externalization
Packages to externalize are detected by reading dependency types in package.json
. Only dependencies listed in devDependencies
are bundled in.
When generating type declarations (.d.ts
files), this also bundles and tree-shakes type dependencies declared in devDependencies
as well.
// package.json { // ... "peerDependencies": { // Externalized }, "dependencies": { // Externalized }, "optionalDependencies": { // Externalized }, "devDependencies": { // Bundled }, }
Aliases
Aliases can be configured in the import map, defined in package.json#imports
.
For native Node.js import mapping, all entries must be prefixed with #
to indicate an internal subpath import. Pkgroll takes advantage of this behavior to define entries that are not prefixed with #
as an alias.
Native Node.js import mapping supports conditional imports (eg. resolving different paths for Node.js and browser), but Pkgroll does not.
Aliases are not supported in type declaration generation. If you need type support, do not use aliases.
{ // ... "imports": { // Mapping '~utils' to './src/utils' "~utils": "./src/utils", // Native Node.js import mapping (can't reference ./src) "#internal-package": "./vendors/package/index.js", } }
Target
Pkgroll uses esbuild to handle TypeScript and JavaScript transformation and minification.
The target specifies the environments the output should support. Depending on how new the target is, it can generate less code using newer syntax. Read more about it in the esbuild docs.
By default, the target is set to the version of Node.js used. It can be overwritten with the --target
flag:
pkgroll --target es2020 --target node14.18.0
It will also automatically detect and include the target
specified in tsconfig.json#compilerOptions
.
Strip node:
protocol
Node.js builtin modules can be prefixed with the node:
protocol for explicitness:
import fs from 'node:fs/promises';
This is a new feature and may not work in older versions of Node.js. While you can opt out of using it, your dependencies may still be using it (example package using node:
: path-exists).
Pass in a Node.js target that that doesn't support it to strip the node:
protocol from imports:
pkgroll --target node12.19
ESM ⇄ CJS interoperability
Node.js ESM offers interoperability with CommonJS via static analysis. However, not all bundlers compile ESM to CJS syntax in a way that is statically analyzable.
Because pkgroll uses Rollup, it's able to produce CJS modules that are minimal and interoperable with Node.js ESM.
This means you can technically output in CommonJS to get ESM and CommonJS support.
require()
in ESM
Sometimes it's useful to use require()
or require.resolve()
in ESM. This can be seamlessly compiled to CommonJS. But when compiling to ESM, Node.js will error because require
doesn't exist in the module scope.
When compiling to ESM, Pkgroll detects require()
usages and shims it with createRequire(import.meta.url)
.
Minification
Pass in the --minify
flag to minify assets.
pkgroll --minify
Watch mode
Run the bundler in watch mode during development:
pkgroll --watch
Why bundle with Rollup?
Rollup has the best tree-shaking performance, outputs simpler code, and produces seamless CommonJS and ESM formats (minimal interop code). Notably, CJS outputs generated by Rollup supports named exports so it can be parsed by Node.js ESM. TypeScript & minification transformations are handled by esbuild for speed.
Why bundle Node.js packages?
-
ESM and CommonJS outputs
As the Node.js ecosystem migrates to ESM, there will be both ESM and CommonJS users. A bundler helps accommodate both distribution types.
-
Dependency bundling yields smaller and faster installation.
Tree-shaking only pulls in used code from dependencies, preventing unused code and unnecessary files (eg.
README.md
,package.json
, etc.) from getting downloaded.Removing dependencies also eliminates dependency tree traversal, which is one of the biggest bottlenecks.
-
Inadvertent breaking changes
Dependencies can introduce breaking changes due to a discrepancy in environment support criteria, by accident, or in rare circumstances, maliciously.
Compiling dependencies will make sure new syntax & features are downgraded to support the same environments. And also prevent any unexpected changes from sneaking in during installation.
-
Type dependencies must be declared in the
dependencies
object inpackage.json
for it to be resolved by the consumer.This can be unintuitive because types are a development enhancement and also adds installation bloat. Bundling filters out unused types and allows type dependencies to be declared in
devDependencies
. -
Minification strips dead-code, comments, white-space, and shortens variable names.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK