.. _vue-js:
Vue.js
======
`Vue.js `_ is an open-source JavaScript component-based framework that makes it easy to develop reactive applications.
Here you will find some guidelines explaining how you have to proceed to build up you application.
Before beginning
----------------
Vue.js can be used along with some additional libraries like `VueX `_ or `Vue-router `_.
To know when you have to use these libraries, here is a table that will help you to decide:
+-------------------------------------------------+----------------------------+
| Your app | Which lib should I use? |
+=================================================+============================+
| Has a component with few responsibilities | Vue.js |
+-------------------------------------------------+----------------------------+
| Is medium-sized and has complex workflows | Vue.js + Vuex |
+-------------------------------------------------+----------------------------+
| Has several pages to display | Vue.js + Vuex + Vue-router |
+-------------------------------------------------+----------------------------+
.. NOTE:: We strongly suggest you to install the `vue-devtools browser extension `_.
It provides nice features that ease the development of your applications.
Folder structure of a Vue application
-------------------------------------
A Vue app has to be split out in distinct parts.
Here is the folder structure you have to follow:
.. code-block:: bash
my-plugin/
|-- build-manifest.json # Edit it to declare your app for the build system and for translations
|-- scripts/
|-- my-vue-app/
|-- po/ # Localization strings
|-- src/ # The app source-code
|-- api/ # REST API consumers
|-- components/ # Vue components
|-- MyFeature/
`-- OtherFeature/
|-- store/ # Vuex store modules (actions/mutations/getters)
|-- router/ # Vue-router modules
|-- index.ts # App bootstrapping
|-- jest.config.js # Unit tests bootstrapping
|-- package.json # Declares the App, its dependencies and its build script.
|-- package-lock.json # Generated by npm. Never edit manually.
|-- tsconfig.json # Typescript configuration
`-- webpack.config.js # Webpack configuration to build the App
Build your Vue application
--------------------------
The build system will read ``build-manifest.json`` to understand where it needs to run ``npm install`` and ``npm run build``.
.. code-block:: JavaScript
// tuleap/plugins/my-plugin/build-manifest.json
{
"name": "my-plugin",
"components": [
"scripts/"
],
"gettext-vue": {
"my-vue-app": {
"src": "scripts/my-vue-app/src",
"po": "scripts/my-vue-app/po"
}
}
}
To build up your application, you will have to update or create a ``webpack.config.js`` file.
This file should be located in ``my-plugin/scripts/``.
.. code-block:: JavaScript
// tuleap/plugins/my-plugin/scripts/webpack.config.js
const path = require("path");
const webpack_configurator = require("../../../tools/utils/scripts/webpack-configurator.js");
const assets_dir_path = path.resolve(__dirname, "./assets");
const webpack_config_for_my_awesome_vue_app = {
entry: {
"my-vue-app": "./my-vue-app/src/index.ts"
},
context: path.resolve(__dirname),
output: webpack_configurator.configureOutput(assets_dir_path),
externals: {
tlp: "tlp"
},
module: {
rules: [
...webpack_configurator.configureTypescriptRules(webpack_configurator.babel_options_ie11),
webpack_configurator.rule_easygettext_loader,
webpack_configurator.rule_vue_loader
]
},
plugins: [webpack_configurator.getManifestPlugin(), webpack_configurator.getVueLoaderPlugin()],
resolveLoader: {
alias: webpack_configurator.easygettext_loader_alias
}
};
module.exports = webpack_config_for_my_awesome_vue_app;
.. _npm_scripts:
Once you have a webpack config, you will need a ``package.json`` in ``my-plugin/scripts/``.
.. code-block:: JavaScript
// tuleap/plugins/my-plugin/scripts/package.json
{
"author": "you",
"name": "my-vue-app",
"version": "0.0.1",
"private": true,
"dependencies": {
"vue": "^2.6.10",
"vue-gettext": "^2.1.0",
"vuex": "^3.1.1"
},
"devDependencies": {},
"config": {
"bin": "../../../../node_modules/.bin" // This should point to the node_modules/.bin folder in tuleap/ root folder
},
"scripts": {
"build": "NODE_ENV=production $npm_package_config_bin/webpack --mode=production",
"watch": "NODE_ENV=watch $npm_package_config_bin/concurrently --raw --kill-others '$npm_package_config_bin/webpack --watch --mode=development' 'CI=true npm test -- --watch'",
"test": "$npm_package_config_bin/jest"
}
}
.. NOTE:: All the development dependencies are available at the tuleap root, hence the ``config.bin``.
Use the npm scripts to build up the application or to launch the unit tests.
.. code-block:: bash
npm run build # For a production build, outputs minified code.
npm run watch # Run the :ref:`Jest ` unit tests and build the app in watch mode at the same time.
npm run test # Run the :ref:`Jest ` unit tests only once.
Once you have a ``package.json`` file, you will also need a ``tsconfig.json`` file to configure Typescript.
.. code-block:: JavaScript
// tuleap/plugins/my-plugin/scripts/tsconfig.json
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"module": "es2015" //redefine otherwise vue-jest crashes: https://github.com/vuejs/vue-jest/issues/144
},
"include": [
"my-vue-app/**/*"
],
"exclude": [
"node_modules",
"my-vue-app/**/*.test.ts"
]
}
Bootstrap your application
--------------------------
This section will explain you how to properly integrate your application in Tuleap.
Create a mount point
^^^^^^^^^^^^^^^^^^^^
To allow your app to run in Tuleap, you may need to create a mount point in a mustache template.
Your mount point needs to have a unique identifier in order to be easily retrieved from the DOM.
This is also the place where you can pass some data from PHP to JavaScript via ``data-*`` attributes:
.. code-block:: html
Once your mount point is ready, head to your ``index.ts`` file.
.. code-block:: TypeScript
// tuleap/plugins/my-plugin/scripts//src/index.ts
import Vue from 'vue';
import MyVueApp from './components/MyVueApp.vue';
document.addEventListener('DOMContentLoaded', () => {
// Retrieve the mount point from the DOM
const vue_mount_point_id = "my-vue-app-mount-point";
const vue_mount_point = document.getElementById(vue_mount_point_id);
if (!vue_mount_point) {
throw new Error(`Could not find Vue mount point ${vue_mount_point_id}`);
}
const MyVueAppComponent = Vue.extend(MyVueApp);
new MyVueAppComponent({ // Create a new component
propsData: {
user: JSON.parse(vue_mount_point.dataset.user) // Pass the data bound to the mount point to the app
}
}).$mount(vue_mount_point); // Mount the app on the moint point
});
Vue and Typescript
------------------
The reference language to use with Vue.js is now `Typescript `_.
Best-practices for Tuleap
-------------------------
When you submit a patch for review, we may request changes to better match the following best practices. Please try to follow them.
Many rules are already enforced by the pre-commit hook that runs eslint_ with `eslint-plugin-vue`_.
* Please avoid the usage of `vue directives shorthands `_. Shorthands are nice to use but it is not obvious for the others to figure out which directive you are actually using.
* Always use ``PascalCase`` for component names.
* Always use multi-word names for components, for example: "DocumentSearch". In templates, this translates as ````. See `the dedicated Vue Style Guide rule `_.
* Always use ``snake_case`` for computed properties. I know, there are parentheses when we define them, but they really are *properties*, not methods. See :ref:`Tuleap coding standards `.
* Always use ``snake_case`` for props. They follow the same rule as variables.
* Always use ``camelCase`` for methods.
* Always use ``snake_case`` for Vuex State properties and Getters. They are properties too.
* Always use ``camelCase`` for Vuex Mutations and Actions. They are methods.
* Always name files and folders inside ``components/`` with ``PascalCase`` (just like component names).
* Always name javascript files (in all other folders) with ``dash-case``.
* Avoid having too many components that depend on ``this.$route``. Inject what you need via props instead.
* Always use named exports in Vuex Getters, Mutations and Actions. Default export may be used for State definition. Named exports make it easier to import only what we want.
* Always use the inline export syntax ``export function myAction()`` or ``export const myMutation() => {}``. It makes it easy to add "private" (non-exported) functions that will be reused.
Resources
^^^^^^^^^
- Vue.js doc: https://vuejs.org/v2/guide/
- Vuex doc: https://vuex.vuejs.org/
- Vue-router doc: https://router.vuejs.org/
- Vue.js Official Style Guide: https://vuejs.org/v2/style-guide/
- eslint-plugin-vue's rules: https://vuejs.github.io/eslint-plugin-vue/rules/
- TypeScript reference: https://www.typescriptlang.org
- vue-gettext: https://github.com/Polyconseil/vue-gettext
.. _eslint: https://eslint.org/
.. _eslint-plugin-vue: https://github.com/vuejs/eslint-plugin-vue
.. _Vue Style Guide: https://vuejs.org/v2/style-guide/
.. _vue-gettext: https://github.com/Polyconseil/vue-gettext