- VueJS
- Vuex
- Webpack
- ESLint
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
For detailed explanation on how things work, checkout the guide and docs for vue-loader.
- Introduction
- Project structure
- Code format
- Trello workflow
- Git flow
- Styling
- Routes
- Data
- Authorization
- Internationalization (i18n)
- Forms
- Services
This document describes the essentials of the principles that we are following during the development of this application. If you feel that we can improve some of them feel free to create the PR and we will discuss the changes.
We always have to hold the balance between the speed of the feature delivery and the technical quality.
src/components
- (generic) UI components. Re-usable, mostly stateless elements.src/containers
- (not-generic) UI element, like pages, blocks (e.g. footer, navigation, user card and etc), that are integrated with store and are not generic. Try to avoid custom styles in the containers.src/containers/pages
- pagessrc/containers/layouts
- layoutssrc/containers/blocks
- not-generic components and data-specific, like UserInfoCard, navigation or footer.src/filters
- Vue filterssrc/services
- configuration of 3rd party services/librariessrc/store
- Vuex store filessrc/router
- Vue-router files
Important: try to avoid custom styles in the containers. They usually can be just the composition of the components. In this case in long-term we would have consistent UI, because the most of our style will be encapsulated inside the components.
ComponentName/index.vue
- entry point of a component. Container template and loaders for scripts and styles' filesComponentName/scripts.js
- JS scripts of a componentComponentName/styles.scss
- styles of a component
Project contains ESLint config. We recommend you to configure your favorite code-editor to use it to check you code during the development. This will save the time in PR reviews.
Application has @
alias, that is related to src
folder. Use it, instead of the relative paths.
Bad example
import Page from '../../../../components/Page'
Good example
import Page from '@/components/Page'
We have the several columns in out trello board:
Backlog
- entry point for the tickets/ideas or bug reportsBlocked
- tickets, that are blocked by another tickets of by other reasons. If it's a frontend tickets it usually has the corresponing API change in the ticket description and these ticket is not ready yet.To do
- tickets, that are ready to be taked for development. <- Take these ticket for developmentIn progress
- tickets, that are currently in development. We need this column to understand that you are working on.In review
- tickets, that are development and they have the corresponging PR and you've requested for the review.Released
- tickets after review. This column is using by project manager to check the implemented feature
- Stable branch -
master
- Don't push directly to the stable branch. Use PRs instead
Workflow:
- Start a ticket with a new branch
- Write code
- Create Pull Request
- Get an approve from one of your coworkers
- Merge PR's branch with the stable branch
We are not following some strict rule on this stage of branch naming. So we have a single rule for the branch names:
- Make you branch names meaningful.
Bad example
fix-1
redesign
Good example
fix-signals-table
new-user-profile-page
We have the release script ./bin/release.sh. You will need Github Token to be able to create releases.
Create new Github Personal Access Token with scopes (repo
and admin:org
)
./bin/release.sh --github-token=$GITHUB_TOKEN [--increment]
Use --increment
flag, if you want to patch version of the project.
Script contains the steps:
- Increment version (if
--increment
flag is passed): update version inpackage.json
- Create Github release and git tag
- Build project
- Create tag archive and attach it to the Github release
Deployment scripts are in deployment folder.
To configure new environment
We will need NGINX installed on the server.
- Go to project folder
mkdir -p /var/www/project_name
cd /var/www/project_name
- Copy deployment folder to the project folder using you favorite client.
- Change folder rights
chmod -R 755 deployment
- Configure NGINX
Example of NGINX config can be found here. You have to specify SSL parameters, server_name and paths to the sources.
Default NGINX configuration is looking /var/www/project_name/current
. Let's create symlink to current symlink in deployment folder.
ln -s deployment/current current
- Create environment configuration file
Default NGINX config is looking into /var/www/project_name/configs/config.js
.
To create this file, execute this code.
mkdir -p configs
echo "window.__CONFIG__ = {};" > configs/config.js
Configure environment
Available configuration parameters can be found here - BUILT_IN_VALUES
.
For example, you can specify API_URL
parameter, like this.
window.__CONFIG__ = {
API_URL: 'https://example.com',
};
Deploy new version
- Run
deploy-version.sh
./deployment/deploy-version.sh --version=0.0.5 --github-token=$GITHUB_TOKEN
It will download build assets from Github Release, unpack it and change current symlink.
- deployment/downloads/ - downloaded release archives
- deployment/versions/ - unpacked folders of the versions
- deployment/current - symlink to current version
How to rollback to previous version*
If you want to rollback to 0.0.5 version and it exists in versions folder, use this command.
ln -s versions/0.0.5 deployment/current
TODO
: write specific script, that will check availability of the version in the versions folder
Key points:
- all assets are caching and compressing with gzip
- service worker is not caching
config.js
is not caching
We're using scoped styles, so you don't need to use BEM or other methodology to avoid conflicts in the styles. In BEM terminology, you don't have to use elements. Use only block and modificators. If you feel that you also need an element - think, probably you have to extract a new component from this template.
Bad example
.page {
&__title {}
&__content {
&_active {}
}
}
Good example
.root {} // root element
.title {}
.content {
&.is-active {}
}
Use is-
prefix for the modificators.
- You have to be able to load page information based only on the URL params.
- Use the same URL as a main API endpoint of the page.
Page URL: /users
API request: get:/users
Page URL: /users/:id
API request: get:/users/:id
Page URL: /users/new
API request: post:/users
- If you need to save the state of the page - use the URL query params.
e.g /users?page=1, /users?q=John Doe
- Make URLs meaningful
API can return tiny numbers with big precision. Default JS math methods can be not enough for the number manipulation.
Use bignumber.js
, when you have to manipulate with numbers.
We're making normalization of the data. We need it for 2 reasons:
- to have a single version of the entity in the store and be able to update it and be sure, these changes will be made in the rest of the app. Caution. If you don't need to update the entity globally - don't use/update the instance in the store.
- to have a cache of the entities
Key points:
- For the normalization of the data we'are using normalizr
- The schemas are defined here
- Normalization is performing in actions
- Denormalization is performing in getters
Example: If you have to show the list of the entities in the page, you have to store the list of ids on the page component and then use getter to denormalize data by these ids.
import { mapActions } from 'vuex';
import Page from '@/components/Page';
import SignalsTable from '@/containers/blocks/SignalsTable';
export default {
components: {
SignalsTable,
Page
},
data: () => ({
signalsIds: []
}),
computed: {
signals() {
return this.$store.getters.signals(this.signalsIds);
}
},
methods: {
...mapActions([
'getSignals'
])
},
async mounted() {
this.signalsIds = await this.getSignals();
}
};
We are using vuex
for the state-management. Vuex folder structure:
store/
store/index.js - entry point
store/mutations.js
store/initialState.js
store/actions.js
store/getters.js
store/modules/
store/modules/moduleA/index.js
store/modules/moduleA/mutations.js
store/modules/moduleA/getters.js
store/modules/moduleA/actions.js
store/modules/moduleA/initialState.js
References:
- https://medium.com/3yourmind/large-scale-vuex-application-structures-651e44863e2f
- https://github.com/bstavroulakis/vue-wordpress-pwa/tree/master/src/vuex
Vuex architecture
If you are not familliar with the Vuex - read the documentation first. https://vuex.vuejs.org
– TODO
See the example in the normalization section
API uses devise_token_auth gem. Here is the description: https://github.com/lynndylanhurley/ng-token-auth.
At the start of the application, we send a request to validate_token to validate token from storage and fetch user's info. See the logic in here
Next, we're checking authorization on the router-level.
We also handle 401 Unauthorized
API errors and terminate user session if it is caught.
The application has to store this data in the localStorage. These parameters are required in the validate_token request, that is used to fetch user's data at a boot time.
- uid
- client
- token
They can be also passed via URL query params. Query params are using, for example, in a reset password confirmation page.
- uid
- client_id
- token
To be ready for i18n - avoid the strings in the logic (JS scripts) - only in the view-layer (templates). In this case we will be ready to extract them and replace with the loader for the translates.
We're using per-field validation with vee-validate
.
To show the server side validation errors use this helper.
It will map server side errors to the client format and will add them to a vee-validate
instance. These errors message will be displayed according to your rules in the views. e.g.
<div v-show="errors.has('username')" class="help-block alert alert-danger">
{{ errors.first('username') }}
</div>
Example.
async onSubmit() {
const result = await this.$validator.validateAll();
if (!result) return;
try {
await this.signUp({
password: this.password,
email: this.email,
account_name: this.account_name
});
this.$router.push('/');
} catch (e) {
showServerValidationErrors(e, this.$validator);
}
}
You can define new validation rules here. There are also the example of the custom validation rule
Example of i18n for the validation messages.
Application uses several 3rd party services. They are configured here.
We collect all analytics events thought Google Tag Manager. The configuration contains 2 part: in index.html we are loading a gtm script, in main.js we configure an integration GTM with Vue JS app.
GTM - Vue integration includes:
- handling router changes
- passing GTM to the Vue component
You can configure GTM using these configuration parameters:
GTM_TRACKING_ID
GTM_APP_NAME
Rollbar is used to collect the errors.
You can configure Rollbar instance with:
ROLLBAR_ACCESS_TOKEN
ROLLBAR_ACCESS_ENVIRONMENT