Project structure
Where files go, how the compiler reads them, how names work across a project, and how a project grows without getting lost.
The layout
text
my-site/
├── webfluent.app.json the config (chapter 38)
├── .env build-time values, not committed (chapter 30)
├── src/
│ ├── App.wf the app shell and the Router
│ ├── theme.wf themes
│ ├── types.wf shared types, enums, constants, data
│ ├── api.wf services
│ ├── pages/ one page per file
│ ├── components/ components
│ ├── stores/ stores
│ ├── styles.css your own stylesheets, anywhere under src/
│ ├── translations/ en.json, ar.json (with "i18n" in the config)
│ ├── posts.json data files a `data` declaration reads
│ └── about.md Markdown pages
├── public/ copied to the build's root: favicon, fonts, images
├── tests/ `test` declarations, and __snapshots__/
└── build/ what `wf build` writes (not committed)
Only webfluent.app.json and one .wf or .wfx file under src/ are required. The folders are convention: the compiler does not care where a declaration lives.
One program, no imports
Every .wf and .wfx file under src/ is read and merged into a single program. A page uses a component declared in another file, and a component a store, by name, with nothing to import. App.wf is read first; the rest in path order, which matters for nothing but the order of equal things in the output.
That makes the namespace global: two components, two pages, two stores, two types or two constants with one name is an error, naming both places.
- Names that declare — pages, components, stores, types, enums, themes, services, externals, animations — are PascalCase.
- Constants are conventionally UPPER_SNAKE:
API,PAGE_SIZE. - A page's name is its identity, not its title. Suffix it when it would collide with a component:
page PostPagebesidecomponent Post. - A component used by one page can live in that page's file.
use Store at the top of a page or component says it reads that store. It is documentation for the reader and the editor; it scopes nothing.
Files that are not .wf
| File | Where | Becomes |
|---|---|---|
|
|
anywhere under |
bundled into |
|
|
under |
a page (Content) |
|
|
named by a |
a constant, read at build time |
| images |
named by an |
resized, hashed files and a |
|
|
the |
the locale tables |
| anything |
|
copied as it is to the build's root: |
Starting files: wf generate
bash
Each writes a starter declaration in the conventional folder — src/pages/Pricing.wf with path: "/pricing", a title, a description and an h1; src/components/PriceCard.wf; src/stores/Cart.wf — in .wfx when the project is written that way. It never overwrites a file.
What to commit
Commit webfluent.app.json, src/, public/, tests/ (with __snapshots__/). Do not commit:
text
build/
.wf-cache/
.wf-sizes.json
.env
.wf-cache/ holds resized images between builds; .wf-sizes.json what the last build weighed, for wf build --stats to compare against.
As a project grows
- Group by feature once folders get long.
src/billing/holding the billing pages, components, store and types reads better than four folders each holding a slice of it. The compiler does not mind either way. - Put a service in one place. One
apiper backend, in its own file, with the types its endpoints return beside it. - Keep shared types together, in a
types.wfper area, so the editor's go-to-definition lands somewhere predictable. - Write docs as you declare. A
///comment above a component, a prop, an event or a type is shown on hover and inwf docs, the gallery of everything the project declares. - Hold it to the formatter.
wf fmt --checkin CI keeps every file in one layout.
On this page
The layout One program, no imports Files that are not .wf Starting files: wf generate What to commit As a project growsChecked by the test suite
Every code block in the guide is parsed, checked and type-checked on each release.