Skip to content

Commit 916df03

Browse files
committed
feat(laroux): laroux packages are introduced.
1 parent c2705be commit 916df03

File tree

301 files changed

+26601
-327
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

301 files changed

+26601
-327
lines changed

.claude/skills/architecture-guidelines/SKILL.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export function processFile() {}
2424
- Document architectural decisions with ADRs including trade-offs
2525
- Write automated tests with CI (target 80%+ coverage for critical paths)
2626
- Use naming conventions: PascalCase for components, camelCase for utilities
27+
- Hexagonal architecture: domain + ports together, adapters separate
28+
- Explicit composition only: import adapters directly, pass as parameters (no
29+
magic config strings, no convenience factories)
2730

2831
## References
2932

.claude/skills/architecture-guidelines/references/rules.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,128 @@ Incorrect:
235235
- No date/status in headers
236236
- Vague filenames like `notes.md` or `stuff.md`
237237
- Missing code examples
238+
239+
---
240+
241+
## Hexagonal Architecture (Double-Layered)
242+
243+
Scope: All projects with external dependencies
244+
245+
Rule: Use double-layered hexagonal architecture. Domain layer contains both
246+
business logic AND port interfaces/types together (no separate ports directory).
247+
Adapters implement domain interfaces. Composition happens at call site via
248+
explicit imports.
249+
250+
**Structure:**
251+
252+
```
253+
package/
254+
├── domain/ # Business logic + port interfaces
255+
│ ├── mod.ts # Re-exports all domain types
256+
│ ├── bundler.ts # Bundler interface + BundleData type
257+
│ ├── framework-plugin.ts # FrameworkPlugin interface + related types
258+
│ └── build-cache.ts # BuildCache interface + implementation
259+
260+
├── adapters/ # All adapter implementations
261+
│ ├── react/ # implements FrameworkPlugin
262+
│ ├── tailwindcss/ # implements CssPlugin
263+
│ ├── lightningcss/ # implements CssTransformer
264+
│ └── prebuilt-bundler/ # implements Bundler
265+
266+
└── mod.ts # Main entry, exports bundle function
267+
```
268+
269+
**Composition via Explicit Imports:**
270+
271+
Correct:
272+
273+
```typescript
274+
// User explicitly imports what they need
275+
import { bundle } from "@eser/laroux-bundler";
276+
import { reactPlugin } from "@eser/laroux-bundler/adapters/react";
277+
import { tailwindPlugin } from "@eser/laroux-bundler/adapters/tailwindcss";
278+
import { PrebuiltBundler } from "@eser/laroux-bundler/adapters/prebuilt-bundler";
279+
280+
// Pass adapters as parameters
281+
await bundle(config, {
282+
framework: reactPlugin,
283+
css: tailwindPlugin,
284+
bundler: new PrebuiltBundler(bundlerConfig),
285+
});
286+
```
287+
288+
Incorrect:
289+
290+
```typescript
291+
// Magic config strings - avoid
292+
const config = {
293+
framework: "react", // String-based selection
294+
css: "tailwindcss",
295+
bundler: "prebuilt",
296+
};
297+
await bundle(config); // Dynamic imports inside
298+
299+
// Convenience factory functions - avoid
300+
const plugins = await createReactTailwindPlugins(); // Hides adapter selection
301+
await bundle(config, plugins); // User doesn't control what loads
302+
303+
// Separate ports directory - avoid
304+
import type { Plugin } from "./ports/plugin.ts"; // Don't separate ports
305+
import { logic } from "./domain/logic.ts"; // from domain
306+
```
307+
308+
**Why Avoid Convenience Factories:**
309+
310+
- Hide which adapters are loaded (opaque dependency graph)
311+
- Require dynamic imports that break tree-shaking
312+
- Make it harder to swap individual adapters
313+
- Add unnecessary abstraction layer
314+
- Users should always know exactly what they're importing
315+
316+
**Benefits:**
317+
318+
- Tree-shaking works naturally (unused adapters not bundled)
319+
- Type-safe: TypeScript validates plugin interfaces at compile time
320+
- User controls exactly what gets loaded
321+
- Easy to swap adapters (Vue instead of React, UnoCSS instead of Tailwind)
322+
- No runtime magic or dynamic imports
323+
- Clear dependency graph
324+
325+
**Domain Layer Design:**
326+
327+
Domain files contain both interfaces and business logic:
328+
329+
```typescript
330+
// domain/framework-plugin.ts
331+
export type ClientComponent = {
332+
filePath: string;
333+
relativePath: string;
334+
exportNames: string[];
335+
};
336+
337+
export type FrameworkPlugin = {
338+
name: string;
339+
analyzeClientComponents?: (srcDir: string) => Promise<ClientComponent[]>;
340+
transformClientComponents?: (components: ClientComponent[]) => Promise<void>;
341+
};
342+
343+
// Noop implementation for when no framework is configured
344+
export const noopPlugin: FrameworkPlugin = {
345+
name: "noop",
346+
analyzeClientComponents: () => Promise.resolve([]),
347+
};
348+
```
349+
350+
**Adapter Implementation:**
351+
352+
```typescript
353+
// adapters/react/plugin.ts
354+
import type { FrameworkPlugin } from "../../domain/framework-plugin.ts";
355+
356+
export const reactPlugin: FrameworkPlugin = {
357+
name: "react",
358+
analyzeClientComponents: async (srcDir) => {
359+
// React-specific implementation
360+
},
361+
};
362+
```

.claude/skills/javascript-practices/references/rules.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,53 @@ if (!array.length) {} // false for length 0
445445
if (user) {} // false for null, undefined, 0, ""
446446
```
447447

448+
### Iteration Over Optional Values
449+
450+
Scope: JS/TS
451+
452+
Rule: Use explicit undefined checks before iterating. Avoid nullish
453+
coalescing that creates empty objects just to iterate over nothing.
454+
455+
Correct:
456+
457+
```typescript
458+
const props: Record<string, unknown> | undefined = getProps();
459+
460+
if (props !== undefined) {
461+
for (const [key, value] of Object.entries(props)) {
462+
process(key, value);
463+
}
464+
}
465+
466+
// Also acceptable for arrays
467+
if (items !== undefined) {
468+
for (const item of items) {
469+
handle(item);
470+
}
471+
}
472+
```
473+
474+
Incorrect:
475+
476+
```typescript
477+
// Creates empty object just to iterate nothing
478+
for (const [key, value] of Object.entries(props ?? {})) {
479+
process(key, value);
480+
}
481+
482+
// Same issue with arrays
483+
for (const item of items ?? []) {
484+
handle(item);
485+
}
486+
```
487+
488+
Rationale:
489+
490+
- More efficient: avoids creating temporary empty objects/arrays
491+
- More explicit: makes the conditional nature of iteration clear
492+
- More readable: intent is obvious at a glance
493+
- Better for debugging: easier to add logging or breakpoints
494+
448495
---
449496

450497
## Async

.pre-commit-config.yaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ repos:
1313
- id: check-json
1414
exclude: |
1515
(?x)^(
16-
test/apps/vite-app/tsconfig.app.json|
17-
test/apps/cf-workers-app/tsconfig.json
16+
etc/templates/vite-app/tsconfig.app.json|
17+
etc/templates/cf-workers-app/tsconfig.json
1818
)$
1919
- id: check-merge-conflict
2020
- id: check-shebang-scripts-are-executable
@@ -34,8 +34,8 @@ repos:
3434
args: ["--autofix", "--no-ensure-ascii", "--no-sort-keys"]
3535
exclude: |
3636
(?x)^(
37-
test/apps/vite-app/tsconfig.app.json|
38-
test/apps/cf-workers-app/tsconfig.json
37+
etc/templates/vite-app/tsconfig.app.json|
38+
etc/templates/cf-workers-app/tsconfig.json
3939
)$
4040
- id: trailing-whitespace
4141

@@ -96,6 +96,9 @@ repos:
9696
pkg/@eser/functions/README.md|
9797
pkg/@eser/http/README.md|
9898
pkg/@eser/jsx-runtime/README.md|
99+
pkg/@eser/laroux-bundler/README.md|
100+
pkg/@eser/laroux-react/README.md|
101+
pkg/@eser/laroux-server/README.md|
99102
pkg/@eser/logging/README.md|
100103
pkg/@eser/parsing/README.md|
101104
pkg/@eser/primitives/README.md|
@@ -105,7 +108,7 @@ repos:
105108
pkg/@eser/writer/README.md|
106109
pkg/@cool/cli/README.md|
107110
pkg/@cool/lime/README.md|
108-
test/apps/vite-app/README.md|
111+
etc/templates/vite-app/README.md|
109112
Dockerfile|
110113
LICENSE|
111114
README.md

deno.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
{
22
"nodeModulesDir": "auto",
33
"unstable": [
4-
"temporal"
4+
"temporal",
5+
"unsafe-proto",
6+
"sloppy-imports",
7+
"bundle"
58
],
69
"lint": {
710
"rules": {
@@ -37,6 +40,7 @@
3740
"docs/",
3841
"etc/temp/",
3942
"node_modules/",
40-
"pkg/@eser/cli/dist/"
43+
"pkg/@eser/cli/dist/",
44+
"etc/templates/laroux-app/dist/"
4145
]
4246
}

0 commit comments

Comments
 (0)