Please use vue3-vite-vue-class-component for vue3 vite setup.
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.
yarn install --frozen-lockfile
. You can fall back tonpm
if you still use it.- [OPTIONAL] If compilation above crashes on binaries, do
nvm use
. In total you need yarn and nvm
This runs the development server w/o a linter on.
yarn start
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
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.
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.
- 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
- 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
To check the styleguide use:
yarn lint
yarn run build:prod
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 urlAPP_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 thanindex.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.
- Every vue component has injected
.$logger
object, to log something to console usethis.logger.log('Hello {}', {1:'world'})();
Note calling function again in the end. Logger is disabled for production. For more info visit lines-logger - Every component has injected
$.api
object. You should do http calls with$this.$api
. If you prefer redux style you can call http in vuex actions. - If you're using git as your version control tool
window.APP_VERSION
will be exported to global scope. - .nycrc.json is a configuration for istanbul code coverage. It can customize reports formats, coverage percentage and other build related things.
- .stylelintrc is a configuration for css linting
- .drone.yml is a configuration file for Drone CI.
- .github/workflows/main.yml is a configuration file for Github Actions.
- .eslintrc.json is a configuration for ts linting
- .mocharc.json is a configuration for testing library mocha (deprecated mocha.opts)
- cypress.json is a configuration for cypress e2e testing
- package.json is a configuration for yarn (npm), since it doesn't have versions of sublibs they are stored in yarn.lock
- tsconfig.json is a configuration for typescript. While this files is used to build static files for FE, cypress/tsconfig is used to build files that would run test in cypress.
- webpack is a directory with webpack configurations
- mocha.opts is a configuration for unit test (mocha).
Set indentation to 2
2
4
for html, sass, js, ts in settings -> editor -> code style
- New
- Edit files templates...
- 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>
Tslint is not used for files, since it's deprecated. Use eslint instead and disable tslint
- Settings
- Typescript
- Tslint
- Disable tslint
To avoid mixing warnings from eslint and jetbrains, you can turn them off by default
- project preferences
- editor
- inspection
- javascript
- turn off everything, but leave: Code quality tools -> eslint
- Editor
- Code style
- Hard wrap at 120
Mark nyc
and dist
directories ex excluded. Mouse 2 on the directory in the project explorer tree -> mark directory as -> excluded
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')
})
})
chai and chai-as-promised
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);
});
- 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 sectionloaders
. - 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 runyarn 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 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
- 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
- @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.
This project has support for continuous integration servers:
- Project supports with DroneCI.
- TravisCI pipelines out of the box.
- Github actions. Config
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.
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 usedhttp
) - 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'shttp
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
- 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
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
.
- Multiple imports go before singular
- Uppercase letter go before lowercase
- 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
- Take a look at vue.js devtools
- window has many useful objects like
consts
,vue
,store
,router
,api
, so you can do things likestore.dispatch('alertError', 'Hello')
directly from chrome dev console
- 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
- cypress-io/code-coverage#332
- @for sass loops doesn't work in linter AleshaOleg/postcss-sass#53
- https://github.com/bahmutov/cypress-vue-unit-test
- vuejs/eslint-plugin-vue#987
- istanbuljs/nyc#1148
- import-js/eslint-plugin-import#1543
- typescript-eslint/typescript-eslint#801 (comment)
- https://github.com/mysticatea/eslint-plugin-node
- bahmutov/start-server-and-test#283