Parcel 25 ๐ฆ Library Mode โ Building distributable packages
Parcel’s library mode is not a separate command. It is the same parcel build pipeline, pointed at a package.json that declares library targets. The source field points to the entry file, and the target fieldsโmain, module, types, browserโdeclare the outputs. Parcel reads the fields, compiles the source for each target, and writes the output to the path in each field . The distinction between a library build and an application build is the defaults: library targets do not bundle node_modules dependencies, and minification is disabled by default . The library is a package that another bundler or a Node.js runtime will consume, so Parcel leaves the dependencies for the consumer to resolve.
The target fields are the entire configuration. The main field outputs CommonJS by default, the module field outputs an ES module, and the types field outputs a .d.ts file if the source is TypeScript . The browser field is a browser-specific override of main. The .mjs and .cjs extensions override the format inference: a target with a .mjs extension outputs an ES module, and a target with a .cjs extension outputs CommonJS . The format inference is the reason the extension and the field must agree; a mismatch produces the “declared output format does not match expected output format” error .
Key point: A Parcel library is a package.json with a source field and one or more target fields. The main field outputs CommonJS, the module field outputs ESM, and the types field outputs the declaration file. The .mjs and .cjs extensions override the format inference. Library targets do not bundle dependencies from node_modules and do not minify by default.
Why library mode has different defaults
The dependency problem. An application bundles its dependencies because the application is the final consumer of its own bundle. A library is not the final consumer. When a library bundles React, the consumer ends up with two React instances, and the hooks throw the “invalid hook call” error. Parcel’s library targets do not bundle node_modules dependencies by default for this reason . The consumer’s bundler resolves the dependencies from the consumer’s node_modules, and the library provides only its own code.
The minification problem. A library’s output is consumed by another bundler, which performs its own minification. Minifying the library’s output first removes the pure annotations and the whitespace that the consumer’s tree-shaking depends on. Parcel disables minification for library targets by default, and the consumer’s bundler performs the final minification .
The scope hoisting constraint. Scope hoisting cannot be disabled for library targets. The hoisting concatenates the modules into a single scope, which is what allows the consumer’s tree-shaking to see the individual exports. A library that disabled scope hoisting would produce a wrapped output that the consumer’s bundler cannot tree-shake effectively .
The trade-off. The library mode is less configurable than the application mode. The scope hoisting cannot be disabled, the dependencies are not bundled, and the minification is off by default. The configuration is the target fields, and the fields determine the output format. The library author who needs a different behaviorโbundling a dependency, enabling minificationโmust override the defaults in the targets object, and the override is a deliberate choice that changes the consumer’s experience.
a. The target fields and the format inference
The target fields are the top-level keys in package.json that correspond to the built-in library targets. The source field is the entry point, and the target fields are the outputs.
// package.json โ a library with CommonJS, ESM, and TypeScript types
{
"name": "my-library",
"version": "1.0.0",
"source": "src/index.ts",
"main": "dist/main.js",
"module": "dist/module.js",
"types": "dist/types.d.ts",
"devDependencies": {
"parcel": "latest"
}
}
The main field outputs CommonJS by default. The module field outputs an ES module. The types field outputs the declaration file. The source field points to the TypeScript entry, and Parcel compiles it for each target . The output files are written to the paths in the fields.
// package.json โ a library with explicit extensions
{
"name": "my-library",
"source": "src/index.ts",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts"
}
The .cjs extension on the main field overrides the default and outputs CommonJS. The .mjs extension on the module field outputs an ES module. The extension is the format declaration; the field name is the target name. The two must agree, or the build fails with the format mismatch error . The .cjs and .mjs extensions are the explicit form; the .js extension relies on the field name and the "type" field for the inference.
The "type": "module" field is the third mechanism. When it is set, the main field is treated as an ES module, and the output is ESM even if the extension is .js . The field is the package-level default, and it applies to the main target unless the extension overrides it.
b. The @parcel/bundler-library plugin for tree-shaking
By default, Parcel bundles the whole library into a single file for each target. A library with many exports that are used independentlyโa component library, a utility libraryโproduces a single large file that the consumer’s bundler cannot tree-shake effectively. The @parcel/bundler-library plugin changes the bundling strategy: it distributes the exports into separate files, so the consumer’s bundler can import only the files it needs .
// .parcelrc โ the library bundler
{
"extends": "@parcel/config-default",
"bundler": "@parcel/bundler-library"
}
The plugin is the extension point for the tree-shaking problem. A library with a single export does not need it; a library with dozens of exports does. The plugin produces multiple output files, and the consumer’s bundler follows the imports to the files it uses. The trade-off is more files in the package, and the benefit is a smaller consumer bundle.
Complete Example Session
// ============================================
// PART 1: THE PACKAGE.JSON
// ============================================
// package.json
{
"name": "my-library",
"version": "1.0.0",
"source": "src/index.ts",
"main": "dist/main.js",
"module": "dist/module.js",
"types": "dist/types.d.ts",
"scripts": {
"build": "parcel build",
"watch": "parcel watch"
},
"devDependencies": {
"parcel": "latest"
}
}
// ============================================
// PART 2: THE SOURCE FILE
// ============================================
// src/index.ts
export function add(a: number, b: number): number {
return a + b;
}
// ============================================
// PART 3: THE BUILD
// ============================================
// npx parcel build
//
// The build produces:
// dist/main.js โ CommonJS
// dist/module.js โ ES module
// dist/types.d.ts โ the declaration file
// ============================================
// PART 4: THE EXPLICIT EXTENSION
// ============================================
// package.json โ with .cjs and .mjs
{
"source": "src/index.ts",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts"
}
// The .cjs extension outputs CommonJS.
// The .mjs extension outputs ESM.
// The extension is the format declaration.
// ============================================
// PART 5: THE "type": "module" FIELD
// ============================================
// package.json
{
"type": "module",
"source": "src/index.ts",
"main": "dist/index.js"
}
// The "type": "module" field treats the main field as ESM.
// The output is an ES module even though the extension is .js.
// ============================================
// PART 6: THE TREE-SHAKING PLUGIN
// ============================================
// .parcelrc
{
"extends": "@parcel/config-default",
"bundler": "@parcel/bundler-library"
}
// The plugin distributes the exports into separate files.
// The consumer's bundler can tree-shake the unused exports.
// ============================================
// PART 7: THE TYPESCRIPT DECLARATION
// ============================================
// The source field points to a .ts file.
// The types field points to a .d.ts file.
// Parcel generates the declaration file automatically.
// src/index.ts
export function add(a: number, b: number): number { return a + b; }
// dist/types.d.ts
export declare function add(a: number, b: number): number;
// ============================================
// PART 8: THE FORMAT MISMATCH ERROR
// ============================================
// โ A target with a .cjs extension and an explicit esmodule format:
// {
// "main": "dist/index.cjs",
// "targets": {
// "main": { "outputFormat": "esmodule" }
// }
// }
//
// The extension says CommonJS, the outputFormat says ESM.
// The build throws "Declared output format does not match expected output format".
// ============================================
// PART 9: THE PACKAGE EXPORTS
// ============================================
// Parcel 2.9+ supports package.json "exports".
// It is opt-in and must be enabled in the consumer's package.json.
// {
// "@parcel/resolver-default": {
// "packageExports": true
// }
// }
// Without the opt-in, Parcel cannot resolve subpath imports
// from a package that declares "exports".
// ============================================
// PART 10: THE VERIFICATION CHECKLIST
// ============================================
// [ ] The source field points to the entry file.
// [ ] The main field points to the CommonJS output.
// [ ] The module field points to the ES module output.
// [ ] The types field points to the declaration file.
// [ ] The extension and the field agree on the format.
// [ ] The library bundler is configured for tree-shaking.
// [ ] The build produces the expected files.
The ten parts covered the package.json, the source, the build, the explicit extension, the "type" field, the tree-shaking plugin, the TypeScript declaration, the format mismatch error, the package exports, and the verification. The through-line is that the target fields are the configuration, and the format inference is the constraint.
Quick Reference
The library targets
| Field | Output format | Notes |
|---|---|---|
main | CommonJS (or ESM with .mjs or "type": "module") | The default entry |
module | ES module | The ESM entry |
browser | CommonJS | The browser override of main |
types | .d.ts | The declaration file |
The format inference
| Extension | "type" field | Output format |
|---|---|---|
.js | (none) | CommonJS |
.js | "module" | ESM |
.mjs | (any) | ESM |
.cjs | (any) | CommonJS |
The library defaults
| Setting | Library default |
|---|---|
Bundling node_modules | Disabled |
| Minification | Disabled |
| Scope hoisting | Cannot be disabled |
Best Practices
โ Do This:
// Use the target fields
"source": "src/index.ts", "main": "dist/main.js", "module": "dist/module.js" // โ
// Use the extension to declare the format
"main": "dist/index.cjs", "module": "dist/index.mjs" // โ
// Use the types field for TypeScript
"types": "dist/types.d.ts" // โ
// Use the library bundler for tree-shaking
"bundler": "@parcel/bundler-library" // โ
โ Don’t Do This:
// Mismatch the extension and the outputFormat
"main": "dist/index.cjs", "targets": { "main": { "outputFormat": "esmodule" } } // โ
// Bundle the dependencies
// The library targets do not bundle node_modules by default. // โ
// Enable minification
// The consumer's bundler performs the final minification. // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The format mismatch error | The extension and the outputFormat disagree | Align the extension with the format |
| The declaration file is missing | The types field is not set | Add the types field |
| The consumer cannot resolve subpaths | packageExports is not enabled | Add "@parcel/resolver-default": { "packageExports": true } |
| The consumer bundle is large | The library is a single file | Use @parcel/bundler-library |
| The library bundles React | The dependencies are bundled | Check the library targets’ defaults |
Real-World Examples
1. Basic library
{ "source": "src/index.js", "main": "dist/main.js" }
2. CommonJS and ESM
{ "source": "src/index.js", "main": "dist/main.js", "module": "dist/module.js" }
3. TypeScript library
{ "source": "src/index.ts", "main": "dist/main.js", "types": "dist/types.d.ts" }
4. Explicit extensions
{ "main": "dist/index.cjs", "module": "dist/index.mjs" }
5. "type": "module"
{ "type": "module", "main": "dist/index.js" }
6. Tree-shaking plugin
{ "extends": "@parcel/config-default", "bundler": "@parcel/bundler-library" }
7. Build script
"scripts": { "build": "parcel build", "watch": "parcel watch" }
8. Format mismatch
// โ "main": "dist/index.cjs", "targets": { "main": { "outputFormat": "esmodule" } }
9. Package exports
{ "@parcel/resolver-default": { "packageExports": true } }
10. The verification
npx parcel build && ls dist/
Visual
The Target Fields
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ package.json โ
โ โ
โ source: "src/index.ts" โ the entry โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ main: dist/main.js โ CommonJS by default โ
โ โ module: dist/module โ ES module โ
โ โ types: dist/types โ .d.ts โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Each target is a separate output file. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Format Inference
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE EXTENSION AND THE FIELD AGREE โ
โ โ
โ main: "dist/index.js" โ CommonJS (default) โ
โ main: "dist/index.cjs" โ CommonJS (explicit) โ
โ main: "dist/index.mjs" โ ESM (explicit) โ
โ main: "dist/index.js" โ ESM (with "type": "module") โ
โ โ
โ A mismatch between the extension and the outputFormat โ
โ throws a build error. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Library Defaults
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LIBRARY VS APPLICATION โ
โ โ
โ Application Library โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Bundles deps โ โ Does not bundle depsโ โ
โ โ Minifies โ โ Does not minify โ โ
โ โ Scope hoisting โ โ Scope hoisting โ โ
โ โ โ โ (cannot be disabled)โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The library leaves the final optimization to the consumer. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Entry field | source |
| CommonJS target | main |
| ESM target | module |
| TypeScript declarations | types |
| Browser override | browser |
| Format override | .mjs (ESM), .cjs (CommonJS) |
| Tree-shaking plugin | @parcel/bundler-library |
| Dependency bundling | Disabled by default |
| Minification | Disabled by default |
Key takeaways:
- A Parcel library is a
package.jsonwith asourcefield and target fields. Thesourcefield points to the entry, and themain,module, andtypesfields declare the outputs. Parcel reads the fields and builds the targets . - The format inference is determined by the field and the extension. The
mainfield outputs CommonJS by default. The.mjsextension or the"type": "module"field overrides it to ESM. The extension and the field must agree, or the build fails . - Library targets do not bundle dependencies from
node_modules. The consumer’s bundler resolves the dependencies. Bundling them in the library produces duplicate copies and the “invalid hook call” error for React . - The
@parcel/bundler-libraryplugin improves tree-shaking. By default, the library is bundled into a single file per target. The plugin distributes the exports into separate files, so the consumer’s bundler can tree-shake the unused exports . - The package exports support is opt-in. Parcel 2.9+ supports
package.json"exports", but it must be enabled in the consumer’spackage.jsonwith"@parcel/resolver-default": { "packageExports": true }.
Remember: Library mode is the same parcel build pipeline pointed at a package.json with library targets. The target fields are the configuration, and the defaults are different from the application mode: no dependency bundling, no minification, and scope hoisting that cannot be disabled. The format is inferred from the extension and the field, and a mismatch is a build error. The tree-shaking plugin is the extension point for a library with many exports. The library provides the code and the types; the consumer provides the dependencies and the final optimization.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!