Skip to content

akoidan/vue-webpack-typescript

Repository files navigation

HitCount Build Status codecov Github actions

Vue3

Please use vue3-vite-vue-class-component for vue3 vite setup.

Vue-webpack-typescript project starter

This project is generated via vue-webpack-minimal and features:

  • Base example of CRUD pages with written api classes, tests and predefined vue.pychat.org
  • Typescript loading with babel with typechecking in a parallel thread. Everything (vuex, cypress, vue-data) is type safe!
  • Vue with vuetify, vuex, router, sass, vuex-module-decorators, vue-property-decorator support typescript
  • Cypress with code-coverage support, unit test support, screenshot assert and typescript support.
  • Lint: a compilation of very strict lint rules for everything: vue, styles (sass), typescript that don't conflict with each other.

Get started

Install dependencies:

  • yarn install --frozen-lockfile. You can fall back to npm if you still use it.
  • [OPTIONAL] If compilation above crashes on binaries, do nvm use. In total you need yarn and nvm

Run development server

This runs the development server w/o a linter on.

yarn start

Tests

There're multiple things you can do with test:

  • Build into static files and run headless cypress against it
  • Run development server and run headfull cypress against it
  • Check coverage and generate reports for test

Headless cypress upon static server

The command bellow builds static files with coverage babel plugin information, creates a http server and runs cypress (e2e) tests located in the integrationFolder against that server in headless mode.

yarn test

You can check reports in nyc directory, including coverage information.

Headful cypress upon webpack-dev-server

This command is useful during development. You can click and inspect in live mode in cypress, and just develop while your tests are being ran automatically on file save. I usually have cypress test on one display, IDE on another one, and browser on 3rd one. So I instantly see which changes my code introduces.

  1. Start dev-server in test mode. yarn start won't work, cause of missing coverage info and some other polyfills and tweaks required.
yarn start:test
  1. Open cypress in debug mode. This mode means you can inspect the cypress UI directly in the browser and your test could automatically run upon file save.
yarn run test:cypress:debug

Lint

To check the styleguide use:

yarn lint

Build for productions to static files:

yarn run build:prod

Configurations

Environment variables

  • APP_TEST - true/false, adds required code for testing to output files when set to True (istanbul coverage, XHR polyfill for cypress)
  • APP_API_URL - url e.g. https://jsonplaceholder.typicode.com, public http API url
  • APP_PUBLIC_PATH - string, e.g. https://s3.amazonaws.com/, url for images/js/css/fonts instead of relative path like './main.js. Can be used if you're using CDN that's on a different domain than index.html
  • APP_VERSION - string, e.g. build-v378, some unique string which identifies your souce code. I usually pass git version or git tag to this variable in Continuous Integration.
  • APP_FILE_MODE - true/false, sets whether static files should be built for file mode (dragging index.html to browser) or not. By setting to true, this turns off history mode in browser and removes crossOriginLoading links.

Configuration files

WebStorm IDE

Indentation

Set indentation to 2 2 4for html, sass, js, ts in settings -> editor -> code style

Set template

  1. New
  2. Edit files templates...
  3. Vue single file component
<template>
  <div>#[[$END$]]#</div>
</template>

<script lang="ts">
import {Component, Prop, Vue, Watch, Ref} from "vue-property-decorator";

@Component
export default class ${COMPONENT_NAME} extends Vue {

}
</script>
<!-- eslint-disable -->
<style lang="sass" scoped>

</style>

Disable tslint

Tslint is not used for files, since it's deprecated. Use eslint instead and disable tslint

  1. Settings
  2. Typescript
  3. Tslint
  4. Disable tslint

Disable inspections [Optional]

To avoid mixing warnings from eslint and jetbrains, you can turn them off by default

  1. project preferences
  2. editor
  3. inspection
  4. javascript
  5. turn off everything, but leave: Code quality tools -> eslint

Max line length

  1. Editor
  2. Code style
  3. Hard wrap at 120

Exclude directories from indexing

Mark nyc and dist directories ex excluded. Mouse 2 on the directory in the project explorer tree -> mark directory as -> excluded

Style guide and how to

Code samples with libs it belongs to

Typescript (or ts shortly) allows to write typesafe code:

const a: number = 3;
  • To get started with ts I would recommend watching this 10 minute video
  • To get started with decorators I recommend this video
  • For advanced learning I recommend checking what's new in every version of typescript. You may find a lot of interesting things.

Vue allows to write SFC that would generate html to the page. Vue is only responsible for UI layer, this is not an MVC framework. The only thing that it does is creates <div></div codeblocks. Everything else is handled by libraries below .

Vuex is a state management pattern. It allows multiple vue components to have single model/data (source of truth). So if you have a user object like {age: 3, name: 'eric'} it can be accessible in multiple places. This is redux/mobx analogue for React.

Vue router allows navigation across pages in vue, w/o sending get request to the server. And produces access to URL parameters. The examples of routes is here:

new VueRouter({
  routes: [{
    path: '/posts', // this is url address
    component: PostsPage // this is vue component
  }]
});

Sass allows to write code that would be compiled into css

$font-stack:    Helvetica, sans-serif
body
  font: 100% $font-stack
  a
    display: block

Vue class component allows to write vue component in class manner instead of object:

export default class App extends Vue {
  // initial data
  msg = 123

  // use prop values for initial data
  helloMsg = 'Hello, ' + this.propMessage

  // lifecycle hook
  mounted () {
    this.greet()
  }

  // computed
  get computedMsg () {
    return 'computed ' + this.msg
  }

  // method
  greet () {
    alert('greeting: ' + this.msg)
  }
}

Since vue-class-component forces you to have decorators above the class like this:

@Component({
  props: {
    propMessage: String
  }
})
export default class App extends Vue {}

the following construction can be used instead:

import { Vue, Component, Prop, Watch, Emit, Ref } from 'vue-property-decorator'

@Component
export class MyComp extends Vue {

  @Ref
  button: HTMLInputElement;

  @Prop readonly propA!: number;

  @Watch('child')
  onChildChanged(val: string, oldVal: string) { }

  @Emit()
  changedProps() {}
}

This is a wrapper with static getters for vuex. Check store/users instead of writing vuex modules as a dict, for instance:

import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'

@Module
export default class UserState extends VuexModule {
 count = 0
 @Mutation
 increment(delta: number) {
   this.count += delta
 }
 // action 'decr' commits mutation 'increment' when done with return value as payload
 @Action({ commit: 'increment' })
 decr() {
   return 5
 }
}

State can be injected into the vue component this way:

class A extends Vue {
    @UserState
    public readonly count!: number;
}

This test library: allows to write

describe('console', () => {
  it('should print', () => {
    console.log('Hello world')
  })
})

Those are assertion libraries that adds bdd assertions:

expect([1, 2]).to.be.an('array').that.does.not.include(3);

This is mocking library that allows you to write:

const myAPI = { method: function () {} };
const spy = sinon.spy();
const mock = sinon.mock(myAPI);
mock.expects("method").once().throws();

To to write tests in BDD-like:

expect(mySpy).to.have.been.calledWith("foo");

This wrapper provides a single interface to console.log and displays the origin source file location:

logger.log('Hello world')(); // pay attention to () in the end.

A testing framework that allows running test-cases directly in chrome (alternative to Selenium, that runs it on server) That part you've already seen on mocha above can be appended with cypress assertions and helpers:

it("should contain 5 elements", (): void => {
  cy.get("[data-cy=filtered-users-container]").children().should("have.length", 1);
});

Build process libraries

  • webpack allows to combine (bundle) multiple input javascript files into a single output file. Provides plugins and loaders api that allows transformation like typescript or sass. All configs are located under webpack directory, take a look at it to understand how source files are being processed. Its start point is entry: ['./src/user.ts']. Since webpack can only handle .js files, to let webpack know about everything else we should pass the code through a corresponding loader. Everything is imported in this files are being processed by section loaders.
  • node-sass allows to compile sass into css, it doesn't know anything about webpack and loaders
  • node-sass-utils - allow to access json via css, thus sharing variables across js and css
  • hard-source-webpack-plugin (increases dev server speed compilation by caching)
  • optimize-css-assets-webpack-plugin (minimizes css)
  • clean-webpack-plugin (deletes the dist before build)
  • compression-webpack-plugin (generates .tar.gz files in dist directories)
  • mini-css-extract-plugin (gets all css files from .vue components and creates a single .css file in production build)
  • webpack-subresource-integrity ( generates integrity attribute on scripts tag)
  • stylelint-webpack-plugin (during webpack build find all the css and lints it via stylelint)
  • speed-measure-webpack-plugin shows information about compilation speed. Helps to find out why compilation goes slow.
  • html-webpack-plugin ( compiles html from index.ejs)
  • webpack-dev-server is used for development purposes with hot reloading, every time you save the file it will automatically apply. This doesn't affect node running files, only watching files. So files like webpack/base.config.js. To build project for production set APP_PUBLIC_PATH and run yarn run buiild:prod. This generates static files in ./dist directory.
  • webpack-cli allows to run webpack from the command line
  • Loaders: sass-loader (pipes sass into css), css-loader - resolves import css files inside js modules, ' vue-loader (resolves vue sfc), vue-template-compiler (compiles <template into dom api), style-loader (packs css into js file with eval and pushes it to syle tag, file-loader (saves it as a file, and puting exported path to file as an url)
  • fork-ts-checker-webpack-plugin - runs typescript compiler into a separate thread
  • source-map-support - adds support for source map (show source file in browser instead of transpiled one)

Typescript compilation libraries

Typescript is compiled via babel, this means that it doesn't have typechecks, this speeds up build a lot! But since we still want to take advantages of typechecks we run typescript compiler runs in a separate process, giving errors to stdout.

  • typescript compiler ts into js, we use babel instead of this, it just runs in a separate thread
  • @babel/plugin-proposal-class-properties - allows class properties like class A { a: number }
  • @babel/plugin-proposal-decorators - adds es6 decorators, e.g. @Component
  • babel-plugin-transform-typescript-metadata - allows babel decorator to work the same way as ts, this is required for libs like vue-property-decorator with @Prop annotation
  • @babel/preset-env - transpiles code into browser compatible one, TODO this breaks decorators
  • babel-preset-typescript-vue vue files are not supported by typescript loader (only via ts-loader), we need to mangle the output of vue to be able for babel to handle them
  • ts-node doesn't do anything but allows IDEs like Webstorm to run ts unit test via m2 -> run
  • @types/XXX are types declaration for 3rd party libraries. Ts only knows about specific types like dom and etc. Everything custom should be installed separately.
  • babel - global loader and transpiler that allows ts compilation into es and allowing additional babel plugins for use. here's why you need babel with ts

Test libraries

  • ts-loader - load test with typescript
  • start-server-and-test - allows to simultaneously start 2 processes during testing: cypress and frotnend
  • servor allows to serve static files for cypress test.
  • istanbul - code coverage with cli interface, generates html cov report
  • nyc - cli interface to istanbul
  • @cypress/code-coverage - allows to add istanbul coverage to cypress
  • @cypress/webpack-preprocessor allows to transpile cypress test via typescript
  • @istanbuljs/nyc-config-typescript - allows to coverage .ts files during test
  • @testing-library/cypress - adds cypress test
  • istanbul-lib-coverage the source code that allow direct cypress coverage integration - alpha version fixes mocha ts-node cannot read start

Lint libraries

  • @typescript-eslint/eslint-plugin": "2.7.1-alpha.17", - typescript-eslint/typescript-eslint#1189
  • eslint javascript set of linting rules.
  • eslint-loader - allows webpack to pass js via eslint
  • eslint-plugin-vue lints your <template in vue SFC.
  • @vue/eslint-config-typescript - adds config to eslint for vue typescript specific rules
  • eslint parser check .eslintrc.json for set of rules
  • @typescript-eslint/eslint-plugin - adds ts specific rules to eslint
  • @typescript-eslint/parser - allows eslint to parse ts files
  • stylelint-scss - linter rules specific to sass
  • stylelint-order appends stylelint with order rules. This forces css attributes/imports to have specific order.
  • stylelint linter for css files.

Continuous integration

This project has support for continuous integration servers:

You don't need to have all of them. So I recommend leave only 1. I would pick github actions since it doesn't require any setup.

Github actions

In order to setup continuous delivery via github:

  • Generate a new pair of ssh keys mkdir /tmp/sshkey; ssh-keygen -t rsa -b 4096 -C "github actions" -f /tmp/sshkey/id_rsa
  • Append content of /tmp/sshkey/id_rsa.pub to your server last line of file ~/.ssh/authorized_keys where ~ is the home for ssh user to use ( I used http)
  • Create ssh variables at https://github.com/akoidan/pychat/settings/secrets/actions (where akoidan/pychat is your repo) :
    • HOST -ssh host (your domain)
    • PORT - ssh port (22)
    • SSH_USER - ssh user, if you used my setup it's http
    • ID_RSA - what ssh-keygen has generated in step above to/tmp/sshkey/id_rsa
  • I used alias to give http user to access tornado systemd service like in this example. So append /etc/sudoers with
Cmnd_Alias RESTART_TORNADO = /usr/bin/systemctl restart tornado
http ALL=(ALL) NOPASSWD: RESTART_TORNADO

Tips

How to ignore linting errors

  • Exclude from coverage: /* istanbul ignore if */ guide
  • ignore tslint error: // tslint:disable-next-line:variable-name guide
  • ignore eslint error: // eslint-disable-line no-prototype-builtins guide
  • ignore typescript error: // @ts-ignore: next-line guide
  • ignore stylelint error: /* stylelint-disable-line */ guide

Where I find icons?

Repo uses material desing icons (mdi). Check materialdesignicons.com. And node_modules/@mdi/font/scss/_variables.scss directory, this vars are prefixed with mdi-, like mdi-home.

What's the import order in typescript:

  1. Multiple imports go before singular
  2. Uppercase letter go before lowercase
  3. Imports are aligned alphabetically

For example:

import VueRouter, {RawLocation, Route, RouteRecord} from "vue-router"; // Multiple imports with `VueRouter` that starts with an UpperCase letter
import {globalLogger, jwtStorage} from "@/utils/singletons"; // multiple imports start with lowercase `globalLogger`
import DemoPage from "@/components/pages/DemoPage.vue"; // single import with lowercase
import HomePage from "@/components/pages/HomePage.vue"; // single import with lowercase but alphabetically next
import {ROUTER_HISTORY_MODE} from "@/utils/consts"; // this is still a single import from a capital letter
import {defaultModule} from "@/store/default"; // this is a single import with a lower letter

Debugging

  • Take a look at vue.js devtools
  • window has many useful objects like consts, vue, store, router, api, so you can do things like store.dispatch('alertError', 'Hello') directly from chrome dev console

Be aware

  • cypress test is run on different browser when you run it locally, the source of truth is drone/ci here. Thing like screenshot could also be different. So this things should be aligned in the future if any issues occur.
  • if build process is killed or get stack that could be because of out of memory. The only option is to build frontend on machine with more memory and copy files

TODO