Skip to content

chore(ep-commerce): move the commerce package off tsdx to the shared build.mjs pattern #439

Description

@field123

plasmicpkgs/commerce-providers/elastic-path builds with tsdx build && node build-server.mjs. The tsdx half costs 31 minutes. The repo's shared pattern does the same work in about ten seconds:

"build":        "pnpm build:types && pnpm build:index && pnpm build:server",
"build:types":  "tsc",
"build:index":  "node ../../../build.mjs ./src/index.tsx --use-client",
"build:server": "node ../../../build.mjs ./src/server.ts"

Measured here: tsc emits declarations for both entry graphs in 6s, esbuild produces all four bundles in ~1s. commerce-providers/ is one level deeper than the eleven already-migrated packages, so the paths are ../../../.

The 31 minutes is not the main prize. CI never builds or type-checks this packagetests.yml runs jest and vitest for plasmicpkgs/ and nothing else, so the build only ever runs at prepublishOnly. A 6-second build:types is cheap enough to gate on every PR, which is also what makes api-extractor's report-drift check bite (localBuild: !process.env.CI).

The earlier version of this issue argued from tsdx's TypeScript 3.9.10 ceiling. That is gone: since #440 flattened the nested node_modules/tsdx/node_modules/typescript, tsdx compiles with the workspace's 5.2.2. Cost is the whole argument now.

Two hacks this deletes rather than relocates

build-server.mjs. Its next/*next/*.js regex over the ESM output exists because Next ships no exports map, so Node's ESM resolver cannot resolve a bare next/server. But the package imports next/server in exactly one file, and next/server.js + server.d.ts exist in every Next 12 through 15. Changing that one specifier in src/auth/ep-plugin/middleware.ts makes the ESM bundle resolve natively — verified by importing the real bundle from Node with no post-processing — so /server ships dual ESM+CJS through plain build.mjs.

The hand-written dist/server.d.ts, a 15-line re-export list carrying a comment that it must be kept in lockstep with src/server.ts by hand. api-extractor rolls up 992 lines / 80 exported symbols for that entry. It runs clean on both entry points as-is — 0 errors, no .pnpm paths leaking into emitted declarations. api/index.api.md and api/server.api.md get checked in.

The postpublish hook the other packages carry is not adopted: it aws s3 syncs to Plasmic's api-doc-model bucket.

A latent defect the ESM path exposes

src/ep-server-functions/session-context.ts loads async_hooks through eval("require") to hide it from bundlers. Native ESM has no require, so it throws, the catch swallows it, and it falls through to the no-op storage — withEpSession becomes a passthrough and every ep.* function fail-softs to null/[]. Masked today because Next's server build emits CommonJS chunks where require exists. Replaced with process.getBuiltinModule("async_hooks"), which behaves identically in both formats and is not a bare import.

tsconfig

Extends ../../../tsconfig.types.json with include: ["./src"], outDir: "./api/tsc", and three deviations:

  • moduleResolution: "bundler" + module: "esnext" — NodeNext produces six TS1479 on better-auth, which is ESM-only while this package publishes CJS. Conforming means either dynamic-importing the auth plugin, which makes createEpAuth async, or "type": "module", which breaks CJS consumers. emitDeclarationOnly means module never reaches output. The published resolution contract gets guarded properly instead, by publint and @arethetypeswrong/cli in CI — both already in verify:package, neither ever run automatically.
  • target: "ES2020" — the shared config sets no target, so src/utils/common.ts fails TS2802 iterating a Set.
  • noUnusedLocals / noUnusedParameters: false — root eslint already errors on @typescript-eslint/no-unused-vars, with a ^_ escape hatch tsc does not honour for locals. tsconfig.types.json's own comment sanctions disabling these as duplicative.

Three TS2742 on @hey-api/client-fetch are fixed by annotating with Client, which @epcc-sdk/sdks-shopper re-exports — no new dependency, no pin on a transitive.

Sequenced behind a deletion

tsc over include: ["./src"] checks code tsdx never looked at, and finds 21 orphaned source files carrying 61 of the 64 errors: src/api/endpoints/checkout/**, src/api/schemas/**, src/api/utils/**, and four unused barrels — one of them re-exporting a file that has never existed in git history. They still expect the CustomerData/AddressData field names #438 renamed, and they import only each other, so nothing reaches in. src/api/endpoints/checkout-session/** is the live one and stays.

Those come out in a separate PR first, so a regression in either half is bisectable. This PR then lands with the full include and a real guard from day one.

Also in scope

package.json needs types, main, module and both exports import fields normalised to the shapes build.mjs validates by exact string comparison. The tsdx devDependency and the lint script go; start becomes build.mjs --watch plus tsc --watch; the size/analyze scripts go, having never had a size-limit config. engines declares >=22.12, since the CJS bundle requires ESM-only better-auth. Publishes as 0.5.0.

Accepted on: the example app's next build and a browser walk of PLP → PDP → cart → checkout → confirmation; a platform/canvas-packages build and Studio canvas check; and a verdaccio publish installed into a clean app.

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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions