@rsaf/bundler 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +11 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/dist/bundler/Bundler.d.ts +60 -0
- package/dist/bundler/Bundler.js +150 -0
- package/dist/cache/cache-store.d.ts +64 -0
- package/dist/cache/cache-store.js +83 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/types/config.d.ts +58 -0
- package/dist/types/config.js +1 -0
- package/eslint.config.js +36 -0
- package/package.json +37 -0
- package/src/bundler/Bundler.ts +167 -0
- package/src/cache/cache-store.ts +89 -0
- package/src/index.ts +4 -0
- package/src/types/config.ts +95 -0
- package/tsconfig.json +27 -0
- package/tsconfig.tsbuildinfo +1 -0
package/.prettierrc
ADDED
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Riju
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import esbuild from 'esbuild';
|
|
2
|
+
import type { BuildResult } from 'esbuild';
|
|
3
|
+
import type { ESBuildConfig } from '../types/config.js';
|
|
4
|
+
/**
|
|
5
|
+
* EsbuildCompiler
|
|
6
|
+
*
|
|
7
|
+
* Thin wrapper around esbuild that provides:
|
|
8
|
+
* - build
|
|
9
|
+
* - watch
|
|
10
|
+
* - rebuild
|
|
11
|
+
* - close
|
|
12
|
+
*
|
|
13
|
+
* It uses esbuild's `context()` + `watch()` APIs under the hood.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Bundler {
|
|
16
|
+
private options;
|
|
17
|
+
private context?;
|
|
18
|
+
private lastResult?;
|
|
19
|
+
private isWatching;
|
|
20
|
+
constructor(options: ESBuildConfig);
|
|
21
|
+
/**
|
|
22
|
+
* Adds an esbuild plugin to the compiler configuration.
|
|
23
|
+
*/
|
|
24
|
+
addPlugin(plugin: esbuild.Plugin): this;
|
|
25
|
+
/**
|
|
26
|
+
* Performs an initial build or rebuild depending on mode.
|
|
27
|
+
*/
|
|
28
|
+
build(): Promise<BuildResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Enables esbuild's internal watch mode.
|
|
31
|
+
*
|
|
32
|
+
* Note:
|
|
33
|
+
* If you're using chokidar for file watching,
|
|
34
|
+
* esbuild's onRebuild callback is NOT used anymore.
|
|
35
|
+
*
|
|
36
|
+
* Esbuild will still keep its rebuild context alive,
|
|
37
|
+
* but file watching is controlled externally.
|
|
38
|
+
*/
|
|
39
|
+
watch(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Manually trigger a rebuild.
|
|
42
|
+
*
|
|
43
|
+
* This is what chokidar will call when a file changes.
|
|
44
|
+
*/
|
|
45
|
+
rebuild(): Promise<BuildResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Dispose of resources.
|
|
48
|
+
*
|
|
49
|
+
* Releases esbuild’s internal rebuild/watcher context.
|
|
50
|
+
*/
|
|
51
|
+
dispose(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Get last esbuild result.
|
|
54
|
+
*/
|
|
55
|
+
getLastResult(): BuildResult | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the compiler is currently in watch mode.
|
|
58
|
+
*/
|
|
59
|
+
isInWatchMode(): boolean;
|
|
60
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// compiler.ts
|
|
2
|
+
import { AppError } from '@rsaf/core';
|
|
3
|
+
import esbuild from 'esbuild';
|
|
4
|
+
/**
|
|
5
|
+
* EsbuildCompiler
|
|
6
|
+
*
|
|
7
|
+
* Thin wrapper around esbuild that provides:
|
|
8
|
+
* - build
|
|
9
|
+
* - watch
|
|
10
|
+
* - rebuild
|
|
11
|
+
* - close
|
|
12
|
+
*
|
|
13
|
+
* It uses esbuild's `context()` + `watch()` APIs under the hood.
|
|
14
|
+
*/
|
|
15
|
+
export class Bundler {
|
|
16
|
+
options;
|
|
17
|
+
context;
|
|
18
|
+
lastResult;
|
|
19
|
+
isWatching = false;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adds an esbuild plugin to the compiler configuration.
|
|
25
|
+
*/
|
|
26
|
+
addPlugin(plugin) {
|
|
27
|
+
if (!this.options.plugins) {
|
|
28
|
+
this.options.plugins = [];
|
|
29
|
+
}
|
|
30
|
+
this.options.plugins.push(plugin);
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Performs an initial build or rebuild depending on mode.
|
|
35
|
+
*/
|
|
36
|
+
async build() {
|
|
37
|
+
try {
|
|
38
|
+
if (this.isWatching && this.context) {
|
|
39
|
+
// In watch mode, we can use rebuild
|
|
40
|
+
this.lastResult = await this.context.rebuild();
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// Initial build
|
|
44
|
+
this.lastResult = await esbuild.build(this.options);
|
|
45
|
+
}
|
|
46
|
+
return this.lastResult;
|
|
47
|
+
}
|
|
48
|
+
catch (error // eslint-disable-line
|
|
49
|
+
) {
|
|
50
|
+
throw new AppError('Looks like there is some errors while building', {
|
|
51
|
+
code: 'BUILD_FAILED',
|
|
52
|
+
category: 'build',
|
|
53
|
+
cause: error,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Enables esbuild's internal watch mode.
|
|
59
|
+
*
|
|
60
|
+
* Note:
|
|
61
|
+
* If you're using chokidar for file watching,
|
|
62
|
+
* esbuild's onRebuild callback is NOT used anymore.
|
|
63
|
+
*
|
|
64
|
+
* Esbuild will still keep its rebuild context alive,
|
|
65
|
+
* but file watching is controlled externally.
|
|
66
|
+
*/
|
|
67
|
+
async watch() {
|
|
68
|
+
if (this.isWatching)
|
|
69
|
+
return;
|
|
70
|
+
this.isWatching = true;
|
|
71
|
+
// If an old context exists, dispose it
|
|
72
|
+
if (this.context) {
|
|
73
|
+
await this.dispose();
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
// Create a new esbuild build context
|
|
77
|
+
this.context = await esbuild.context(this.options);
|
|
78
|
+
// Start esbuild's watch mode with no onRebuild handlers
|
|
79
|
+
await this.context.watch();
|
|
80
|
+
}
|
|
81
|
+
catch (error // eslint-disable-line
|
|
82
|
+
) {
|
|
83
|
+
throw new AppError('Looks like there is some errors while building', {
|
|
84
|
+
code: 'BUILD_FAILED',
|
|
85
|
+
category: 'build',
|
|
86
|
+
cause: error,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Manually trigger a rebuild.
|
|
92
|
+
*
|
|
93
|
+
* This is what chokidar will call when a file changes.
|
|
94
|
+
*/
|
|
95
|
+
async rebuild() {
|
|
96
|
+
if (!this.context) {
|
|
97
|
+
throw new AppError('You must call "watch()" before usinf "rebuild()', {
|
|
98
|
+
code: 'BUILD_FAILED',
|
|
99
|
+
category: 'build',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
this.lastResult = await this.context.rebuild();
|
|
104
|
+
return this.lastResult;
|
|
105
|
+
}
|
|
106
|
+
catch (error //eslint-disable-line
|
|
107
|
+
) {
|
|
108
|
+
throw new AppError('Looks like there is some errors while rebuilding', {
|
|
109
|
+
code: 'BUILD_FAILED',
|
|
110
|
+
category: 'build',
|
|
111
|
+
cause: error,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Dispose of resources.
|
|
117
|
+
*
|
|
118
|
+
* Releases esbuild’s internal rebuild/watcher context.
|
|
119
|
+
*/
|
|
120
|
+
async dispose() {
|
|
121
|
+
if (!this.context)
|
|
122
|
+
return;
|
|
123
|
+
try {
|
|
124
|
+
await this.context.dispose();
|
|
125
|
+
}
|
|
126
|
+
catch (error //eslint-disable-line
|
|
127
|
+
) {
|
|
128
|
+
throw new AppError('Failed to stop. Try again', {
|
|
129
|
+
code: 'BUILD_FAILED',
|
|
130
|
+
category: 'build',
|
|
131
|
+
cause: error,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
this.context = undefined;
|
|
135
|
+
this.lastResult = undefined;
|
|
136
|
+
this.isWatching = false;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get last esbuild result.
|
|
140
|
+
*/
|
|
141
|
+
getLastResult() {
|
|
142
|
+
return this.lastResult;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Whether the compiler is currently in watch mode.
|
|
146
|
+
*/
|
|
147
|
+
isInWatchMode() {
|
|
148
|
+
return this.isWatching;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type-safe cache that stores values with keys matching a given type structure.
|
|
3
|
+
* @template T - The type defining the cache's structure: keys as property names and values as corresponding types.
|
|
4
|
+
*/
|
|
5
|
+
export declare class CacheStore<T extends Record<string, any>> {
|
|
6
|
+
private store;
|
|
7
|
+
/**
|
|
8
|
+
* Retrieves a value from the cache by key.
|
|
9
|
+
* @template K - The specific key type (extends keyof T).
|
|
10
|
+
* @param key - The key to look up in the cache.
|
|
11
|
+
* @returns The value associated with the key, or undefined if not found.
|
|
12
|
+
* @example
|
|
13
|
+
* // Given T = { name: string, age: number }
|
|
14
|
+
* cache.get('name'); // Returns string | undefined
|
|
15
|
+
*/
|
|
16
|
+
get<K extends keyof T>(key: K): T[K] | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Retrieves a value from the cache, throwing an error if the key doesn't exist.
|
|
19
|
+
* Useful when you expect a key to always be present.
|
|
20
|
+
* @template K - The specific key type (extends keyof T).
|
|
21
|
+
* @param key - The key to look up in the cache.
|
|
22
|
+
* @returns The value associated with the key.
|
|
23
|
+
* @throws {Error} If the key is not found in the cache.
|
|
24
|
+
* @example
|
|
25
|
+
* // Given T = { name: string, age: number }
|
|
26
|
+
* cache.require('name'); // Returns string, throws if 'name' not found
|
|
27
|
+
*/
|
|
28
|
+
require<K extends keyof T>(key: K): T[K];
|
|
29
|
+
/**
|
|
30
|
+
* Sets a value in the cache for the given key.
|
|
31
|
+
* @template K - The specific key type (extends keyof T).
|
|
32
|
+
* @param key - The key to associate with the value.
|
|
33
|
+
* @param value - The value to store (must match the type T[K]).
|
|
34
|
+
* @example
|
|
35
|
+
* // Given T = { name: string, age: number }
|
|
36
|
+
* cache.set('name', 'Alice'); // OK
|
|
37
|
+
* cache.set('age', 'thirty'); // Type error: string not assignable to number
|
|
38
|
+
*/
|
|
39
|
+
set<K extends keyof T>(key: K, value: T[K]): void;
|
|
40
|
+
/**
|
|
41
|
+
* Checks if a key exists in the cache.
|
|
42
|
+
* @template K - The specific key type (extends keyof T).
|
|
43
|
+
* @param key - The key to check.
|
|
44
|
+
* @returns True if the key exists in the cache, false otherwise.
|
|
45
|
+
* @example
|
|
46
|
+
* cache.has('name'); // Returns boolean
|
|
47
|
+
*/
|
|
48
|
+
has<K extends keyof T>(key: K): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Removes a key-value pair from the cache.
|
|
51
|
+
* @template K - The specific key type (extends keyof T).
|
|
52
|
+
* @param key - The key to remove.
|
|
53
|
+
* @returns True if the key existed and was removed, false otherwise.
|
|
54
|
+
* @example
|
|
55
|
+
* cache.delete('name'); // Returns boolean
|
|
56
|
+
*/
|
|
57
|
+
delete<K extends keyof T>(key: K): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Removes all key-value pairs from the cache.
|
|
60
|
+
* @example
|
|
61
|
+
* cache.clear(); // Empties the entire cache
|
|
62
|
+
*/
|
|
63
|
+
clear(): void;
|
|
64
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type-safe cache that stores values with keys matching a given type structure.
|
|
3
|
+
* @template T - The type defining the cache's structure: keys as property names and values as corresponding types.
|
|
4
|
+
*/
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
export class CacheStore {
|
|
7
|
+
// Internal storage using Map to maintain key-value pairs
|
|
8
|
+
store = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Retrieves a value from the cache by key.
|
|
11
|
+
* @template K - The specific key type (extends keyof T).
|
|
12
|
+
* @param key - The key to look up in the cache.
|
|
13
|
+
* @returns The value associated with the key, or undefined if not found.
|
|
14
|
+
* @example
|
|
15
|
+
* // Given T = { name: string, age: number }
|
|
16
|
+
* cache.get('name'); // Returns string | undefined
|
|
17
|
+
*/
|
|
18
|
+
get(key) {
|
|
19
|
+
// Type assertion is safe because we only store T[keyof T] values
|
|
20
|
+
return this.store.get(key);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves a value from the cache, throwing an error if the key doesn't exist.
|
|
24
|
+
* Useful when you expect a key to always be present.
|
|
25
|
+
* @template K - The specific key type (extends keyof T).
|
|
26
|
+
* @param key - The key to look up in the cache.
|
|
27
|
+
* @returns The value associated with the key.
|
|
28
|
+
* @throws {Error} If the key is not found in the cache.
|
|
29
|
+
* @example
|
|
30
|
+
* // Given T = { name: string, age: number }
|
|
31
|
+
* cache.require('name'); // Returns string, throws if 'name' not found
|
|
32
|
+
*/
|
|
33
|
+
require(key) {
|
|
34
|
+
const value = this.store.get(key);
|
|
35
|
+
if (value === undefined) {
|
|
36
|
+
throw new Error(`Cache key '${String(key)}' is not initialized`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Sets a value in the cache for the given key.
|
|
42
|
+
* @template K - The specific key type (extends keyof T).
|
|
43
|
+
* @param key - The key to associate with the value.
|
|
44
|
+
* @param value - The value to store (must match the type T[K]).
|
|
45
|
+
* @example
|
|
46
|
+
* // Given T = { name: string, age: number }
|
|
47
|
+
* cache.set('name', 'Alice'); // OK
|
|
48
|
+
* cache.set('age', 'thirty'); // Type error: string not assignable to number
|
|
49
|
+
*/
|
|
50
|
+
set(key, value) {
|
|
51
|
+
this.store.set(key, value);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Checks if a key exists in the cache.
|
|
55
|
+
* @template K - The specific key type (extends keyof T).
|
|
56
|
+
* @param key - The key to check.
|
|
57
|
+
* @returns True if the key exists in the cache, false otherwise.
|
|
58
|
+
* @example
|
|
59
|
+
* cache.has('name'); // Returns boolean
|
|
60
|
+
*/
|
|
61
|
+
has(key) {
|
|
62
|
+
return this.store.has(key);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Removes a key-value pair from the cache.
|
|
66
|
+
* @template K - The specific key type (extends keyof T).
|
|
67
|
+
* @param key - The key to remove.
|
|
68
|
+
* @returns True if the key existed and was removed, false otherwise.
|
|
69
|
+
* @example
|
|
70
|
+
* cache.delete('name'); // Returns boolean
|
|
71
|
+
*/
|
|
72
|
+
delete(key) {
|
|
73
|
+
return this.store.delete(key);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Removes all key-value pairs from the cache.
|
|
77
|
+
* @example
|
|
78
|
+
* cache.clear(); // Empties the entire cache
|
|
79
|
+
*/
|
|
80
|
+
clear() {
|
|
81
|
+
this.store.clear();
|
|
82
|
+
}
|
|
83
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Loader, Plugin } from 'esbuild';
|
|
2
|
+
export interface ESBuildBaseConfig {
|
|
3
|
+
minify: boolean;
|
|
4
|
+
metafile: true;
|
|
5
|
+
color: true;
|
|
6
|
+
logLevel: 'silent';
|
|
7
|
+
treeShaking: true;
|
|
8
|
+
minifyWhitespace: boolean;
|
|
9
|
+
minifyIdentifiers: boolean;
|
|
10
|
+
minifySyntax: boolean;
|
|
11
|
+
splitting: false;
|
|
12
|
+
format: 'esm';
|
|
13
|
+
sourcemap: boolean | 'inline' | 'linked';
|
|
14
|
+
absWorkingDir: string;
|
|
15
|
+
outdir?: string;
|
|
16
|
+
outfile?: string;
|
|
17
|
+
entryPoints: string[] | Record<string, string>;
|
|
18
|
+
write?: boolean;
|
|
19
|
+
plugins: Plugin[];
|
|
20
|
+
}
|
|
21
|
+
export type LoaderFiles = Record<string, Loader>;
|
|
22
|
+
export interface ESBuildClientConfig {
|
|
23
|
+
platform: 'browser';
|
|
24
|
+
target: ['es2022'];
|
|
25
|
+
loader: LoaderFiles;
|
|
26
|
+
bundle: true;
|
|
27
|
+
splitting: true;
|
|
28
|
+
}
|
|
29
|
+
export interface ESBuildServerConfig {
|
|
30
|
+
platform: 'node';
|
|
31
|
+
target: ['node18'];
|
|
32
|
+
loader: LoaderFiles;
|
|
33
|
+
packages: 'external';
|
|
34
|
+
external: string[];
|
|
35
|
+
bundle: true;
|
|
36
|
+
splitting: false;
|
|
37
|
+
}
|
|
38
|
+
export interface ESBuildDevConfig {
|
|
39
|
+
minify: false;
|
|
40
|
+
minifyWhitespace: false;
|
|
41
|
+
minifyIdentifiers: false;
|
|
42
|
+
minifySyntax: false;
|
|
43
|
+
sourcemap: 'inline' | true;
|
|
44
|
+
write: false;
|
|
45
|
+
}
|
|
46
|
+
export interface ESBuildProdConfig {
|
|
47
|
+
minify: true;
|
|
48
|
+
minifyWhitespace: true;
|
|
49
|
+
minifyIdentifiers: true;
|
|
50
|
+
minifySyntax: true;
|
|
51
|
+
sourcemap: 'linked' | false;
|
|
52
|
+
write: true;
|
|
53
|
+
}
|
|
54
|
+
export type ESBuildClientDevConfig = ESBuildBaseConfig & ESBuildClientConfig & ESBuildDevConfig;
|
|
55
|
+
export type ESBuildClientProdConfig = ESBuildBaseConfig & ESBuildClientConfig & ESBuildProdConfig;
|
|
56
|
+
export type ESBuildServerDevConfig = ESBuildBaseConfig & ESBuildServerConfig & ESBuildDevConfig;
|
|
57
|
+
export type ESBuildServerProdConfig = ESBuildBaseConfig & ESBuildServerConfig & ESBuildProdConfig;
|
|
58
|
+
export type ESBuildConfig = ESBuildClientDevConfig | ESBuildClientProdConfig | ESBuildServerDevConfig | ESBuildServerProdConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import baseConfig from '../../eslint.config.js';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
// Project-specific overrides
|
|
7
|
+
files: ['**/*.ts'],
|
|
8
|
+
rules: {
|
|
9
|
+
// Add these common useful rules
|
|
10
|
+
semi: ['error', 'always'],
|
|
11
|
+
'prefer-const': 'error',
|
|
12
|
+
eqeqeq: ['error', 'always'],
|
|
13
|
+
|
|
14
|
+
// TypeScript specific
|
|
15
|
+
'@typescript-eslint/naming-convention': [
|
|
16
|
+
'error',
|
|
17
|
+
{
|
|
18
|
+
selector: 'variable',
|
|
19
|
+
format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
selector: 'typeLike',
|
|
23
|
+
format: ['PascalCase'],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
// Test-specific configuration
|
|
30
|
+
files: ['**/*.test.ts', '**/*.spec.ts'],
|
|
31
|
+
rules: {
|
|
32
|
+
'no-console': 'off',
|
|
33
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rsaf/bundler",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Bundler package for rsaf",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "Riju Mondal",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@rsaf/core": "^0.0.3",
|
|
18
|
+
"esbuild": "^0.27.2"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"lint": "eslint src --max-warnings=0",
|
|
25
|
+
"lint:fix": "eslint src --fix",
|
|
26
|
+
"type-check": "tsc --noEmit",
|
|
27
|
+
"type-check:watch": "tsc --noEmit --watch",
|
|
28
|
+
"build": "pnpm clean && tsc -b",
|
|
29
|
+
"dev": "tsc -b --watch",
|
|
30
|
+
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
31
|
+
"format": "prettier . --write",
|
|
32
|
+
"format:check": "prettier . --check",
|
|
33
|
+
"test": "vitest",
|
|
34
|
+
"test:run": "vitest run",
|
|
35
|
+
"test:coverage": "vitest run --coverage"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// compiler.ts
|
|
2
|
+
import { AppError } from '@rsaf/core';
|
|
3
|
+
import esbuild from 'esbuild';
|
|
4
|
+
import type { BuildResult } from 'esbuild';
|
|
5
|
+
|
|
6
|
+
import type { ESBuildConfig } from '../types/config.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* EsbuildCompiler
|
|
10
|
+
*
|
|
11
|
+
* Thin wrapper around esbuild that provides:
|
|
12
|
+
* - build
|
|
13
|
+
* - watch
|
|
14
|
+
* - rebuild
|
|
15
|
+
* - close
|
|
16
|
+
*
|
|
17
|
+
* It uses esbuild's `context()` + `watch()` APIs under the hood.
|
|
18
|
+
*/
|
|
19
|
+
export class Bundler {
|
|
20
|
+
private options: ESBuildConfig;
|
|
21
|
+
private context?: esbuild.BuildContext;
|
|
22
|
+
private lastResult?: BuildResult;
|
|
23
|
+
private isWatching = false;
|
|
24
|
+
|
|
25
|
+
constructor(options: ESBuildConfig) {
|
|
26
|
+
this.options = options;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Adds an esbuild plugin to the compiler configuration.
|
|
31
|
+
*/
|
|
32
|
+
addPlugin(plugin: esbuild.Plugin): this {
|
|
33
|
+
if (!this.options.plugins) {
|
|
34
|
+
this.options.plugins = [];
|
|
35
|
+
}
|
|
36
|
+
this.options.plugins.push(plugin);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Performs an initial build or rebuild depending on mode.
|
|
42
|
+
*/
|
|
43
|
+
async build(): Promise<BuildResult> {
|
|
44
|
+
try {
|
|
45
|
+
if (this.isWatching && this.context) {
|
|
46
|
+
// In watch mode, we can use rebuild
|
|
47
|
+
this.lastResult = await this.context.rebuild();
|
|
48
|
+
} else {
|
|
49
|
+
// Initial build
|
|
50
|
+
this.lastResult = await esbuild.build(this.options as esbuild.BuildOptions);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return this.lastResult;
|
|
54
|
+
} catch (
|
|
55
|
+
error: any // eslint-disable-line
|
|
56
|
+
) {
|
|
57
|
+
throw new AppError('Looks like there is some errors while building', {
|
|
58
|
+
code: 'BUILD_FAILED',
|
|
59
|
+
category: 'build',
|
|
60
|
+
cause: error,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Enables esbuild's internal watch mode.
|
|
67
|
+
*
|
|
68
|
+
* Note:
|
|
69
|
+
* If you're using chokidar for file watching,
|
|
70
|
+
* esbuild's onRebuild callback is NOT used anymore.
|
|
71
|
+
*
|
|
72
|
+
* Esbuild will still keep its rebuild context alive,
|
|
73
|
+
* but file watching is controlled externally.
|
|
74
|
+
*/
|
|
75
|
+
async watch(): Promise<void> {
|
|
76
|
+
if (this.isWatching) return;
|
|
77
|
+
|
|
78
|
+
this.isWatching = true;
|
|
79
|
+
|
|
80
|
+
// If an old context exists, dispose it
|
|
81
|
+
if (this.context) {
|
|
82
|
+
await this.dispose();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Create a new esbuild build context
|
|
87
|
+
this.context = await esbuild.context(this.options as esbuild.BuildOptions);
|
|
88
|
+
|
|
89
|
+
// Start esbuild's watch mode with no onRebuild handlers
|
|
90
|
+
await this.context.watch();
|
|
91
|
+
} catch (
|
|
92
|
+
error: any // eslint-disable-line
|
|
93
|
+
) {
|
|
94
|
+
throw new AppError('Looks like there is some errors while building', {
|
|
95
|
+
code: 'BUILD_FAILED',
|
|
96
|
+
category: 'build',
|
|
97
|
+
cause: error,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Manually trigger a rebuild.
|
|
104
|
+
*
|
|
105
|
+
* This is what chokidar will call when a file changes.
|
|
106
|
+
*/
|
|
107
|
+
async rebuild(): Promise<BuildResult> {
|
|
108
|
+
if (!this.context) {
|
|
109
|
+
throw new AppError('You must call "watch()" before usinf "rebuild()', {
|
|
110
|
+
code: 'BUILD_FAILED',
|
|
111
|
+
category: 'build',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
this.lastResult = await this.context.rebuild();
|
|
117
|
+
return this.lastResult;
|
|
118
|
+
} catch (
|
|
119
|
+
error: any //eslint-disable-line
|
|
120
|
+
) {
|
|
121
|
+
throw new AppError('Looks like there is some errors while rebuilding', {
|
|
122
|
+
code: 'BUILD_FAILED',
|
|
123
|
+
category: 'build',
|
|
124
|
+
cause: error,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Dispose of resources.
|
|
131
|
+
*
|
|
132
|
+
* Releases esbuild’s internal rebuild/watcher context.
|
|
133
|
+
*/
|
|
134
|
+
async dispose(): Promise<void> {
|
|
135
|
+
if (!this.context) return;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await this.context.dispose();
|
|
139
|
+
} catch (
|
|
140
|
+
error: any //eslint-disable-line
|
|
141
|
+
) {
|
|
142
|
+
throw new AppError('Failed to stop. Try again', {
|
|
143
|
+
code: 'BUILD_FAILED',
|
|
144
|
+
category: 'build',
|
|
145
|
+
cause: error,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.context = undefined;
|
|
150
|
+
this.lastResult = undefined;
|
|
151
|
+
this.isWatching = false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get last esbuild result.
|
|
156
|
+
*/
|
|
157
|
+
getLastResult(): BuildResult | undefined {
|
|
158
|
+
return this.lastResult;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Whether the compiler is currently in watch mode.
|
|
163
|
+
*/
|
|
164
|
+
isInWatchMode(): boolean {
|
|
165
|
+
return this.isWatching;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type-safe cache that stores values with keys matching a given type structure.
|
|
3
|
+
* @template T - The type defining the cache's structure: keys as property names and values as corresponding types.
|
|
4
|
+
*/
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
export class CacheStore<T extends Record<string, any>> {
|
|
7
|
+
// Internal storage using Map to maintain key-value pairs
|
|
8
|
+
private store = new Map<keyof T, T[keyof T]>();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves a value from the cache by key.
|
|
12
|
+
* @template K - The specific key type (extends keyof T).
|
|
13
|
+
* @param key - The key to look up in the cache.
|
|
14
|
+
* @returns The value associated with the key, or undefined if not found.
|
|
15
|
+
* @example
|
|
16
|
+
* // Given T = { name: string, age: number }
|
|
17
|
+
* cache.get('name'); // Returns string | undefined
|
|
18
|
+
*/
|
|
19
|
+
get<K extends keyof T>(key: K): T[K] | undefined {
|
|
20
|
+
// Type assertion is safe because we only store T[keyof T] values
|
|
21
|
+
return this.store.get(key) as T[K] | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Retrieves a value from the cache, throwing an error if the key doesn't exist.
|
|
26
|
+
* Useful when you expect a key to always be present.
|
|
27
|
+
* @template K - The specific key type (extends keyof T).
|
|
28
|
+
* @param key - The key to look up in the cache.
|
|
29
|
+
* @returns The value associated with the key.
|
|
30
|
+
* @throws {Error} If the key is not found in the cache.
|
|
31
|
+
* @example
|
|
32
|
+
* // Given T = { name: string, age: number }
|
|
33
|
+
* cache.require('name'); // Returns string, throws if 'name' not found
|
|
34
|
+
*/
|
|
35
|
+
require<K extends keyof T>(key: K): T[K] {
|
|
36
|
+
const value = this.store.get(key);
|
|
37
|
+
if (value === undefined) {
|
|
38
|
+
throw new Error(`Cache key '${String(key)}' is not initialized`);
|
|
39
|
+
}
|
|
40
|
+
return value as T[K];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets a value in the cache for the given key.
|
|
45
|
+
* @template K - The specific key type (extends keyof T).
|
|
46
|
+
* @param key - The key to associate with the value.
|
|
47
|
+
* @param value - The value to store (must match the type T[K]).
|
|
48
|
+
* @example
|
|
49
|
+
* // Given T = { name: string, age: number }
|
|
50
|
+
* cache.set('name', 'Alice'); // OK
|
|
51
|
+
* cache.set('age', 'thirty'); // Type error: string not assignable to number
|
|
52
|
+
*/
|
|
53
|
+
set<K extends keyof T>(key: K, value: T[K]): void {
|
|
54
|
+
this.store.set(key, value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Checks if a key exists in the cache.
|
|
59
|
+
* @template K - The specific key type (extends keyof T).
|
|
60
|
+
* @param key - The key to check.
|
|
61
|
+
* @returns True if the key exists in the cache, false otherwise.
|
|
62
|
+
* @example
|
|
63
|
+
* cache.has('name'); // Returns boolean
|
|
64
|
+
*/
|
|
65
|
+
has<K extends keyof T>(key: K): boolean {
|
|
66
|
+
return this.store.has(key);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Removes a key-value pair from the cache.
|
|
71
|
+
* @template K - The specific key type (extends keyof T).
|
|
72
|
+
* @param key - The key to remove.
|
|
73
|
+
* @returns True if the key existed and was removed, false otherwise.
|
|
74
|
+
* @example
|
|
75
|
+
* cache.delete('name'); // Returns boolean
|
|
76
|
+
*/
|
|
77
|
+
delete<K extends keyof T>(key: K): boolean {
|
|
78
|
+
return this.store.delete(key);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Removes all key-value pairs from the cache.
|
|
83
|
+
* @example
|
|
84
|
+
* cache.clear(); // Empties the entire cache
|
|
85
|
+
*/
|
|
86
|
+
clear(): void {
|
|
87
|
+
this.store.clear();
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Loader, Plugin } from 'esbuild';
|
|
2
|
+
|
|
3
|
+
// ########################################
|
|
4
|
+
// ESBuild Config
|
|
5
|
+
// ########################################
|
|
6
|
+
// Base configuration
|
|
7
|
+
export interface ESBuildBaseConfig {
|
|
8
|
+
// Build optimizations
|
|
9
|
+
minify: boolean;
|
|
10
|
+
metafile: true;
|
|
11
|
+
color: true;
|
|
12
|
+
logLevel: 'silent';
|
|
13
|
+
|
|
14
|
+
// Performance
|
|
15
|
+
treeShaking: true;
|
|
16
|
+
minifyWhitespace: boolean;
|
|
17
|
+
minifyIdentifiers: boolean;
|
|
18
|
+
minifySyntax: boolean;
|
|
19
|
+
|
|
20
|
+
// Code splitting
|
|
21
|
+
splitting: false;
|
|
22
|
+
|
|
23
|
+
// Format
|
|
24
|
+
format: 'esm';
|
|
25
|
+
|
|
26
|
+
// Source maps for debugging
|
|
27
|
+
sourcemap: boolean | 'inline' | 'linked';
|
|
28
|
+
|
|
29
|
+
// Paths
|
|
30
|
+
absWorkingDir: string;
|
|
31
|
+
outdir?: string; // Optional in dev mode (memory)
|
|
32
|
+
outfile?: string; // Alternative to outdir
|
|
33
|
+
entryPoints: string[] | Record<string, string>;
|
|
34
|
+
|
|
35
|
+
// Write to disk or memory
|
|
36
|
+
write?: boolean; // false = keep in memory (dev mode)
|
|
37
|
+
|
|
38
|
+
// Plugins
|
|
39
|
+
plugins: Plugin[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type LoaderFiles = Record<string, Loader>;
|
|
43
|
+
|
|
44
|
+
// Client-specific configuration
|
|
45
|
+
export interface ESBuildClientConfig {
|
|
46
|
+
platform: 'browser';
|
|
47
|
+
target: ['es2022'];
|
|
48
|
+
loader: LoaderFiles;
|
|
49
|
+
bundle: true;
|
|
50
|
+
splitting: true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Server-specific configuration
|
|
54
|
+
export interface ESBuildServerConfig {
|
|
55
|
+
platform: 'node';
|
|
56
|
+
target: ['node18'];
|
|
57
|
+
loader: LoaderFiles;
|
|
58
|
+
packages: 'external';
|
|
59
|
+
external: string[];
|
|
60
|
+
bundle: true;
|
|
61
|
+
splitting: false; // Server doesn't need splitting
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Dev Mode configuration
|
|
65
|
+
export interface ESBuildDevConfig {
|
|
66
|
+
minify: false;
|
|
67
|
+
minifyWhitespace: false;
|
|
68
|
+
minifyIdentifiers: false;
|
|
69
|
+
minifySyntax: false;
|
|
70
|
+
sourcemap: 'inline' | true;
|
|
71
|
+
write: false; // Keep in memory for dev
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Prod Mode configuration
|
|
75
|
+
export interface ESBuildProdConfig {
|
|
76
|
+
minify: true;
|
|
77
|
+
minifyWhitespace: true;
|
|
78
|
+
minifyIdentifiers: true;
|
|
79
|
+
minifySyntax: true;
|
|
80
|
+
sourcemap: 'linked' | false;
|
|
81
|
+
write: true; // Write to disk for prod
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Combined configuration types
|
|
85
|
+
export type ESBuildClientDevConfig = ESBuildBaseConfig & ESBuildClientConfig & ESBuildDevConfig;
|
|
86
|
+
export type ESBuildClientProdConfig = ESBuildBaseConfig & ESBuildClientConfig & ESBuildProdConfig;
|
|
87
|
+
export type ESBuildServerDevConfig = ESBuildBaseConfig & ESBuildServerConfig & ESBuildDevConfig;
|
|
88
|
+
export type ESBuildServerProdConfig = ESBuildBaseConfig & ESBuildServerConfig & ESBuildProdConfig;
|
|
89
|
+
|
|
90
|
+
// Union type for all possible configs
|
|
91
|
+
export type ESBuildConfig =
|
|
92
|
+
| ESBuildClientDevConfig
|
|
93
|
+
| ESBuildClientProdConfig
|
|
94
|
+
| ESBuildServerDevConfig
|
|
95
|
+
| ESBuildServerProdConfig;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
// Output settings
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"module": "NodeNext",
|
|
8
|
+
"moduleResolution": "NodeNext",
|
|
9
|
+
|
|
10
|
+
// Project structure
|
|
11
|
+
"rootDir": "src",
|
|
12
|
+
"outDir": "dist",
|
|
13
|
+
|
|
14
|
+
// Declarations
|
|
15
|
+
"declaration": true,
|
|
16
|
+
"emitDeclarationOnly": false,
|
|
17
|
+
|
|
18
|
+
// Strictness + compatibility
|
|
19
|
+
"strict": true,
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"esModuleInterop": true,
|
|
22
|
+
"resolveJsonModule": true
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// Files to include
|
|
26
|
+
"include": ["src"]
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts","./src/bundler/Bundler.ts","./src/cache/cache-store.ts","./src/types/config.ts"],"version":"5.9.3"}
|