Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/monorail/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ The role that serves or produces browser-facing renderer content. Multiple pages
The complete Rsbuild configuration for one role, extended with Rselectron-owned Electron behavior for that role.

**Role preset**
Rselectron's defaults and invariants for a role. A preset may be overridden where safe, while role identity constraints remain enforced.
Rselectron's defaults and invariants for a role. A preset may be overridden where safe, while role identity constraints remain enforced. Unset `output.distPath` receives the Conventional role outputs layout; other preset values (targets, formats, Node entry naming) are owned by their respective contracts.

**Conventional role outputs**
The default Role output roots when `output.distPath` is unset: `out/main`, `out/preload`, and `out/renderer`, resolved against the Application root (not against each Role `root`). Explicit `distPath` (string or object with `root`) always wins.

**Role module format**
The module system used for a Main or Preload source-build output: `cjs` or `esm`. It is derived from Electron capability, the application manifest `"type"`, and the role-level `electron.format`, and is applied through Rsbuild `output.module`.
Expand Down
5 changes: 4 additions & 1 deletion docs/monorail/CONTEXT.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ Rselectron 有意排除的基线能力。Vite 插件、字节码编译,以及
某一角色的完整 Rsbuild 配置,并扩展该角色由 Rselectron 拥有的 Electron 行为。

**Role preset(角色预设)**
Rselectron 对某一角色的默认值与不变量。在安全处可覆盖预设,但角色身份约束仍被强制执行。
Rselectron 对某一角色的默认值与不变量。在安全处可覆盖预设,但角色身份约束仍被强制执行。未设置 `output.distPath` 时采用 Conventional role outputs 布局;其余预设(目标、格式、Node 入口命名)由各自契约拥有。

**Conventional role outputs(约定角色产物目录)**
未设置 `output.distPath` 时的默认角色输出根:`out/main`、`out/preload`、`out/renderer`,相对 Application root 解析(不相对各 Role 的 `root`)。显式 `distPath`(字符串或带 `root` 的对象)始终优先。

**Role module format(角色模块格式)**
Main 或 Preload 源码构建产物使用的模块系统:`cjs` 或 `esm`。由 Electron 能力、应用清单 `"type"` 与角色级 `electron.format` 推导,并通过 Rsbuild `output.module` 生效。
Expand Down
2 changes: 1 addition & 1 deletion docs/monorail/compatibility-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Implementation evidence remains **Pending / 待实现** until automated tests or
- Contract / 契约: preserve the same conventions and warn for omitted roles.
- Classification / 分类: Target / 目标
- Acceptance / 验收: zero-config Vanilla fixture resolves the documented entries and output directories.
- Evidence / 证据: Partial — ADR `docs/monorail/adr/0007-electron-role-build-contract.md` documents `src/<role>` → `out/<role>` conventions; `tests/build.test.ts` and `examples/vanilla` prove explicit Role configs emit those outputs. Automatic zero-config discovery without `rselectron.config.*` is not implemented yet. / 部分完成——ADR `docs/monorail/adr/0007-electron-role-build-contract.md` 记录 `src/<role>` → `out/<role>` 约定;`tests/build.test.ts` 与 `examples/vanilla` 证明显式 Role 配置产出这些路径。尚无无 `rselectron.config.*` 的自动零配置发现。
- Evidence / 证据: Partial — ADR `docs/monorail/adr/0007-electron-role-build-contract.md` documents `src/<role>` → `out/<role>` conventions; `normalizeRuntime` injects Conventional role outputs (`out/<role>` under the Application root) when `output.distPath` is unset (`tests/unit/electron-runtime.test.ts`); getting-started / examples document and point `package.json#main` at `out/main`. Automatic zero-config discovery without `rselectron.config.*` is not implemented yet. / 部分完成——ADR `docs/monorail/adr/0007-electron-role-build-contract.md` 记录 `src/<role>` → `out/<role>` 约定;未设置 `output.distPath` 时 `normalizeRuntime` 会注入约定角色产物(应用根下的 `out/<role>`,见 `tests/unit/electron-runtime.test.ts`);getting-started / examples 将 `package.json#main` 指到 `out/main`。尚无无 `rselectron.config.*` 的自动零配置发现。

### BUILD-002 — Independent role compilation / 独立角色编译

Expand Down
39 changes: 39 additions & 0 deletions docs/monorail/out-layout-preset/align.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Align: out-layout-preset

## Intent

Close the gap between the accepted Electron role build contract (default outputs under `out/<role>`) and today’s unset fallback (`<roleRoot>/dist`). Conventional apps should stop hand-writing `output.distPath` (and absolute path workarounds) just to get the layout ADR 0007 already promises. Make the Role preset inject that layout when unset, keep explicit overrides intact, and align docs/examples with the real default.

## Decisions settled

- **Gap class:** implementation of an already-accepted convention (ADR 0007 / BUILD-001), not a new packaging or scaffolding product. No new ADR; optionally note in BUILD-001 evidence when landed.
- **When to inject:** for each configured Role, if `output.distPath` is unset **or** `output.distPath` is an object without a usable `root` (and not a string form), inject the conventional root. Any explicit string `distPath` or object with `root` wins and keeps today’s resolution rules (relative paths resolve against the Role `root`, else application root).
- **Injected path:** Application-root-relative convention `out/main`, `out/preload`, `out/renderer`. During normalization, set `output.distPath.root` to `resolve(appRoot, 'out', role)` (absolute) so custom Role `root` values (e.g. `./src/main`) still emit beside the app root, not under `src/main/out/...` or `src/main/dist`.
- **Planned entry / diagnostics:** `plannedMainEntry`, `roleDistRoot`, entry↔manifest mismatch checks, and renderer-only reuse validation must observe the injected path the same way they observe an explicit one.
- **Other Node defaults unchanged here:** Main/Preload `filenameHash: false` and entry filename policy remain as already shipped (ADR 0007 / 0009). This effort does not re-litigate them.
- **Docs / examples:** Getting started and learning examples must describe `out/<role>` as the unset default (today’s copy still teaches `root/dist` + optional `distPath`). Example `package.json#main` (or documented entry) tracks the planned Main output under the entry filename policy. Fixtures that already set `out/<role>` may stay explicit or drop the redundant `distPath` once the preset exists.
- **Migration posture:** no production-user migration window claimed; changing the unset default from `<roleRoot>/dist` → `out/<role>` is acceptable for beta. Document the change in migration / getting-started so early adopters who relied on `root/dist` without setting `distPath` know to set an explicit `distPath` or point `main` at `out/...`.
- **Acceptance seam:** unit/normalize asserts that unset configs receive `out/<role>` under `appRoot`; explicit `distPath` is preserved; at least one build or example path proves outputs land under `out/` without hand-written `distPath`. Prefer extending existing `electron-runtime` / build / docs-site seams.

## Deferred

- Renderer default `output.assetPrefix: './'` for `build` / `preview` (file-protocol relative assets) when unset — related Electron-preset ergonomics, not required to close the `out/` layout gap
- Zero-config Role discovery with no `rselectron.config.*` (remaining BUILD-001 partial)
- Helpers that generate BrowserWindow `loadURL` / `loadFile` / preload path resolution boilerplate
- Packaging `extraResources` / asarUnpack compose guides (separate compose-docs effort)
- Changing resolution rules for **explicit** relative `distPath` strings

## Out of scope

- Default-enabling Main/Preload `watch` in `dev` (ADR 0003)
- Embedding electron-builder / Forge or a project scaffolder (ADR 0001)
- Implicit cross-Role `shared` config block (ADR 0002; keep `mergeRselectronConfig`)
- Format / externalizeDeps / ESM-native behaviour (owned by `role-esm-native` / `cjs-import-only-diag`)
- Electron support snapshot / peer range changes (ADR 0005)

## Domain pointers

- Glossary: `docs/monorail/CONTEXT.md` — Role preset; Conventional role outputs (added/clarified by this align); Application root; Electron entry
- ADR: `docs/monorail/adr/0007-electron-role-build-contract.md` (default outputs `out/<role>`; Role presets)
- Matrix: `docs/monorail/compatibility-matrix.md` — BUILD-001 (Partial: convention documented, preset injection / zero-config discovery not complete)
- Consumer pain context: migration apps hand-writing absolute `distPath` while ADR already names `out/<role>`
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 01 — Inject conventional out/<role> distPath

Status: done
Blocked by: None

## What to build

In `normalizeRuntime`, apply Conventional role outputs: when a Role’s `output.distPath` is unset or is an object without a usable `root`, set `output.distPath.root` to `resolve(appRoot, 'out', role)`. Preserve explicit string `distPath` and objects that already set `root`. Keep sibling `distPath` fields when injecting into an object missing `root`. Ensure `plannedMainEntry` / `roleDistRoot` consumers (already post-normalize) observe the injected path.

## Acceptance criteria

- [x] Unset three-role `normalizeRuntime` fixture: each Role’s `distPath.root` is `join(appRoot, 'out', role)` (absolute)
- [x] Explicit string `distPath` and object-with-`root` are unchanged; object without `root` receives injection
- [x] `plannedMainEntry` for an unset Main config (after normalize) resolves under `out/main/` with the entry filename policy
- [x] Coverage lives in `tests/unit/electron-runtime.test.ts` (extend existing seam); oxlint/format clean for touched files

## Comments

- 2026-08-01: Claimed + implemented. `applyConventionalDistPath` in `normalizeRuntime`; unit tests green.
20 changes: 20 additions & 0 deletions docs/monorail/out-layout-preset/issues/02-docs-and-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 02 — Docs and examples for out/<role> default

Status: done
Blocked by: 01

## What to build

Update getting-started (en + zh) so the Electron entry section teaches `out/<role>` as the unset default (not `src/main/dist`). Add a short migration / getting-started note that early beta unset `root/dist` behaviour is replaced. Point learning examples’ `package.json#main` at the planned Main output under Conventional role outputs + entry filename policy. Optionally note BUILD-001 matrix evidence for preset injection (zero-config discovery still deferred).

## Acceptance criteria

- [x] Getting-started en + zh describe `out/main` (or `out/<role>`) as the default unset layout and no longer present `src/main/dist` as that default
- [x] Migration (en + zh) or getting-started mentions the beta default change / how to keep an explicit `distPath: 'dist'` layout
- [x] `examples/vanilla` and `examples/react` declare `package.json#main` aligned with planned Main output under `out/main`
- [x] `tests/docs/docs-site.test.ts` asserts the getting-started default-layout copy anchors
- [x] BUILD-001 evidence updated to credit preset injection while leaving zero-config-without-config as deferred/Partial if still accurate

## Comments

- 2026-08-01: Docs, examples, matrix evidence, and docs-site asserts landed.
58 changes: 58 additions & 0 deletions docs/monorail/out-layout-preset/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Spec: out-layout-preset

## Problem Statement

ADR 0007 and BUILD-001 document Conventional role outputs under `out/main`, `out/preload`, and `out/renderer`. When `output.distPath` is unset, normalization nevertheless falls through to Rsbuild’s `<roleRoot>/dist` (see `plannedMainEntry` / `roleDistRoot` fallbacks of `'dist'`). Authors and learning docs either hand-write `distPath` (often absolute) or teach `src/main/dist/...` as the default—contradicting the accepted contract and recreating the migration boilerplate this effort exists to remove.

## Solution

Inject Conventional role outputs in Role preset normalization:

1. **Preset injection** — for each configured Role, when `output.distPath` is unset or is an object without a usable `root`, set `output.distPath.root` to `resolve(appRoot, 'out', role)`. Explicit string `distPath` or object with `root` always wins and keeps today’s relative-resolution rules (relative → Role `root`, else application root).
2. **Downstream consistency** — `plannedMainEntry`, `roleDistRoot`, entry↔manifest mismatch, and renderer-only reuse continue to read the normalized Role config (already post-`normalizeRuntime`); no change to mismatch severity.
3. **Docs / examples** — getting-started (en + zh) and migration note the unset default as `out/<role>`; learning examples’ `package.json#main` (and any documented entry) track the planned Main output under the entry filename policy. Optional: drop redundant explicit `distPath` from fixtures that only restate `out/<role>`.

No new ADR. Glossary already defines Conventional role outputs / Role preset.

## User Stories

1. As an app author with a minimal three-role config and no `distPath`, I want outputs under `out/<role>` at the application root, so I do not hand-write absolute paths.
2. As an app author who sets an explicit `distPath`, I want that layout preserved unchanged, so custom monorepo / packaging layouts keep working.
3. As an app author reading getting-started, I want `package.json#main` examples to match the real unset default, so entry mismatch diagnostics do not surprise me.
4. As a maintainer, I want normalize/unit and docs-site asserts for the preset, so BUILD-001’s preset-injection gap does not regress.

## Implementation Decisions

- **Contract authority:** ADR 0007; glossary Conventional role outputs, Role preset, Application root, Electron entry. Matrix BUILD-001 (advance evidence for preset injection; zero-config discovery remains deferred).
- **Injection site:** `normalizeRuntime` (same place other Role presets apply). Helper may live beside runtime/entry for testability (`applyConventionalDistPath` or equivalent).
- **Usable `root`:** treat as present when `distPath` is a non-empty string, or when `distPath` is an object whose `root` is a non-empty string. Empty string / missing `root` → inject.
- **Absolute injected root:** always `resolve(appRoot, 'out', role)` so Role `root` like `./src/main` does not nest outputs under the source tree.
- **Do not change:** Main/Preload `filenameHash` / entry filename policy; watch defaults; packaging; renderer `assetPrefix`; resolution rules for explicit relative `distPath`.
- **Docs:** dual-write getting-started Electron entry section; short migration / getting-started note that early beta `root/dist` unset behaviour is replaced—set explicit `distPath: 'dist'` (or role-relative) to keep the old layout, or point `main` at `out/...`.
- **Examples:** vanilla / react `package.json#main` align with planned Main output after preset + entry filename policy (`type: module` → `.cjs` or `.mjs` per ADR 0009).
- **BUILD-001:** when landed, update matrix evidence to note preset injection for unset `distPath`; leave zero-config-without-config-file as still Partial / deferred.

## Testing Decisions

External behaviour over implementation details. Confirmed seams:

1. **`tests/unit/electron-runtime.test.ts`** — unset three-role fixture: normalized `distPath.root` equals `join(appRoot, 'out', role)` for main/preload/renderer; explicit string and object-with-`root` preserved (including relative-to-role-root semantics where already tested or trivially asserted); object without `root` receives injection while preserving sibling `distPath` fields if any.
2. **Build or example path** — at least one path proves artifacts land under `out/` without hand-written `distPath` (extend an existing build fixture / example assert, or a focused unit that reads normalized config into `plannedMainEntry` and asserts `out/main/...`).
3. **`tests/docs/docs-site.test.ts`** — getting-started (en + zh) mentions `out/main` (or `out/<role>`) as the default layout and does not teach `src/main/dist` as the unset default.

Prefer extending these seams; do not invent a parallel test stack.

## Out of Scope

- Renderer default `assetPrefix: './'` for build/preview
- Zero-config Role discovery without `rselectron.config.*`
- Window `loadURL` / `loadFile` / preload path helpers
- Packaging compose guides
- Changing explicit relative `distPath` resolution
- Default Main/Preload watch; packaging/scaffolding; implicit `shared`; ESM/externals; Electron snapshot

## Further Notes

- Align source: `docs/monorail/out-layout-preset/align.md`.
- Today’s getting-started still documents `./src/main/dist/index.js` — that copy is wrong relative to ADR 0007 and must flip with this effort.
- Consumer migration pain: hand-written absolute `distPath` while the ADR already named `out/<role>`.
1 change: 1 addition & 0 deletions examples/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "rselectron-example-react",
"private": true,
"type": "module",
"main": "./out/main/index.mjs",
"scripts": {
"dev": "rselectron dev",
"build": "rselectron build",
Expand Down
1 change: 1 addition & 0 deletions examples/vanilla/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "rselectron-example-vanilla",
"private": true,
"type": "module",
"main": "./out/main/index.mjs",
"scripts": {
"dev": "rselectron dev",
"build": "rselectron build",
Expand Down
48 changes: 47 additions & 1 deletion packages/core/src/electron/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,52 @@ interface ApplicationManifest {
type?: string;
}

/**
* Conventional role outputs (ADR 0007): unset distPath → out/<role> under appRoot.
*/
export function applyConventionalDistPath(
appRoot: string,
role: Role,
config: RoleConfig,
): RoleConfig {
const dist = config.output?.distPath;
if (typeof dist === 'string') {
if (dist.length > 0) {
return config;
}
} else if (
typeof dist === 'object' &&
dist !== null &&
!Array.isArray(dist) &&
typeof dist.root === 'string' &&
dist.root.length > 0
) {
return config;
}

const root = resolve(appRoot, 'out', role);
if (typeof dist === 'object' && dist !== null && !Array.isArray(dist)) {
return {
...config,
output: {
...config.output,
distPath: {
...dist,
root,
},
},
};
}

return {
...config,
output: {
...config.output,
distPath: { root },
},
};
}

function readManifest(
appRoot: string,
packageJson?: string,
Expand Down Expand Up @@ -438,7 +484,7 @@ export function normalizeRuntime(options: {
const pendingWarnings: Diagnostic[] = [];

for (const [role, config] of roles) {
let next = config;
let next = applyConventionalDistPath(options.appRoot, role, config);

if (role === 'main' || role === 'preload') {
const format = deriveFormat(next, manifest, electron);
Expand Down
5 changes: 5 additions & 0 deletions tests/docs/docs-site.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ test('guide pages cover concepts, parity, and learning sources in both languages
expect(gettingStarted).toContain('examples/');
expect(gettingStarted).not.toMatch(/tests\/fixtures|fixtures\//);
expect(gettingStarted).toMatch(/\/guide\/concepts|concepts/);
expect(gettingStarted).toMatch(/out\/main|out\/<role>/);
expect(gettingStarted).toContain('./out/main/index.js');
expect(gettingStarted).not.toContain('./src/main/dist/index.js');

expect(troubleshooting).toContain('RSELECTRON_ELECTRON_NOT_FOUND');
expect(troubleshooting).toContain('RSELECTRON_ROLE_MISSING');
Expand All @@ -371,6 +374,8 @@ test('guide pages cover concepts, parity, and learning sources in both languages
expect(migration).toMatch(/swc/i);
expect(migration).toMatch(/\/config\//);
expect(migration).toMatch(/\/api\//);
expect(migration).toMatch(/out\/main/);
expect(migration).toMatch(/distPath/);
}
});

Expand Down
Loading
Loading