Skip to content

Commit aa10951

Browse files
committed
v0.1.4: Node strict-ESM compat (type: module + .js-extension rewrites)
Published 0.1.3 wouldn't load under Node's strict ESM loader: missing `"type": "module"` made Node parse `dist/*.js` as CommonJS, so consumers got `does not provide an export named 'createPool'` on a plain `import { createPool } from '@perryts/mysql'`. Compounding that, the compiled `dist/*.{js,d.ts}` carried extensionless relative imports (`from './connection'`) that Node strict ESM refuses to resolve. Two surgical fixes: - `"type": "module"` in package.json so dist/*.js is parsed as ESM. - Post-build script (scripts/fix-esm-imports.mjs) walks dist/, rewrites every relative import to `./foo.js`. Source files stay extensionless (24 files / 96 imports preserved); only build output is patched. Verified locally: `node --input-type=module -e "import { createPool } from './dist/index.js'"` resolves all named exports. Bun + Perry paths are unaffected — failure was Node-strict-ESM only.
1 parent fe9c206 commit aa10951

3 files changed

Lines changed: 87 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## v0.1.4
4+
5+
- Make the published package importable under Node's strict ESM loader.
6+
`package.json` was missing `"type": "module"`, so Node parsed
7+
`dist/*.js` as CommonJS and reported `does not provide an export
8+
named 'createPool'` when consumers used `import { createPool } from
9+
'@perryts/mysql'`. Compounding it, the compiled `dist/*.{js,d.ts}`
10+
carried extensionless relative imports (`from './connection'`) that
11+
Node strict ESM refuses to resolve.
12+
- Fix both: declare `"type": "module"` and add a post-build pass
13+
(`scripts/fix-esm-imports.mjs`) that rewrites every relative import
14+
in `dist/` to include an explicit `.js` extension. Source files stay
15+
extensionless — only the compiled artifacts are patched.
16+
- Bun + Perry paths are unaffected; the failure was Node-strict-ESM only.
17+
318
## v0.1.3
419

520
- Fix the Perry path of `transport/net-socket.ts::openSocket` to call

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"name": "@perryts/mysql",
3-
"version": "0.1.3",
3+
"version": "0.1.4",
4+
"type": "module",
45
"description": "Pure-TypeScript MySQL/MariaDB wire-protocol driver. Runs on Node.js and Bun, and compiles to a native binary via Perry (LLVM). Zero native dependencies.",
56
"keywords": [
67
"mysql",
@@ -42,7 +43,7 @@
4243
},
4344
"files": ["src/", "dist/", "README.md", "LICENSE", "CLAUDE.md"],
4445
"scripts": {
45-
"build": "tsc",
46+
"build": "tsc && node scripts/fix-esm-imports.mjs",
4647
"test": "bun test",
4748
"test:unit": "bun test tests/unit",
4849
"test:integration": "bun test tests/integration",

scripts/fix-esm-imports.mjs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Post-tsc pass: rewrite relative imports in dist/*.{js,d.ts} to include
2+
// explicit `.js` extensions.
3+
//
4+
// Why this exists: package.json declares `type: module`, but the
5+
// TypeScript sources author imports as `from './connection'` (no
6+
// extension) because tsconfig uses `moduleResolution: bundler`. Node's
7+
// strict ESM loader (Node 22+) requires the file extension and won't
8+
// resolve `./connection` against `./connection.js` on disk.
9+
//
10+
// Rather than touching all ~24 source files (and re-locking developers
11+
// into adding `.js` to every TS import), patch only the compiled output.
12+
// Source files stay clean; published `.js` is Node-strict-ESM compliant.
13+
14+
import { readdir, readFile, writeFile } from 'node:fs/promises';
15+
import { join, dirname } from 'node:path';
16+
import { fileURLToPath } from 'node:url';
17+
18+
const DIST = join(dirname(fileURLToPath(import.meta.url)), '..', 'dist');
19+
20+
// Walk dist/ recursively, return absolute paths matching the predicate.
21+
async function walk(dir) {
22+
const out = [];
23+
for (const entry of await readdir(dir, { withFileTypes: true })) {
24+
const p = join(dir, entry.name);
25+
if (entry.isDirectory()) out.push(...(await walk(p)));
26+
else out.push(p);
27+
}
28+
return out;
29+
}
30+
31+
// Match: from|import|export ... from '...' / "..." where '...' is a
32+
// relative path (./ or ../) with no extension. We deliberately do NOT
33+
// touch absolute/package imports, nor imports that already end in a
34+
// recognized extension (.js, .json, .css, .cjs, .mjs).
35+
const RE = /((?:\bfrom\s+|\bimport\s*\(\s*|\bexport\s+\{[^}]*\}\s+from\s+|\bimport\s+(?:[\w*${},\s]+?\s+from\s+)?))(['"])(\.\.?\/[^'"\n]+?)\2/g;
36+
37+
function shouldSkip(path) {
38+
return (
39+
path.endsWith('.js') ||
40+
path.endsWith('.cjs') ||
41+
path.endsWith('.mjs') ||
42+
path.endsWith('.json') ||
43+
path.endsWith('.css')
44+
);
45+
}
46+
47+
function rewrite(source) {
48+
let changed = 0;
49+
const out = source.replace(RE, (match, prefix, quote, path) => {
50+
if (shouldSkip(path)) return match;
51+
changed++;
52+
return `${prefix}${quote}${path}.js${quote}`;
53+
});
54+
return { out, changed };
55+
}
56+
57+
let totalFiles = 0;
58+
let totalChanged = 0;
59+
for (const file of await walk(DIST)) {
60+
if (!file.endsWith('.js') && !file.endsWith('.d.ts')) continue;
61+
const src = await readFile(file, 'utf8');
62+
const { out, changed } = rewrite(src);
63+
if (changed > 0) {
64+
await writeFile(file, out);
65+
totalFiles++;
66+
totalChanged += changed;
67+
}
68+
}
69+
console.log(`fix-esm-imports: rewrote ${totalChanged} import(s) across ${totalFiles} file(s)`);

0 commit comments

Comments
 (0)