Skip to content

Documentation older than v1.0.0

Hendrik-Jan de Harder edited this page Dec 15, 2017 · 1 revision

Table of Contents

  1. Prerequisite
  2. Installation
  3. Code/Folder conventions
  4. Yarn Commands
  5. Seng generator templates
  6. Configuration
    1. Webpack configuration
    2. Project configuration
    3. Site configuration
  7. SCSS
  8. Component Structure
  9. Vuex store modules
  10. VueExposePlugin
  11. Dependency Injection
  12. Axios
  13. Gateway
  14. Using SVGs
  15. Autoprefixer
  16. Modernizr
  17. Asset management
    1. Types of assets
    2. Referencing assets processed by webpack
    3. Referencing static assets
  18. Previewing a build
  19. Polyfill configuration
  20. Localization support
  21. Application startup
  22. Pre-push hooks
  23. Code splitting
  24. Setting up a (api)proxy

Prerequisite

Installation

git clone https://github.com/hjeti/vue-skeleton.git

After cloning run yarn in the project root to get started.

Code/Folder conventions

  • Every component folder is formatted in PascalCase
  • Every component contains an index.js to integrate vuex-connect and for easy import import HomePage from 'page/HomePage'
  • Every page name is appended with Page
  • Always use the PascalCase formatting for components in templates <ScrollBar/>

Yarn Commands

  • yarn dev: Starts the development server
  • yarn dev-dhr: Starts the development server without hot reloading
  • yarn build: Creates a build. To override the publicPath use: yarn build -- --publicPath=/v/vue-skeleton/
  • yarn preview: Preview the latest build in the browser
  • yarn eslint: Runs eslint
  • yarn tslint: Runs tslint
  • yarn stylelint: Runs stylelint
  • yarn analyze: Analyze webpack bundle after a build using Webpack Bundle Analyzer

Seng generator templates

Vue skeleton comes with seng-generator predefined templates and configuration which is used to scaffold components, pages and store modules.

Global installation of seng-generator is mandatory. To install it globally run the following command:

npm i seng-generator -g

After installation the following scaffolding commands are available:

  • component (sg component <name>) : Creates a component
  • connected-component (sg connected-component <name>): Creates a component with vuex-connect integrated
  • page (sg page <name>): Creates a page
  • connected-page (sg connected-page <name>): Creates a page with vuex-connect integrated
  • store (sg store <name>): Creates a store module
  • complex-store (sg complex-store <name>): Creates a complex store module

Check the seng-generator documentation for detailed information about modifying or adding templates.

Configuration

There are 3 configurations in Vue skeleton.

Webpack configuration

The webpack configuration is located in the build folder. It consists of a base (webpack.base.conf.js) that contains all the configuration that is shared between development (webpack.dev.conf.js) and production (webpack.prod.conf.js). To avoid config duplication there is a webpackHelpers file with some helpers that return the right config context for development and production. Webpack is completely configured out of the box. But there is always room for customization.

Project configuration

The project config is located in the config folder. The project config contains variables that are used by webpack like the environment variables, location of the index file and the version path. If the site is running in a subfolder on the server it's possible to change the publicpath to avoid problems.

This file contains some other non webpack related settings. These additional settings are:

  • enable/disable eslint, tslint and stylelint
  • configuration of prepush tasks
  • enable/disable https during development

Site configuration

In development there needs to be a place to store urls of APIs like the facebook app id etc. Vue skeleton uses seng-config because it has straightforward API and comes packed with a lot of features. It has support for properties, urls and variables and environments. The latter is very important because most of the config is environment based. Seng-config environments can extend each other for easy configuration.

All the app configuration related files are stored in src/config:

  • config.js: Contains the config and the environment logic. The environment is set based on the host. This configuration already has some common configuration set like:
    • API path
    • locale path
    • default locale
    • disabled locale routing
    • disabled locale
  • configManager: Is available using Dependency Injection reference data/Injectables.js.Check the documentation for all available methods.
  • localeConfig.js: Contains the locale config.

A ConfigManager instance is exposed to all Vue components as $config. In other places the injector needs to be used to get the ConfigManager instance.

SCSS

Vue skeleton uses SCSS for styling. It uses CSS modules to local scope the styling of Vue components. Check CSS Modules for more information.

There are two main SCSS files:

  • screen.scss Application global styling goes here. By default it only imports the normalize.css module.
  • utils.scss Application wide available mixins and variables. By default it imports seng-scss.

utils.scss Automatically imported in every component SCSS file.

Note: Make sure that utils.scss NEVER outputs CSS. Outputting CSS to utils.scss will add this CSS to every component.

Component structure

A component consists of 4 files:

  • {Name}.vue: This is the main file it contains the imports of the style and logic of the component. Besides the imports it also contains the template(html) of the component.
  • {Name}.js: This is the javascript file that contains all component logic.
  • {Name}.scss: This is the SCSS file that contains all the styling of a component. Vue skeleton uses css modules so all the styling is scoped to the component.
  • index.js: This javascript file is in every component for two reasons:
  1. The index.js makes your component imports shorter import HomePage from 'page/HomePage' instead of import Homepage from 'page/HomePage/HomePage'.
  2. This makes it easy to implement vuex-connect in your components.

Vuex store modules

It's a best practice to split your data in namespaced modules in vuex. The store seng-generator templates makes it easy to work with modules as the store templates already contain statics for the namespace and the mutation types.

Example of using statics in modules and components:

store/module/user.js:

// declare the mutation types for use in the mutations object and in the index.js
export const SET_FIRST_NAME = 'setFirstName';
export const SET_LAST_NAME = 'setLastName';

export default {
	namespaced: true,
	state: {
		firstName: '',
		lastName: '',
	},
	getters: {
		fullName: state => `${state.firstName} ${state.lastName}`,
	},
	mutations: {
		[SET_FIRST_NAME]: (state, payload) => {
			state.firstName = payload;
		},
		[SET_LAST_NAME]: (state, payload) => {
			state.lastName = payload;
		},
	},
};

store/module/index.js:

// import the mututation types 
import user, { SET_FIRST_NAME, SET_LAST_NAME } from './user';

//The namespace of the module. Value has to match with the name used in store/modules.js
export const UserNamespace = 'user';

// The mutation types for use in the component
export const UserMutationTypes = {
	SET_FIRST_NAME: `${UserNamespace}/${SET_FIRST_NAME}`,
	SET_LAST_NAME: `${UserNamespace}/${SET_LAST_NAME}`,
};

export default user;

Component:

import { mapState, mapMutations } from 'vuex';
import { UserNamespace, UserMutationTypes } from 'store/module/user';

export default {
	name: 'HomePage',
	computed: {
		...mapState(UserNamespace, [
			'firstName',
			'lastName',
		]),
	},
	methods: {
		...mapMutations({
			setFirstName: UserMutationTypes.SET_FIRST_NAME,
			setLastName: UserMutationTypes.SET_LAST_NAME,
		}),
	},
};

VueExposePlugin

Vue skeleton contains a little plugin that makes development faster and easier.

The VueExposePlugin exposes code(enums, functions, classes etc.) in Vue components.

By default it's impossible to use imported code in the templates of components. The VueExposePlugin provides a workaround.

Without the plugin:

<router-link :to="{ name: 'contact', params: {id: 5}}">Contact</router-link>

With the plugin:

<router-link :to="{ name: PageNames.CONTACT, params: {[Params.ID]: 5}}">Contact</router-link>
<a :href="$config.getURL(URLNames.TERMS)" target="_blank">terms</a>

The VueExposePlugin is registered in the startUp.js. By default it exposes page and config enums and the configmanager instance.

NOTE: VueExposePlugin adds everything on the Vue prototype so watch out for naming conflicts and expose only what is really needed.

Dependency Injection

Some instances of classes need to accessed in multiple places across an application. Using singletons to fix this problem is not a great solution, because it prevents creating multiple instances of the same class. Vue skeleton has a simple solution to fix this problem. Instances are registered with a name in the injector. Using the registered name makes it possible to get the instance from the injector. Before Vue is initialized the setupInjects function is called from the bootstrap.js where injects can be setup. This makes sure that the injects are available before the app/website is initialized.

Example:

// setupInjects.js
import { CONFIG_MANAGER } from 'data/Injectables';
import config from 'config/config';
import ConfigManager from 'seng-config';
import { setValue } from './injector';

const setupInjects = () => {
	const configManager = new ConfigManager();
	configManager.init(config.config, config.environment);

	setValue(CONFIG_MANAGER, configManager);
};

// somewhere else in the application
import { getValue} from 'util/injector';
import { CONFIG_MANAGER } from 'data/Injectables';

const configManager = getValue(CONFIG_MANAGER);
const apiURL = configManager.getURL('api');

It's also possible to setup different injects based on environment, build type etc. This makes it possible to use a stub gateway in development but a real gateway in a production build without changing code.

Axios

The skeleton uses Axios for http requests.

Axios is exposed to all Vue components as $http. In other places axios needs to be imported first before use:

import axios from 'axios';

axios.get('en-gb.json').then((response) => console.log(response.data));

Gateway

The skeleton also comes with an preconfigured Axios based gateway to communicate with the backend.

The gateway is setup in util/setupInjects.js. The api url can be changed in the config. It also has an interceptor setup for easy retrieval of data returned by the gateway. All data that is returned from the gateway is added directly to the response instead of the data property of the response:

// default axios
response.data.data
response.data.pagination
response.data.error.message

// vue skeleton
response.data
response.pagination
response.error.message

The gateway is exposed to all Vue components as $gateway. In other places the injector needs to be used to get the gateway instance:

import { getValue} from 'util/injector';
import { GATEWAY } from 'data/Injectables';

const gateway = getValue(GATEWAY);
gateway.get('/init').then(response => console.log(response.data));

Read the Gateway API spec for more information about gateway response types.

Using SVGs

Webpack automatically parses and optimizes SVGs using SVGo. Vue skeleton comes with predefined SVGo settings which can be found in build/webpack.base.conf.js.

Implementing a icon can be done using the Icon component. The SVG file is referenced using the name property of the Icon component. The value of this property is the SVG filename without the SVG file extension.

<Icon name="zoom-in" class="icon-check" />

The Icon component is globally registered in Vue allowing it to be used directly without importing and registering within Vue components.

Autoprefixer

Autoprefixer is enabled by default. To configure which browser(s) need prefixing adjust the browser list in the /package.json file.

Modernizr

Modernizr is built-in the Vue skeleton. The Modernizr configuration is located in the /.modernizrrc file. Reference the Modernizr Configuration for all options and feature-detects.

Asset management

Managing and working with assets is important. The Vue skeleton comes with a hassle-free solution for managing assets.

Types of assets

  • Static assets
  • Assets that are processed by webpack

Assets that need to be processed by webpack are stored in the src/asset folder. Examples of those assets are fonts, images, SVGs and SCSS files. Referencing these files in .vue templates can be done by prepending ~.

Referencing assets processed by webpack

*.vue template example A image is located in src/asset/image/example.png to reference this image so it can be picked up by webpack use the following syntax <img src="~asset/image/example.png" />

*.scss example A image is located in src/asset/image/example.png to reference this image so it can be picked up by webpack use the following syntax background-image: url(asset/image/example.png);

Referencing static assets

There are two folders for static assets:

  • static This folder is for assets that need to be versioned. Examples: locale JSONs, data JSONs, videos and images. After a build this folder will end up in the root of the versioned folder (by default: version/${timestamp}/static.
  • staticRoot This folder is for assets that don't need to be versioned. Examples: favicon and share images. After a build the content is copied over in a static folder in the root of the build next to the index .html.

static assets won't be processed by webpack (e.g. manually file optimization). It is mandatory to prefix static paths in code using a variable. As stated above the versioned static folder is placed in a versioned folder with a timestamp in the path. It's impossible to know the timestamp during development the only option is to prefix assets.

Luckily it's super easy to prefix paths because Vue skeleton provides all the necessary variables:

process.env.VERSIONED_STATIC_ROOT
process.env.STATIC_ROOT

Prefixing paths using these variables is important not using them can result in unresolvable assets during development/build.

import { getValue } from 'util/injector';
import { CONFIG_MANAGER } from 'data/Injectables';
import { VariableNames } from 'data/enum/configNames';

const configManager = getValue(CONFIG_MANAGER);
const backgroundImage = `${configManager.getVariable(VariableNames.VERSIONED_STATIC_ROOT)}image/background.png`;
const shareImage = `${configManager.getVariable(VariableNames.STATIC_ROOT)}image/share.png`;

The VueExposePlugin sets $versionRoot and $staticRoot variables which allows easy access to version/static root. These variables are only available in a Vue component or Vue template.

Within a component

const video = `${this.$versionRoot}video/intro.mp4`;
const video = `${this.$staticRoot}intro.mp4`;

Within a .vue template

<img :src="`${$versionRoot}image/example.jpg`" />
<img :src="`${$staticRoot}example.jpg`" />

Reference the configuration chapter for more information.

Previewing a build

After creating a new build it is possible to preview it by running the yarn preview command. Due to config differences between development and production it may occur that it runs perfectly fine on development but not in a production build. It is good to test builds on a regular basis to avoid issues when deploying to an environment.

Polyfill configuration

All required polyfills are imported in the src/polyfill/index.js file. Vue skeleton uses babel polyfill in combination with the env babel preset so only required polyfills are included.

By default it includes polyfills for the following features

  • Array.includes
  • Classlist

Localization support

The Vue skeleton is packaged with vue-i18n-manager for localization.

Configuration can be changed in the project config (src/config/config.js) and in the locale config (src/config/localeConfig.js).

In most cases the standard config should be sufficient. The config has the following variables that determine how and if localization is used:

  • VariableNames.LOCALE_ENABLED: Enable/Disable localization
  • VariableNames.LOCALE_ROUTING_ENABLED: Enable/Disable localized routing (/en/home)
  • URLNames.LOCALE: Path to the locale files (Defaults to ${process.env.VERSIONED_STATIC_ROOT}locale/{locale}.json)
  • PropertyNames.DEFAULT_LOCALE: The default locale
  • PropertyNames.AVAILABLE_LOCALES: An array with all available locales

The value of the locales can be an object that i18n-manager accepts or a string. A string value (eg. en-gb) in the config has to match the JSON filename and will also be present in the url if localized routing is enabled.

localeConfig.js contains the config of the i18n manager.

The locales are loaded by the localeLoader (util/localeLoader.js). The i18n-manager uses the localeLoader as a proxy to load the locales. Change the localeLoader if a custom load implementation is required.

Check the i18nManager documentation for usage within Vue components.

Application startup

Add methods to control/startUp that need to be run before app initialisation. The startUp returns a promise allowing to chain startup tasks.

Examples of startup tasks:

  • Registering/Initialisation of Vue plugins
  • Requesting async initialisation data

Pre-push hooks

Before pushing to a repository it's possible to run tasks to abort a push. If an another task needs to run before pushing add them in bin/prePush.js file.

Standard pre-push tasks enabled

  • esLintCheck
  • tsLintCheck
  • styleLintCheck

Disabling or enabling task can be done in config/index.js by changing the prePush property contents. Removing the prePush property or emptying the array will disable pre-push hooks.

Code splitting

It is also possible to use code splitting in Vue Skeleton. This can improve load times if an app consists of a lot of pages for example.

Splitting happens in src\router\routes.js where all the routes are defined. In a normal situation without code splitting all pages are imported at the top of the file. Instead of importing, a page needs to be imported using import().

const HomePage = () => import(/* webpackChunkName: 'HomePage' */ 'page/HomePage').then(page => page.default);

The route definition where the page component is used stays exactly the same.

It's also possible to group multiple pages in a seperate bundle by giving them the same chunk name. In the example above the chunkname is set to HomePage.

More info

Setting up a (api)proxy

The development server runs on node. This can cause problems when a project also has a backend. The backend runs on a different domain/port. Vue skeleton has a proxy functionality built-in this way the backend can be reached in the same way as on the production environment.

Proxy setup is done in the config (config/index.js). The development config contains a proxyTable property where proxies can be added. The development server always needs to be restarted when changes are made.

Example:

proxyTable: {'/api': {target: 'https://localhost/project', changeOrigin: true}},

When a call is made to /api it will be proxied to https://localhost/project/api. The source url (/api in this case) has to exist on the target, because the source url will be added to the target when a call is made.

More info and configuration options