Skip to content

perf(build): make the bundle tree-shakeable without breaking the namespace export #137

Description

@kalwalt

Summary

Make the published bundle tree-shakeable additively: add named per-module
exports and "sideEffects": false, while keeping the default namespace export
exactly as it is today.

No breaking change, no migration guide, no 2.0.0.

Why

Tree-shaking does not currently work at all, for a structural reason:

  • package.json exposes only "." in exports and declares no "sideEffects".
  • src/index.ts default-exports the namespace object that src/jsfeatNext.ts
    builds by importing and instantiating every module.

A bundler cannot drop anything, because the namespace object holds a live
reference to every singleton. A consumer calling only imgproc.grayscale()
still ships ORB, YAPE, optical flow, the motion estimator and the 18 KB
src/orb/bit_pattern_31.ts table — the whole 57 KB UMD / 86 KB ESM bundle,
all or nothing.

This has been true since the 0.9.0 restructure and has simply not been costly
enough to notice. The TEBLID tables change that: they are the largest single
addition the library has taken on, and they land on every consumer regardless of
use.

Proposed change

Additive, in three parts:

  1. Export each module by name from src/index.ts, sourced directly from its
    module file
    rather than from the aggregator, so importing { imgproc }
    does not pull the aggregator's evaluation in with it.
  2. Add "sideEffects": false to package.json, so bundlers may drop unused
    module evaluations.
  3. Keep export default jsfeatNext unchanged.

Consumers importing the default namespace are unaffected and see the same
bundle. Consumers who care about size switch to named imports and get only what
they use. The jsfeatNext.<module> convention, and everything #96 assumes about
it, stays intact.

Why not the alternatives

  • Replacing the namespace with named exports would give the same benefit and
    break every existing consumer — a API/behavioral parity: static-vs-instance, double namespace, per-instance cache (Axis 2) #41-scale event, and post-1.0 that means a
    major version. The additive route gets the same result for free.
  • Subpath exports (@webarkit/jsfeat-next/teblid) would solve it for the new
    modules only, diverge from the single-namespace convention, and penalise CDN
    users who rely on one script tag.

Acceptance criteria

  • import { imgproc } from "@webarkit/jsfeat-next" works and type-checks.
  • A fixture app importing only { imgproc } produces a measurably smaller
    bundle than one importing the default — with the before/after numbers
    recorded in this issue.
  • import jsfeatNext from "@webarkit/jsfeat-next" behaves identically to
    today; the API-shape test is unchanged and green.
  • The UMD build still exposes the full namespace for CDN consumers.
  • "sideEffects": false is correct — verified by checking that no module
    relies on import-time side effects beyond registering itself on the
    namespace.

Out of scope

  • Removing or deprecating the default namespace export.
  • Splitting the package into multiple entry points.
  • Reducing the size of any individual table.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions