Single Page Apps
This page focuses on the fundamentals needed to develop and build SPA apps with Tohama.
Install
Since Tohama packages are private GitHub packages, add a .npmrc
file to the root of your app.
// .npmrc
@dija:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=[YOUR_AUTH_TOKEN]
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"help": "vite --help"
},
"dependencies": {
"@dija/tohama-assets": "^2.0.0",
"@dija/tohama-components": "^2.0.0",
"@dija/tohama-plugins": "^2.0.0",
"@dija/tohama-shared": "^2.0.0",
"@dija/tohama-spa": "^2.0.0",
"@dija/tohama-state": "^2.0.0",
"@dija/tohama-use": "^2.0.0",
"@dija/tohama-validation": "^2.0.0",
"@dija/tohama-vite": "^2.0.0",
"vue": "^3",
"vite": "^4"
},
"devDependencies": {
"@dija/eslint-config": "^2.1.0"
}
}
Structure
This is the recommended structure for a production-ready app with Tohama. See the @dija/tohama-starter
repository for a full example.
my-app/
src/
app/
component.tsx
index.scss
index.ts
components/
locales/
en.json
pages/
home-page.tsx
views/
home-view
component.tsx
index.ts
main.ts
dist/
.eslintrc
.npmrc
index.html
package.json
tsconfig.eslint.json
tsconfig.json
tohama.config.ts
css.config.ts
vite.config.ts
vite.config.ts
This is vite
configurations file. Use it to add custom plugins, customize dev server, and more. Make sure to add VitePluginTohama
to the list of plugins.
import { VitePluginTohama } from "@dija/tohama-vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [VitePluginTohama()],
});
tohama.config.ts
Use this file to override Tohama build-time default configurations.
import { defineConfig } from "@dija/tohama-vite";
export default defineConfig({
/**
* Components libraries you want to generate CSS for, On-demand.
*/
cssTranspile: [
{
name: "@dija/tohama-components",
},
{
name: "@dija/tohama-plugins",
},
],
/**
* Autogenerate head meta attributes in output index.html and manifest.webmanifest.
*/
meta: {
host: "https://tohama.io",
title: "Tohama Starter",
titleFull: "Tohama Starter",
description: "This is a starter template to quickly start developing web apps with tohama.",
includeCards: true,
includeFavicons: true,
includeManifest: true,
social: {
twitterHandle: "@dijasols",
twitter: "https://twitter.com/dijasols",
},
},
});
css.config.ts
Use this file to override Tohama default configurations for UnoCSS
.
import { createUnocssConfig, colors } from "@dija/tohama-vite";
export default createUnocssConfig({
/**
* Pre-generate CSS helper classes.
*/
generator: {
/**
* Autogenerate flex classes.
*/
flex: {
direction: ["row", "col"],
justify: ["center", "between", "end"],
align: ["start", "center", "stretch"],
breakpoints: ["re"],
},
/**
* Autogenerate grid classes.
*/
grid: {
cols: ["6"],
gap: ["2"],
breakpoints: ["re"],
},
},
/**
* Customize UnoCSS theme.
*/
theme: {
colors: {
primary: colors.indigo,
secondary: colors.slate,
},
},
});
index.html
This is vite
entry file. Use it to directly add external dependencies.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
main.ts
This is your app's entry file. Use it to define your app component, routes, locales, and setup plugins.
import { createDefaults } from "@dija/tohama-components";
import { runApp } from "@dija/tohama-spa";
/**
* Tohama css output is injected to this virtual module
*/
import "virtual:tohama.css";
/**
* Your css output is injected to this virtual module
*/
import "uno.css";
/**
* Create App
*/
export default runApp(
{
app: () => import("@/app"),
initialLocale: "en",
locales: [
{
code: "en",
direction: "ltr",
messages: () => import("@/locales/en.json"),
},
{
code: "ar",
direction: "rtl",
messages: () => import("@/locales/ar.json"),
},
],
routes: [
{
path: "/",
name: "home",
component: () => import("@/pages/home-page"),
},
{
path: "/about",
name: "about",
component: () => import("@/pages/about-page"),
},
{
path: "/contact",
name: "contact",
component: () => import("@/pages/contact-page"),
},
],
},
/**
* Hook to configure plugins before the app starts
*/
async ({ app }) => {
/**
* Set components defaults
*/
const defaults = createDefaults({
title: {
color: "primary",
weight: "semibold",
},
});
app.use(defaults);
}
);
app
This directory contains the first component in your app. Use it to define global attributes and style. It should return a t-app
component in order to enable many of the required features such as CSS resets, fonts, light/dark theme support.
// app/index.ts
import component from "./component";
export default component;
// app/component.tsx
import { TApp, TCell, TDivider, TRouterView } from "@dija/tohama-components";
import { useGlobalTranslate, useHeadMeta } from "@dija/tohama-plugins";
import { usePwaSnackbar } from "@dija/tohama-spa";
import { computed, defineComponent } from "vue";
import "./index.scss";
export default defineComponent({
setup() {
usePwaSnackbar();
const translate = useGlobalTranslate();
const name = computed(() => {
return translate.t("page-title");
});
useHeadMeta({ name });
return () => (
<TApp class="app">
<header>My header</header>
<TCell fill>
<TRouterView transition="slide-y" fill />
</TCell>
</TApp>
);
},
});
// app/index.scss
.app {
@apply transition-colors light:bg-secondary-50 dark:bg-secondary-900;
}
locales
This directory contains global locales files. Create a json
file per locale containing
its messages.
// locales/en.json
{
"title": "Tohama",
"description": "This library includes components and utils to make building apps easier."
}
pages
This directory contains route pages' components. A page component is used to read the route's parameters and pass their values to a view
. You can also use it to define the page head meta.
// pages/home-page.tsx
import { HomeView } from "@/views";
import { useHeadMeta } from "@dija/tohama-plugins";
import { computed, defineComponent } from "vue";
import { useRouter } from "vue-router";
export default defineComponent({
setup() {
const router = useRouter();
const id = computed<string | undefined>(() => {
return router.currentRoute.value.params.id?.toString();
});
useHeadMeta({
title: "Home",
});
return () => <HomeView id={id.value} />;
},
});
views
This directory contains components that are reusable across pages
and popups
. Separating the view layer gives you the most consistency, where your components
can be reused easily.
// views/home-view/component.tsx
import { TText } from "@dija/tohama-components";
import { defineComponent } from "vue";
export default defineComponent({
props: {
id: String,
},
setup(props) {
return () => <TText>{props.id}</TText>;
},
});