install basic eslint

This commit is contained in:
Ulf Gebhardt 2023-11-15 11:56:07 +01:00
parent 88d65711af
commit 722be1eb05
Signed by: ulfgebhardt
GPG Key ID: DA6B843E748679C9
16 changed files with 1608 additions and 222 deletions

39
.eslintrc.json Normal file
View File

@ -0,0 +1,39 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-essential",
"plugin:vue/vue3-recommended"
],
"parserOptions": {
"ecmaVersion": "latest",
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"vue"
],
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
}
}

View File

@ -5,6 +5,7 @@
[![vike][badge-vike-img]][badge-vike-href] [![vike][badge-vike-img]][badge-vike-href]
[![vuetify][badge-vuetify-img]][badge-vuetify-href] [![vuetify][badge-vuetify-img]][badge-vuetify-href]
[![pinia][badge-pinia-img]][badge-pinia-href] [![pinia][badge-pinia-img]][badge-pinia-href]
[![eslint][badge-eslint-img]][badge-eslint-href]
[![storybook][badge-storybook-img]][badge-storybook-href] [![storybook][badge-storybook-img]][badge-storybook-href]
The IT4C Boilerplate for frontends The IT4C Boilerplate for frontends
@ -19,21 +20,6 @@ To be able to build this project you need:
The following commands are available: The following commands are available:
<!--
| Command | Description |
|---------------------------|------------------------------------------|
| `npm install` | Project setup |
| `npm run build` | Compiles and minifies for production |
| **Develop** | |
| `npm run dev` | Compiles and hot-reloads for development |
| `npm run preview` | Run production preview |
| **Test** | |
| `npm run lint` | Runs all linters |
| `npm test` | Run all tests & linters |
| **Storybook** | |
| `npm run storybook` | Run Storybook |
| `npm run build:storybook` | Build static storybook |
-->
| Command | Description | | Command | Description |
|---------------------------|------------------------------------------| |---------------------------|------------------------------------------|
| `npm install` | Project setup | | `npm install` | Project setup |
@ -43,6 +29,7 @@ The following commands are available:
| `npm run dev` | Compiles and hot-reloads for development | | `npm run dev` | Compiles and hot-reloads for development |
| `npm run server:dev` | Run development server | | `npm run server:dev` | Run development server |
| **Test** | | | **Test** | |
| `npm run lint` | Runs all linters |
| `npm test` | Run all tests & linters | | `npm test` | Run all tests & linters |
| **Storybook** | | | **Storybook** | |
| `npm run storybook` | Run Storybook | | `npm run storybook` | Run Storybook |
@ -56,13 +43,14 @@ The following commands are available:
- [x] vuetify - [x] vuetify
- [x] pinia store - [x] pinia store
- [x] storybook - [x] storybook
- [ ] eslint - [x] eslint
- [ ] figma - [ ] figma
- [ ] chromatic - [ ] chromatic
- [ ] jest - [ ] jest
- [ ] localization? - [ ] localization?
- [ ] documentation? - [ ] documentation?
- [ ] docker - [ ] docker
- [ ] github actions
## Known Problems ## Known Problems
@ -96,5 +84,8 @@ See [vite-plugin-ssr-vuetify](https://github.com/brillout/vite-plugin-ssr-vuetif
[badge-pinia-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=dependencies.pinia&label=pinia&color=green [badge-pinia-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=dependencies.pinia&label=pinia&color=green
[badge-pinia-href]: https://pinia.vuejs.org/ [badge-pinia-href]: https://pinia.vuejs.org/
[badge-eslint-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.eslint&label=eslint&color=yellow
[badge-eslint-href]: https://eslint.org/
[badge-storybook-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.storybook&label=storybook&color=yellow [badge-storybook-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.storybook&label=storybook&color=yellow
[badge-storybook-href]: https://storybook.js.org/ [badge-storybook-href]: https://storybook.js.org/

1368
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,8 @@
"vue", "vue",
"vuetify", "vuetify",
"pinia", "pinia",
"storybook" "storybook",
"eslint"
], ],
"author": "Ulf Gebhardt", "author": "Ulf Gebhardt",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -33,7 +34,8 @@
"server:prod": "cross-env NODE_ENV=production npm run server", "server:prod": "cross-env NODE_ENV=production npm run server",
"storybook": "cross-env STORYBOOK=true storybook dev -p 6006", "storybook": "cross-env STORYBOOK=true storybook dev -p 6006",
"build-storybook": "cross-env STORYBOOK=true && storybook build", "build-storybook": "cross-env STORYBOOK=true && storybook build",
"test": "echo \"Error: no test specified\" && exit 1" "lint": "eslint --ignore-path .gitignore .",
"test": "npm run lint"
}, },
"dependencies": { "dependencies": {
"@mdi/font": "^7.3.67", "@mdi/font": "^7.3.67",
@ -65,6 +67,10 @@
"@storybook/testing-library": "^0.2.2", "@storybook/testing-library": "^0.2.2",
"@storybook/vue3": "^7.5.3", "@storybook/vue3": "^7.5.3",
"@storybook/vue3-vite": "^7.5.3", "@storybook/vue3-vite": "^7.5.3",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint": "^8.53.0",
"eslint-plugin-vue": "^9.18.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"storybook": "^7.5.3" "storybook": "^7.5.3"

View File

@ -5,10 +5,10 @@ import type { PageContextClient } from './types'
// This render() hook only supports SSR, see https://vike.dev/render-modes for how to modify render() to support SPA // 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) { async function render(pageContext: PageContextClient) {
const { Page, pageProps } = pageContext const { Page, pageProps } = pageContext
if (!Page) throw new Error('Client-side render() hook expects pageContext.Page to be defined') if (!Page) throw new Error('Client-side render() hook expects pageContext.Page to be defined')
const app = createApp(Page, pageProps, pageContext) const app = createApp(Page, pageProps, pageContext)
app.mount('#app') app.mount('#app')
} }
/* To enable Client-side Routing: /* To enable Client-side Routing:

View File

@ -10,19 +10,19 @@ import logoUrl from './logo.svg'
import type { PageContextServer } from './types' import type { PageContextServer } from './types'
async function render(pageContext: PageContextServer) { async function render(pageContext: PageContextServer) {
const { Page, pageProps } = pageContext const { Page, pageProps } = pageContext
// This render() hook only supports SSR, see https://vike.dev/render-modes for how to modify render() to support SPA // 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') if (!Page) throw new Error('My render() hook expects pageContext.Page to be defined')
const app = createApp(Page, pageProps, pageContext) const app = createApp(Page, pageProps, pageContext)
const appHtml = await renderToString(app) const appHtml = await renderToString(app)
// See https://vike.dev/head // See https://vike.dev/head
const { documentProps } = pageContext.exports const { documentProps } = pageContext.exports
const title = (documentProps && documentProps.title) || 'Vite SSR app' const title = (documentProps && documentProps.title) || 'Vite SSR app'
const desc = (documentProps && documentProps.description) || 'App using Vite + Vike' const desc = (documentProps && documentProps.description) || 'App using Vite + Vike'
const documentHtml = escapeInject`<!DOCTYPE html> const documentHtml = escapeInject`<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -36,21 +36,21 @@ async function render(pageContext: PageContextServer) {
</body> </body>
</html>` </html>`
return { return {
documentHtml, documentHtml,
pageContext: { pageContext: {
// We can add some `pageContext` here, which is useful if we want to do page redirection https://vike.dev/page-redirection // We can add some `pageContext` here, which is useful if we want to do page redirection https://vike.dev/page-redirection
}
} }
}
} }
async function renderToString(app: App) { async function renderToString(app: App) {
let err: unknown let err: unknown
// Workaround: renderToString_() swallows errors in production, see https://github.com/vuejs/core/issues/7876 // Workaround: renderToString_() swallows errors in production, see https://github.com/vuejs/core/issues/7876
app.config.errorHandler = (err_) => { app.config.errorHandler = (err_) => {
err = err_ err = err_
} }
const appHtml = await renderToString_(app) const appHtml = await renderToString_(app)
if (err) throw err if (err) throw err
return appHtml return appHtml
} }

View File

@ -9,27 +9,27 @@ import { createPinia } from 'pinia'
export { createApp } export { createApp }
function createApp(Page: Component, pageProps: PageProps | undefined, pageContext: PageContext) { function createApp(Page: Component, pageProps: PageProps | undefined, pageContext: PageContext) {
const PageWithLayout = defineComponent({ const PageWithLayout = defineComponent({
render() { render() {
return h( return h(
PageShell, PageShell,
{}, {},
{ {
default() { default() {
return h(Page, pageProps || {}) return h(Page, pageProps || {})
} }
}
)
} }
) })
}
})
const pinia = createPinia() const pinia = createPinia()
const app = createSSRApp(PageWithLayout) const app = createSSRApp(PageWithLayout)
app.use(pinia) app.use(pinia)
app.use(vuetify) app.use(vuetify)
// Make pageContext available from any Vue component // Make pageContext available from any Vue component
setPageContext(app, pageContext) setPageContext(app, pageContext)
return app return app
} }

View File

@ -1,20 +1,21 @@
export type { export type {
PageContextServer, PageContextServer,
/* /*
// When using Client Routing https://vike.dev/clientRouting // When using Client Routing https://vike.dev/clientRouting
PageContextClient, PageContextClient,
PageContext, PageContext,
/*/ /*/
// When using Server Routing // When using Server Routing
PageContextClientWithServerRouting as PageContextClient, PageContextClientWithServerRouting as PageContextClient,
PageContextWithServerRouting as PageContext PageContextWithServerRouting as PageContext
//*/ //*/
} from 'vike/types' } from 'vike/types'
export type { PageProps } export type { PageProps }
export type { Component } export type { Component }
// https://vike.dev/pageContext#typescript // https://vike.dev/pageContext#typescript
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Vike { namespace Vike {
interface PageContext { interface PageContext {
Page: Page Page: Page
@ -34,4 +35,4 @@ import type { ComponentPublicInstance } from 'vue'
type Component = ComponentPublicInstance // https://stackoverflow.com/questions/63985658/how-to-type-vue-instance-out-of-definecomponent-in-vue-3/63986086#63986086 type Component = ComponentPublicInstance // https://stackoverflow.com/questions/63985658/how-to-type-vue-instance-out-of-definecomponent-in-vue-3/63986086#63986086
type Page = Component type Page = Component
type PageProps = {} type PageProps = object

View File

@ -11,11 +11,11 @@ export { setPageContext }
const key: InjectionKey<PageContext> = Symbol() const key: InjectionKey<PageContext> = Symbol()
function usePageContext() { function usePageContext() {
const pageContext = inject(key) const pageContext = inject(key)
if (!pageContext) throw new Error('setPageContext() not called in parent') if (!pageContext) throw new Error('setPageContext() not called in parent')
return pageContext return pageContext
} }
function setPageContext(app: App, pageContext: PageContext) { function setPageContext(app: App, pageContext: PageContext) {
app.provide(key, pageContext) app.provide(key, pageContext)
} }

View File

@ -5,6 +5,6 @@ import * as components from 'vuetify/lib/components/index.mjs'
import * as directives from 'vuetify/lib/directives/index.mjs' import * as directives from 'vuetify/lib/directives/index.mjs'
export default createVuetify({ export default createVuetify({
components, components,
directives directives
}) })

View File

@ -20,55 +20,55 @@ const isProduction = process.env.NODE_ENV === 'production'
startServer() startServer()
async function startServer() { async function startServer() {
const app = express() const app = express()
app.use(compression()) app.use(compression())
// Vite integration // Vite integration
if (isProduction) { if (isProduction) {
// In production, we need to serve our static assets ourselves. // In production, we need to serve our static assets ourselves.
// (In dev, Vite's middleware serves our static assets.) // (In dev, Vite's middleware serves our static assets.)
const sirv = (await import('sirv')).default const sirv = (await import('sirv')).default
app.use(sirv(`${root}/dist/client`)) app.use(sirv(`${root}/dist/client`))
} else { } else {
// We instantiate Vite's development server and integrate its middleware to our server. // We instantiate Vite's development server and integrate its middleware to our server.
// ⚠️ We instantiate it only in development. (It isn't needed in production and it // ⚠️ We instantiate it only in development. (It isn't needed in production and it
// would unnecessarily bloat our production server.) // would unnecessarily bloat our production server.)
const vite = await import('vite') const vite = await import('vite')
const viteDevMiddleware = ( const viteDevMiddleware = (
await vite.createServer({ await vite.createServer({
root, root,
server: { middlewareMode: true } server: { middlewareMode: true }
}) })
).middlewares ).middlewares
app.use(viteDevMiddleware) app.use(viteDevMiddleware)
}
// ...
// Other middlewares (e.g. some RPC middleware such as Telefunc)
// ...
// Vike middleware. It should always be our last middleware (because it's a
// catch-all middleware superseding any middleware placed after it).
app.get('*', async (req, res, next) => {
const pageContextInit = {
urlOriginal: req.originalUrl
} }
const pageContext = await renderPage(pageContextInit)
const { httpResponse } = pageContext
if (!httpResponse) {
return next()
} else {
const { body, statusCode, headers, earlyHints } = httpResponse
if (res.writeEarlyHints) res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) })
headers.forEach(([name, value]) => res.setHeader(name, value))
res.status(statusCode)
// For HTTP streams use httpResponse.pipe() instead, see https://vike.dev/stream
res.send(body)
}
})
const port = process.env.PORT || 3000 // ...
app.listen(port) // Other middlewares (e.g. some RPC middleware such as Telefunc)
console.log(`Server running at http://localhost:${port}`) // ...
// Vike middleware. It should always be our last middleware (because it's a
// catch-all middleware superseding any middleware placed after it).
app.get('*', async (req, res, next) => {
const pageContextInit = {
urlOriginal: req.originalUrl
}
const pageContext = await renderPage(pageContextInit)
const { httpResponse } = pageContext
if (!httpResponse) {
return next()
} else {
const { body, statusCode, headers, earlyHints } = httpResponse
if (res.writeEarlyHints) res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) })
headers.forEach(([name, value]) => res.setHeader(name, value))
res.status(statusCode)
// For HTTP streams use httpResponse.pipe() instead, see https://vike.dev/stream
res.send(body)
}
})
const port = process.env.PORT || 3000
app.listen(port)
console.log(`Server running at http://localhost:${port}`)
} }

View File

@ -1,22 +1,22 @@
import type { Meta, StoryObj } from '@storybook/vue3'; import type { Meta, StoryObj } from '@storybook/vue3'
import Button from './Button.vue'; import Button from './Button.vue'
// More on how to set up stories at: https://storybook.js.org/docs/vue/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/vue/writing-stories/introduction
const meta = { const meta = {
title: 'Example/Button', title: 'Example/Button',
component: Button, component: Button,
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs // This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
size: { control: 'select', options: ['small', 'medium', 'large'] }, size: { control: 'select', options: ['small', 'medium', 'large'] },
backgroundColor: { control: 'color' }, backgroundColor: { control: 'color' },
onClick: { action: 'clicked' }, onClick: { action: 'clicked' },
}, },
args: { primary: false }, // default value args: { primary: false }, // default value
} satisfies Meta<typeof Button>; } satisfies Meta<typeof Button>
export default meta; export default meta
type Story = StoryObj<typeof meta>; type Story = StoryObj<typeof meta>;
/* /*
*👇 Render functions are a framework specific feature to allow you control on how the component renders. *👇 Render functions are a framework specific feature to allow you control on how the component renders.
@ -24,29 +24,29 @@ type Story = StoryObj<typeof meta>;
* to learn how to use render functions. * to learn how to use render functions.
*/ */
export const Primary: Story = { export const Primary: Story = {
args: { args: {
primary: true, primary: true,
label: 'Button', label: 'Button',
}, },
}; }
export const Secondary: Story = { export const Secondary: Story = {
args: { args: {
primary: false, primary: false,
label: 'Button', label: 'Button',
}, },
}; }
export const Large: Story = { export const Large: Story = {
args: { args: {
label: 'Button', label: 'Button',
size: 'large', size: 'large',
}, },
}; }
export const Small: Story = { export const Small: Story = {
args: { args: {
label: 'Button', label: 'Button',
size: 'small', size: 'small',
}, },
}; }

View File

@ -1,42 +1,43 @@
import type { Meta, StoryObj } from '@storybook/vue3'; import type { Meta, StoryObj } from '@storybook/vue3'
import MyHeader from './Header.vue'; import MyHeader from './Header.vue'
const meta = { const meta = {
/* 👇 The title prop is optional. /* 👇 The title prop is optional.
* See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading * See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
* to learn how to generate automatic titles * to learn how to generate automatic titles
*/ */
title: 'Example/Header', title: 'Example/Header',
component: MyHeader, component: MyHeader,
render: (args: any) => ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any
components: { MyHeader }, render: (args: any) => ({
setup() { components: { MyHeader },
return { args }; setup() {
}, return { args }
template: '<my-header :user="args.user" />', },
}), template: '<my-header :user="args.user" />',
parameters: { }),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
layout: 'fullscreen', layout: 'fullscreen',
}, },
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs // This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
tags: ['autodocs'], tags: ['autodocs'],
} satisfies Meta<typeof MyHeader>; } satisfies Meta<typeof MyHeader>
export default meta; export default meta
type Story = StoryObj<typeof meta>; type Story = StoryObj<typeof meta>;
export const LoggedIn: Story = { export const LoggedIn: Story = {
args: { args: {
user: { user: {
name: 'Jane Doe', name: 'Jane Doe',
},
}, },
}, }
};
export const LoggedOut: Story = { export const LoggedOut: Story = {
args: { args: {
user: null, user: null,
}, },
}; }

View File

@ -1,34 +1,35 @@
import type { Meta, StoryObj } from '@storybook/vue3'; import type { Meta, StoryObj } from '@storybook/vue3'
import { within, userEvent } from '@storybook/testing-library'; import { within, userEvent } from '@storybook/testing-library'
import MyPage from './Page.vue'; import MyPage from './Page.vue'
const meta = { const meta = {
title: 'Example/Page', title: 'Example/Page',
component: MyPage, component: MyPage,
render: () => ({ render: () => ({
components: { MyPage }, components: { MyPage },
template: '<my-page />', template: '<my-page />',
}), }),
parameters: { parameters: {
// More on how to position stories at: https://storybook.js.org/docs/vue/configure/story-layout // More on how to position stories at: https://storybook.js.org/docs/vue/configure/story-layout
layout: 'fullscreen', layout: 'fullscreen',
}, },
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs // This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
tags: ['autodocs'], tags: ['autodocs'],
} satisfies Meta<typeof MyPage>; } satisfies Meta<typeof MyPage>
export default meta; export default meta
type Story = StoryObj<typeof meta>; type Story = StoryObj<typeof meta>;
// More on interaction testing: https://storybook.js.org/docs/vue/writing-tests/interaction-testing // More on interaction testing: https://storybook.js.org/docs/vue/writing-tests/interaction-testing
export const LoggedIn: Story = { export const LoggedIn: Story = {
play: async ({ canvasElement }: any) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const canvas = within(canvasElement); play: async ({ canvasElement }: any) => {
const loginButton = await canvas.getByRole('button', { const canvas = within(canvasElement)
name: /Log in/i, const loginButton = await canvas.getByRole('button', {
}); name: /Log in/i,
await userEvent.click(loginButton); })
}, await userEvent.click(loginButton)
}; },
}
export const LoggedOut: Story = {}; export const LoggedOut: Story = {}

1
src/vue.d.ts vendored
View File

@ -1,4 +1,5 @@
declare module '*.vue' { declare module '*.vue' {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Component: any const Component: any
export default Component export default Component
} }

View File

@ -3,11 +3,11 @@ import vike from 'vike/plugin'
import { UserConfig } from 'vite' import { UserConfig } from 'vite'
const config: UserConfig = { const config: UserConfig = {
plugins: [ plugins: [
vue(), vue(),
process.env.STORYBOOK !== 'true' && vike() // SSR only when storybook is not running process.env.STORYBOOK !== 'true' && vike() // SSR only when storybook is not running
], ],
ssr: { noExternal: ['vuetify'] } ssr: { noExternal: ['vuetify'] }
} }
export default config export default config