Skip to content

Commit

Permalink
Merge pull request #13 from IT4Change/boilerplate-function-layout
Browse files Browse the repository at this point in the history
feat(frontend): boilerplate function & layout
  • Loading branch information
ulfgebhardt authored Nov 23, 2023
2 parents f847458 + c81bdcd commit 55604e5
Show file tree
Hide file tree
Showing 32 changed files with 352 additions and 167 deletions.
3 changes: 3 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# META
PUBLIC_ENV__META__DEFAULT_TITLE="IT4C"
PUBLIC_ENV__META__DEFAULT_DESCRIPTION="IT4C Frontend Boilerplate"
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"import/no-relative-packages": "error",
"import/no-relative-parent-imports": [
"error",
{ "ignore": ["#[src,root,components,pages,assets,plugins,context,types]/*"] }
{ "ignore": ["#[src,root,components,pages,assets,layouts,stores,plugins,context,types]/*"] }
],
"import/no-self-import": "error",
"import/no-unresolved": "error",
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ coverage/
!.vuepress/
.vuepress/.temp/
.vuepress/.cache/
build-storybook.log
build-storybook.log
.env
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The project uses `vite` as builder, `vike` to do the SSR. The design framework i

Testing is done with `vitest` and code style is enforced with `eslint`, `remark-cli` and `stylelint`.

This projects utilizes `storybook` to develop frontend components and `vuepress` for static documentation generation.
This projects utilizes `storybook` and `chromatic` to develop, document & test frontend components and `vuepress` for static documentation generation.

## Commands

Expand Down Expand Up @@ -97,8 +97,8 @@ The following endpoints are provided given the right command is executed or all

## TODO

- [ ] figma
- [ ] feature zähler -> pinia tore
- [ ] tests
- [ ] stories

## Known Problems

Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 12 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@
"url": "git+https://github.com/IT4Change/boilerplate-frontend.git"
},
"keywords": [
"npm",
"nodejs",
"vite",
"vike",
"npm",
"docker",
"jq",
"vue",
"vike",
"vuetify",
"pinia",
"storybook",
"vue-i18n",
"eslint",
"remark-cli",
"stylelint",
"vitest",
"vue-i18n",
"storybook",
"vuepress",
"docker",
"remark-cli"
"chromatic"
],
"author": {
"name": "Ulf Gebhardt"
Expand Down Expand Up @@ -67,6 +69,7 @@
"cross-env": "^7.0.3",
"express": "^4.18.2",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"sass": "^1.69.5",
"sass-loader": "^13.3.2",
"sirv": "^2.0.3",
Expand Down Expand Up @@ -133,6 +136,8 @@
"#components/*": "./src/components/*",
"#pages/*": "./src/pages/*",
"#assets/*": "./src/assets/*",
"#layouts/*": "./src/layouts/*",
"#stores/*": "./src/stores/*",
"#plugins/*": "./renderer/plugins/*",
"#context/*": "./renderer/context/*",
"#types/*": "./types/*"
Expand Down
33 changes: 23 additions & 10 deletions renderer/_default.page.client.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import { createApp } from './app'

import type { PageContextClient } from '#types/PageContext'
import type { PageContext, VikePageContext } from '#types/PageContext'

// This render() hook only supports SSR, see https://vike.dev/render-modes for how to modify render() to support SPA
async function render(pageContext: PageContextClient) {
const { Page, pageProps } = pageContext
if (!Page) throw new Error('Client-side render() hook expects pageContext.Page to be defined')
const app = createApp(Page, pageProps, pageContext)
app.mount('#app')
let app: ReturnType<typeof createApp>
async function render(pageContext: VikePageContext & PageContext) {
if (!app) {
app = createApp(pageContext)
app.mount('#app')
} else {
app.changePage(pageContext)
}
}

/* To enable Client-side Routing:
export const clientRouting = true
// !! WARNING !! Before doing so, read https://vike.dev/clientRouting */
function onHydrationEnd() {
// console.log('Hydration finished; page is now interactive.')
}
function onPageTransitionStart() {
// console.log('Page transition start')
}
function onPageTransitionEnd() {
// console.log('Page transition end')
}

export const clientRouting = true
export const prefetchStaticAssets = 'viewport'
export { render }
export { onHydrationEnd }
export { onPageTransitionStart }
export { onPageTransitionEnd }
16 changes: 7 additions & 9 deletions renderer/_default.page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@ import { renderToString as renderToString_ } from '@vue/server-renderer'
import { escapeInject, dangerouslySkipEscape } from 'vike/server'

import logoUrl from '#assets/favicon.ico'
import { META } from '#src/env'

import { createApp } from './app'

import type { PageContextServer } from '#types/PageContext'
import type { PageContextServer, PageContext } from '#types/PageContext'
import type { App } from 'vue'

// See https://vike.dev/data-fetching
export const passToClient = ['pageProps', 'urlPathname']
export const passToClient = ['pageProps', /* 'urlPathname', */ 'routeParams']

async function render(pageContext: PageContextServer) {
const { Page, pageProps } = pageContext
// This render() hook only supports SSR, see https://vike.dev/render-modes for how to modify render() to support SPA
if (!Page) throw new Error('My render() hook expects pageContext.Page to be defined')
const app = createApp(Page, pageProps, pageContext)
async function render(pageContext: PageContextServer & PageContext) {
const app = createApp(pageContext, false)

const appHtml = await renderToString(app)

// See https://vike.dev/head
const { documentProps } = pageContext.exports
const title = (documentProps && documentProps.title) || 'Vite SSR app'
const desc = (documentProps && documentProps.description) || 'App using Vite + Vike'
const title = (documentProps && documentProps.title) || META.DEFAULT_TITLE
const desc = (documentProps && documentProps.description) || META.DEFAULT_DESCRIPTION

const documentHtml = escapeInject`<!DOCTYPE html>
<html lang="en">
Expand Down
58 changes: 47 additions & 11 deletions renderer/app.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,75 @@
import { createSSRApp, defineComponent, h } from 'vue'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { createSSRApp, defineComponent, h, markRaw, reactive } from 'vue'

import PageShell from '#components/PageShell.vue'
import { setPageContext } from '#context/usePageContext'
import i18n from '#plugins/i18n'
import pinia from '#plugins/pinia'
import CreateVuetify from '#plugins/vuetify'
import { Page } from '#types/Page'
import { PageProps } from '#types/PageProps'

import type { PageContext } from '#types/PageContext'
import type { Component } from '#types/Component'
import type { PageContext, VikePageContext } from '#types/PageContext'

function createApp(Page: Page, pageProps: PageProps | undefined, pageContext: PageContext) {
const PageWithLayout = defineComponent({
const vuetify = CreateVuetify(i18n)

function createApp(pageContext: VikePageContext & PageContext, isClient = true) {
let rootComponent: Component
const PageWithWrapper = defineComponent({
data: () => ({
Page: markRaw(pageContext.Page),
pageProps: markRaw(pageContext.pageProps || {}),
isClient,
}),
created() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
rootComponent = this
},
render() {
return h(
PageShell,
{},
{
default() {
return h(Page, pageProps || {})
default: () => {
return h(this.Page, this.pageProps)
},
},
)
},
})

const app = createSSRApp(PageWithLayout)
if (isClient) {
pinia.use(piniaPluginPersistedstate)
}

const app = createSSRApp(PageWithWrapper)
app.use(pinia)
app.use(i18n)
app.use(CreateVuetify(i18n))
app.use(vuetify)

objectAssign(app, {
changePage: (pageContext: VikePageContext & PageContext) => {
Object.assign(pageContextReactive, pageContext)
rootComponent.Page = markRaw(pageContext.Page)
rootComponent.pageProps = markRaw(pageContext.pageProps || {})
},
})

// When doing Client Routing, we mutate pageContext (see usage of `app.changePage()` in `_default.page.client.js`).
// We therefore use a reactive pageContext.
const pageContextReactive = reactive(pageContext)

// Make pageContext available from any Vue component
setPageContext(app, pageContext)
setPageContext(app, pageContextReactive)

return app
}

// Same as `Object.assign()` but with type inference
function objectAssign<Obj extends object, ObjAddendum>(
obj: Obj,
objAddendum: ObjAddendum,
): asserts obj is Obj & ObjAddendum {
Object.assign(obj, objAddendum)
}

export { createApp }
6 changes: 3 additions & 3 deletions renderer/context/usePageContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@

import { inject } from 'vue'

import { PageContext } from '#types/PageContext'
import { PageContext, VikePageContext } from '#types/PageContext'

import type { App, InjectionKey } from 'vue'

const key: InjectionKey<PageContext> = Symbol(undefined)
const key: InjectionKey<VikePageContext & PageContext> = Symbol(undefined)

function usePageContext() {
const pageContext = inject(key)
if (!pageContext) throw new Error('setPageContext() not called in parent')
return pageContext
}

function setPageContext(app: App, pageContext: PageContext) {
function setPageContext(app: App, pageContext: VikePageContext & PageContext) {
app.provide(key, pageContext)
}

Expand Down
3 changes: 2 additions & 1 deletion renderer/plugins/pinia.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createPinia } from 'pinia'

export default createPinia()
const pinia = createPinia()
export default pinia
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mount, config } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'

import ClickCounter from './ClickCounter.vue'
import ClickCounter from './ClickCounter.delete.vue'

describe('clickCounter', () => {
const wrapper = mount(ClickCounter)
Expand Down
File renamed without changes.
13 changes: 13 additions & 0 deletions src/components/ClientOnly.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<template v-if="isMounted"><slot /></template>
<template v-else><slot name="placeholder" /></template>
</template>

<script setup>
import { ref, onMounted } from 'vue'
const isMounted = ref(false)
onMounted(() => {
isMounted.value = true
})
</script>
63 changes: 1 addition & 62 deletions src/components/PageShell.vue
Original file line number Diff line number Diff line change
@@ -1,66 +1,5 @@
<template>
<v-app>
<v-container>
<div class="layout">
<div class="navigation">
<a href="/" class="logo">
<img :src="Logo" height="64" width="64" alt="logo" />
</a>
<VikeLink href="/">Home</VikeLink>
<VikeLink href="/about">About</VikeLink>
</div>
<div class="content"><slot /></div>
</div>
</v-container>
<slot />
</v-app>
</template>

<script lang="ts" setup>
import Logo from '#assets/it4c-logo2-clean-bg_alpha-128x128.png'
import VikeLink from './VikeLink.vue'
</script>

<style>
body {
margin: 0;
font-family: sans-serif;
}
* {
box-sizing: border-box;
}
a {
text-decoration: none;
}
</style>

<style scoped>
.layout {
display: flex;
max-width: 900px;
margin: auto;
}
.content {
min-height: 100vh;
padding: 20px;
padding-bottom: 50px;
border-left: 2px solid #eee;
}
.navigation {
display: flex;
flex-direction: column;
flex-shrink: 0;
align-items: center;
padding: 20px;
line-height: 1.8em;
}
.logo {
margin-top: 20px;
margin-bottom: 10px;
}
</style>
Loading

0 comments on commit 55604e5

Please sign in to comment.