JST 如何為 React 起始專案加入可執行的架構約束
Building a React starter with enforceable architecture
Borodach 將自己反覆採用的架構方法整理成 JST,一個以 ESLint 規則和倉庫檢查落實 React 專案架構邊界的起始專案。JST 提供生成器,以及為編碼智能體提供架構背景、生成器和有限範圍檢查的本地 MCP server;專案倉庫是規則的依據,網址為 https://github.com/jst-stack/jst。
My first website was planned on a sheet of squared notebook paper. The code in my early projects followed roughly the same standard: no tests, no clear structure, and a lot of work done just well enough to make the page run.
As those projects grew, working in my own mess became uncomfortable. I learned DRY, KISS, YAGNI, OOP, and SOLID, then tried to apply them to my pet projects. The code improved, but I still could not picture the boundaries of a growing application.
Around that time a friend and I started building a Telegram bot with a small character game. We spent hours discussing clean code, began implementing the bot, and soon found ourselves arguing about the directory structure. I thought it had to be organised one way; he thought it should be organised another way. Looking back, neither proposal was universally correct. That argument was where my search for a useful definition of good architecture started.
I spent a lot of time studying GitLab. Its codebase did not give me a structure I could copy into React, but it helped me understand how large projects assign responsibilities to layers and leave room for later growth. I then read about legacy refactoring and gradually assembled an approach I was comfortable using in my own projects.
JST began when I noticed I had recreated that approach more than once. I wanted to keep working this way without rebuilding the same foundation for every project, so I turned it into a template.
The first version was mostly conventional tooling: React, TypeScript, server rendering, routing, tests, and a directory structure. That was useful, but it did not preserve an architecture. A team can start with tidy folders and still end up calling HTTP clients from views, importing private files across features, and putting unrelated workflows into the same service.
So the project moved from providing a structure to defining responsibilities.
Pages compose user flows. Features own user outcomes. Entities own domain behavior and data contracts. External effects sit behind narrow interfaces. Concrete implementations are connected at the composition root. A typical remote-data path can contain a DTO, runtime validation, a mapper, a repository, a service, a view model, and a props-driven view.
This takes more code than calling fetch from a component. In return, transport formats do not leak into the UI, effects can be replaced in tests, and each part has a smaller reason to change.
A documented boundary is still easy to cross
Documentation helped people understand the intended structure, but it did not stop violations. The same issues kept returning in reviews:
importing an internal module instead of its public API;
reversing the dependency direction between layers;
placing workflow logic in a view;
letting shared infrastructure acquire product-specific behavior;
combining data access, mapping, state, and rendering in one file.
I started converting the rules that could be checked mechanically into ESLint rules and repository checks. They now run in the editor, before a commit, and in CI. Rejecting an import is easy. The diagnostic also needs to explain which boundary was crossed and indicate the expected route.
The architecture rules and general code-quality rules were later separated into different plugins. They overlap in daily use, but they represent different decisions. A project may want complexity and maintainability checks without adopting JST’s architecture. Another may want JST’s boundaries while choosing different limits for file or function size.
Generators as a starting constraint
JST includes generators for entities, features, widgets, modules, and packages. Their purpose is not to save a few keystrokes. They create the smallest valid public API and dependency shape so that a developer starts from a known state.
The generated code is intentionally small. The generator should establish a boundary, not predict the application’s future design.
Applying the same idea to coding agents
Support for coding agents came later. An agent can read an architecture document, summarize it correctly, and still violate it in the next edit. Executable checks give the agent feedback tied to the code it just changed.
JST therefore includes a project-local MCP server. It provides task-sized architecture context, exposes the existing generators, and runs bounded checks. The server does not contain a second copy of the project policy; the repository remains the source of truth.
The first evaluation was not flattering to JST. Under the original time limit, the control setup completed five of five retained runs, while JST completed one of five retained runs. I kept the result because it exposed a real problem: a constrained environment is not useful when an agent spends most of its time discovering how to work inside it.
After that run I reduced the amount of context, made diagnostics more direct, and added a short reproducible architecture proof. The post-fix canary confirms those specific repairs. It is not presented as a rerun of the original experiment.
The boundary of the project
JST does not teach SOLID on its own, choose the right domain model, or guarantee maintainable software. Static analysis cannot determine whether a product abstraction makes sense.
The project makes a chosen set of responsibilities and dependency boundaries visible while code is being written. Developers can learn inside those boundaries, discuss them, and change the configuration when the current rules stop fitting the project.
The repository is available at https://github.com/jst-stack/jst.
To create a project:
npm create jst@latest my-app
To reproduce the architecture violation-and-repair loop in the repository:
npm run proof:architecture
I am still working out which constraints provide useful feedback and which merely add friction. That distinction matters more to the project than the number of rules it can enforce.
來源:DEV Community · dev.to