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]
[![vuetify][badge-vuetify-img]][badge-vuetify-href]
[![pinia][badge-pinia-img]][badge-pinia-href]
[![eslint][badge-eslint-img]][badge-eslint-href]
[![storybook][badge-storybook-img]][badge-storybook-href]
The IT4C Boilerplate for frontends
@ -19,21 +20,6 @@ To be able to build this project you need:
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 |
|---------------------------|------------------------------------------|
| `npm install` | Project setup |
@ -43,6 +29,7 @@ The following commands are available:
| `npm run dev` | Compiles and hot-reloads for development |
| `npm run server:dev` | Run development server |
| **Test** | |
| `npm run lint` | Runs all linters |
| `npm test` | Run all tests & linters |
| **Storybook** | |
| `npm run storybook` | Run Storybook |
@ -56,13 +43,14 @@ The following commands are available:
- [x] vuetify
- [x] pinia store
- [x] storybook
- [ ] eslint
- [x] eslint
- [ ] figma
- [ ] chromatic
- [ ] jest
- [ ] localization?
- [ ] documentation?
- [ ] docker
- [ ] github actions
## 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-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-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",
"vuetify",
"pinia",
"storybook"
"storybook",
"eslint"
],
"author": "Ulf Gebhardt",
"license": "Apache-2.0",
@ -33,7 +34,8 @@
"server:prod": "cross-env NODE_ENV=production npm run server",
"storybook": "cross-env STORYBOOK=true storybook dev -p 6006",
"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": {
"@mdi/font": "^7.3.67",
@ -65,6 +67,10 @@
"@storybook/testing-library": "^0.2.2",
"@storybook/vue3": "^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-dom": "^18.2.0",
"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
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')
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')
}
/* To enable Client-side Routing:

View File

@ -10,19 +10,19 @@ import logoUrl from './logo.svg'
import type { PageContextServer } from './types'
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)
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)
const appHtml = await renderToString(app)
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'
// 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 documentHtml = escapeInject`<!DOCTYPE html>
const documentHtml = escapeInject`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@ -36,21 +36,21 @@ async function render(pageContext: PageContextServer) {
</body>
</html>`
return {
documentHtml,
pageContext: {
// We can add some `pageContext` here, which is useful if we want to do page redirection https://vike.dev/page-redirection
return {
documentHtml,
pageContext: {
// 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) {
let err: unknown
// Workaround: renderToString_() swallows errors in production, see https://github.com/vuejs/core/issues/7876
app.config.errorHandler = (err_) => {
err = err_
}
const appHtml = await renderToString_(app)
if (err) throw err
return appHtml
let err: unknown
// Workaround: renderToString_() swallows errors in production, see https://github.com/vuejs/core/issues/7876
app.config.errorHandler = (err_) => {
err = err_
}
const appHtml = await renderToString_(app)
if (err) throw err
return appHtml
}

View File

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

View File

@ -1,20 +1,21 @@
export type {
PageContextServer,
/*
PageContextServer,
/*
// When using Client Routing https://vike.dev/clientRouting
PageContextClient,
PageContext,
/*/
// When using Server Routing
PageContextClientWithServerRouting as PageContextClient,
PageContextWithServerRouting as PageContext
//*/
// When using Server Routing
PageContextClientWithServerRouting as PageContextClient,
PageContextWithServerRouting as PageContext
//*/
} from 'vike/types'
export type { PageProps }
export type { Component }
// https://vike.dev/pageContext#typescript
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Vike {
interface PageContext {
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 Page = Component
type PageProps = {}
type PageProps = object

View File

@ -11,11 +11,11 @@ export { setPageContext }
const key: InjectionKey<PageContext> = Symbol()
function usePageContext() {
const pageContext = inject(key)
if (!pageContext) throw new Error('setPageContext() not called in parent')
return pageContext
const pageContext = inject(key)
if (!pageContext) throw new Error('setPageContext() not called in parent')
return 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'
export default createVuetify({
components,
directives
components,
directives
})

View File

@ -20,55 +20,55 @@ const isProduction = process.env.NODE_ENV === 'production'
startServer()
async function startServer() {
const app = express()
const app = express()
app.use(compression())
app.use(compression())
// Vite integration
if (isProduction) {
// Vite integration
if (isProduction) {
// In production, we need to serve our static assets ourselves.
// (In dev, Vite's middleware serves our static assets.)
const sirv = (await import('sirv')).default
app.use(sirv(`${root}/dist/client`))
} else {
const sirv = (await import('sirv')).default
app.use(sirv(`${root}/dist/client`))
} else {
// 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
// would unnecessarily bloat our production server.)
const vite = await import('vite')
const viteDevMiddleware = (
await vite.createServer({
root,
server: { middlewareMode: true }
})
).middlewares
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 vite = await import('vite')
const viteDevMiddleware = (
await vite.createServer({
root,
server: { middlewareMode: true }
})
).middlewares
app.use(viteDevMiddleware)
}
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}`)
// ...
// 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)
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
const meta = {
title: 'Example/Button',
component: Button,
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
tags: ['autodocs'],
argTypes: {
size: { control: 'select', options: ['small', 'medium', 'large'] },
backgroundColor: { control: 'color' },
onClick: { action: 'clicked' },
},
args: { primary: false }, // default value
} satisfies Meta<typeof Button>;
title: 'Example/Button',
component: Button,
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
tags: ['autodocs'],
argTypes: {
size: { control: 'select', options: ['small', 'medium', 'large'] },
backgroundColor: { control: 'color' },
onClick: { action: 'clicked' },
},
args: { primary: false }, // default value
} satisfies Meta<typeof Button>
export default meta;
export default meta
type Story = StoryObj<typeof meta>;
/*
*👇 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.
*/
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
};
args: {
primary: true,
label: 'Button',
},
}
export const Secondary: Story = {
args: {
primary: false,
label: 'Button',
},
};
args: {
primary: false,
label: 'Button',
},
}
export const Large: Story = {
args: {
label: 'Button',
size: 'large',
},
};
args: {
label: 'Button',
size: 'large',
},
}
export const Small: Story = {
args: {
label: 'Button',
size: 'small',
},
};
args: {
label: 'Button',
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 = {
/* 👇 The title prop is optional.
/* 👇 The title prop is optional.
* See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'Example/Header',
component: MyHeader,
render: (args: any) => ({
components: { MyHeader },
setup() {
return { args };
},
template: '<my-header :user="args.user" />',
}),
parameters: {
title: 'Example/Header',
component: MyHeader,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render: (args: any) => ({
components: { MyHeader },
setup() {
return { args }
},
template: '<my-header :user="args.user" />',
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
layout: 'fullscreen',
},
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
tags: ['autodocs'],
} satisfies Meta<typeof MyHeader>;
layout: 'fullscreen',
},
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
tags: ['autodocs'],
} satisfies Meta<typeof MyHeader>
export default meta;
export default meta
type Story = StoryObj<typeof meta>;
export const LoggedIn: Story = {
args: {
user: {
name: 'Jane Doe',
args: {
user: {
name: 'Jane Doe',
},
},
},
};
}
export const LoggedOut: Story = {
args: {
user: null,
},
};
args: {
user: null,
},
}

View File

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

View File

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