Skip to content

Commit 1ae896f

Browse files
committed
chore: add typescript config support
1 parent f917110 commit 1ae896f

7 files changed

Lines changed: 127 additions & 57 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ This package provides services that ”build providers” can consume, it provid
7070
If no provider suits your specific needs, you can create a build file in your project folder. A variety of file formats is supported, they need to match any of the following names:
7171

7272
- `package.json` with `buildium` object
73+
- `buildium.config.cts`
74+
- `buildium.config.mts`
75+
- `buildium.config.ts`
7376
- `buildium.config.cjs`
7477
- `buildium.config.js`
7578
- `buildium.config.json`
@@ -79,8 +82,12 @@ If no provider suits your specific needs, you can create a build file in your pr
7982
- `buildium.config.pkl`
8083
- `buildium.config.yaml`
8184
- `buildium.config.yml`
85+
- `.buildium.cts`
86+
- `.buildium.mts`
87+
- `.buildium.ts`
8288
- `.buildium.cjs`
8389
- `.buildium.js`
90+
- `.buildium.js`
8491
- `.buildium.json`
8592
- `.buildium.json5`
8693
- `.buildium.jsonc`

lib/buildium.js

Lines changed: 49 additions & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"cosmiconfig": "^10.0.0",
3333
"cosmiconfig-loader-pkl": "^0.2.0",
3434
"cross-spawn": "^7.0.6",
35+
"jiti": "^2.7.0",
3536
"nucleo-matcher-wasm": "^0.4.2",
3637
"tree-kill": "^1.2.2",
3738
"xregexp": "^5.1.2"

src/atom-build.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
1+
import { cosmiconfig } from 'cosmiconfig';
22
import EventEmitter from 'events';
33
import fs from 'fs';
44
import os from 'os';
@@ -23,16 +23,23 @@ const explorer = cosmiconfig(pkg.name, {
2323
// rare, so there is nothing here for a cache to win.
2424
cache: false,
2525

26+
// Only the extensions whose built-in loader is wrong or missing. cosmiconfig
27+
// merges this map over `defaultLoaders`, so an extension left out here keeps
28+
// its built-in loader rather than losing support — and every entry present is
29+
// a deliberate override. The JavaScript ones displace `loadJs`, which tries a
30+
// dynamic `import()` first; `.mts`/`.cts`/`.ts` displace it too, since it does
31+
// no type stripping at all. See `loaders.ts`.
2632
loaders: {
2733
'.cjs': loaders.javascript,
2834
'.js': loaders.javascript,
35+
'.ts': loaders.typescript,
36+
'.cts': loaders.typescript,
37+
'.mts': loaders.typescript,
2938
'.toml': loaders.toml,
3039
'.json': loaders.jsonc,
3140
'.json5': loaders.json5,
3241
'.jsonc': loaders.jsonc,
3342
'.pkl': loaders.pkl,
34-
'.yaml': defaultLoaders['.yaml'],
35-
'.yml': defaultLoaders['.yml'],
3643
'noExt': loaders.jsonc
3744
}
3845
});

src/loaders.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { parseJSON5, parseJSONC, parseTOML, type JSONCParseError } from 'confbox';
22
import { pklLoader } from 'cosmiconfig-loader-pkl';
3+
import { createJiti } from 'jiti';
34

45
/** Matches cosmiconfig's `Loader` signature, which may also be synchronous. */
56
type Loader = (filePath: string, content: string) => object | null | Promise<object | null>;
@@ -38,6 +39,18 @@ function rethrow(format: string, filePath: string, error: unknown): never {
3839
throw error;
3940
}
4041

42+
/**
43+
* Unwraps `export default`. A transpiled ES module arrives as `{ default: … }`,
44+
* while `module.exports =` and `export =` arrive as the target itself — and a
45+
* build file has no legitimate `default` key of its own to confuse this.
46+
*
47+
* jiti's `interopDefault` does not cover this: it leaves the wrapper in place
48+
* for the modules it hands back here.
49+
*/
50+
function unwrapDefault(module: Record<string, unknown> | null): object | null {
51+
return (module && 'default' in module ? module.default : module) as object | null;
52+
}
53+
4154
/** Whether a failed `require` was really an ES module being fed to a CommonJS loader. */
4255
function isEsmFailure(error: unknown): boolean {
4356
if ((error as NodeJS.ErrnoException | null)?.code === 'ERR_REQUIRE_ESM') {
@@ -60,7 +73,15 @@ function describe(error: JSONCParseError, content: string): string {
6073
return `${parseErrorNames[error.error] ?? '<unknown ParseErrorCode>'} at line ${line}, column ${column}`;
6174
}
6275

63-
const loaders: Record<'javascript' | 'json5' | 'jsonc' | 'pkl' | 'toml', Loader> = {
76+
/**
77+
* Both caches are off for the reason the `javascript` loader clears
78+
* `require.cache`: jiti memoises by resolved path in memory and writes
79+
* transpiled output to disk, either of which would keep serving an edited build
80+
* file's previous contents — and a refresh is precisely when it must be re-read.
81+
*/
82+
const jiti = createJiti('', { interopDefault: true, moduleCache: false, fsCache: false });
83+
84+
const loaders: Record<'javascript' | 'json5' | 'jsonc' | 'pkl' | 'toml' | 'typescript', Loader> = {
6485
// Must be `require`, and must not be `import()`.
6586
//
6687
// Package code runs in Pulsar's *renderer* process, where a dynamic `import()`
@@ -82,9 +103,7 @@ const loaders: Record<'javascript' | 'json5' | 'jsonc' | 'pkl' | 'toml', Loader>
82103
}
83104

84105
try {
85-
const module = require(filePath) as Record<string, unknown> | null;
86-
87-
return (module && 'default' in module ? module.default : module) as object | null;
106+
return unwrapDefault(require(filePath) as Record<string, unknown> | null);
88107
} catch (error) {
89108
// Atom's module loader compiles every file as CommonJS regardless of the
90109
// nearest `package.json`, so an ESM build file fails to *parse* rather
@@ -145,6 +164,26 @@ const loaders: Record<'javascript' | 'json5' | 'jsonc' | 'pkl' | 'toml', Loader>
145164
} catch (error) {
146165
rethrow('TOML', filePath, error);
147166
}
167+
},
168+
169+
// TypeScript, and any build file written as an ES module. Neither `require`
170+
// nor cosmiconfig's `loadJs` can read these: `loadJs` does no type stripping
171+
// at all, so it falls back to `require`, which reads the source as CommonJS
172+
// JavaScript and reports `Unexpected token 'export'`.
173+
//
174+
// jiti transpiles and evaluates in-process, which also means the dynamic
175+
// `import()` described on the `javascript` loader is never reached. That is
176+
// why this is the synchronous `jiti()` call and not `jiti.import()` — the
177+
// latter can hand the file to a native `import()`, and taking the renderer
178+
// down is not an error anyone gets to see. jiti marks the synchronous call
179+
// deprecated in favour of `jiti.import()` "for better compatibility"; here
180+
// that trade runs the wrong way, so the deprecation is accepted knowingly.
181+
typescript(filePath: string) {
182+
try {
183+
return unwrapDefault(jiti(filePath) as Record<string, unknown> | null);
184+
} catch (error) {
185+
rethrow('TypeScript', filePath, error);
186+
}
148187
}
149188
};
150189

src/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ function getVersion(): string {
131131
*/
132132
const buildFileNames = [
133133
'package.json',
134+
'buildium.config.cts',
135+
'buildium.config.mts',
136+
'buildium.config.ts',
134137
'buildium.config.cjs',
135138
'buildium.config.js',
136139
'buildium.config.json',
@@ -140,6 +143,9 @@ const buildFileNames = [
140143
'buildium.config.pkl',
141144
'buildium.config.yaml',
142145
'buildium.config.yml',
146+
'.buildium.cts',
147+
'.buildium.mts',
148+
'.buildium.ts',
143149
'.buildium.cjs',
144150
'.buildium.js',
145151
'.buildium.json',

0 commit comments

Comments
 (0)