summaryrefslogtreecommitdiff
path: root/doc/development/fe_guide
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/fe_guide')
-rw-r--r--doc/development/fe_guide/accessibility.md13
-rw-r--r--doc/development/fe_guide/axios.md18
-rw-r--r--doc/development/fe_guide/design_patterns.md4
-rw-r--r--doc/development/fe_guide/droplab/droplab.md18
-rw-r--r--doc/development/fe_guide/droplab/plugins/ajax.md2
-rw-r--r--doc/development/fe_guide/droplab/plugins/filter.md2
-rw-r--r--doc/development/fe_guide/droplab/plugins/input_setter.md2
-rw-r--r--doc/development/fe_guide/event_tracking.md4
-rw-r--r--doc/development/fe_guide/frontend_faq.md2
-rw-r--r--doc/development/fe_guide/graphql.md24
-rw-r--r--doc/development/fe_guide/icons.md17
-rw-r--r--doc/development/fe_guide/index.md23
-rw-r--r--doc/development/fe_guide/performance.md15
-rw-r--r--doc/development/fe_guide/security.md31
-rw-r--r--doc/development/fe_guide/style/javascript.md5
-rw-r--r--doc/development/fe_guide/style/scss.md51
-rw-r--r--doc/development/fe_guide/style/vue.md6
-rw-r--r--doc/development/fe_guide/tooling.md4
-rw-r--r--doc/development/fe_guide/vue.md216
-rw-r--r--doc/development/fe_guide/vue3_migration.md124
-rw-r--r--doc/development/fe_guide/vuex.md85
21 files changed, 403 insertions, 263 deletions
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md
index 4fd9a4fed60..998c71135fb 100644
--- a/doc/development/fe_guide/accessibility.md
+++ b/doc/development/fe_guide/accessibility.md
@@ -2,19 +2,12 @@
## Resources
-[Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools]
+[Chrome Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools)
are useful for testing for potential accessibility problems in GitLab.
-The [axe][axe-website] browser extension (available for [Firefox][axe-firefox-extension] and [Chrome][axe-chrome-extension]) is
+The [axe](https://www.deque.com/axe/) browser extension (available for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/) and [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd)) is
also a handy tool for running audits and getting feedback on markup, CSS and even potentially problematic color usages.
Accessibility best-practices and more in-depth information are available on
-[the Audit Rules page][audit-rules] for the Chrome Accessibility Developer Tools. The "[awesome a11y][awesome-a11y]" list is also a
+[the Audit Rules page](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) for the Chrome Accessibility Developer Tools. The [Awesome Accessibility](https://github.com/brunopulis/awesome-a11y) list is also a
useful compilation of accessibility-related material.
-
-[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools
-[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules
-[axe-website]: https://www.deque.com/axe/
-[axe-firefox-extension]: https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/
-[axe-chrome-extension]: https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd
-[awesome-a11y]: https://github.com/brunopulis/awesome-a11y
diff --git a/doc/development/fe_guide/axios.md b/doc/development/fe_guide/axios.md
index 6e7cf523f36..f8d301dac5e 100644
--- a/doc/development/fe_guide/axios.md
+++ b/doc/development/fe_guide/axios.md
@@ -1,13 +1,13 @@
# Axios
-We use [axios][axios] to communicate with the server in Vue applications and most new code.
+We use [axios](https://github.com/axios/axios) to communicate with the server in Vue applications and most new code.
In order to guarantee all defaults are set you *should not use `axios` directly*, you should import `axios` from `axios_utils`.
## CSRF token
All our request require a CSRF token.
-To guarantee this token is set, we are importing [axios][axios], setting the token, and exporting `axios` .
+To guarantee this token is set, we are importing [axios](https://github.com/axios/axios), setting the token, and exporting `axios` .
This exported module should be used instead of directly using `axios` to ensure the token is set.
@@ -32,19 +32,16 @@ This exported module should be used instead of directly using `axios` to ensure
## Mock axios response in tests
-To help us mock the responses we are using [axios-mock-adapter][axios-mock-adapter].
+To help us mock the responses we are using [axios-mock-adapter](https://github.com/ctimmerm/axios-mock-adapter).
-Advantages over [`spyOn()`]:
+Advantages over [`spyOn()`](https://jasmine.github.io/api/edge/global.html#spyOn):
- no need to create response objects
- does not allow call through (which we want to avoid)
- simple API to test error cases
- provides `replyOnce()` to allow for different responses
-We have also decided against using [axios interceptors] because they are not suitable for mocking.
-
-[axios interceptors]: https://github.com/axios/axios#interceptors
-[`spyOn()`]: https://jasmine.github.io/api/edge/global.html#spyOn
+We have also decided against using [axios interceptors](https://github.com/axios/axios#interceptors) because they are not suitable for mocking.
### Example
@@ -77,8 +74,3 @@ Because polling function requires a header object, we need to always include an
```javascript
mock.onGet('/users').reply(200, { foo: 'bar' }, {});
```
-
-[axios]: https://github.com/axios/axios
-[axios-instance]: #creating-an-instance
-[axios-interceptors]: https://github.com/axios/axios#interceptors
-[axios-mock-adapter]: https://github.com/ctimmerm/axios-mock-adapter
diff --git a/doc/development/fe_guide/design_patterns.md b/doc/development/fe_guide/design_patterns.md
index 72a7861ffcb..5b7b16a9a8a 100644
--- a/doc/development/fe_guide/design_patterns.md
+++ b/doc/development/fe_guide/design_patterns.md
@@ -75,6 +75,4 @@ class Foo {
new Foo({ container: '.my-element' });
```
-You can find an example of the above in this [class][container-class-example];
-
-[container-class-example]: https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+You can find an example of the above in this [class](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/mini_pipeline_graph_dropdown.js);
diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md
index 4d7c882dc09..83bc4216403 100644
--- a/doc/development/fe_guide/droplab/droplab.md
+++ b/doc/development/fe_guide/droplab/droplab.md
@@ -26,7 +26,7 @@ If you do not provide any arguments, it will globally query and instantiate all
<ul>
```
-```js
+```javascript
const droplab = new DropLab();
droplab.init();
```
@@ -47,7 +47,7 @@ You can add static list items.
<ul>
```
-```js
+```javascript
const droplab = new DropLab();
droplab.init();
```
@@ -65,7 +65,7 @@ a non-global instance of DropLab using the `DropLab.prototype.init` method.
<ul>
```
-```js
+```javascript
const trigger = document.getElementById('trigger');
const list = document.getElementById('list');
@@ -83,7 +83,7 @@ You can also add hooks to an existing DropLab instance using `DropLab.prototype.
<ul id="list" data-dropdown><!-- ... --><ul>
```
-```js
+```javascript
const droplab = new DropLab();
droplab.init();
@@ -114,7 +114,7 @@ for all `data-dynamic` dropdown lists tracked by that DropLab instance.
</ul>
```
-```js
+```javascript
const droplab = new DropLab();
droplab.init().addData([{
@@ -137,7 +137,7 @@ the data as the second argument and the `id` of the trigger element as the first
</ul>
```
-```js
+```javascript
const droplab = new DropLab();
droplab.init().addData('trigger', [{
@@ -167,7 +167,7 @@ dropdown lists, one of which is dynamic.
</div>
```
-```js
+```javascript
const droplab = new DropLab();
droplab.init().addData('trigger', [{
@@ -224,7 +224,7 @@ Some plugins require configuration values, the config object can be passed as th
<ul id="list" data-dropdown><!-- ... --><ul>
```
-```js
+```javascript
const droplab = new DropLab();
const trigger = document.getElementById('trigger');
@@ -249,7 +249,7 @@ droplab.init(trigger, list, [droplabAjax], {
When plugins are initialised for a droplab trigger+dropdown, DropLab will
call the plugins `init` function, so this must be implemented in the plugin.
-```js
+```javascript
class MyPlugin {
static init() {
this.someProp = 'someProp';
diff --git a/doc/development/fe_guide/droplab/plugins/ajax.md b/doc/development/fe_guide/droplab/plugins/ajax.md
index 77ba6f739e6..abc208e7568 100644
--- a/doc/development/fe_guide/droplab/plugins/ajax.md
+++ b/doc/development/fe_guide/droplab/plugins/ajax.md
@@ -18,7 +18,7 @@ Add the `Ajax` object to the plugins array of a `DropLab.prototype.init` or `Dro
<ul id="list" data-dropdown><!-- ... --><ul>
```
-```js
+```javascript
const droplab = new DropLab();
const trigger = document.getElementById('trigger');
diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md
index b867394a241..876149e4872 100644
--- a/doc/development/fe_guide/droplab/plugins/filter.md
+++ b/doc/development/fe_guide/droplab/plugins/filter.md
@@ -18,7 +18,7 @@ Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or `D
<ul>
```
-```js
+```javascript
const droplab = new DropLab();
const trigger = document.getElementById('trigger');
diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md
index db492da478a..9b2e1e8faab 100644
--- a/doc/development/fe_guide/droplab/plugins/input_setter.md
+++ b/doc/development/fe_guide/droplab/plugins/input_setter.md
@@ -23,7 +23,7 @@ You can also set the `InputSetter` config to an array of objects, which will all
<ul>
```
-```js
+```javascript
const droplab = new DropLab();
const trigger = document.getElementById('trigger');
diff --git a/doc/development/fe_guide/event_tracking.md b/doc/development/fe_guide/event_tracking.md
index ae555e99c6b..93e135772ef 100644
--- a/doc/development/fe_guide/event_tracking.md
+++ b/doc/development/fe_guide/event_tracking.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../../telemetry/index.md'
+redirect_to: '../telemetry/index.md'
---
-This document was moved to [another location](../../telemetry/index.md).
+This document was moved to [another location](../telemetry/index.md).
diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md
index 3b0b1d8f0da..8f8f162609a 100644
--- a/doc/development/fe_guide/frontend_faq.md
+++ b/doc/development/fe_guide/frontend_faq.md
@@ -39,7 +39,7 @@ bundle exec rake routes | grep "issues"
### 2. `modal_copy_button` vs `clipboard_button`
-The `clipboard_button` uses the `copy_to_clipboard.js` behaviour, which is
+The `clipboard_button` uses the `copy_to_clipboard.js` behavior, which is
initialized on page load, so if there are vue-based clipboard buttons that
don't exist at page load (such as ones in a `GlModal`), they do not have the
click handlers associated with the clipboard package.
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index 8ccc734ee35..caf84d04490 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -10,13 +10,13 @@ their execution by clicking **Execute query** button on the top left:
![GraphiQL interface](img/graphiql_explorer_v12_4.png)
-We use [Apollo] and [Vue Apollo][vue-apollo] for working with GraphQL
+We use [Apollo](https://www.apollographql.com/) and [Vue Apollo](https://github.com/vuejs/vue-apollo) for working with GraphQL
on the frontend.
## Apollo Client
To save duplicated clients getting created in different apps, we have a
-[default client][default-client] that should be used. This setups the
+[default client](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/lib/graphql.js) that should be used. This setups the
Apollo client with the correct URL and also sets the CSRF headers.
Default client accepts two parameters: `resolvers` and `config`.
@@ -73,7 +73,7 @@ More about fragments:
## Usage in Vue
-To use Vue Apollo, import the [Vue Apollo][vue-apollo] plugin as well
+To use Vue Apollo, import the [Vue Apollo](https://github.com/vuejs/vue-apollo) plugin as well
as the default client. This should be created at the same point
the Vue application is mounted.
@@ -94,7 +94,7 @@ new Vue({
});
```
-Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation](https://vue-apollo.netlify.com/guide/).
+Read more about [Vue Apollo](https://github.com/vuejs/vue-apollo) in the [Vue Apollo documentation](https://vue-apollo.netlify.com/guide/).
### Local state with Apollo
@@ -212,7 +212,7 @@ Read more about local state management with Apollo in the [Vue Apollo documentat
When Apollo Client is used within Vuex and fetched data is stored in the Vuex store, there is no need in keeping Apollo Client cache enabled. Otherwise we would have data from the API stored in two places - Vuex store and Apollo Client cache. More to say, with Apollo default settings, a subsequent fetch from the GraphQL API could result in fetching data from Apollo cache (in the case where we have the same query and variables). To prevent this behavior, we need to disable Apollo Client cache passing a valid `fetchPolicy` option to its constructor:
-```js
+```javascript
import fetchPolicies from '~/graphql_shared/fetch_policy_constants';
export const gqClient = createGqClient(
@@ -298,7 +298,8 @@ handleClick() {
GitLab's GraphQL API uses [Relay-style cursor pagination](https://www.apollographql.com/docs/react/data/pagination/#cursor-based)
for connection types. This means a "cursor" is used to keep track of where in the data
-set the next items should be fetched from.
+set the next items should be fetched from. [GraphQL Ruby Connection Concepts](https://graphql-ruby.org/pagination/connection_concepts.html)
+is a good overview and introduction to connections.
Every connection type (for example, `DesignConnection` and `DiscussionConnection`) has a field `pageInfo` that contains an information required for pagination:
@@ -426,7 +427,7 @@ This should be resolved in the scope of the issue
#### Mocking response as component data
-With [Vue test utils][vue-test-utils] it is easy to quickly test components that
+With [Vue test utils](https://vue-test-utils.vuejs.org/) it is easy to quickly test components that
fetch GraphQL queries. The simplest way is to use `shallowMount` and then set
the data on the component
@@ -598,11 +599,4 @@ defaultClient.query({ query })
.then(result => console.log(result));
```
-Read more about the [Apollo] client in the [Apollo documentation](https://www.apollographql.com/docs/tutorial/client/).
-
-[Apollo]: https://www.apollographql.com/
-[vue-apollo]: https://github.com/vuejs/vue-apollo
-[feature-flags]: ../feature_flags.md
-[default-client]: https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/lib/graphql.js
-[vue-test-utils]: https://vue-test-utils.vuejs.org/
-[apollo-link-state]: https://www.apollographql.com/docs/link/links/state.html
+Read more about the [Apollo](https://www.apollographql.com/) client in the [Apollo documentation](https://www.apollographql.com/docs/tutorial/client/).
diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md
index ea321330c41..4fb738f5466 100644
--- a/doc/development/fe_guide/icons.md
+++ b/doc/development/fe_guide/icons.md
@@ -1,8 +1,8 @@
# Icons and SVG Illustrations
-We manage our own Icon and Illustration library in the [`gitlab-svgs`][gitlab-svgs] repository.
-This repository is published on [npm][npm] and managed as a dependency via yarn.
-You can browse all available Icons and Illustrations [here][svg-preview].
+We manage our own Icon and Illustration library in the [`gitlab-svgs`](https://gitlab.com/gitlab-org/gitlab-svgs) repository.
+This repository is published on [npm](https://www.npmjs.com/package/@gitlab/svgs) and managed as a dependency via yarn.
+You can browse all available Icons and Illustrations [here](https://gitlab-org.gitlab.io/gitlab-svgs).
To upgrade to a new version run `yarn upgrade @gitlab/svgs`.
## Icons
@@ -22,7 +22,7 @@ sprite_icon(icon_name, size: nil, css_class: '')
```
- **icon_name** Use the icon_name that you can find in the SVG Sprite
- ([Overview is available here][svg-preview]).
+ ([Overview is available here](https://gitlab-org.gitlab.io/gitlab-svgs)).
- **size (optional)** Use one of the following sizes : 16, 24, 32, 48, 72 (this will be translated into a `s16` class)
- **css_class (optional)** If you want to add additional css classes
@@ -42,7 +42,7 @@ sprite_icon(icon_name, size: nil, css_class: '')
### Usage in Vue
-[GitLab UI][gitlab-ui], our components library, provides a component to display sprite icons.
+[GitLab UI](https://gitlab-org.gitlab.io/gitlab-ui/), our components library, provides a component to display sprite icons.
Sample usage :
@@ -65,7 +65,7 @@ export default {
</template>
```
-- **name** Name of the Icon in the SVG Sprite ([Overview is available here][svg-preview]).
+- **name** Name of the Icon in the SVG Sprite ([Overview is available here](https://gitlab-org.gitlab.io/gitlab-svgs)).
- **size (optional)** Number value for the size which is then mapped to a specific CSS class
(Available Sizes: 8, 12, 16, 18, 24, 32, 48, 72 are mapped to `sXX` css classes)
- **css-classes (optional)** Additional CSS Classes to add to the svg tag.
@@ -111,8 +111,3 @@ export default {
<img :src="svgIllustrationPath" />
</template>
```
-
-[npm]: https://www.npmjs.com/package/@gitlab/svgs
-[gitlab-svgs]: https://gitlab.com/gitlab-org/gitlab-svgs
-[svg-preview]: https://gitlab-org.gitlab.io/gitlab-svgs
-[gitlab-ui]: https://gitlab-org.gitlab.io/gitlab-ui/
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 790cd94cec4..8fd6ba459b9 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -5,17 +5,17 @@ across GitLab's frontend team.
## Overview
-GitLab is built on top of [Ruby on Rails](https://rubyonrails.org) using [Haml][haml] and also a JavaScript based Frontend with [Vue.js](https://vuejs.org).
-Be wary of [the limitations that come with using Hamlit][hamlit-limits]. We also use [SCSS](https://sass-lang.com) and plain JavaScript with
-modern ECMAScript standards supported through [Babel][babel] and ES module support through [webpack][webpack].
+GitLab is built on top of [Ruby on Rails](https://rubyonrails.org) using [Haml](http://haml.info/) and also a JavaScript based Frontend with [Vue.js](https://vuejs.org).
+Be wary of [the limitations that come with using Hamlit](https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations). We also use [SCSS](https://sass-lang.com) and plain JavaScript with
+modern ECMAScript standards supported through [Babel](https://babeljs.io/) and ES module support through [webpack](https://webpack.js.org/).
Working with our frontend assets requires Node (v10.13.0 or greater) and Yarn
(v1.10.0 or greater). You can find information on how to install these on our
-[installation guide][install].
+[installation guide](../../install/installation.md#4-node).
### Browser Support
-For our currently-supported browsers, see our [requirements][requirements].
+For our currently-supported browsers, see our [requirements](../../install/requirements.md#supported-web-browsers).
Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers. Login to BrowserStack with the credentials saved in GitLab's [shared 1Password account](https://about.gitlab.com/handbook/security/#1password-for-teams).
@@ -83,7 +83,7 @@ Read the [frontend's FAQ](frontend_faq.md) for common small pieces of helpful in
See the relevant style guides for our guidelines and for information on linting:
- [JavaScript](style/javascript.md). Our guide is based on
-the excellent [Airbnb][airbnb-js-style-guide] style guide with a few small
+the excellent [Airbnb](https://github.com/airbnb/javascript) style guide with a few small
changes.
- [SCSS](style/scss.md): our SCSS conventions which are enforced through [`scss-lint`](https://github.com/sds/scss-lint).
- [HTML](style/html.md). Guidelines for writing HTML code consistent with the rest of the codebase.
@@ -109,14 +109,3 @@ Our accessibility standards and resources.
Frontend internationalization support is described in [this document](../i18n/).
The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available.
-
-[haml]: http://haml.info/
-[hamlit]: https://github.com/k0kubun/hamlit
-[hamlit-limits]: https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations
-[babel]: https://babeljs.io/
-[webpack]: https://webpack.js.org/
-[jquery]: https://jquery.com/
-[axios]: https://github.com/axios/axios
-[airbnb-js-style-guide]: https://github.com/airbnb/javascript
-[install]: ../../install/installation.md#4-node
-[requirements]: ../../install/requirements.md#supported-web-browsers
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index fcba8758c2f..aaa6bb16fab 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -41,9 +41,9 @@ But in general it should be handled automatically through a `MutationObserver` i
Only animate `opacity` & `transform` properties. Other properties (such as `top`, `left`, `margin`, and `padding`) all cause
Layout to be recalculated, which is much more expensive. For details on this, see "Styles that Affect Layout" in
-[High Performance Animations][high-perf-animations].
+[High Performance Animations](https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/).
-If you _do_ need to change layout (e.g. a sidebar that pushes main content over), prefer [FLIP][flip] to change expensive
+If you _do_ need to change layout (e.g. a sidebar that pushes main content over), prefer [FLIP](https://aerotwist.com/blog/flip-your-animations/) to change expensive
properties once, and handle the actual animation with transforms.
## Reducing Asset Footprint
@@ -160,18 +160,13 @@ General tips:
- If some functionality can reasonably be achieved without adding extra libraries, avoid them.
- Use page-specific JavaScript as described above to load libraries that are only needed on certain pages.
- Use code-splitting dynamic imports wherever possible to lazy-load code that is not needed initially.
-- [High Performance Animations][high-perf-animations]
+- [High Performance Animations](https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/)
---
## Additional Resources
- [WebPage Test](https://www.webpagetest.org) for testing site loading time and size.
-- [Google PageSpeed Insights][pagespeed-insights] grades web pages and provides feedback to improve the page.
+- [Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) grades web pages and provides feedback to improve the page.
- [Profiling with Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/)
-- [Browser Diet][browser-diet] is a community-built guide that catalogues practical tips for improving web page performance.
-
-[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/
-[browser-diet]: https://browserdiet.com/
-[high-perf-animations]: https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/
-[flip]: https://aerotwist.com/blog/flip-your-animations/
+- [Browser Diet](https://browserdiet.com/) is a community-built guide that catalogues practical tips for improving web page performance.
diff --git a/doc/development/fe_guide/security.md b/doc/development/fe_guide/security.md
index 7dba61d6b45..a001dd83ab7 100644
--- a/doc/development/fe_guide/security.md
+++ b/doc/development/fe_guide/security.md
@@ -2,8 +2,8 @@
## Resources
-[Mozilla’s HTTP Observatory CLI][observatory-cli] and the
-[Qualys SSL Labs Server Test][qualys-ssl] are good resources for finding
+[Mozilla’s HTTP Observatory CLI](https://github.com/mozilla/http-observatory-cli) and the
+[Qualys SSL Labs Server Test](https://www.ssllabs.com/ssltest/analyze.html) are good resources for finding
potential problems and ensuring compliance with security best practices.
<!-- Uncomment these sections when CSP/SRI are implemented.
@@ -29,14 +29,14 @@ Some exceptions include:
- Connecting with GitHub, Bitbucket, GitLab.com, etc. to allow project importing.
- Connecting with Google, Twitter, GitHub, etc. to allow OAuth authentication.
-We use [the Secure Headers gem][secure_headers] to enable Content
+We use [the Secure Headers gem](https://github.com/twitter/secureheaders) to enable Content
Security Policy headers in the GitLab Rails app.
Some resources on implementing Content Security Policy:
-- [MDN Article on CSP][mdn-csp]
-- [GitHub’s CSP Journey on the GitHub Engineering Blog][github-eng-csp]
-- The Dropbox Engineering Blog's series on CSP: [1][dropbox-csp-1], [2][dropbox-csp-2], [3][dropbox-csp-3], [4][dropbox-csp-4]
+- [MDN Article on CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP)
+- [GitHub’s CSP Journey on the GitHub Engineering Blog](http://githubengineering.com/githubs-csp-journey/)
+- The Dropbox Engineering Blog's series on CSP: [1](https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/), [2](https://blogs.dropbox.com/tech/2015/09/unsafe-inline-and-nonce-deployment/), [3](https://blogs.dropbox.com/tech/2015/09/csp-the-unexpected-eval/), [4](https://blogs.dropbox.com/tech/2015/09/csp-third-party-integrations-and-privilege-separation/)
### Subresource Integrity (SRI)
@@ -52,8 +52,8 @@ All CSS and JavaScript assets should use Subresource Integrity.
Some resources on implementing Subresource Integrity:
-- [MDN Article on SRI][mdn-sri]
-- [Subresource Integrity on the GitHub Engineering Blog][github-eng-sri]
+- [MDN Article on SRI](https://developer.mozilla.org/en-us/docs/web/security/subresource_integrity)
+- [Subresource Integrity on the GitHub Engineering Blog](http://githubengineering.com/subresource-integrity/)
-->
@@ -67,7 +67,7 @@ such as with reCAPTCHA, which cannot be used without an `iframe`.
## Avoiding inline scripts and styles
-In order to protect users from [XSS vulnerabilities][xss], we will disable
+In order to protect users from [XSS vulnerabilities](https://en.wikipedia.org/wiki/Cross-site_scripting), we will disable
inline scripts in the future using Content Security Policy.
While inline scripts can be useful, they're also a security concern. If
@@ -77,16 +77,3 @@ inject scripts into the web app.
Inline styles should be avoided in almost all cases, they should only be used
when no alternatives can be found. This allows reusability of styles as well as
readability.
-
-[observatory-cli]: https://github.com/mozilla/http-observatory-cli
-[qualys-ssl]: https://www.ssllabs.com/ssltest/analyze.html
-[secure_headers]: https://github.com/twitter/secureheaders
-[mdn-csp]: https://developer.mozilla.org/en-US/docs/Web/Security/CSP
-[github-eng-csp]: http://githubengineering.com/githubs-csp-journey/
-[dropbox-csp-1]: https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/
-[dropbox-csp-2]: https://blogs.dropbox.com/tech/2015/09/unsafe-inline-and-nonce-deployment/
-[dropbox-csp-3]: https://blogs.dropbox.com/tech/2015/09/csp-the-unexpected-eval/
-[dropbox-csp-4]: https://blogs.dropbox.com/tech/2015/09/csp-third-party-integrations-and-privilege-separation/
-[mdn-sri]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
-[github-eng-sri]: http://githubengineering.com/subresource-integrity/
-[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
diff --git a/doc/development/fe_guide/style/javascript.md b/doc/development/fe_guide/style/javascript.md
index 7951c702601..b69a6f1941c 100644
--- a/doc/development/fe_guide/style/javascript.md
+++ b/doc/development/fe_guide/style/javascript.md
@@ -184,6 +184,9 @@ This can help to quickly understand the control flow.
// bad
if (isThingNull) return '';
+if (isThingNull)
+ return '';
+
// good
if (isThingNull) {
return '';
@@ -192,7 +195,7 @@ if (isThingNull) {
## ESLint
-ESLint behaviour can be found in our [tooling guide](../tooling.md).
+ESLint behavior can be found in our [tooling guide](../tooling.md).
## IIFEs
diff --git a/doc/development/fe_guide/style/scss.md b/doc/development/fe_guide/style/scss.md
index 6c0247ad00c..336c9b8ca35 100644
--- a/doc/development/fe_guide/style/scss.md
+++ b/doc/development/fe_guide/style/scss.md
@@ -9,30 +9,25 @@ easy to maintain, and performant for the end-user.
## Rules
+Our CSS is a mixture of current and legacy approaches. That means sometimes it may be difficult to follow this guide to the letter; it means you will definitely run into exceptions, where following the guide is difficult to impossible without outsized effort. In those cases, you may work with your reviewers and maintainers to identify an approach that does not fit these rules. Please endeavor to limit these cases.
+
### Utility Classes
-As part of the effort for [cleaning up our CSS and moving our components into `gitlab-ui`](https://gitlab.com/groups/gitlab-org/-/epics/950)
-led by the [GitLab UI WG](https://gitlab.com/gitlab-com/www-gitlab-com/-/merge_requests/20623) we prefer the use of utility classes over adding new CSS. However, complex CSS can be addressed by adding component classes.
+In order to reduce the generation of more CSS as our site grows, prefer the use of utility classes over adding new CSS. In complex cases, CSS can be addressed by adding component classes.
#### Where are utility classes defined?
-- [Bootstrap's Utility Classes](https://getbootstrap.com/docs/4.3/utilities/)
-- [`common.scss`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/stylesheets/framework/common.scss) (old)
-- [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/stylesheets/utilities.scss) (new)
+Prefer the use of [utility classes defined in GitLab UI](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/master/doc/css.md#utilities). An easy list of classes can also be [seen on Unpkg](https://unpkg.com/browse/@gitlab/ui/src/scss/utilities.scss).
-#### Where should I put new utility classes?
+Classes in [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/stylesheets/utilities.scss) and [`common.scss`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/stylesheets/framework/common.scss) are being deprecated. Classes in [`common.scss`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/stylesheets/framework/common.scss) that use non-design system values should be avoided in favor of conformant values.
-New utility classes should be added to [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/stylesheets/utilities.scss). Existing classes include:
+Avoid [Bootstrap's Utility Classes](https://getbootstrap.com/docs/4.3/utilities/).
-| Name | Pattern | Example |
-|------|---------|---------|
-| Background color | `.bg-{variant}-{shade}` | `.bg-warning-400` |
-| Text color | `.text-{variant}-{shade}` | `.text-success-500` |
-| Font size | `.text-{size}` | `.text-2` |
+#### Where should I put new utility classes?
-- `{variant}` is one of 'primary', 'secondary', 'success', 'warning', 'error'
-- `{shade}` is one of the shades listed on [colors](https://design.gitlab.com/product-foundations/colors/)
-- `{size}` is a number from 1-6 from our [Type scale](https://design.gitlab.com/product-foundations/typography/)
+If a class you need has not been added to GitLab UI, you get to add it! Follow the naming patterns documented in the [utility files](https://gitlab.com/gitlab-org/gitlab-ui/-/tree/master/src/scss/utility-mixins) and refer to [GitLab UI's CSS documentation](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/master/doc/contributing/adding_css.md#adding-utility-mixins) for more details, especially about adding responsive and stateful rules.
+
+If it is not possible to wait for a GitLab UI update (generally one day), add the class to [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/stylesheets/utilities.scss) following the same naming conventions documented in GitLab UI. A follow—up issue to backport the class to GitLab UI and delete it from GitLab should be opened.
#### When should I create component classes?
@@ -77,6 +72,24 @@ CSS classes should use the `lowercase-hyphenated` format rather than
}
```
+Class names should be used instead of tag name selectors.
+Using tag name selectors are discouraged in CSS because
+they can affect unintended elements in the hierarchy.
+Also, since they are not meaningful names, they do not
+add meaning to the code.
+
+```scss
+// Bad
+ul {
+ color: #fff;
+}
+
+// Good
+.class-name {
+ color: #fff;
+}
+```
+
### Formatting
You should always use a space before a brace, braces should be on the same
@@ -254,8 +267,8 @@ documentation includes [a full list of their linters](https://github.com/sds/scs
### Fixing issues
If you want to automate changing a large portion of the codebase to conform to
-the SCSS style guide, you can use [CSSComb][csscomb]. First install
-[Node][node] and [NPM][npm], then run `npm install csscomb -g` to install
+the SCSS style guide, you can use [CSSComb](https://github.com/csscomb/csscomb.js). First install
+[Node](https://github.com/nodejs/node) and [NPM](https://www.npmjs.com/), then run `npm install csscomb -g` to install
CSSComb globally (system-wide). Run it in the GitLab directory with
`csscomb app/assets/stylesheets` to automatically fix issues with CSS/SCSS.
@@ -279,7 +292,3 @@ Make sure a comment is added on the line above the `disable` rule, otherwise the
linter will throw a warning. `DisableLinterReason` is enabled to make sure the
style guide isn't being ignored, and to communicate to others why the style
guide is ignored in this instance.
-
-[csscomb]: https://github.com/csscomb/csscomb.js
-[node]: https://github.com/nodejs/node
-[npm]: https://www.npmjs.com/
diff --git a/doc/development/fe_guide/style/vue.md b/doc/development/fe_guide/style/vue.md
index a7d8fc61752..46305cc7217 100644
--- a/doc/development/fe_guide/style/vue.md
+++ b/doc/development/fe_guide/style/vue.md
@@ -53,7 +53,7 @@ Please check this [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules)
## Naming
-1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension ([#34371]).
+1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension ([#34371](https://gitlab.com/gitlab-org/gitlab-foss/issues/34371)).
1. **Reference Naming**: Use PascalCase for their instances:
```javascript
@@ -89,8 +89,6 @@ Please check this [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules)
<component my-prop="prop" />
```
-[#34371]: https://gitlab.com/gitlab-org/gitlab-foss/issues/34371
-
## Alignment
1. Follow these alignment styles for the template method:
@@ -327,7 +325,7 @@ When using `v-for` you need to provide a *unique* `:key` attribute for each item
</div>
```
-1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute
+1. When the elements being iterated don't have a unique ID, you can use the array index as the `:key` attribute
```html
<div
diff --git a/doc/development/fe_guide/tooling.md b/doc/development/fe_guide/tooling.md
index 0e8f5ed05ed..585cd969c96 100644
--- a/doc/development/fe_guide/tooling.md
+++ b/doc/development/fe_guide/tooling.md
@@ -56,13 +56,13 @@ When declaring multiple globals, always use one `/* global [name] */` line per v
## Formatting with Prettier
-Our code is automatically formatted with [Prettier](https://prettier.io) to follow our style guides. Prettier is taking care of formatting .js, .vue, and .scss files based on the standard prettier rules. You can find all settings for Prettier in `.prettierrc`.
+Our code is automatically formatted with [Prettier](https://prettier.io) to follow our style guides. Prettier is taking care of formatting `.js`, `.vue`, and `.scss` files based on the standard prettier rules. You can find all settings for Prettier in `.prettierrc`.
### Editor
The easiest way to include prettier in your workflow is by setting up your preferred editor (all major editors are supported) accordingly. We suggest setting up prettier to run automatically when each file is saved. Find [here](https://prettier.io/docs/en/editors.html) the best way to set it up in your preferred editor.
-Please take care that you only let Prettier format the same file types as the global Yarn script does (.js, .vue, and .scss). In VSCode by example you can easily exclude file formats in your settings file:
+Please take care that you only let Prettier format the same file types as the global Yarn script does (`.js`, `.vue`, and `.scss`). In VSCode by example you can easily exclude file formats in your settings file:
```json
"prettier.disableLanguages": [
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index aeedd57fd83..972c2ded9c9 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -12,17 +12,17 @@ What is described in the following sections can be found in these examples:
## Vue architecture
-All new features built with Vue.js must follow a [Flux architecture][flux].
+All new features built with Vue.js must follow a [Flux architecture](https://facebook.github.io/flux/).
The main goal we are trying to achieve is to have only one data flow and only one data entry.
In order to achieve this goal we use [vuex](#vuex).
-You can also read about this architecture in vue docs about [state management][state-management]
-and about [one way data flow][one-way-data-flow].
+You can also read about this architecture in vue docs about [state management](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch)
+and about [one way data flow](https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow).
### Components and Store
-In some features implemented with Vue.js, like the [issue board][issue-boards]
-or [environments table][environments-table]
+In some features implemented with Vue.js, like the [issue board](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/assets/javascripts/boards)
+or [environments table](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/assets/javascripts/environments)
you can find a clear separation of concerns:
```plaintext
@@ -47,7 +47,7 @@ of the new feature should be.
The Store and the Service should be imported and initialized in this file and
provided as a prop to the main component.
-Be sure to read about [page-specific JavaScript][page_specific_javascript].
+Be sure to read about [page-specific JavaScript](./performance.md#page-specific-javascript).
### Bootstrapping Gotchas
@@ -162,7 +162,7 @@ For example, tables are used in a quite amount of places across GitLab, a table
would be a good fit for a component. On the other hand, a table cell used only
in one table would not be a good use of this pattern.
-You can read more about components in Vue.js site, [Component System][component-system]
+You can read more about components in Vue.js site, [Component System](https://vuejs.org/v2/guide/#Composing-with-Components).
### A folder for the Store
@@ -189,96 +189,135 @@ Each Vue component has a unique output. This output is always present in the ren
Although we can test each method of a Vue component individually, our goal must be to test the output
of the render/template function, which represents the state at all times.
-Make use of the [axios mock adapter](axios.md#mock-axios-response-in-tests) to mock data returned.
-
-Here's how we would test the Todo App above:
+Here's an example of a well structured unit test for [this Vue component](#appendix---vue-component-subject-under-test):
```javascript
-import Vue from 'vue';
-import axios from '~/lib/utils/axios_utils';
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import App from '~/todos/app.vue';
+
+const TEST_TODOS = [
+ { text: 'Lorem ipsum test text' },
+ { text: 'Lorem ipsum 2' },
+];
+const TEST_NEW_TODO = 'New todo title';
+const TEST_TODO_PATH = '/todos';
-describe('Todos App', () => {
- let vm;
+describe('~/todos/app.vue', () => {
+ let wrapper;
let mock;
beforeEach(() => {
- // Create a mock adapter for stubbing axios API requests
+ // IMPORTANT: Use axios-mock-adapter for stubbing axios API requests
mock = new MockAdapter(axios);
-
- const Component = Vue.extend(component);
-
- // Mount the Component
- vm = new Component().$mount();
+ mock.onGet(TEST_TODO_PATH).reply(200, TEST_TODOS);
+ mock.onPost(TEST_TODO_PATH).reply(200);
});
afterEach(() => {
- // Reset the mock adapter
- mock.restore();
- // Destroy the mounted component
- vm.$destroy();
- });
+ // IMPORTANT: Clean up the component instance and axios mock adapter
+ wrapper.destroy();
+ wrapper = null;
- it('should render the loading state while the request is being made', () => {
- expect(vm.$el.querySelector('i.fa-spin')).toBeDefined();
+ mock.restore();
});
- it('should render todos returned by the endpoint', done => {
- // Mock the get request on the API endpoint to return data
- mock.onGet('/todos').replyOnce(200, [
- {
- title: 'This is a todo',
- text: 'This is the text',
+ // NOTE: It is very helpful to separate setting up the component from
+ // its collaborators (i.e. Vuex, axios, etc.)
+ const createWrapper = (props = {}) => {
+ wrapper = shallowMount(App, {
+ propsData: {
+ path: TEST_TODO_PATH,
+ ...props,
},
- ]);
+ });
+ };
+ // NOTE: Helper methods greatly help test maintainability and readability.
+ const findLoader = () => wrapper.find(GlLoadingIcon);
+ const findAddButton = () => wrapper.find('[data-testid="add-button"]');
+ const findTextInput = () => wrapper.find('[data-testid="text-input"]');
+ const findTodoData = () => wrapper.findAll('[data-testid="todo-item"]').wrappers.map(wrapper => ({ text: wrapper.text() }));
+
+ describe('when mounted and loading', () => {
+ beforeEach(() => {
+ // Create request which will never resolve
+ mock.onGet(TEST_TODO_PATH).reply(() => new Promise(() => {}));
+ createWrapper();
+ });
- Vue.nextTick(() => {
- const items = vm.$el.querySelectorAll('.js-todo-list div')
- expect(items.length).toBe(1);
- expect(items[0].textContent).toContain('This is the text');
- done();
+ it('should render the loading state', () => {
+ expect(findLoader().exists()).toBe(true);
});
});
- it('should add a todos on button click', (done) => {
+ describe('when todos are loaded', () => {
+ beforeEach(() => {
+ createWrapper();
+ // IMPORTANT: This component fetches data asynchronously on mount, so let's wait for the Vue template to update
+ return wrapper.vm.$nextTick();
+ });
- // Mock the put request and check that the sent data object is correct
- mock.onPut('/todos').replyOnce((req) => {
- expect(req.data).toContain('text');
- expect(req.data).toContain('title');
+ it('should not show loading', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
- return [201, {}];
+ it('should render todos', () => {
+ expect(findTodoData()).toEqual(TEST_TODOS);
});
- vm.$el.querySelector('.js-add-todo').click();
+ it('when todo is added, should post new todo', () => {
+ findTextInput().vm.$emit('update', TEST_NEW_TODO)
+ findAddButton().vm.$emit('click');
- // Add a new interceptor to mock the add Todo request
- Vue.nextTick(() => {
- expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(2);
- done();
+ return wrapper.vm.$nextTick()
+ .then(() => {
+ expect(mock.history.post.map(x => JSON.parse(x.data))).toEqual([{ text: TEST_NEW_TODO }]);
+ });
});
});
});
```
-### `mountComponent` helper
+### Test the component's output
-There is a helper in `spec/javascripts/helpers/vue_mount_component_helper.js` that allows you to mount a component with the given props:
+The main return value of a Vue component is the rendered output. In order to test the component we
+need to test the rendered output. [Vue](https://vuejs.org/v2/guide/unit-testing.html) guide's to unit test show us exactly that:
+
+### Events
+
+We should test for events emitted in response to an action within our component, this is useful to verify the correct events are being fired with the correct arguments.
+
+For any DOM events we should use [`trigger`](https://vue-test-utils.vuejs.org/api/wrapper/#trigger) to fire out event.
```javascript
-import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper'
-import component from 'component.vue'
+// Assuming SomeButton renders: <button>Some button</button>
+wrapper = mount(SomeButton);
-const Component = Vue.extend(component);
-const data = {prop: 'foo'};
-const vm = mountComponent(Component, data);
+...
+it('should fire the click event', () => {
+ const btn = wrapper.find('button')
+
+ btn.trigger('click');
+ ...
+})
```
-### Test the component's output
+When we need to fire a Vue event, we should use [`emit`](https://vuejs.org/v2/guide/components-custom-events.html) to fire our event.
-The main return value of a Vue component is the rendered output. In order to test the component we
-need to test the rendered output. [Vue][vue-test] guide's to unit test show us exactly that:
+```javascript
+wrapper = shallowMount(DropdownItem);
+
+...
+
+it('should fire the itemClicked event', () => {
+ DropdownItem.vm.$emit('itemClicked');
+ ...
+})
+```
+
+We should verify an event has been fired by asserting against the result of the [`emitted()`](https://vue-test-utils.vuejs.org/api/wrapper/#emitted) method
## Vue.js Expert Role
@@ -287,15 +326,50 @@ One should apply to be a Vue.js expert by opening an MR when the Merge Request's
- Deep understanding of Vue and Vuex reactivity
- Vue and Vuex code are structured according to both official and our guidelines
- Full understanding of testing a Vue and Vuex application
-- Vuex code follows the [documented pattern](vuex.md#actions-pattern-request-and-receive-namespaces)
+- Vuex code follows the [documented pattern](vuex.md#naming-pattern-request-and-receive-namespaces)
- Knowledge about the existing Vue and Vuex applications and existing reusable components
-[issue-boards]: https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/assets/javascripts/boards
-[environments-table]: https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/assets/javascripts/environments
-[page_specific_javascript]: ./performance.md#page-specific-javascript
-[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
-[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
-[one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow
-[vue-test]: https://vuejs.org/v2/guide/unit-testing.html
-[flux]: https://facebook.github.io/flux/
-[axios]: https://github.com/axios/axios
+## Vue 2 -> Vue 3 Migration
+
+> This section is added temporarily to support the efforts to migrate the codebase from Vue 2.x to Vue 3.x
+
+Currently, we recommend to minimize adding certain features to the codebase to prevent increasing the tech debt for the eventual migration:
+
+- filters;
+- event buses;
+- functional templated
+- `slot` attributes
+
+You can find more details on [Migration to Vue 3](vue3_migration.md)
+
+## Appendix - Vue component subject under test
+
+This is the template for the example component which is tested in the [Testing Vue components](#testing-vue-components) section:
+
+```html
+<template>
+ <div class="content">
+ <gl-loading-icon v-if="isLoading" />
+ <template v-else>
+ <div
+ v-for="todo in todos"
+ :key="todo.id"
+ :class="{ 'gl-strike': todo.isDone }"
+ data-testid="todo-item"
+ >{{ toddo.text }}</div>
+ <footer class="gl-border-t-1 gl-mt-3 gl-pt-3">
+ <gl-form-input
+ type="text"
+ v-model="todoText"
+ data-testid="text-input"
+ >
+ <gl-button
+ variant="success"
+ data-testid="add-button"
+ @click="addTodo"
+ >Add</gl-button>
+ </footer>
+ </template>
+ </div>
+</template>
+```
diff --git a/doc/development/fe_guide/vue3_migration.md b/doc/development/fe_guide/vue3_migration.md
new file mode 100644
index 00000000000..7ab48db7f76
--- /dev/null
+++ b/doc/development/fe_guide/vue3_migration.md
@@ -0,0 +1,124 @@
+# Migration to Vue 3
+
+In order to prepare for the eventual migration to Vue 3.x, we should be wary about adding the following features to the codebase:
+
+## Vue filters
+
+**Why?**
+
+Filters [are removed](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0015-remove-filters.md) from the Vue 3 API completely.
+
+**What to use instead**
+
+Component's computed properties / methods or external helpers.
+
+## Event hub
+
+**Why?**
+
+`$on`, `$once`, and `$off` methods [are removed](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0020-events-api-change.md) from the Vue instance, so in Vue 3 it can't be used to create an event hub.
+
+**What to use instead**
+
+Vue docs recommend using [mitt](https://github.com/developit/mitt) library. It's relatively small (200 bytes gzipped) and has a simple API:
+
+```javascript
+import mitt from 'mitt'
+
+const emitter = mitt()
+
+// listen to an event
+emitter.on('foo', e => console.log('foo', e) )
+
+// listen to all events
+emitter.on('*', (type, e) => console.log(type, e) )
+
+// fire an event
+emitter.emit('foo', { a: 'b' })
+
+// working with handler references:
+function onFoo() {}
+
+emitter.on('foo', onFoo) // listen
+emitter.off('foo', onFoo) // unlisten
+```
+
+**Event hub factory**
+
+To make it easier for you to migrate existing event hubs to the new recommended approach, or simply
+to create new ones, we have created a factory that you can use to instantiate a new mitt-based
+event hub.
+
+```javascript
+import createEventHub from '~/helpers/event_hub_factory';
+
+export default createEventHub();
+```
+
+Event hubs created with the factory expose the same methods as Vue 2 event hubs (`$on`, `$once`, `$off` and
+`$emit`), making them backward compatible with our previous approach.
+
+## \<template functional>
+
+**Why?**
+
+In Vue 3, `{ functional: true }` option [is removed](https://github.com/vuejs/rfcs/blob/functional-async-api-change/active-rfcs/0007-functional-async-api-change.md) and `<template functional>` is no longer supported.
+
+**What to use instead**
+
+Functional components must be written as plain functions:
+
+```javascript
+import { h } from 'vue'
+
+const FunctionalComp = (props, slots) => {
+ return h('div', `Hello! ${props.name}`)
+}
+```
+
+## Old slots syntax with `slot` attribute
+
+**Why?**
+
+In Vue 2.6 `slot` attribute was already deprecated in favor of `v-slot` directive but its usage is still allowed and sometimes we prefer using them because it simplifies unit tests (with old syntax, slots are rendered on `shallowMount`). However, in Vue 3 we can't use old syntax anymore.
+
+**What to use instead**
+
+The syntax with `v-slot` directive. To fix rendering slots in `shallowMount`, we need to stub a child component with slots explicitly.
+
+```html
+<!-- MyAwesomeComponent.vue -->
+<script>
+import SomeChildComponent from './some_child_component.vue'
+
+export default {
+ components: {
+ SomeChildComponent
+ }
+}
+
+</script>
+
+<template>
+ <div>
+ <h1>Hello GitLab!</h1>
+ <some-child-component>
+ <template #header>
+ Header content
+ </template>
+ </some-child-component>
+ </div>
+</template>
+```
+
+```javascript
+// MyAwesomeComponent.spec.js
+
+import SomeChildComponent from '~/some_child_component.vue'
+
+shallowMount(MyAwesomeComponent, {
+ stubs: {
+ SomeChildComponent
+ }
+})
+```
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index 65661b0cc3b..e7be67b8da5 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -1,6 +1,6 @@
# Vuex
-When there's a clear benefit to separating state management from components (e.g. due to state complexity) we recommend using [Vuex][vuex-docs] over any other Flux pattern. Otherwise, feel free to manage state within the components.
+When there's a clear benefit to separating state management from components (e.g. due to state complexity) we recommend using [Vuex](https://vuex.vuejs.org) over any other Flux pattern. Otherwise, feel free to manage state within the components.
Vuex should be strongly considered when:
@@ -9,7 +9,7 @@ Vuex should be strongly considered when:
- There are complex interactions with Backend, e.g. multiple API calls
- The app involves interacting with backend via both traditional REST API and GraphQL (especially when moving the REST API over to GraphQL is a pending backend task)
-_Note:_ All of the below is explained in more detail in the official [Vuex documentation][vuex-docs].
+_Note:_ All of the below is explained in more detail in the official [Vuex documentation](https://vuex.vuejs.org).
## Separation of concerns
@@ -86,71 +86,35 @@ You can use `mapState` to access state properties in the components.
An action is a payload of information to send data from our application to our store.
-An action is usually composed by a `type` and a `payload` and they describe what happened.
-Enforcing that every change is described as an action lets us have a clear understanding of what is going on in the app.
+An action is usually composed by a `type` and a `payload` and they describe what happened. Unlike [mutations](#mutationsjs), actions can contain asynchronous operations - that's why we always need to handle asynchronous logic in actions.
-In this file, we will write the actions that will call the respective mutations:
+In this file, we will write the actions that will call mutations for handling a list of users:
```javascript
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
- export const requestUsers = ({ commit }) => commit(types.REQUEST_USERS);
- export const receiveUsersSuccess = ({ commit }, data) => commit(types.RECEIVE_USERS_SUCCESS, data);
- export const receiveUsersError = ({ commit }, error) => commit(types.RECEIVE_USERS_ERROR, error);
-
export const fetchUsers = ({ state, dispatch }) => {
- dispatch('requestUsers');
+ commit(types.REQUEST_USERS);
axios.get(state.endpoint)
- .then(({ data }) => dispatch('receiveUsersSuccess', data))
+ .then(({ data }) => commit(types.RECEIVE_USERS_SUCCESS, data))
.catch((error) => {
- dispatch('receiveUsersError', error)
+ commit(types.RECEIVE_USERS_ERROR, error)
createFlash('There was an error')
});
}
- export const requestAddUser = ({ commit }) => commit(types.REQUEST_ADD_USER);
- export const receiveAddUserSuccess = ({ commit }, data) => commit(types.RECEIVE_ADD_USER_SUCCESS, data);
- export const receiveAddUserError = ({ commit }, error) => commit(types.REQUEST_ADD_USER_ERROR, error);
-
export const addUser = ({ state, dispatch }, user) => {
- dispatch('requestAddUser');
+ commit(types.REQUEST_ADD_USER);
axios.post(state.endpoint, user)
- .then(({ data }) => dispatch('receiveAddUserSuccess', data))
- .catch((error) => dispatch('receiveAddUserError', error));
+ .then(({ data }) => commit(types.RECEIVE_ADD_USER_SUCCESS, data))
+ .catch((error) => commit(types.REQUEST_ADD_USER_ERROR, error));
}
```
-#### Actions Pattern: `request` and `receive` namespaces
-
-When a request is made we often want to show a loading state to the user.
-
-Instead of creating an action to toggle the loading state and dispatch it in the component,
-create:
-
-1. An action `requestSomething`, to toggle the loading state
-1. An action `receiveSomethingSuccess`, to handle the success callback
-1. An action `receiveSomethingError`, to handle the error callback
-1. An action `fetchSomething` to make the request.
- 1. In case your application does more than a `GET` request you can use these as examples:
- - `POST`: `createSomething`
- - `PUT`: `updateSomething`
- - `DELETE`: `deleteSomething`
-
-The component MUST only dispatch the `fetchNamespace` action. Actions namespaced with `request` or `receive` should not be called from the component
-The `fetch` action will be responsible to dispatch `requestNamespace`, `receiveNamespaceSuccess` and `receiveNamespaceError`
-
-By following this pattern we guarantee:
-
-1. All applications follow the same pattern, making it easier for anyone to maintain the code
-1. All data in the application follows the same lifecycle pattern
-1. Actions are contained and human friendly
-1. Unit tests are easier
-1. Actions are simple and straightforward
-
#### Dispatching actions
To dispatch an action from a component, use the `mapActions` helper:
@@ -181,6 +145,8 @@ Remember that actions only describe that something happened, they don't describe
**Never commit a mutation directly from a component**
+Instead, you should create an action that will commit a mutation.
+
```javascript
import * as types from './mutation_types';
@@ -210,6 +176,31 @@ Remember that actions only describe that something happened, they don't describe
};
```
+#### Naming Pattern: `REQUEST` and `RECEIVE` namespaces
+
+When a request is made we often want to show a loading state to the user.
+
+Instead of creating an mutation to toggle the loading state, we should:
+
+1. A mutation with type `REQUEST_SOMETHING`, to toggle the loading state
+1. A mutation with type `RECEIVE_SOMETHING_SUCCESS`, to handle the success callback
+1. A mutation with type `RECEIVE_SOMETHING_ERROR`, to handle the error callback
+1. An action `fetchSomething` to make the request and commit mutations on mentioned cases
+ 1. In case your application does more than a `GET` request you can use these as examples:
+ - `POST`: `createSomething`
+ - `PUT`: `updateSomething`
+ - `DELETE`: `deleteSomething`
+
+As a result, we can dispatch the `fetchNamespace` action from the component and it will be responsible to commit `REQUEST_NAMESPACE`, `RECEIVE_NAMESPACE_SUCCESS` and `RECEIVE_NAMESPACE_ERROR` mutations.
+
+> Previously, we were dispatching actions from the `fetchNamespace` action instead of committing mutation, so please don't be confused if you find a different pattern in the older parts of the codebase. However, we encourage leveraging a new pattern whenever you write new Vuex stores
+
+By following this pattern we guarantee:
+
+1. All applications follow the same pattern, making it easier for anyone to maintain the code
+1. All data in the application follows the same lifecycle pattern
+1. Unit tests are easier
+
### `getters.js`
Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
@@ -477,8 +468,6 @@ To prevent this error from happening, you need to export an empty function as `d
export default () => {};
```
-[vuex-docs]: https://vuex.vuejs.org
-
### Two way data binding
When storing form data in Vuex, it is sometimes necessary to update the value stored. The store should never be mutated directly, and an action should be used instead.