@chr33s/solarflare 0.0.4 → 0.0.7

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/bin/solarflare ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
3
+ exec node --experimental-transform-types "$SCRIPT_DIR/../src/build.ts" "$@"
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@chr33s/solarflare",
3
- "version": "0.0.4",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/chr33s/solarflare"
8
8
  },
9
9
  "bin": {
10
- "solarflare": "src/build.ts"
10
+ "solarflare": "bin/solarflare"
11
11
  },
12
12
  "files": [
13
+ "bin",
13
14
  "src",
14
15
  "tsconfig.json",
15
16
  "!src/*.test.ts"
@@ -3,11 +3,18 @@ import { readFile, unlink, mkdir, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import ts from "typescript";
5
5
  import { rolldown } from "rolldown";
6
+ import type { RolldownOptions } from "rolldown";
6
7
  import { replacePlugin } from "rolldown/plugins";
7
8
  import { transform } from "lightningcss";
8
9
  import { createProgram, getDefaultExportInfo } from "./ast.ts";
9
10
  import { exists } from "./fs.ts";
10
- import { assetUrlPrefixPlugin, type BuildArgs, moduleTypes } from "./build.bundle.ts";
11
+ import {
12
+ assetUrlPrefixPlugin,
13
+ type BuildArgs,
14
+ mergeInputOptions,
15
+ mergeOutputOptions,
16
+ moduleTypes,
17
+ } from "./build.bundle.ts";
11
18
  import { parsePath } from "./paths.ts";
12
19
  import { generateClientScript } from "./console-forward.ts";
13
20
  import { createScanner } from "./build.scan.ts";
@@ -22,6 +29,7 @@ export interface BuildClientOptions {
22
29
  distClient: string;
23
30
  publicDir: string;
24
31
  chunksPath: string;
32
+ userConfig?: RolldownOptions;
25
33
  }
26
34
 
27
35
  async function remove(path: string) {
@@ -82,7 +90,7 @@ async function getComponentMeta(program: ts.Program, appDir: string, file: strin
82
90
  }
83
91
 
84
92
  export async function buildClient(options: BuildClientOptions) {
85
- const { args, rootDir, appDir, distDir, distClient, publicDir, chunksPath } = options;
93
+ const { args, rootDir, appDir, distDir, distClient, publicDir, chunksPath, userConfig } = options;
86
94
  const distClientAssets = join(distClient, "assets");
87
95
  const scanner = createScanner({ rootDir, appDir });
88
96
 
@@ -255,80 +263,90 @@ export async function buildClient(options: BuildClientOptions) {
255
263
 
256
264
  const packageImports = await scanner.getPackageImports();
257
265
 
258
- const bundle = await rolldown({
259
- input,
260
- platform: "browser",
261
- tsconfig: true,
262
- moduleTypes,
263
- plugins: [
264
- replacePlugin({
265
- "globalThis.__SF_DEV__": JSON.stringify(!args.production),
266
- }),
266
+ const bundle = await rolldown(
267
+ mergeInputOptions(
267
268
  {
268
- name: "raw-css-loader",
269
- resolveId(source: string, importer: string | undefined) {
270
- if (source.endsWith("?raw") && source.includes(".css")) {
271
- const realPath = source.replace(/\?raw$/, "");
272
- if (importer) {
273
- const importerDir = importer.split("/").slice(0, -1).join("/");
274
- return {
275
- id: join(importerDir, realPath) + "?raw",
276
- external: false,
277
- };
278
- }
279
- return { id: realPath + "?raw", external: false };
280
- }
281
- return null;
269
+ input,
270
+ platform: "browser",
271
+ tsconfig: true,
272
+ moduleTypes,
273
+ plugins: [
274
+ replacePlugin({
275
+ "globalThis.__SF_DEV__": JSON.stringify(!args.production),
276
+ }),
277
+ {
278
+ name: "raw-css-loader",
279
+ resolveId(source: string, importer: string | undefined) {
280
+ if (source.endsWith("?raw") && source.includes(".css")) {
281
+ const realPath = source.replace(/\?raw$/, "");
282
+ if (importer) {
283
+ const importerDir = importer.split("/").slice(0, -1).join("/");
284
+ return {
285
+ id: join(importerDir, realPath) + "?raw",
286
+ external: false,
287
+ };
288
+ }
289
+ return { id: realPath + "?raw", external: false };
290
+ }
291
+ return null;
292
+ },
293
+ async load(id: string) {
294
+ if (id.endsWith("?raw")) {
295
+ const realPath = id.replace(/\?raw$/, "");
296
+ try {
297
+ const content = await readFile(realPath, "utf-8");
298
+ return {
299
+ code: /* tsx */ `export default ${JSON.stringify(content)};`,
300
+ moduleType: "js",
301
+ };
302
+ } catch {
303
+ console.warn(`[raw-css-loader] Could not load: ${realPath}`);
304
+ return { code: `export default "";`, moduleType: "js" };
305
+ }
306
+ }
307
+ return null;
308
+ },
309
+ },
310
+ assetUrlPrefixPlugin,
311
+ ],
312
+ resolve: {
313
+ alias: packageImports,
282
314
  },
283
- async load(id: string) {
284
- if (id.endsWith("?raw")) {
285
- const realPath = id.replace(/\?raw$/, "");
286
- try {
287
- const content = await readFile(realPath, "utf-8");
288
- return {
289
- code: /* tsx */ `export default ${JSON.stringify(content)};`,
290
- moduleType: "js",
291
- };
292
- } catch {
293
- console.warn(`[raw-css-loader] Could not load: ${realPath}`);
294
- return { code: `export default "";`, moduleType: "js" };
295
- }
296
- }
297
- return null;
315
+ transform: {
316
+ target: "es2020",
317
+ jsx: {
318
+ runtime: "automatic",
319
+ development: !args.production,
320
+ },
298
321
  },
299
322
  },
300
- assetUrlPrefixPlugin,
301
- ],
302
- resolve: {
303
- alias: packageImports,
304
- },
305
- transform: {
306
- target: "es2020",
307
- jsx: {
308
- runtime: "automatic",
309
- development: !args.production,
310
- },
311
- },
312
- });
313
-
314
- await bundle.write({
315
- dir: distClientAssets,
316
- format: "esm",
317
- entryFileNames: "[name].js",
318
- minify: args.production,
319
- chunkFileNames: "[name]-[hash].js",
320
- assetFileNames: "[name]-[hash][extname]",
321
- codeSplitting: {
322
- minSize: 20000,
323
- groups: [
324
- {
325
- name: "vendor",
326
- test: /node_modules/,
323
+ userConfig,
324
+ ),
325
+ );
326
+
327
+ await bundle.write(
328
+ mergeOutputOptions(
329
+ {
330
+ dir: distClientAssets,
331
+ format: "esm",
332
+ entryFileNames: "[name].js",
333
+ minify: args.production,
334
+ chunkFileNames: "[name]-[hash].js",
335
+ assetFileNames: "[name]-[hash][extname]",
336
+ codeSplitting: {
337
+ minSize: 20000,
338
+ groups: [
339
+ {
340
+ name: "vendor",
341
+ test: /node_modules/,
342
+ },
343
+ ],
327
344
  },
328
- ],
329
- },
330
- ...(args.sourcemap && { sourcemap: true }),
331
- });
345
+ ...(args.sourcemap && { sourcemap: true }),
346
+ },
347
+ userConfig,
348
+ ),
349
+ );
332
350
 
333
351
  if (cssOutputsByBase.size > 0) {
334
352
  const emittedCss = new Set(cssOutputsByBase.values());
@@ -2,8 +2,15 @@ import { mkdir, unlink, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { glob } from "node:fs/promises";
4
4
  import { rolldown } from "rolldown";
5
+ import type { RolldownOptions } from "rolldown";
5
6
  import { createProgram } from "./ast.ts";
6
- import { assetUrlPrefixPlugin, type BuildArgs, moduleTypes } from "./build.bundle.ts";
7
+ import {
8
+ assetUrlPrefixPlugin,
9
+ type BuildArgs,
10
+ mergeInputOptions,
11
+ mergeOutputOptions,
12
+ moduleTypes,
13
+ } from "./build.bundle.ts";
7
14
  import { createScanner } from "./build.scan.ts";
8
15
  import { validateRoutes, generateRoutesTypeFile } from "./build.validate.ts";
9
16
  import { generateModulesFile } from "./build.emit-manifests.ts";
@@ -16,10 +23,12 @@ export interface BuildServerOptions {
16
23
  modulesPath: string;
17
24
  chunksPath: string;
18
25
  routesTypePath: string;
26
+ userConfig?: RolldownOptions;
19
27
  }
20
28
 
21
29
  export async function buildServer(options: BuildServerOptions) {
22
- const { args, rootDir, appDir, distServer, modulesPath, chunksPath, routesTypePath } = options;
30
+ const { args, rootDir, appDir, distServer, modulesPath, chunksPath, routesTypePath, userConfig } =
31
+ options;
23
32
  const scanner = createScanner({ rootDir, appDir });
24
33
 
25
34
  console.log("🔍 Scanning for route modules...");
@@ -73,51 +82,61 @@ export async function buildServer(options: BuildServerOptions) {
73
82
 
74
83
  const packageImports = await scanner.getPackageImports();
75
84
 
76
- const bundle = await rolldown({
77
- input: join(appDir, "index.ts"),
78
- platform: "node",
79
- tsconfig: true,
80
- moduleTypes,
81
- external: [
82
- "cloudflare:workers",
83
- "preact",
84
- "preact/hooks",
85
- "preact/compat",
86
- "preact/jsx-runtime",
87
- "preact/jsx-dev-runtime",
88
- "preact/debug",
89
- "@preact/signals",
90
- "@preact/signals-core",
91
- "@preact/signals-debug",
92
- "preact-render-to-string",
93
- "preact-render-to-string/stream",
94
- "preact-custom-element",
95
- ],
96
- resolve: {
97
- alias: {
98
- ...packageImports,
99
- ".modules.generated": modulesPath,
100
- ".chunks.generated.json": chunksPath,
85
+ const bundle = await rolldown(
86
+ mergeInputOptions(
87
+ {
88
+ input: join(appDir, "index.ts"),
89
+ platform: "node",
90
+ tsconfig: true,
91
+ moduleTypes,
92
+ external: [
93
+ "cloudflare:workers",
94
+ "preact",
95
+ "preact/hooks",
96
+ "preact/compat",
97
+ "preact/jsx-runtime",
98
+ "preact/jsx-dev-runtime",
99
+ "preact/debug",
100
+ "@preact/signals",
101
+ "@preact/signals-core",
102
+ "@preact/signals-debug",
103
+ "preact-render-to-string",
104
+ "preact-render-to-string/stream",
105
+ "preact-custom-element",
106
+ ],
107
+ resolve: {
108
+ alias: {
109
+ ...packageImports,
110
+ ".modules.generated": modulesPath,
111
+ ".chunks.generated.json": chunksPath,
112
+ },
113
+ },
114
+ plugins: [assetUrlPrefixPlugin],
115
+ transform: {
116
+ jsx: {
117
+ runtime: "automatic",
118
+ development: false,
119
+ },
120
+ },
101
121
  },
102
- },
103
- plugins: [assetUrlPrefixPlugin],
104
- transform: {
105
- jsx: {
106
- runtime: "automatic",
107
- development: false,
122
+ userConfig,
123
+ ),
124
+ );
125
+
126
+ await bundle.write(
127
+ mergeOutputOptions(
128
+ {
129
+ dir: distServer,
130
+ format: "esm",
131
+ codeSplitting: false,
132
+ entryFileNames: "index.js",
133
+ assetFileNames: "[name]-[hash][extname]",
134
+ minify: args.production,
135
+ ...(args.sourcemap && { sourcemap: true }),
108
136
  },
109
- },
110
- });
111
-
112
- await bundle.write({
113
- dir: distServer,
114
- format: "esm",
115
- codeSplitting: false,
116
- entryFileNames: "index.js",
117
- assetFileNames: "[name]-[hash][extname]",
118
- minify: args.production,
119
- ...(args.sourcemap && { sourcemap: true }),
120
- });
137
+ userConfig,
138
+ ),
139
+ );
121
140
 
122
141
  await bundle.close();
123
142
 
@@ -1,4 +1,82 @@
1
- import type { ModuleTypes, NormalizedOutputOptions, OutputBundle } from "rolldown";
1
+ import { join } from "node:path";
2
+ import type {
3
+ ExternalOption,
4
+ InputOptions,
5
+ ModuleTypes,
6
+ NormalizedOutputOptions,
7
+ OutputBundle,
8
+ OutputOptions,
9
+ Plugin,
10
+ RolldownOptions,
11
+ RolldownPluginOption,
12
+ } from "rolldown";
13
+ import { exists } from "./fs.ts";
14
+
15
+ /** Load a `rolldown.config.ts` from `rootDir` if it exists. */
16
+ export async function loadUserConfig(rootDir: string): Promise<RolldownOptions | undefined> {
17
+ const configPath = join(rootDir, "rolldown.config.ts");
18
+ if (!(await exists(configPath))) return undefined;
19
+ const { loadConfig } = await import("rolldown/config");
20
+ const exported = await loadConfig(configPath);
21
+ if (typeof exported === "function") return (await exported({})) as RolldownOptions;
22
+ if (Array.isArray(exported)) return exported[0];
23
+ return exported;
24
+ }
25
+
26
+ /** Merge user input options onto a framework base (plugins appended, resolve/external merged). */
27
+ export function mergeInputOptions(base: InputOptions, user?: RolldownOptions): InputOptions {
28
+ if (!user) return base;
29
+ const {
30
+ output: _,
31
+ plugins: userPlugins,
32
+ resolve: userResolve,
33
+ external: userExternal,
34
+ ...rest
35
+ } = user;
36
+ return {
37
+ ...base,
38
+ ...rest,
39
+ plugins: [...toArray(base.plugins), ...toArray(userPlugins)] as RolldownPluginOption[],
40
+ resolve: {
41
+ ...base.resolve,
42
+ ...userResolve,
43
+ alias: {
44
+ ...(base.resolve?.alias as Record<string, string>),
45
+ ...(userResolve?.alias as Record<string, string>),
46
+ },
47
+ },
48
+ external: [...toArray(base.external), ...toArray(userExternal)] as ExternalOption,
49
+ };
50
+ }
51
+
52
+ /** Merge user output options onto a framework base. */
53
+ export function mergeOutputOptions(base: OutputOptions, user?: RolldownOptions): OutputOptions {
54
+ if (!user?.output) return base;
55
+ const out = Array.isArray(user.output) ? user.output[0] : user.output;
56
+ return { ...base, ...out };
57
+ }
58
+
59
+ function toArray<T>(value: T | T[] | undefined | null): T[] {
60
+ if (value == null) return [];
61
+ return Array.isArray(value) ? value : [value];
62
+ }
63
+
64
+ /** Raw text replacement plugin for `%KEY%` patterns (like Vite's html env replacement). */
65
+ export function htmlReplacePlugin(replacements: Record<string, string>): Plugin {
66
+ const entries = Object.entries(replacements);
67
+ if (entries.length === 0) return { name: "html-replace" };
68
+ return {
69
+ name: "html-replace",
70
+ transform(code) {
71
+ let result = code;
72
+ for (const [key, value] of entries) {
73
+ result = result.replaceAll(key, value);
74
+ }
75
+ if (result === code) return null;
76
+ return { code: result };
77
+ },
78
+ };
79
+ }
2
80
 
3
81
  export const assetUrlPrefixPlugin = {
4
82
  name: "asset-url-prefix",
package/src/build.ts CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import { spawn, type ChildProcess } from "node:child_process";
3
2
  import { watch } from "node:fs";
4
3
  import { writeFile } from "node:fs/promises";
@@ -7,6 +6,7 @@ import { argv, env } from "node:process";
7
6
  import { parseArgs } from "node:util";
8
7
  import { buildClient } from "./build.bundle-client.ts";
9
8
  import { buildServer } from "./build.bundle-server.ts";
9
+ import { loadUserConfig } from "./build.bundle.ts";
10
10
  import { exists } from "./fs.ts";
11
11
 
12
12
  /** Resolve paths relative to the current working directory. */
@@ -110,6 +110,9 @@ async function build() {
110
110
 
111
111
  await scaffoldTemplates();
112
112
 
113
+ const userConfig = await loadUserConfig(ROOT_DIR);
114
+ if (userConfig) console.log("📋 Loaded rolldown.config.ts");
115
+
113
116
  await buildClient({
114
117
  args,
115
118
  rootDir: ROOT_DIR,
@@ -118,6 +121,7 @@ async function build() {
118
121
  distClient: DIST_CLIENT,
119
122
  publicDir: PUBLIC_DIR,
120
123
  chunksPath: CHUNKS_PATH,
124
+ userConfig,
121
125
  });
122
126
 
123
127
  await buildServer({
@@ -128,6 +132,7 @@ async function build() {
128
132
  modulesPath: MODULES_PATH,
129
133
  chunksPath: CHUNKS_PATH,
130
134
  routesTypePath: ROUTES_TYPE_PATH,
135
+ userConfig,
131
136
  });
132
137
 
133
138
  const duration = ((performance.now() - startTime) / 1000).toFixed(2);
@@ -5,13 +5,12 @@ interface ImportMeta {
5
5
  ): Record<string, () => Promise<T>>;
6
6
  /** The file path of the current module (Node runtime) */
7
7
  path?: string;
8
- /** Environment variables (bundler) */
9
- env?: {
8
+ /** Environment variables replaced at build time via `transform.define` in `rolldown.config.ts`. */
9
+ env: {
10
10
  DEV?: boolean;
11
11
  PROD?: boolean;
12
12
  MODE?: string;
13
- [key: string]: unknown;
14
- };
13
+ } & Record<string, string>;
15
14
  }
16
15
 
17
16
  declare module "*.css" {