Asset Management
CSS, JavaScript, and components auto-loaded by naming convention. No build step. No manifest file.
Directory Structure
Each module keeps its assets in an assets/ directory alongside its controller:
Automatic Symlinking
Module assets aren't served from the modules/ directory — the framework symlinks them into the public web root automatically. On the first request to a module, Module::__construct() creates a symlink:
The symlink target uses the lowercase module name (no hyphens, no PascalCase). This happens once and is transparent — you never need to create symlinks manually or run a build step.
The symlink is created on the first request and persists until you delete it. If you rename or move a module, remove the old symlink from www/assets/ and it will be recreated on the next request.
Auto-Loading by Convention
Zero auto-loads CSS and JS files based on their filenames, using kebab-case names that match URL segments:
| File Name Pattern | Loaded When | Example |
|---|---|---|
{module}.css / {module}.js |
Every endpoint in the module | hello-world.css loads for all HelloWorld pages |
{endpoint}.css / {endpoint}.js |
That specific endpoint only | say-hello.css loads only for /hello-world/say-hello |
Names use kebab-case to match URL format, not the PascalCase/camelCase class names. The framework converts internally.
Example: HelloWorld Module
Given this asset structure:
css/ ├── hello-world.css → Loaded for ALL HelloWorld endpoints └── say-hello.css → Loaded only for /hello-world/say-hello js/ ├── hello-world.js → Loaded for ALL HelloWorld endpoints └── say-hello.js → Loaded only for /hello-world/say-hello
When a user visits /hello-world/say-hello, the framework automatically includes:
hello-world.css— matches the module namesay-hello.css— matches the endpoint namehello-world.js— matches the module namesay-hello.js— matches the endpoint name
When the same user visits /hello-world (index endpoint), only the module-level files load — say-hello.css and say-hello.js are excluded.
Auto-Load Helper Methods
The framework provides three methods on the Response base class that output the appropriate HTML tags for auto-loaded assets:
| Method | Output | Description |
|---|---|---|
$this->getStylesheets() |
<link> tags |
Outputs CSS files matching the module and endpoint names |
$this->getScripts() |
<script> tags |
Outputs JS files matching the module and endpoint names |
$this->getComponents() |
Component includes | Loads ShadowComponent HTML files for the module |
In the default frame, these methods are called automatically in frame/head.php. You never need to call them unless you override the frame.
Frame Overrides
If a module provides its own frame files (for example modules/MyModule/frame/head.php), you must call the auto-load helpers manually to preserve automatic asset loading:
<head>
<meta charset="UTF-8">
<title><?php echo $this->title; ?></title>
<!-- Your custom head content -->
<link rel="stylesheet" href="/assets/mymodule/css/custom-base.css">
<!-- Preserve auto-loading -->
<?php $this->getStylesheets(); ?>
<?php $this->getScripts(); ?>
<?php $this->getComponents(); ?>
</head>
If you override frame files without calling $this->getStylesheets() and $this->getScripts(), your module's convention-based CSS and JS files won't load. The framework has no other mechanism to inject them.
Non-Convention Assets
Files that don't match the {module}.css or {endpoint}.css naming pattern won't be auto-loaded. To include them, reference them directly in your frame or view:
<!-- Manually include a non-convention asset --> <link rel="stylesheet" href="/assets/mymodule/css/vendor-lib.css"> <script src="/assets/mymodule/js/chart-library.js"></script> <!-- Then auto-load convention-based assets --> <?php $this->getStylesheets(); ?> <?php $this->getScripts(); ?>
This is useful for third-party libraries, shared utility files, or any CSS/JS that doesn't follow the naming convention.