Skip to content

fix(build): shrink published package from ~202 MB to ~3.5 MB (PER-15197)#127

Merged
dshoen619 merged 1 commit into
mainfrom
david/per-15197-permitio-npm-package-size-exploded-to-202-mb-in-v275-hybrid
Jun 24, 2026
Merged

fix(build): shrink published package from ~202 MB to ~3.5 MB (PER-15197)#127
dshoen619 merged 1 commit into
mainfrom
david/per-15197-permitio-npm-package-size-exploded-to-202-mb-in-v275-hybrid

Conversation

@dshoen619

@dshoen619 dshoen619 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

The permitio npm package size exploded in v2.7.5 — from 7.82 MB (v2.7.2) to ~202 MB (v2.7.5) — breaking a customer's AWS Lambda deploy (exceeds the code-size limit). This PR brings the published package down to ~3.5 MB unpacked / 195 KB packed with zero functional changes.

Fixes PER-15197. Reported by a customer (Matthew Karges) in #support; their workaround was pinning to v2.7.2.

Closes #90 (published package contains many unnecessary files).

Root cause

v2.7.5's "hybrid build" (PR #118, commit 2cdc9ef) switched from tsc to tsup but the config caused the entire per-file build/ tree to be published:

entry: ['src/**/*.ts'],   // every source file (incl. all tests + ~300 openapi type files) is its own entry
format: ['cjs', 'esm'],   // every file emitted twice (.js + .mjs)
splitting: false,         // no shared chunks -> each entry inlines its WHOLE internal import graph
sourcemap: true,          // a .map for every output file

With splitting: false + a per-file entry glob, the large generated src/openapi client got duplicated in full into every output bundle, then multiplied by two formats and a .map per file. The published tarball also still contained the test specs.

Measured breakdown of the bloat: 77 MB of source maps (708 .map files), build/api 62 MB, build/openapi 22 MB, test specs ~109 MB.

The fix

The published artifact only needs the self-contained build/index.js / build/index.mjs bundles (verified: 0 relative imports; only axios/lodash/pino/url-parse externalized, all declared dependencies) plus the type declarations. The per-file outputs exist solely so the test suite can import internal modules by relative path (require('../../api/api-client'), ../../enforcement/enforcer, ../../index), so they must keep being built — they just don't need to be published.

package.jsonfiles: publish only the bundle + types instead of the whole build/:

"files": [
  "build/**/*.d.ts",
  "build/index.js",
  "build/index.mjs",
  "!build/tests/**",
  "CHANGELOG.md", "LICENSE", "README.md"
]

tsup.config.ts: sourcemap: false — maps are unused by the test suite and were ~77 MB of dead weight.

No source code changed; the hybrid ESM/CJS output is preserved.

Results (npm pack)

Metric Before (v2.7.5) After
Unpacked 202 MB (94 MB locally) 3.5 MB
Packed 6.2 MB 195 KB
.map files 708 0
spec/test files included (~109 MB) 0

Smaller than the last known-good v2.7.2 (7.82 MB).

Verification

  • ✅ CJS consume: require('./build/index.js')Permit instantiates; .check/.api/.elements present
  • ✅ ESM consume: import { Permit } from './build/index.mjs' → same
  • npm pack: 3.5 MB, only index.js/index.mjs + .d.ts; no maps, no tests
  • ✅ Per-file build intact (build/api/*.js, build/enforcement/*.js, …); yarn test unaffected
  • ✅ Module-import suite passes 6/6 (CJS + ESM relative-import tests)

Follow-ups (post-merge)

  • Cut a patch release (e.g. 2.7.6)
  • Notify the customer they can unpin from 2.7.2

References

🤖 Generated with Claude Code

v2.7.5's hybrid tsup build shipped the entire per-file build/ tree —
every internal module bundled with the OpenAPI client inlined, in both
CJS and ESM, plus a .map for every file (708 maps, 77 MB) and the test
specs. Result: ~202 MB published, breaking AWS Lambda's code-size limit.

The published artifact only needs the self-contained build/index.js /
build/index.mjs bundles (0 relative imports; axios/lodash/pino/url-parse
externalized) plus type declarations. The per-file outputs exist solely
so the test suite can import internal modules by relative path, so they
must keep being built — but they don't need to be published.

- package.json files: publish only build/index.{js,mjs} + build/**/*.d.ts
  (exclude per-file JS, all maps, and tests) instead of the whole build/
- tsup.config.ts: sourcemap false (maps are unused by tests, never shipped)

Verified via npm pack: 202 MB -> 3.5 MB unpacked (195 KB packed), 0 maps,
0 test files. CJS + ESM consume tests pass; module-import suite passes 6/6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jun 23, 2026

Copy link
Copy Markdown

PER-15197

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a major npm package size regression by changing what gets published to npm and by disabling source map emission in the tsup build, restoring the published permitio artifact to a small footprint suitable for environments like AWS Lambda.

Changes:

  • Disable tsup source map emission to avoid generating hundreds of .map files.
  • Restrict the npm published contents to the public entry bundles (build/index.js, build/index.mjs) and TypeScript declarations, excluding built tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
tsup.config.ts Turns off source map generation in the build output.
package.json Narrows the npm files list to publish only entry bundles + .d.ts, excluding build/tests/**.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dshoen619 dshoen619 self-assigned this Jun 23, 2026
@dshoen619 dshoen619 requested review from gemanor and zeevmoney June 23, 2026 08:58

@zeevmoney zeevmoney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at b3c7682 (rebased on current main). The two changed lines are correct — verified by reproducing the tsup + tsc --emitDeclarationOnly build and running npm pack --dry-run against the real files array:

  • Every package.json artifact pointer resolves in the tarball: mainbuild/index.js, modulebuild/index.mjs, typings/types/exports.typesbuild/index.d.ts (matched by build/**/*.d.ts). The .d.ts graph re-exports siblings by relative path and all of them ship.
  • !build/tests/** is necessary and correctly ordered — without it, build/**/*.d.ts would leak test/fixture declarations. It drops nothing the public type graph needs.
  • build/index.js/.mjs are self-contained: all 8 runtime deps are in dependencies (correctly externalized, not inlined), and there are no dynamic require/import or fs/__dirname asset reads in runtime source.
  • sourcemap: false is safe — nothing (tests, typedoc, runtime) consumes maps, and declarationMap is unset, so no .d.ts.map leak.

Approving. The notes below are non-blocking — none is on a line this PR changes.


Non-blocking notes (informational)

1. No CI guard against the size regressing again (follow-up).
Nothing in the publish workflow checks tarball size, so a repeat of the 202 MB regression would ship silently. Subtler: the module-import tests run against the fully-populated build/ and deep-import paths (require('../../api/api-client')) that are not in the published tarball, so the suite passes without validating the package as shipped. A future change that made index.js reference a sibling would keep CI green while breaking consumers. Suggest a post-yarn build step in .github/workflows/node_sdk_publish.yaml: npm pack + a size-threshold assert, and/or install the tarball in a temp dir and require('permitio') to smoke-test the real entry point.

2. package.json files lists CHANGELOG.md, which doesn't exist at repo root. Pre-existing and harmless (npm silently ignores missing files entries); untouched by this diff.

3. src/logger.ts:14 uses pino-8's deprecated prettyPrint option (moved to transport in pino 8). Pre-existing, resolves fine since pino-pretty is a dependency. Unrelated to this PR.

@dshoen619 dshoen619 merged commit 8ce44ec into main Jun 24, 2026
4 checks passed
@dshoen619 dshoen619 deleted the david/per-15197-permitio-npm-package-size-exploded-to-202-mb-in-v275-hybrid branch June 24, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published package contains many unnecessary file

3 participants