build: replace Babel with type-stripping (tsc) and modernize toolchain#1740
build: replace Babel with type-stripping (tsc) and modernize toolchain#1740arthurschreiber wants to merge 3 commits into
Conversation
Code Review: Replace Babel with type-stripping (tsc) and modernize toolchainThis is a well-motivated, coherent change. Removing Babel simplifies the dependency tree significantly (~4331 deletions) and aligns the build with how the TypeScript compiler itself understands the code. Overall the approach is sound. A few items worth discussing: Correctness / Potential Issues1. PR description says The description states: "Run tests through 2. "target": "esnext",
"module": "commonjs"The in-file comment says "Strip types only - keep the modern syntax as-is", but With Node ≥ 22 as the new floor this is perfectly fine in practice, since Node 22 ships V8 with full ESNext support. 3.
export type SQLServerEncryptionType = typeof SQLServerEncryptionType[keyof typeof SQLServerEncryptionType];
4.
Breaking Changes5. Dropping Node 18 and 20 support
6. Node 26 in CI matrix Node 26 was released in April 2025 but is not yet LTS (scheduled October 2025). Testing against it is forward-looking and good practice, but it may introduce unexpected CI flakiness from unstable APIs. Consider marking it Positive Highlights
Minor Nits
Summary: The change is technically solid and the direction is clearly correct — Babel was doing nearly nothing useful here. The main items to address before merge are: (1) updating the PR description to say |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1740 +/- ##
===========================================
+ Coverage 79.98% 90.21% +10.23%
===========================================
Files 90 93 +3
Lines 4901 15995 +11094
Branches 923 1546 +623
===========================================
+ Hits 3920 14430 +10510
- Misses 678 1552 +874
+ Partials 303 13 -290 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
02b63cd to
664d2ae
Compare
Babel was doing little beyond stripping types: the `@babel/preset-env` downleveling is effectively a no-op at the Node target, and the only runtime-emitting TypeScript feature in the codebase was a handful of `enum`s. This switches the build to a pure type-stripping approach. - Convert the four `enum`s in always-encrypted/types.ts to `as const` objects (plus type aliases where the name is used as a type), so the source no longer relies on TypeScript-only runtime syntax. - Build JavaScript with `tsc` via the new tsconfig.build.json (CommonJS output + source maps). tsc emits plain export assignments, preserving the existing CommonJS interop relied on by the mixed `module.exports`/ESM export files. Declarations are still emitted via tsconfig.build-types.json. - Run tests through `ts-node/register` (transpile-only, CommonJS) instead of `@babel/register`; ts-node matches tsc's emit semantics, which the mixed module pattern requires. - Collect coverage with `c8` (V8 native) instead of `nyc` + `babel-plugin-istanbul`. - Allow the `const` + `type` declaration-merge pattern via `@typescript-eslint/no-redeclare`. - Raise the Node floor to the oldest supported release line (>=22) and update CI to test on Node 22/24/26. https://claude.ai/code/session_019NDt99jpfPSgh4VPnTW3kK
664d2ae to
9df896c
Compare
Code Review: Replace Babel with type-stripping (tsc) and modernize toolchainThis is a well-motivated toolchain cleanup. The core insight is sound. Babel was adding complexity (50+ transitive packages) to solve a problem that did not really exist at modern Node targets. OverviewThe PR replaces Babel + nyc with a lighter stack:
The direction is correct and the execution is mostly clean. Concerns1. PR description says ts-node, but the implementation uses tsx The PR body says "Run tests through ts-node/register (transpile-only, CommonJS)" but the actual implementation adds tsx as the dev dependency and uses node-option: import=tsx in the mocha config. tsx uses esbuild under the hood, not the TypeScript compiler, while ts-node uses the actual TS compiler API. The PR description should be corrected. For this codebase (no decorators, no complex emit features), tsx is a perfectly fine choice -- faster and lower-overhead than ts-node. But it does mean tests compile via esbuild while the published build uses tsc, which is a subtle semantic gap. This is the same trade-off the project already accepted with Babel, so not a regression. 2. Node 22 is dropped from CI despite engines.node >=22 All CI jobs now run Node 24, and the Azure-specific jobs are renamed to reflect this. But the engines field still advertises Node 22 support. Users on Node 22 will not have any CI coverage. Either keep a Node 22 job in the matrix to validate the stated floor, or bump engines.node to >=24 if Node 22 is being intentionally dropped. The PR description says "update CI to test on Node 22/24/26" but the diff only shows changes to 24 for the single-version jobs. If there is a matrix that still includes 22, that resolves this concern. 3. module.exports removal is a behavioral change for CJS consumers of internal paths Files like src/bulk-load.ts previously had both export default BulkLoad and module.exports = BulkLoad. With Babel, the module.exports override meant require('./lib/bulk-load') returned BulkLoad directly. After this PR, that returns { default: BulkLoad, __esModule: true }. If any consumer does deep-requires into lib/ paths (unlikely but possible), they would break silently. The public API (lib/tedious.js) is fine, and the removal is correct for a tsc build -- just worth noting in the PR description as a technically-breaking change for non-standard usage. Positive observationsEnum-to-const conversion is well-executed. The explicit ordinal values in the resulting objects (e.g., KeyOrdinal: 0, DbId: 1) are actually more readable than implicit TypeScript enum numbering. The as const + type alias pattern correctly preserves type semantics for SQLServerEncryptionType and SQLServerStatementColumnEncryptionSetting. Not adding type aliases for DescribeParameterEncryptionResultSet1/2 is appropriate given they are only used as value lookups. tsconfig.build.json is correctly structured. Disabling verbatimModuleSyntax for the CommonJS build (while keeping it on in the base tsconfig.json for type-checking) is exactly the right approach. target: esnext + module: commonjs means tsc strips types but preserves modern syntax, which is correct for Node 22+. c8 is a meaningful upgrade. V8-native coverage does not require Babel instrumentation middleware, gives more accurate branch/line counts, and works correctly with TypeScript source maps. The exclusion of test and lib in the c8 config is appropriate. Dependency footprint reduction is significant. Removing six Babel packages plus nyc and babel-plugin-istanbul eliminates hundreds of transitive dependencies. The package-lock.json shrinks by roughly 5,400 lines. Minor nits
SummaryThis is a solid modernization PR with a well-reasoned rationale. The main actionable items are:
|
Code Review: Replace Babel with type-stripping (tsc) and modernize toolchainThis is a well-scoped toolchain modernization. The direction is correct and the implementation is clean. A few items worth discussing before merging. OverviewThis PR replaces Babel with native Potential Issues1. Breaking CJS deep-require interop (high priority) Every source file previously had the pattern: export default Connection;
module.exports = Connection; // ← removed by this PRWith Babel, the trailing Internal imports are fine — tsc inserts 2. Node >=22 is a breaking change Dropping support for Node 18 and 20 is a legitimate direction, but it is a semver-major breaking change. The PR has no version bump or changelog entry, and the CI matrix silently removed older versions. If this is intentional, it should be called out in the release notes and the commit should be tagged accordingly. If the intent is to ship this as a patch/minor on an existing major, the Node floor change needs its own major bump first. 3.
Minor Notes
Positives
SummaryThe build architecture change and enum migration are solid. The two items I would resolve before merging: confirm the deep-require CJS interop story (or explicitly document it as unsupported), and make sure the Node 22 floor is shipped as a major-version bump (or that it is intentionally landing on a new major already in flight). |
Every module already declares its exports with ESM `export` syntax. The
parallel `module.exports = X` / `module.exports.x = x` assignments existed
only to make the Babel-compiled CommonJS output expose a bare value from a
deep `require()`. They are no longer needed: tsc emits standard CommonJS
interop (`exports.default` / named `exports.x`), and all internal imports
are ESM default/named imports that resolve through that interop.
This leaves the source as clean ESM, which is a prerequisite for emitting
a dual ESM/CommonJS package later. The public API (the package's main
entry) is unchanged - it has always used named exports. Only undocumented
deep imports into `lib/*` change shape (e.g. `require('tedious/lib/...')`
now returns `{ default: X }` instead of `X`).
https://claude.ai/code/session_019NDt99jpfPSgh4VPnTW3kK
`ts-node` was only chosen over `tsx` because the previous mixed `module.exports`/ESM export pattern required a loader with tsc's plain-assignment emit; esbuild-based loaders emit read-only getter exports, which collided with the `module.exports.x = x` assignments. Now that those assignments are gone and the source is clean ESM, `tsx` works directly. It is faster and needs no compiler configuration, so the dedicated `ts-node` block in tsconfig.json is removed as well. https://claude.ai/code/session_019NDt99jpfPSgh4VPnTW3kK
9df896c to
9f648b2
Compare
Code Review: Replace Babel with type-stripping (tsc) and modernize toolchainThis is a clean and well-motivated toolchain simplification. Removing Babel eliminates ~5 layers of transformation (preset-env, preset-typescript, register hook, babel-plugin-istanbul) in favor of tools that are closer to the metal. Here are my observations: What's Well Done
Issues / Things to Verify1. Missing type aliases for two enums
2. Public API break for direct internal imports Removing const Foo = require('tedious/lib/some-module'); // was X, now { default: X }The benchmarks were correctly patched, but this affects any consumers using internal paths directly. Worth a callout in the changelog/release notes even if this is technically unsupported usage. 3. Two-pass tsc build The build runs 4.
5. Node >=22 floor is a breaking change Raising the 6. Node 26 in the CI matrix The matrix includes Minor Observations
SummarySolid modernization PR. The main things to double-check before merging are (1) the missing type aliases for the two |
Babel was doing little beyond stripping types: the
@babel/preset-envdownleveling is effectively a no-op at the Node target, and the only
runtime-emitting TypeScript feature in the codebase was a handful of
enums. This switches the build to a pure type-stripping approach.enums in always-encrypted/types.ts toas constobjects (plus type aliases where the name is used as a type), so the
source no longer relies on TypeScript-only runtime syntax.
tscvia the new tsconfig.build.json (CommonJSoutput + source maps). Declarations are still emitted via
tsconfig.build-types.json.
module.exportsre-exports, leaving thesource as clean ESM.
tsxas the Mocha loader instead of@babel/register.tsxworks directly now that the mixedmodule.exports/ESM pattern is gone, and it needs no compilerconfiguration.
c8(V8 native) instead ofnyc+babel-plugin-istanbul.const+typedeclaration-merge pattern via@typescript-eslint/no-redeclare.update CI to test on Node 22/24/26.
https://claude.ai/code/session_019NDt99jpfPSgh4VPnTW3kK