-
Notifications
You must be signed in to change notification settings - Fork 21
Documentation older than v1.0.0
- Prerequisite
- Installation
- Code/Folder conventions
- Yarn Commands
- Seng generator templates
- Configuration
- SCSS
- Component Structure
- Vuex store modules
- VueExposePlugin
- Dependency Injection
- Axios
- Gateway
- Using SVGs
- Autoprefixer
- Modernizr
- Asset management
- Previewing a build
- Polyfill configuration
- Localization support
- Application startup
- Pre-push hooks
- Code splitting
- Setting up a (api)proxy
- Node 6.x.x or higher
- NPM 3.x.x or higher
- Yarn 0.2x.x or higher
git clone https://github.com/hjeti/vue-skeleton.git
After cloning run yarn
in the project root to get started.
- 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 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
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.
There are 3 configurations in Vue skeleton.
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.
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
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 referencedata/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.
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.
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:
- The index.js makes your component imports shorter
import HomePage from 'page/HomePage'
instead ofimport Homepage from 'page/HomePage/HomePage'
. - This makes it easy to implement vuex-connect in your components.
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,
}),
},
};
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.
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.
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));
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.
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 is enabled by default. To configure which browser(s) need prefixing adjust the browser list in the /package.json
file.
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.
Managing and working with assets is important. The Vue skeleton comes with a hassle-free solution for managing 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 ~.
*.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);
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 astatic
folder in the root of the build next to theindex .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.
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.
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
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.
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
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.
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
.
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.