Directory structure

A phossil project keeps your site in phossil/, the tooling at the root, and the build in dist/. This site is a phossil project too, laid out the same way.

Overview

After you clone it, run composer install and pnpm install, and build once, a project looks like this:

my-site/
my-site/
├── phossil/
│   ├── components/
│   ├── css/
│   ├── enums/
│   ├── img/
│   ├── includes/
│   ├── js/
│   ├── routes/
│   │   └── web.php
│   ├── services/
│   ├── support/
│   ├── views/
│   │   ├── About/
│   │   ├── Home/
│   │   └── NotFound/
│   ├── config.php
│   └── index.php
├── docker/
├── dist/
├── node_modules/
├── vendor/
├── composer.json
├── docker-compose.yml
├── package.json
└── vite.config.js

The root

The root holds the tools that run and build the site, and the folders they install or create.

Path What it does
phossil/ Your site: routes, views, components, CSS, JS and images. See The phossil directory.
composer.json The PSR-4 autoloading for the Phossil\ namespaces, and the build and build:zip scripts.
package.json Vite, Tailwind, oxfmt and oxlint, and the pnpm scripts that run them.
vite.config.js The Vite entries, Tailwind, and the dev server settings for running in Docker.
docker-compose.yml The php, nginx and vite services.
docker/ The nginx site config and the Vite container's Dockerfile.
.oxfmtrc.json, .oxlintrc.json Settings for the formatter and the linter.

The phossil directory

Everything the site is made of lives in phossil/. Classes in components/, enums/, services/, support/ and views/ autoload under the matching Phossil\ namespace, such as Phossil\Views\About\About, so a new class in one of them needs no require.

Path What it does
index.php The front controller. nginx sends it every page request, and it hands the request to the router.
config.php Settings as a plain array: debug output, where vendor/ is, and the URL assets are served under.
routes/web.php The list of paths and the view each one renders. Add a page here.
views/ One folder per page, such as views/About/About.php. A page made of sections can keep them in its folder as their own views, as views/Home/ does. NotFound is the 404 page.
components/ View, the base class for pages; Layout, the page chrome; and components such as Button and Card, each configured by an *Options class.
services/ The Router, which matches a path to its view, and Build, which renders every route to dist/.
support/ Helpers behind the scenes: Vite links the CSS and JS, Heroicon inlines icons, and Request reads the current path.
enums/ Typed choices, such as Icon for every Heroicon name and Buttons for button styles.
includes/ bootstrap.php loads the config, autoloader, helpers and routes; functions.php defines asset(), vite() and e(); build.php is the script composer build runs.
css/app.css The Tailwind stylesheet, built by Vite.
js/ The JavaScript, built by Vite from app.js.
img/ Images, favicons and the Heroicons, served as they are.
To autoload classes from a new folder, add its namespace to autoload.psr-4 in composer.json, then run composer dump-autoload.

Generated files

Git ignores these, because a command makes each one. Delete any of them and run the command again to get it back.

Path What it does
vendor/ composer install. Holds the class autoloader.
node_modules/ pnpm install.
phossil/hot The Vite dev server, while it runs. Its being there tells vite() to link the dev server.
phossil/dist/ pnpm build. The hashed CSS and JS, and Vite's manifest.
dist/ composer build. The static site.
dist.zip composer build:zip. dist/ zipped, ready to upload.

What gets served

While you develop, nginx serves these from phossil/:

  • Images from img/, at /assets/img/.
  • The built CSS and JS from Vite's dist/, at /assets/.
  • Every other URL as a page, rendered by index.php.

No other file in phossil/ can be requested, so the PHP source stays private.

In production, only dist/ is served. It has an index.html for each route, a 404.html, and the CSS, JS and images under assets/, at the same URLs as in development. None of the PHP is deployed.