Web Application
See how to create web applications using Vite for asset bundling.
Introduction
Vite is a modern frontend build tool that provides an extremely fast development environment and bundles your code for production. When building applications with Athenna, you will typically use Vite to bundle your application's CSS and JavaScript files into production ready assets.
Athenna integrates seamlessly with Vite by providing an official plugin and Edge.js directive to load your assets for development and production. This tool facilitates the development of full-stack web applications using Athenna as the backend framework.
Installing
We recommend to start a new Athenna project to setup all the configurations needed to get Athenna + Vite working together:
athenna new my-web-app
Don't forget to select WEB EDGE
, WEB REACT
or WEB REACT SSR
when
prompted about the application type.
Configuration
The web application adds the vite.config.ts
file that is used to
configure the Vite bundler, and a new plugin to
Path.config('http.ts')
./src/config/http.ts
vite
that is used
by Athenna on the backend.
Vite configuration file
The vite.config.ts
file is a regular configuration
file used by Vite. Per your project requirements, you can install and register
additional Vite plugins inside this file. By default, Vite file will use the
Athenna plugin, which accepts the following options:
entrypoints
The entrypoints refers to the entry point file of your frontend codebase. Usually, it will be a JavaScript or a TypeScript file with additional imports. Each entry point will result in a separate output bundle.
Also, if needed, you can define multiple entrypoints. For example, an entry point for your user-facing app and another for the admin panel.
assetsUrl
It contains the URL to prefix when generating links for assets in production. If you upload the Vite output to a CDN, then the value of this property should be the CDN server URL.
Ensure you update the backend configuration to use the same assetsUrl
value.
buildDirectory
The buildDirectory
option defines a relative path to the output
directory. The option value is supplied to Vite as the build.outDir
option.
If you decide to change the default value, make sure also to update the
buildDirectory
path in the vite
object inside
Path.config('http.ts')
./src/config/http.ts
Default: Path.public('assets')
reload
It contains an array of glob patterns to watch and reload the browser
on file change. By default, we watch for Edge templates in WEB EDGE
application and .tsx
files on WEB REACT
. However, you can
configure additional patterns as well.
Vite backend configuration
dev
Tell Athenna if should create vite development server or not.
By default, this is controlled by the Config.get('app.environment')
configuration which points to the APP_ENV
environment variable
defined in your .env
file.
assetsUrl
The URL to prefix when generating links for assets in production. If you upload the Vite output to a CDN, then the value of this property should be the CDN server URL.
buildDirectory
It contains the path to the Vite's build output directory.
You must also update this backend config if you change the
default value inside the vite.config.ts
file.
ssrEntrypoint
With this option set, SSR will be turn on into your application. This defines where Vite needs to look when compiling your server entrypoint files.
Default: src/resources/app/app.tsx
ssrBuildDirectory
It contains the path to the Vite's SSR build output directory, which needs to export the components so the server could import and serve it.
Default: Path.public('assets/server')
scriptAttributes
You can use the scriptAttributes
property to set attributes
on the script tags generated using the @vite
tag. The attributes
are a collection of key-value pairs.
export default {
vite: {
scriptAttributes: {
defer: true,
async: true
}
}
}
styleAttributes
You can use the styleAttributes
property to set attributes on
the link tags generated using the @vite
tag. The attributes
are a collection of key-value pairs.
export default {
vite: {
styleAttributes: {
'data-turbo-track': 'reload'
}
}
}
You can also apply the attributes conditionally by assigning a
function to the styleAttributes
option.
export default {
vite: {
styleAttributes: ({ src, url }) => {
if (src === 'src/resources/css/admin.css') {
return {
'data-turbo-track': 'reload'
}
}
}
}
}
Starting the dev server
You can start your application as usual, and Athenna will automatically
proxy the needed requests to Vite if the dev
option is true
in your
backend vite configuration:
node artisan serve
Including entrypoints in Edge templates
You can render the script and the style tags for the entrypoints defined
inside the vite.config.ts
file using the @vite
Edge tag. The tag
accepts an array of entrypoints and returns the script and the link tags.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@vite(['src/resources/js/app.ts'])
</head>
<body>
</body>
</html>
We recommend importing CSS files inside your TypeScript files and not registering them separately as an entry point. For example:
src
└── resources
└── css
└── app.css
└── js
└── app.ts
import '../css/app.css'
Referencing assets inside Edge templates
Vite creates a dependency graph of files imported by the entrypoints and auto-updates their paths per the bundled output. However, Vite is unaware of Edge templates and cannot detect their referenced assets.
Therefore, we provide an Edge helper you can use to create URLs for files processed by Vite. In the following example:
- The
asset
helper will return a URL pointing to the Vite dev server during development. - Return a URL pointing to the output filename during production.
<link rel="stylesheet" href="{{ asset('src/resources/css/app.css') }}">
<link rel="stylesheet" href="http://localhost:5173/src/resources/css/app.css">
<link rel="stylesheet" href="/public/assets/app-3bc29777.css">
Processing additional assets with Vite
Vite ignores static assets not imported by the frontend code. It could be static images, fonts, or SVG icons only referenced inside the Edge templates.
Therefore, you will have to notify Vite about the existence of these assets using its Glob imports API.
In the following example, we ask Vite to process all the files within the
src/resources/img
directory. This code should be written within an entry point file:
import.meta.glob(['../img/**'])
Now you can reference the images within your Edge templates as follows:
<img src="{{ asset('src/resources/img/hero.jpg') }}" />
Enabling HMR with React
To enable react-refresh during
development, you must use the @viteReactRefresh
Edge tag. It should be
written before you include the entrypoints using the @vite
tag.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@viteReactRefresh()
@vite(['src/resources/js/app.ts'])
</head>
<body>
</body>
</html>
Once done, you can configure the React plugin as usual in a regular Vite project.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import athenna from '@athenna/vite/plugins/client'
export default defineConfig({
plugins: [
athenna({
entrypoints: ["src/resources/js/app.ts"],
}),
react() 👈
]
})
We highly recommend you to bootstrap a new project using Athenna CLI
to select WEB REACT
application type. Will be easier if you start
this new project with everything configured then to try to do it manually,
just run the following and select WEB REACT
when prompted:
athenna new my-react-app
Deploying assets to a CDN
After you create the production build using Vite, you can upload the bundled output to a CDN server to serve the files.
However, before you do that, you must register the URL of your
CDN server with both Vite and Athenna so that the output URLs
inside the manifest.json
file or lazy loaded chunks should
point to your CDN server.
You must define the assetsUrl
inside the vite.config.ts
file and in vite
object of
Path.config('http.ts')
./src/config/http.ts
import { defineConfig } from 'vite'
import athenna from '@athenna/vite/plugins/client'
export default defineConfig({
plugins: [
athenna({
entrypoints: ['src/resources/js/app.ts'],
reloads: ['src/resources/views/**/*.edge'],
assetsUrl: 'https://cdn.example.com/',
}),
]
})
export default {
vite: {
buildDirectory: 'public/assets',
assetsUrl: 'https://cdn.example.com/'
}
}
Server Side Rendering (SSR)
Server Side Rendering is a technique in web development where the webpage's content is rendered on the server instead of the client's browser. The primary advantage of SSR lies in its ability to significantly enhance user experience by facilitating faster page transitions and quick loading times.
If you use WEB EDGE
application type you will be already using
SSR by default, but you can't use nice frameworks like React without some heavy
configuration.
That's why Athenna exposes the WEB REACT SSR
application type that will come
with everything configured for you to start building your application. The key
difference from WEB REACT
and WEB REACT SSR
is that instead of letting the
client create and load React root, we do that on the server and just hydrate the
root in the client, which is way less expensive for the client machine.
Configurations
The configurations in your vite
object of
Path.config('http.ts')
./src/config/http.ts
export default {
vite: {
// ...
ssrEntrypoint: 'src/resources/app/app.tsx',
ssrBuildDirectory: Path.public('assets/server')
}
}
ssrEntrypoint
will map your server entrypoint for serving your component.ssrBuildDirectory
will map where the server entrypoint compiled code should be stored. This property also defines where the manifest file of your server code will stored, in this case our manifest file would be stored inside.Path.public('assets/server/.vite/manifest.json')
./public/assets/server/.vite/manifest.json
Vite never will compile your backend code, this will still be done by
tsc
. The ssrEntrypoint
file is the entrypoint of your backend
code to the client code, Vite compiles your entrypoint in runtime
(in development) so we avoid adding features to tsc
to load files
like CSS, JSX, SASS and etc.
Hydrate part
In the
Path.resources('app/root.tsx')
./src/resources/app/root.tsx
import { App } from '#app/app'
import { hydrateRoot } from 'react-dom/client'
hydrateRoot(document.getElementById('root'), <App />)
For now Athenna has integration only with React for SSR. If you want support for other frameworks, please open a discussion.
Rendering React components
The @athenna/vite
package exposes the React
helper which you
can use to load and render components inside your backend routes.
The following example is the default example that comes with the
WEB REACT SSR
application type:
import { React } from '@athenna/vite'
import { Controller, type Context } from '@athenna/http'
@Controller()
export class AppController {
public async index({ request, response }: Context) {
const { createApp } = await React.loadEntrypoint()
const element = await React.renderComponent(createApp(request.baseUrl))
return response.view('index', { element })
}
}
- First we use the
loadEntrypoint()
method that internally will use Vite to compile (or search in manifest file) the content of yourssrEntrypoint
file. After that the module is imported and you can use all the resources from the file. - After importing the
createApp()
function from our entrypoint, we call the function to create our<App/>
component and send as parameter to therenderComponent()
method which will usereact-dom/server
to transform the component to an HTML string. - Now is simple, we just need to send our element to
index.edge
view which will render all the elements.
Importing other components
In case you want to import other components beside the entrypoint
you can use the loadComponent()
method:
const { createApp } = await React.loadComponent('src/resources/app/app.tsx')
Manifest file
Vite generates the manifest file alongside the production build of your assets.
The manifest file contains the URLs to the assets processed by Vite,
and Athenna uses this file to create URLs for assets referenced
inside the Edge templates either using the asset
helper or the @vite
tag.