ChaoLiou / vite-starter

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

A frontend boilerplate for vite applications

Supports

  • Language: typescript
  • Vue: vue3, vite, vuex@next, vue-router@next
  • CSS Preprocessor: sass, scss, less
  • UI Library: ant-design-vue@next
  • Testing Library: jest, cypress
  • WebSocket Client: socket.io-client

Enhancements

  • Vuex type intellisense
    • .vue: πŸ”₯ Type intellisense also support on .vue. That's right! Try yourself to feel the difference.
    • Actions: The action context properties are all supporting type intellisense
      • including commit, dispatch, state, rootState, getters, rootGetters
    • Getters: The getter context parameters are all supporting type intellisense
      • including state, rootState, getters, rootGetters
  • Antd
    • Styles import on demand
    • Theme customization
  • WS x Vuex Plugin
    • Receiving WS events will be triggered to dispatch the same name actions of a specified namespaced store.
  • Code-split & Gzip

Please follow steps to scaffold your project based on vite-starter

1. Clone vite-starter and change directory inside

git clone https://github.com/ChaoLiou/vite-starter.git your-project-name
cd your-project-name

2. Remove .git folder to discard git history of vite-starter

rmdir /s /q .git # Windows
rm -rf .git # MacOS or Linux

3. Init git for your-project-name and create first commit

git init
git add .
git commit -m "project init"

4. open your vscode and have fun!

code .
yarn install
yarn dev # wait on localhost:3000

Bonus: You can copy & paste one-line command to finish all these steps.

Windows

git clone https://github.com/ChaoLiou/vite-starter.git your-project-name & cd your-project-name & rmdir /s /q .git & git init & git add . & git commit -m "project init" & code .

MacOS or Linux

git clone https://github.com/ChaoLiou/vite-starter.git your-project-name & cd your-project-name & rm -rf .git & git init & git add . & git commit -m "project init" & code .

Folder structure

β”œβ”€β”€ build                           # all about vite/rollup build
|   β”œβ”€β”€ generateModifyVars.ts       # customize theme(antd)
β”‚   └── styleImport.ts              # [DNC] introduces component library(antd) styles on demand
β”œβ”€β”€ public
β”‚   β”œβ”€β”€ configs
|   |   └── index.js                # global variables: 'processEnv' for environment variables
β”‚   β”œβ”€β”€ fonts                       # Roboto fonts
β”‚   β”œβ”€β”€ *.svg                       # svg files
β”‚   └── favicon.ico
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ asseets
β”‚   β”œβ”€β”€ components
β”‚   β”‚   β”œβ”€β”€ dashboard               # related to dashboard page
β”‚   β”‚   β”œβ”€β”€ debug                   # born for debugging
β”‚   β”‚   β”œβ”€β”€ icons                   # component wrapping with svg
β”‚   β”‚   β”œβ”€β”€ statusLabel             # badge for vm/ltm status
|   |   └── *.vue
β”‚   β”œβ”€β”€ composables                 # divide your component's logical concerns
|   |   β”œβ”€β”€ global                  # composables for global use
|   |   β”œβ”€β”€ tableControl            # related to TableControl
|   |   └── use*.ts
β”‚   β”œβ”€β”€ interfaces
|   |   β”œβ”€β”€ *Interface.ts           # view model extended with data model
|   |   └── UtilityInterface.ts     # utility usage
β”‚   β”œβ”€β”€ miragejs
|   |   β”œβ”€β”€ generators              # fake data generator function or pure json
|   |   β”œβ”€β”€ routes                  # miragejs server route configs
|   |   β”œβ”€β”€ directus.ts             # directus cms api
|   |   β”œβ”€β”€ server.ts               # miragejs server
|   |   └── utils.ts                # utils/helpers, e.g. randomPick, randomAmountOfResult
β”‚   β”œβ”€β”€ models
|   |   β”œβ”€β”€ *Model.ts               # data model for api response
|   |   └── UtilityModel.ts         # utility usage
β”‚   β”œβ”€β”€ pageConfigs
|   |   β”œβ”€β”€ declarations.ts         # interface extended with npm package's declarations
|   |   β”œβ”€β”€ *PageConfig.ts          # specified page configs
|   |   └── utilityPageConfig.ts    # utility usage
β”‚   β”œβ”€β”€ plugins
|   |   β”œβ”€β”€ localStorage
|   |   |   β”œβ”€β”€ index.ts            # local storage utils/helpers
|   |   |   └── mock.ts             # local storage mock
|   |   β”œβ”€β”€ ws
|   |   |   β”œβ”€β”€ declarations.ts     # [DNC] type/interface plugin options
|   |   |   └── socket-io.ts        # socket-io client plugin
|   |   └── formatter.ts            # data format functions
β”‚   β”œβ”€β”€ router
|   |   β”œβ”€β”€ menu                    # menu items
|   |   β”œβ”€β”€ routes                  # route items
|   |   └── index.ts
β”‚   β”œβ”€β”€ services
|   |   └── *Service.ts             # requests to api
β”‚   β”œβ”€β”€ store
|   |   β”œβ”€β”€ dashboard
|   |   β”œβ”€β”€ ltm
|   |   β”œβ”€β”€ profile
|   |   β”œβ”€β”€ server
|   |   β”œβ”€β”€ serverEnv
|   |   β”œβ”€β”€ wsqueue
|   |   β”œβ”€β”€ declaration.ts
|   |   └── index.ts
β”‚   β”œβ”€β”€ styles
|   |   β”œβ”€β”€ modules                 # scss modules
|   |   |   β”œβ”€β”€ _color.scss
|   |   |   └── _font.scss
|   |   └── index.scss              # global scss
β”‚   β”œβ”€β”€ utils
|   |   └── http
|   |       └── axios.ts            # axios client
β”‚   β”œβ”€β”€ views
β”‚   β”‚   β”œβ”€β”€ Server
β”‚   |   |   β”œβ”€β”€ Detail.vue
β”‚   |   |   └── List.vue
β”‚   |   β”œβ”€β”€ Dashboard.vue
β”‚   |   └── LoadBalancing.vue
β”‚   β”œβ”€β”€ App.vue
β”‚   β”œβ”€β”€ env.d.ts                    # [DNC] provided from vite
β”‚   β”œβ”€β”€ main.ts
β”‚   └── router.ts                   # declaractions for Vue Route Meta
└── tests
    β”œβ”€β”€ e2e
    |   β”œβ”€β”€ fixtures                # [DNC]
    |   β”œβ”€β”€ integration             # e2e tests
    |   |   └── basic.spec.ts
    |   β”œβ”€β”€ plugins                 # [DNC]
    |   β”œβ”€β”€ support                 # init cypress
    |   |   β”œβ”€β”€ commands.ts         # for extending {cy} methods
    |   |   └── index.ts            # [DNC]
    |   └── tsconfig.json           # [DNC]
    └── unit
        β”œβ”€β”€ components
        |   β”œβ”€β”€ *.spec.ts           # unit tests for components
        |   └── setupTests.ts       # [DNC]
        β”œβ”€β”€ composables
        |   └── *.spec.ts           # unit tests for composables
        └── *.spec.ts               # unit tests
[DNC]: means 'DO NOT CHANGE'
* declarations.ts: contains related types and interfaces

About ant-design-vue@next

How to customize theme

// build/generateModifyVars.ts

...
export function generateModifyVars(dark = false) {
  const modifyVars = getThemeVariables({ dark });
  return {
    ...modifyVars,
    // custom themes
  };
}
...

You could go to default theme for available variables.

The reason for doing below this

// src/main.ts

if (import.meta.env.DEV) {
  import('ant-design-vue/dist/antd.less');
}

First of all, vite-starter is using import styles on demand feature for ant-design-vue component styles.

// vite.config.ts
...
import { configStyleImportPlugin } from './build/styleImport';
...

export default defineConfig(({ command }: ConfigEnv): UserConfig => {
  const isBuild = command === 'build';
  return {
    ...
    plugins: [..., configStyleImportPlugin(isBuild)],
    ...
  };
});

But if we use import styles on demand feature on development mode, it might slow down the browser refresh speed. Therefore, we only enable in production mode, and import whole theme on development mode.

About interfaces and models

What's difference between interfaces and models?

A model is the definition of data from api service, we only define the fields we needs, and a interface is rewrapping from a model, like a view model for component data scheme.

Example in vite-starter

A fake feature api response:

// public/data/features.json
[
  {
    "id": 1,
    "title": "Language",
    "tags": ["typescript"]
  },
  ...
]

A model for feature api response:

// models/FeatureModel.ts
export interface FeatureModel {
  title: string;
  tags: string[];
}

A interface for FeatureList.vue:

// interfaces/FeatureInterface.ts
import { FeatureModel } from '@/models/FeatureModel';

export interface FeatureInterface extends FeatureModel {
  highlightedTags: string[];
}

You can see that the highlightedTags field in FeatureInterface is just for component logic concerns, not exactly a data field, so we need a view model for division from 2 models.

About store

How to add a new state on existing store?

// store/*/state.ts

...
export const state = {
  ...
  newState: {} as NewStateType,
};
...

Add a new state and define a type for it.

How to add a new getter on existing store?

// store/*/declarations.ts
...
export type DeclareGetters = {
  ...
  newGetter(...context: GetterContext<State, Getters>): NewGetterType;
};

// store/*/getters.ts
...
import { DeclareGetters as Getters } from './declarations';

export const getters: GetterTree<State, RootState> & Getters = {
  ...
  newGetter: (state, getters, rootState, rootGetters) => {
    // return your `NewGetterType` value
  },
};

Add a definition of new getter into Getters type, including name(newGetter) and return type(NewGetterType), then add new getter into getters. According to vuex API documentation, each getter callback should at most have 4 parameters.

* GetterContext is wrapping through our own State, Getters, RootState and RootGetters that let you use intellisense feature.

How to add a new mutation on existing store?

// store/*/types.ts

export enum MutationTypes {
  NEW_MUTATION_TYPE = 'NEW_MUTATION_TYPE',
}

First, add new type name of your new mutation.

// store/*/declarations.ts

export type Mutations<S = State> = {
  ...
  [MutationTypes.NEW_MUTATION_TYPE](state: S, payload: NewMutationPayloadType): void;
};

// store/*/mutations.ts
export const mutations: MutationTree<State> & Mutations = {
  ...
  [MutationTypes.NEW_MUTATION_TYPE](state, payload: NewMutationPayloadType) {
    // mutate your state by payload
  },
};

Add a definition of new mutation into Mutations type, including name(import from MutationTypes) and payload type(NewMutationPayloadType), then add new mutation into mutations.

How to add a action on existing store?

// store/*/types.ts

export enum ActionTypes {
  NEW_ACTION_TYPE = 'NEW_ACTION_TYPE',
}

First, add new type name of your new action.

// store/*/declarations.ts

export interface Actions {
  ...
  [ActionTypes.NEW_ACTION_TYPE]({ commit, dispatch, state, rootState, getters, rootGetters }: ActionContext<State, Getters>, payload: NewActionPayloadType): void;
}

// store/*/actions.ts
export const actions: ActionTree<State, RootState> & Actions = {
  ...
  [ActionTypes.NEW_ACTION_TYPE]({ commit, dispatch, state, rootState, getters, rootGetters }) {
    // commit mutation, or dispatch actions from current store
  },
};

Add a definition of new action into Actions type, including name(import from ActionTypes) and payload type(NewActionPayloadType), then add new action into actions. According to vuex API documentation, each action callback should have a context parameter which contains 6 properties.

* ActionContext is wrapping through our own commit type, dispatch type, State, Getters, RootState and RootGetters that let you use intellisense feature.

How to add a new store?

  1. Clone store/sample folder including *.ts inside, and rename folder with your module name
  2. Here are what you should change in *.ts file and the main store file, such as store/index.ts store/declarations.ts.

store/myModule/declarations.ts

...
type ModuleName = 'myModule';
...

Change sample to your module name.

store/declarations.ts

...
import {
  ModuleName as MyModuleModule,
  Store as MyModuleStore,
  State as MyModuletate,
  NamespacedActions as MyModuleActions,
  NamespacedMutations as MyModuleMutations,
  NamespacedGetters as MyModuleGetters,
} from '@/store/myModule/declarations';

export type RootState = {
  ...
  myModule: MyModuleState;
};

export type RootMutations = ... & MyModuleMutations;
export type RootActions = ... & MyModuleActions;
export type RootGetters = ... & MyModuleGetters;

export type Store = ... &
  SampleStore<Pick<RootState, MyModuleModule>>;
...

store/index.ts

...
import { store as myModule } from '@/store/myModule';
...
export const store = createStore({
  plugins,
  modules: {
    ...
    myModule,
  },
});
...

About composables

According to Vue3 Guide, the purpose of composition api is to extract logical concerns into standalone composition functions, or you can think as what functionalities should be hooked on this component? and then pick them under composables folder.

About tests/unit

According to Vue3 Guide, unit tests allow you to test individual units of code in isolation. The purpose of unit testing is to provide developers with confidence in their code. By writing thorough, meaningful tests, you achieve the confidence that as new features are built or your code is refactored your application will remain functional and stable.

Think about these questions:

  • What's unit in our code?
  • What kind of code is that you'll have no confidence if you change it again?
  • What kind of test is meaningful to you?

About tests/e2e

According to Vue3 Guide, end-to-end (E2E) tests provide coverage on what is arguably the most important aspect of an application: what happens when users actually use your applications. In other words, E2E tests validate all of the layers in your application. This not only includes your frontend code, but all associated backend services and infrastructure that are more representative of the environment that your users will be in.

First time to use cypress, you can type yarn cypress and it'll open the dashboard window, then select base.spec.ts to run test.

About


Languages

Language:TypeScript 84.2%Language:Vue 7.5%Language:JavaScript 4.4%Language:SCSS 1.9%Language:Dockerfile 0.9%Language:HTML 0.8%Language:Shell 0.2%