diff options
author | Simon Knox <psimyn@gmail.com> | 2018-11-30 07:06:29 +0300 |
---|---|---|
committer | Simon Knox <psimyn@gmail.com> | 2018-11-30 07:06:29 +0300 |
commit | 60bd8dcb3734f00dd151b56fd1b47737cb18b327 (patch) | |
tree | fa6a0091cbee83dff75e555a35d7cd0640a0c0fe | |
parent | 979bd9161fe0508f77e9e04973fcb94687f8cd27 (diff) |
Rename new_fe_guide to fe_guide
-rw-r--r-- | doc/development/fe_guide/accessibility.md | 13 | ||||
-rw-r--r-- | doc/development/fe_guide/architecture.md | 22 | ||||
-rw-r--r-- | doc/development/fe_guide/axios.md | 81 | ||||
-rw-r--r-- | doc/development/fe_guide/components.md | 61 | ||||
-rw-r--r-- | doc/development/fe_guide/dependencies.md (renamed from doc/development/new_fe_guide/dependencies.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/design_patterns.md | 78 | ||||
-rw-r--r-- | doc/development/fe_guide/development/accessibility.md (renamed from doc/development/new_fe_guide/development/accessibility.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/development/components.md (renamed from doc/development/new_fe_guide/development/components.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/development/design_patterns.md (renamed from doc/development/new_fe_guide/development/design_patterns.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/development/index.md (renamed from doc/development/new_fe_guide/development/index.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/development/network_requests.md (renamed from doc/development/new_fe_guide/development/network_requests.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/development/performance.md (renamed from doc/development/new_fe_guide/development/performance.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/development/security.md (renamed from doc/development/new_fe_guide/development/security.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/development/testing.md (renamed from doc/development/new_fe_guide/development/testing.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/development_process.md | 77 | ||||
-rw-r--r-- | doc/development/fe_guide/dropdowns.md | 1 | ||||
-rw-r--r-- | doc/development/fe_guide/droplab/droplab.md | 258 | ||||
-rw-r--r-- | doc/development/fe_guide/droplab/plugins/ajax.md | 37 | ||||
-rw-r--r-- | doc/development/fe_guide/droplab/plugins/filter.md | 45 | ||||
-rw-r--r-- | doc/development/fe_guide/droplab/plugins/input_setter.md | 60 | ||||
-rw-r--r-- | doc/development/fe_guide/emojis.md | 27 | ||||
-rw-r--r-- | doc/development/fe_guide/icons.md | 116 | ||||
-rw-r--r-- | doc/development/fe_guide/img/boards_diagram.png | bin | 9518 -> 0 bytes | |||
-rw-r--r-- | doc/development/fe_guide/img/gl-modal.png | bin | 8767 -> 0 bytes | |||
-rw-r--r-- | doc/development/fe_guide/index.md | 121 | ||||
-rw-r--r-- | doc/development/fe_guide/initiatives.md (renamed from doc/development/new_fe_guide/initiatives.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/modules/dirty_submit.md (renamed from doc/development/new_fe_guide/modules/dirty_submit.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/modules/index.md (renamed from doc/development/new_fe_guide/modules/index.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/performance.md | 178 | ||||
-rw-r--r-- | doc/development/fe_guide/principles.md (renamed from doc/development/new_fe_guide/principles.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/security.md | 92 | ||||
-rw-r--r-- | doc/development/fe_guide/style/html.md (renamed from doc/development/new_fe_guide/style/html.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/style/index.md (renamed from doc/development/new_fe_guide/style/index.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/style/javascript.md (renamed from doc/development/new_fe_guide/style/javascript.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/style/prettier.md (renamed from doc/development/new_fe_guide/style/prettier.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/style/scss.md (renamed from doc/development/new_fe_guide/style/scss.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/style/vue.md (renamed from doc/development/new_fe_guide/style/vue.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/style_guide_js.md | 706 | ||||
-rw-r--r-- | doc/development/fe_guide/style_guide_scss.md | 239 | ||||
-rw-r--r-- | doc/development/fe_guide/testing.md | 1 | ||||
-rw-r--r-- | doc/development/fe_guide/tips.md (renamed from doc/development/new_fe_guide/tips.md) | 0 | ||||
-rw-r--r-- | doc/development/fe_guide/vue.md | 242 | ||||
-rw-r--r-- | doc/development/fe_guide/vuex.md | 377 | ||||
-rw-r--r-- | doc/development/new_fe_guide/index.md | 32 |
44 files changed, 16 insertions, 2848 deletions
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md deleted file mode 100644 index 366b220cbb2..00000000000 --- a/doc/development/fe_guide/accessibility.md +++ /dev/null @@ -1,13 +0,0 @@ -# Accessibility - -## Resources - -[Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools] -are useful for testing for potential accessibility problems in GitLab. - -Accessibility best-practices and more in-depth information is available on -[the Audit Rules page][audit-rules] for the Chrome Accessibility Developer Tools. - - -[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools -[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules diff --git a/doc/development/fe_guide/architecture.md b/doc/development/fe_guide/architecture.md deleted file mode 100644 index aebb22caa15..00000000000 --- a/doc/development/fe_guide/architecture.md +++ /dev/null @@ -1,22 +0,0 @@ -# Architecture - -When you are developing a new feature that requires architectural design, or if -you are changing the fundamental design of an existing feature, make sure it is -discussed with one of the Frontend Architecture Experts. - -A Frontend Architect is an expert who makes high-level Frontend design decisions -and decides on technical standards, including coding standards and frameworks. - -Architectural decisions should be accessible to everyone, so please document -them in the relevant Merge Request discussion or by updating our documentation -when appropriate. - -You can find the Frontend Architecture experts on the [team page][team-page]. - -## Examples - -You can find documentation about the desired architecture for a new feature -built with Vue.js [here][vue-section]. - -[team-page]: https://about.gitlab.com/team -[vue-section]: vue.md#frontend.html#how-to-build-a-new-feature-with-vue-js diff --git a/doc/development/fe_guide/axios.md b/doc/development/fe_guide/axios.md deleted file mode 100644 index 0d9397c3bd5..00000000000 --- a/doc/development/fe_guide/axios.md +++ /dev/null @@ -1,81 +0,0 @@ -# Axios -We use [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` . - -This exported module should be used instead of directly using `axios` to ensure the token is set. - -## Usage -```javascript - import axios from './lib/utils/axios_utils'; - - axios.get(url) - .then((response) => { - // `data` is the response that was provided by the server - const data = response.data; - - // `headers` the headers that the server responded with - // All header names are lower cased - const paginationData = response.headers; - }) - .catch(() => { - //handle the error - }); -``` - -## Mock axios response in tests - -To help us mock the responses we are using [axios-mock-adapter][axios-mock-adapter]. - -Advantages over [`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 - -### Example - -```javascript - import axios from '~/lib/utils/axios_utils'; - import MockAdapter from 'axios-mock-adapter'; - - let mock; - beforeEach(() => { - // This sets the mock adapter on the default instance - mock = new MockAdapter(axios); - // Mock any GET request to /users - // arguments for reply are (status, data, headers) - mock.onGet('/users').reply(200, { - users: [ - { id: 1, name: 'John Smith' } - ] - }); - }); - - afterEach(() => { - mock.restore(); - }); -``` - -### Mock poll requests in tests with axios - -Because polling function requires a header object, we need to always include an object as the third argument: - -```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/components.md b/doc/development/fe_guide/components.md deleted file mode 100644 index 0e9126ee667..00000000000 --- a/doc/development/fe_guide/components.md +++ /dev/null @@ -1,61 +0,0 @@ -# Components - -## Contents -* [Dropdowns](#dropdowns) -* [Modals](#modals) - -## Dropdowns - -See also the [corresponding UX guide](https://design.gitlab.com/#/components/dropdowns). - -### How to style a bootstrap dropdown -1. Use the HTML structure provided by the [docs][bootstrap-dropdowns] -1. Add a specific class to the top level `.dropdown` element - - - ```Haml - .dropdown.my-dropdown - %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false } - %span.dropdown-toggle-text - Toggle Dropdown - = icon('chevron-down') - - %ul.dropdown-menu - %li - %a - item! - ``` - - Or use the helpers - ```Haml - .dropdown.my-dropdown - = dropdown_toggle('Toogle!', { toggle: 'dropdown' }) - = dropdown_content - %li - %a - item! - ``` - -[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns - -## Modals - -See also the [corresponding UX guide](https://design.gitlab.com/#/components/modals). - -We have a reusable Vue component for modals: [vue_shared/components/gl_modal.vue](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/vue_shared/components/gl_modal.vue) - -Here is an example of how to use it: - -```html - <gl-modal - id="dogs-out-modal" - :header-title-text="s__('ModalExample|Let the dogs out?')" - footer-primary-button-variant="danger" - :footer-primary-button-text="s__('ModalExample|Let them out')" - @submit="letOut(theDogs)" - > - {{ s__('ModalExample|You’re about to let the dogs out.') }} - </gl-modal> -``` - -![example modal](img/gl-modal.png) diff --git a/doc/development/new_fe_guide/dependencies.md b/doc/development/fe_guide/dependencies.md index 12a4f089d41..12a4f089d41 100644 --- a/doc/development/new_fe_guide/dependencies.md +++ b/doc/development/fe_guide/dependencies.md diff --git a/doc/development/fe_guide/design_patterns.md b/doc/development/fe_guide/design_patterns.md deleted file mode 100644 index e05887a19af..00000000000 --- a/doc/development/fe_guide/design_patterns.md +++ /dev/null @@ -1,78 +0,0 @@ -# Design Patterns - -## Singletons - -When exactly one object is needed for a given task, prefer to define it as a -`class` rather than as an object literal. Prefer also to explicitly restrict -instantiation, unless flexibility is important (e.g. for testing). - -```javascript -// bad - -const MyThing = { - prop1: 'hello', - method1: () => {} -}; - -export default MyThing; - -// good - -class MyThing { - constructor() { - this.prop1 = 'hello'; - } - method1() {} -} - -export default new MyThing(); - -// best - -export default class MyThing { - constructor() { - if (!this.prototype.singleton) { - this.init(); - this.prototype.singleton = this; - } - return this.prototype.singleton; - } - - init() { - this.prop1 = 'hello'; - } - - method1() {} -} - -``` - -## Manipulating the DOM in a JS Class - -When writing a class that needs to manipulate the DOM guarantee a container option is provided. -This is useful when we need that class to be instantiated more than once in the same page. - -Bad: -```javascript -class Foo { - constructor() { - document.querySelector('.bar'); - } -} -new Foo(); -``` - -Good: -```javascript -class Foo { - constructor(opts) { - document.querySelector(`${opts.container} .bar`); - } -} - -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-ce/blob/master/app/assets/javascripts/mini_pipeline_graph_dropdown.js diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/fe_guide/development/accessibility.md index 2a3a126ca5c..2a3a126ca5c 100644 --- a/doc/development/new_fe_guide/development/accessibility.md +++ b/doc/development/fe_guide/development/accessibility.md diff --git a/doc/development/new_fe_guide/development/components.md b/doc/development/fe_guide/development/components.md index 899efb398cd..899efb398cd 100644 --- a/doc/development/new_fe_guide/development/components.md +++ b/doc/development/fe_guide/development/components.md diff --git a/doc/development/new_fe_guide/development/design_patterns.md b/doc/development/fe_guide/development/design_patterns.md index ee06566ed30..ee06566ed30 100644 --- a/doc/development/new_fe_guide/development/design_patterns.md +++ b/doc/development/fe_guide/development/design_patterns.md diff --git a/doc/development/new_fe_guide/development/index.md b/doc/development/fe_guide/development/index.md index cee8e43ebad..cee8e43ebad 100644 --- a/doc/development/new_fe_guide/development/index.md +++ b/doc/development/fe_guide/development/index.md diff --git a/doc/development/new_fe_guide/development/network_requests.md b/doc/development/fe_guide/development/network_requests.md index 047c00313bc..047c00313bc 100644 --- a/doc/development/new_fe_guide/development/network_requests.md +++ b/doc/development/fe_guide/development/network_requests.md diff --git a/doc/development/new_fe_guide/development/performance.md b/doc/development/fe_guide/development/performance.md index 5ccd5357314..5ccd5357314 100644 --- a/doc/development/new_fe_guide/development/performance.md +++ b/doc/development/fe_guide/development/performance.md diff --git a/doc/development/new_fe_guide/development/security.md b/doc/development/fe_guide/development/security.md index 5bb38f17988..5bb38f17988 100644 --- a/doc/development/new_fe_guide/development/security.md +++ b/doc/development/fe_guide/development/security.md diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/fe_guide/development/testing.md index 082acbedcd2..082acbedcd2 100644 --- a/doc/development/new_fe_guide/development/testing.md +++ b/doc/development/fe_guide/development/testing.md diff --git a/doc/development/fe_guide/development_process.md b/doc/development/fe_guide/development_process.md deleted file mode 100644 index 905668eef58..00000000000 --- a/doc/development/fe_guide/development_process.md +++ /dev/null @@ -1,77 +0,0 @@ -# Frontend Development Process - -You can find more about the organization of the frontend team in the [handbook](https://about.gitlab.com/handbook/engineering/frontend/). - -## Development Checklist - -The idea is to remind us about specific topics during the time we build a new feature or start something. This is a common practice in other industries (like pilots) that also use standardised checklists to reduce problems early on. - -Copy the content over to your issue or merge request and if something doesn't apply simply remove it from your current list. - -This checklist is intended to help us during development of bigger features/refactorings, it's not a "use it always and every point always matches" list. - -Please use your best judgement when to use it and please contribute new points through merge requests if something comes to your mind. - ---- - -### Frontend development - -#### Planning development - -- [ ] Check the current set weight of the issue, does it fit your estimate? -- [ ] Are all [departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) that are needed from your perspective already involved in the issue? (For example is UX missing?) -- [ ] Is the specification complete? Are you missing decisions? How about error handling/defaults/edge cases? Take your time to understand the needed implementation and go through its flow. -- [ ] Are all necessary UX specifications available that you will need in order to implement? Are there new UX components/patterns in the designs? Then contact the UI component team early on. How should error messages or validation be handled? -- [ ] **Library usage** Use Vuex as soon as you have even a medium state to manage, use Vue router if you need to have different views internally and want to link from the outside. Check what libraries we already have for which occasions. -- [ ] **Plan your implementation:** - - [ ] **Architecture plan:** Create a plan aligned with GitLab's architecture, how you are going to do the implementation, for example Vue application setup and its components (through [onion skinning](https://gitlab.com/gitlab-org/gitlab-ce/issues/35873#note_39994091)), Store structure and data flow, which existing Vue components can you reuse. It's a good idea to go through your plan with another engineer to refine it. - - [ ] **Backend:** The best way is to kickoff the implementation in a call and discuss with the assigned Backend engineer what you will need from the backend and also when. Can you reuse existing API's? How is the performance with the planned architecture? Maybe create together a JSON mock object to already start with development. - - [ ] **Communication:** It also makes sense to have for bigger features an own slack channel (normally called #f_{feature_name}) and even weekly demo calls with all people involved. - - [ ] **Dependency Plan:** Are there big dependencies in the plan between you and others, then maybe create an execution diagram to show what is blocking which part and the order of the different parts. - - [ ] **Task list:** Create a simple checklist of the subtasks that are needed for the implementation, also consider creating even sub issues. (for example show a comment, delete a comment, update a comment, etc.). This helps you and also everyone else following the implementation -- [ ] **Keep it small** To make it easier for you and also all reviewers try to keep merge requests small and merge into a feature branch if needed. To accomplish that you need to plan that from the start. Different methods are: - - [ ] **Skeleton based plan** Start with an MR that has the skeleton of the components with placeholder content. In following MRs you can fill the components with interactivity. This also makes it easier to spread out development on multiple people. - - [ ] **Cookie Mode** Think about hiding the feature behind a cookie flag if the implementation is on top of existing features - - [ ] **New route** Are you refactoring something big then you might consider adding a new route where you implement the new feature and when finished delete the current route and rename the new one. (for example 'merge_request' and 'new_merge_request') -- [ ] **Setup** Is there any specific setup needed for your implementation (for example a kubernetes cluster)? Then let everyone know if it is not already mentioned where they can find documentation (if it doesn't exist - create it) -- [ ] **Security** Are there any new security relevant implementations? Then please contact the security team for an app security review. If you are not sure ask our [domain expert](https://about.gitlab.com/handbook/engineering/frontend/#frontend-domain-experts) - -#### During development - -- [ ] Check off tasks on your created task list to keep everyone updated on the progress -- [ ] [Share your work early with reviewers/maintainers](#share-your-work-early) -- [ ] Share your work with UXer and Product Manager with Screenshots and/or [GIF's](https://about.gitlab.com/handbook/product/making-gifs/). They are easy to create for you and keep them up to date. -- [ ] If you are blocked on something let everyone on the issue know through a comment. -- [ ] Are you unable to work on this issue for a longer period of time, also let everyone know. -- [ ] **Documentation** Update/add docs for the new feature, see `docs/`. Ping one of the documentation experts/reviewers - -#### Finishing development + Review - -- [ ] **Keep it in the scope** Try to focus on the actual scope and avoid a scope creep during review and keep new things to new issues. -- [ ] **Performance** Have you checked performance? For example do the same thing with 500 comments instead of 1. Document the tests and possible findings in the MR so a reviewer can directly see it. -- [ ] Have you tested with a variety of our [supported browsers](../../install/requirements.md#supported-web-browsers)? You can use [browserstack](https://www.browserstack.com/) to be able to access a wide variety of browsers and operating systems. -- [ ] Did you check the mobile view? -- [ ] Check the built webpack bundle (For the report run `WEBPACK_REPORT=true gdk run`, then open `webpack-report/index.html`) if we have unnecessary bloat due to wrong references, including libraries multiple times, etc.. If you need help contact the webpack [domain expert](https://about.gitlab.com/handbook/engineering/frontend/#frontend-domain-experts) -- [ ] **Tests** Not only greenfield tests - Test also all bad cases that come to your mind. -- [ ] If you have multiple MR's then also smoke test against the final merge. -- [ ] Are there any big changes on how and especially how frequently we use the API then let production know about it -- [ ] Smoke test of the RC on dev., staging., canary deployments and .com -- [ ] Follow up on issues that came out of the review. Create issues for discovered edge cases that should be covered in future iterations. - ---- - -### Share your work early -1. Before writing code, ensure your vision of the architecture is aligned with -GitLab's architecture. -1. Add a diagram to the issue and ask a frontend architect in the slack channel `#fe_architectural` about it. - - ![Diagram of Issue Boards Architecture](img/boards_diagram.png) - -1. Don't take more than one week between starting work on a feature and -sharing a Merge Request with a reviewer or a maintainer. - -### Vue features -1. Follow the steps in [Vue.js Best Practices](vue.md) -1. Follow the style guide. -1. Only a handful of people are allowed to merge Vue related features. -Reach out to one of Vue experts early in this process. diff --git a/doc/development/fe_guide/dropdowns.md b/doc/development/fe_guide/dropdowns.md deleted file mode 100644 index e9d6244355c..00000000000 --- a/doc/development/fe_guide/dropdowns.md +++ /dev/null @@ -1 +0,0 @@ -This page has moved [here](components.md#dropdowns). diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md deleted file mode 100644 index ce96a9fc8ae..00000000000 --- a/doc/development/fe_guide/droplab/droplab.md +++ /dev/null @@ -1,258 +0,0 @@ -# DropLab - -A generic dropdown for all of your custom dropdown needs. - -## Usage - -DropLab can be used by simply adding a `data-dropdown-trigger` HTML attribute. -This attribute allows us to find the "trigger" _(toggle)_ for the dropdown, -whether that is a button, link or input. - -The value of the `data-dropdown-trigger` should be a CSS selector that -DropLab can use to find the trigger's dropdown list. - -You should also add the `data-dropdown` attribute to declare the dropdown list. -The value is irrelevant. - -The DropLab class has no side effects, so you must always call `.init` when -the DOM is ready. `DropLab.prototype.init` takes the same arguments as `DropLab.prototype.addHook`. -If you do not provide any arguments, it will globally query and instantiate all droplab compatible dropdowns. - -```html -<a href="#" data-dropdown-trigger="#list">Toggle</a> - -<ul id="list" data-dropdown> - <!-- ... --> -<ul> -``` -```js -const droplab = new DropLab(); -droplab.init(); -``` - -As you can see, we have a "Toggle" link, that is declared as a trigger. -It provides a selector to find the dropdown list it should control. - -### Static data - -You can add static list items. - -```html -<a href="#" data-dropdown-trigger="#list">Toggle</a> - -<ul id="list" data-dropdown> - <li>Static value 1</li> - <li>Static value 2</li> -<ul> -``` -```js -const droplab = new DropLab(); -droplab.init(); -``` - -### Explicit instantiation - -You can pass the trigger and list elements as constructor arguments to return -a non-global instance of DropLab using the `DropLab.prototype.init` method. - -```html -<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a> - -<ul id="list" data-dropdown> - <!-- ... --> -<ul> -``` -```js -const trigger = document.getElementById('trigger'); -const list = document.getElementById('list'); - -const droplab = new DropLab(); -droplab.init(trigger, list); -``` - -You can also add hooks to an existing DropLab instance using `DropLab.prototype.addHook`. - -```html -<a href="#" data-dropdown-trigger="#auto-dropdown">Toggle</a> -<ul id="auto-dropdown" data-dropdown><!-- ... --><ul> - -<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a> -<ul id="list" data-dropdown><!-- ... --><ul> -``` -```js -const droplab = new DropLab(); - -droplab.init(); - -const trigger = document.getElementById('trigger'); -const list = document.getElementById('list'); - -droplab.addHook(trigger, list); -``` - - -### Dynamic data - -Adding `data-dynamic` to your dropdown element will enable dynamic list rendering. - -You can template a list item using the keys of the data object provided. -Use the handlebars syntax `{{ value }}` to HTML escape the value. -Use the `<%= value %>` syntax to simply interpolate the value. -Use the `<%= value %>` syntax to evaluate the value. - -Passing an array of objects to `DropLab.prototype.addData` will render that data -for all `data-dynamic` dropdown lists tracked by that DropLab instance. - -```html -<a href="#" data-dropdown-trigger="#list">Toggle</a> - -<ul id="list" data-dropdown data-dynamic> - <li><a href="#" data-id="{{id}}">{{text}}</a></li> -</ul> -``` -```js -const droplab = new DropLab(); - -droplab.init().addData([{ - id: 0, - text: 'Jacob', -}, { - id: 1, - text: 'Jeff', -}]); -``` - -Alternatively, you can specify a specific dropdown to add this data to but passing -the data as the second argument and the `id` of the trigger element as the first argument. - -```html -<a href="#" data-dropdown-trigger="#list" id="trigger">Toggle</a> - -<ul id="list" data-dropdown data-dynamic> - <li><a href="#" data-id="{{id}}">{{text}}</a></li> -</ul> -``` -```js -const droplab = new DropLab(); - -droplab.init().addData('trigger', [{ - id: 0, - text: 'Jacob', -}, { - id: 1, - text: 'Jeff', -}]); -``` - -This allows you to mix static and dynamic content with ease, even with one trigger. - -Note the use of scoping regarding the `data-dropdown` attribute to capture both -dropdown lists, one of which is dynamic. - -```html -<input id="trigger" data-dropdown-trigger="#list"> -<div id="list" data-dropdown> - <ul> - <li><a href="#">Static item 1</a></li> - <li><a href="#">Static item 2</a></li> - </ul> - <ul data-dynamic> - <li><a href="#" data-id="{{id}}">{{text}}</a></li> - </ul> -</div> -``` -```js -const droplab = new DropLab(); - -droplab.init().addData('trigger', [{ - id: 0, - text: 'Jacob', -}, { - id: 1, - text: 'Jeff', -}]); -``` - -## Internal selectors - -DropLab adds some CSS classes to help lower the barrier to integration. - -For example, - -* The `droplab-item-selected` css class is added to items that have been selected -either by a mouse click or by enter key selection. -* The `droplab-item-active` css class is added to items that have been selected -using arrow key navigation. -* You can add the `droplab-item-ignore` css class to any item that you do not want to be selectable. For example, -an `<li class="divider"></li>` list divider element that should not be interactive. - -## Internal events - -DropLab uses some custom events to help lower the barrier to integration. - -For example, - -* The `click.dl` event is fired when an `li` list item has been clicked. It is also -fired when a list item has been selected with the keyboard. It is also fired when a -`HookButton` button is clicked (a registered `button` tag or `a` tag trigger). -* The `input.dl` event is fired when a `HookInput` (a registered `input` tag trigger) triggers an `input` event. -* The `mousedown.dl` event is fired when a `HookInput` triggers a `mousedown` event. -* The `keyup.dl` event is fired when a `HookInput` triggers a `keyup` event. -* The `keydown.dl` event is fired when a `HookInput` triggers a `keydown` event. - -These custom events add a `detail` object to the vanilla `Event` object that provides some potentially useful data. - -## Plugins - -Plugins are objects that are registered to be executed when a hook is added (when a droplab trigger and dropdown are instantiated). - -If no modules API is detected, the library will fall back as it does with `window.DropLab` and will add `window.DropLab.plugins.PluginName`. - -### Usage - -To use plugins, you can pass them in an array as the third argument of `DropLab.prototype.init` or `DropLab.prototype.addHook`. -Some plugins require configuration values, the config object can be passed as the fourth argument. - -```html -<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a> -<ul id="list" data-dropdown><!-- ... --><ul> -``` -```js -const droplab = new DropLab(); - -const trigger = document.getElementById('trigger'); -const list = document.getElementById('list'); - -droplab.init(trigger, list, [droplabAjax], { - droplabAjax: { - endpoint: '/some-endpoint', - method: 'setData', - }, -}); -``` - -### Documentation - -* [Ajax plugin](plugins/ajax.md) -* [Filter plugin](plugins/filter.md) -* [InputSetter plugin](plugins/input_setter.md) - -### Development - -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 -class MyPlugin { - static init() { - this.someProp = 'someProp'; - this.someMethod(); - } - - static someMethod() { - this.otherProp = 'otherProp'; - } -} - -export default MyPlugin; -``` diff --git a/doc/development/fe_guide/droplab/plugins/ajax.md b/doc/development/fe_guide/droplab/plugins/ajax.md deleted file mode 100644 index 9c7e56fa448..00000000000 --- a/doc/development/fe_guide/droplab/plugins/ajax.md +++ /dev/null @@ -1,37 +0,0 @@ -# Ajax - -`Ajax` is a droplab plugin that allows for retrieving and rendering list data from a server. - -## Usage - -Add the `Ajax` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call. - -`Ajax` requires 2 config values, the `endpoint` and `method`. - -* `endpoint` should be a URL to the request endpoint. -* `method` should be `setData` or `addData`. -* `setData` completely replaces the dropdown with the response data. -* `addData` appends the response data to the current dropdown list. - -```html -<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a> -<ul id="list" data-dropdown><!-- ... --><ul> -``` -```js - const droplab = new DropLab(); - - const trigger = document.getElementById('trigger'); - const list = document.getElementById('list'); - - droplab.addHook(trigger, list, [Ajax], { - Ajax: { - endpoint: '/some-endpoint', - method: 'setData', - }, - }); -``` - -Optionally you can set `loadingTemplate` to a HTML string. This HTML string will -replace the dropdown list whilst the request is pending. - -Additionally, you can set `onError` to a function to catch any XHR errors. diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md deleted file mode 100644 index 0853ea4d320..00000000000 --- a/doc/development/fe_guide/droplab/plugins/filter.md +++ /dev/null @@ -1,45 +0,0 @@ -# Filter - -`Filter` is a plugin that allows for filtering data that has been added -to the dropdown using a simple fuzzy string search of an input value. - -## Usage - -Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call. - -* `Filter` requires a config value for `template`. -* `template` should be the key of the objects within your data array that you want to compare -to the user input string, for filtering. - -```html -<input href="#" id="trigger" data-dropdown-trigger="#list"> -<ul id="list" data-dropdown data-dynamic> - <li><a href="#" data-id="{{id}}">{{text}}</a></li> -<ul> -``` -```js - const droplab = new DropLab(); - - const trigger = document.getElementById('trigger'); - const list = document.getElementById('list'); - - droplab.init(trigger, list, [Filter], { - Filter: { - template: 'text', - }, - }); - - droplab.addData('trigger', [{ - id: 0, - text: 'Jacob', - }, { - id: 1, - text: 'Jeff', - }]); -``` - -Above, the input string will be compared against the `test` key of the passed data objects. - -Optionally you can set `filterFunction` to a function. This function will be used instead -of `Filter`'s built in string search. `filterFunction` is passed 2 arguments, the first -is one of the data objects, the second is the current input value. diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md deleted file mode 100644 index 94f3c8254a8..00000000000 --- a/doc/development/fe_guide/droplab/plugins/input_setter.md +++ /dev/null @@ -1,60 +0,0 @@ -# InputSetter - -`InputSetter` is a plugin that allows for updating DOM out of the scope of droplab when a list item is clicked. - -## Usage - -Add the `InputSetter` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call. - -* `InputSetter` requires a config value for `input` and `valueAttribute`. -* `input` should be the DOM element that you want to manipulate. -* `valueAttribute` should be a string that is the name of an attribute on your list items that is used to get the value -to update the `input` element with. - -You can also set the `InputSetter` config to an array of objects, which will allow you to update multiple elements. - - -```html -<input id="input" value=""> -<div id="div" data-selected-id=""></div> - -<input href="#" id="trigger" data-dropdown-trigger="#list"> -<ul id="list" data-dropdown data-dynamic> - <li><a href="#" data-id="{{id}}">{{text}}</a></li> -<ul> -``` -```js - const droplab = new DropLab(); - - const trigger = document.getElementById('trigger'); - const list = document.getElementById('list'); - - const input = document.getElementById('input'); - const div = document.getElementById('div'); - - droplab.init(trigger, list, [InputSetter], { - InputSetter: [{ - input: input, - valueAttribute: 'data-id', - } { - input: div, - valueAttribute: 'data-id', - inputAttribute: 'data-selected-id', - }], - }); - - droplab.addData('trigger', [{ - id: 0, - text: 'Jacob', - }, { - id: 1, - text: 'Jeff', - }]); -``` - -Above, if the second list item was clicked, it would update the `#input` element -to have a `value` of `1`, it would also update the `#div` element's `data-selected-id` to `1`. - -Optionally you can set `inputAttribute` to a string that is the name of an attribute on your `input` element that you want to update. -If you do not provide an `inputAttribute`, `InputSetter` will update the `value` of the `input` element if it is an `INPUT` element, -or the `textContent` of the `input` element if it is not an `INPUT` element. diff --git a/doc/development/fe_guide/emojis.md b/doc/development/fe_guide/emojis.md deleted file mode 100644 index 38794c47965..00000000000 --- a/doc/development/fe_guide/emojis.md +++ /dev/null @@ -1,27 +0,0 @@ -# Emojis - -GitLab supports native unicode emojis and fallsback to image-based emojis selectively -when your platform does not support it. - -# How to update Emojis - - 1. Update the `gemojione` gem - 1. Update `fixtures/emojis/index.json` from [Gemojione](https://github.com/jonathanwiesel/gemojione/blob/master/config/index.json). - In the future, we could grab the file directly from the gem. - We should probably make a PR on the Gemojione project to get access to - all emojis after being parsed or just a raw path to the `json` file itself. - 1. Ensure [`emoji-unicode-version`](https://www.npmjs.com/package/emoji-unicode-version) - is up to date with the latest version. - 1. Run `bundle exec rake gemojione:aliases` - 1. Run `bundle exec rake gemojione:digests` - 1. Run `bundle exec rake gemojione:sprite` - 1. Ensure new sprite sheets generated for 1x and 2x - - `app/assets/images/emoji.png` - - `app/assets/images/emoji@2x.png` - 1. Ensure you see new individual images copied into `app/assets/images/emoji/` - 1. Ensure you can see the new emojis and their aliases in the GFM Autocomplete - 1. Ensure you can see the new emojis and their aliases in the award emoji menu - 1. You might need to add new emoji unicode support checks and rules for platforms - that do not support a certain emoji and we need to fallback to an image. - See `app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js` - and `app/assets/javascripts/emoji/support/unicode_support_map.js` diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md deleted file mode 100644 index 533e2001300..00000000000 --- a/doc/development/fe_guide/icons.md +++ /dev/null @@ -1,116 +0,0 @@ -# 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]. -To upgrade to a new version run `yarn upgrade @gitlab/svgs`. - -## Icons - -We are using SVG Icons in GitLab with a SVG Sprite. -This means the icons are only loaded once, and are referenced through an ID. -The sprite SVG is located under `/assets/icons.svg`. - -Our goal is to replace one by one all inline SVG Icons (as those currently bloat the HTML) and also all Font Awesome icons. - -### Usage in HAML/Rails - -To use a sprite Icon in HAML or Rails we use a specific helper function : - -```ruby -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]). -- **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 - -**Example** - -```haml -= sprite_icon('issues', size: 72, css_class: 'icon-danger') -``` - -**Output from example above** - -```html -<svg class="s72 icon-danger"> - <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons.svg#issues"></use> -</svg> -``` - -### Usage in Vue - -We have a special Vue component for our sprite icons in `\vue_shared\components\icon.vue`. - -Sample usage : - -```javascript -<script> -import Icon from "~/vue_shared/components/icon.vue" - -export default { - components: { - Icon, - }, -}; -<script> -<template> - <icon - name="issues" - :size="72" - css-classes="icon-danger" - /> -</template> -``` - -- **name** Name of the Icon in the SVG Sprite ([Overview is available here][svg-preview]). -- **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. - -### Usage in HTML/JS - -Please use the following function inside JS to render an icon: -`gl.utils.spriteIcon(iconName)` - -## SVG Illustrations - -Please use from now on for any SVG based illustrations simple `img` tags to show an illustration by simply using either `image_tag` or `image_path` helpers. -Please use the class `svg-content` around it to ensure nice rendering. - -### Usage in HAML/Rails - -**Example** - -```haml -.svg-content - = image_tag 'illustrations/merge_requests.svg' -``` - -### Usage in Vue - -To use an SVG illustrations in a template provide the path as a property and display it through a standard img tag. - -Component: - -```js -<script> -export default { - props: { - svgIllustrationPath: { - type: String, - required: true, - }, - }, -}; -<script> -<template> - <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 diff --git a/doc/development/fe_guide/img/boards_diagram.png b/doc/development/fe_guide/img/boards_diagram.png Binary files differdeleted file mode 100644 index 856c9b05bbf..00000000000 --- a/doc/development/fe_guide/img/boards_diagram.png +++ /dev/null diff --git a/doc/development/fe_guide/img/gl-modal.png b/doc/development/fe_guide/img/gl-modal.png Binary files differdeleted file mode 100644 index b2d2d637e57..00000000000 --- a/doc/development/fe_guide/img/gl-modal.png +++ /dev/null diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 11b9a2e6a64..bfcca9cec7b 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -1,121 +1,32 @@ # Frontend Development Guidelines -> **Notice:** -We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md) +This guide contains all the information to successfully contribute to GitLab's frontend. +This is a living document, and we welcome contributions, feedback and suggestions. -This document describes various guidelines to ensure consistency and quality -across GitLab's frontend team. +## [Principles](principles.md) -## Overview +Ensure that your frontend contribution starts off in the right direction. -GitLab is built on top of [Ruby on Rails][rails] using [Haml][haml] and also a JavaScript based Frontend with [Vue.js][vue]. -Be wary of [the limitations that come with using Hamlit][hamlit-limits]. We also use [SCSS][scss] and plain JavaScript with -modern ECMAScript standards supported through [Babel][babel] and ES module support through [webpack][webpack]. +## [Initiatives](initiatives.md) -### Javascript development +High level overview of where we are going from a frontend perspective. -[Vue.js][vue] is used for particularly advanced, dynamic elements and based on previous iterations [jQuery][jquery] is used in lot of places through the application's JavaScript. +## [Development](development/index.md) -We also use [Axios][axios] to handle all of our network requests. +Guidance on topics related to development. -We also utilize [webpack][webpack] to handle the bundling, minification, and -compression of our assets. +## [Dependencies](dependencies.md) -Working with our frontend assets requires Node (v6.0 or greater) and Yarn -(v1.2 or greater). You can find information on how to install these on our -[installation guide][install]. +Learn about all the dependencies that make up our frontend, including some of our own custom built libraries. -### Browser Support +## [Modules](modules/index.md) -For our currently-supported browsers, see our [requirements][requirements]. +Learn about all the internal JavaScript modules that make up our frontend. ---- +## [Style guides](style/index.md) -## [Development Process](development_process.md) -How we plan and execute the work on the frontend. +Style guides to keep our code consistent. -## [Architecture](architecture.md) -How we go about making fundamental design decisions in GitLab's frontend team -or make changes to our frontend development guidelines. +## [Tips](tips.md) -## [Testing](../testing_guide/frontend_testing.md) -How we write frontend tests, run the GitLab test suite, and debug test related -issues. - -## [Design Patterns](design_patterns.md) -Common JavaScript design patterns in GitLab's codebase. - -## [Vue.js Best Practices](vue.md) -Vue specific design patterns and practices. - -## [Vuex](vuex.md) -Vuex specific design patterns and practices. - -## [Axios](axios.md) -Axios specific practices and gotchas. - -## [Icons and Illustrations](icons.md) -How we use SVG for our Icons and Illustrations. - -## [Components](components.md) - -How we use UI components. - ---- - -## Style Guides - -### [JavaScript Style Guide](style_guide_js.md) - -We use eslint to enforce our JavaScript style guides. Our guide is based on -the excellent [Airbnb][airbnb-js-style-guide] style guide with a few small -changes. - -### [SCSS Style Guide](style_guide_scss.md) - -Our SCSS conventions which are enforced through [scss-lint][scss-lint]. - ---- - -## [Performance](performance.md) -Best practices for monitoring and maximizing frontend performance. - ---- - -## [Security](security.md) -Frontend security practices. - ---- - -## [Accessibility](accessibility.md) -Our accessibility standards and resources. - -## [Internationalization (i18n) and Translations](../i18n/externalization.md) -Frontend internationalization support is described in [this document](../i18n/). -The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available. - - -[rails]: http://rubyonrails.org/ -[haml]: http://haml.info/ -[hamlit]: https://github.com/k0kubun/hamlit -[hamlit-limits]: https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations -[scss]: http://sass-lang.com/ -[babel]: https://babeljs.io/ -[webpack]: https://webpack.js.org/ -[jquery]: https://jquery.com/ -[vue]: http://vuejs.org/ -[axios]: https://github.com/axios/axios -[airbnb-js-style-guide]: https://github.com/airbnb/javascript -[scss-lint]: https://github.com/brigade/scss-lint -[install]: ../../install/installation.md#4-node -[requirements]: ../../install/requirements.md#supported-web-browsers - ---- - -## [DropLab](droplab/droplab.md) -Our internal `DropLab` dropdown library. - -* [DropLab](droplab/droplab.md) -* [Ajax plugin](droplab/plugins/ajax.md) -* [Filter plugin](droplab/plugins/filter.md) -* [InputSetter plugin](droplab/plugins/input_setter.md) +Tips from our frontend team to develop more efficiently and effectively. diff --git a/doc/development/new_fe_guide/initiatives.md b/doc/development/fe_guide/initiatives.md index c81ed3579f0..c81ed3579f0 100644 --- a/doc/development/new_fe_guide/initiatives.md +++ b/doc/development/fe_guide/initiatives.md diff --git a/doc/development/new_fe_guide/modules/dirty_submit.md b/doc/development/fe_guide/modules/dirty_submit.md index 6c03958b463..6c03958b463 100644 --- a/doc/development/new_fe_guide/modules/dirty_submit.md +++ b/doc/development/fe_guide/modules/dirty_submit.md diff --git a/doc/development/new_fe_guide/modules/index.md b/doc/development/fe_guide/modules/index.md index 0a7f2dbd819..0a7f2dbd819 100644 --- a/doc/development/new_fe_guide/modules/index.md +++ b/doc/development/fe_guide/modules/index.md diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md deleted file mode 100644 index ef0eed786d2..00000000000 --- a/doc/development/fe_guide/performance.md +++ /dev/null @@ -1,178 +0,0 @@ -# Performance - -## Best Practices - -### Realtime Components - -When writing code for realtime features we have to keep a couple of things in mind: -1. Do not overload the server with requests. -1. It should feel realtime. - -Thus, we must strike a balance between sending requests and the feeling of realtime. -Use the following rules when creating realtime solutions. - -1. The server will tell you how much to poll by sending `Poll-Interval` in the header. -Use that as your polling interval. This way it is [easy for system administrators to change the -polling rate](../../administration/polling.md). -A `Poll-Interval: -1` means you should disable polling, and this must be implemented. -1. A response with HTTP status different from 2XX should disable polling as well. -1. Use a common library for polling. -1. Poll on active tabs only. Please use [Visibility](https://github.com/ai/visibilityjs). -1. Use regular polling intervals, do not use backoff polling, or jitter, as the interval will be -controlled by the server. -1. The backend code will most likely be using etags. You do not and should not check for status -`304 Not Modified`. The browser will transform it for you. - -### Lazy Loading Images - -To improve the time to first render we are using lazy loading for images. This works by setting -the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded, -the value of `data-src` will be moved to `src` automatically if the image is in the current viewport. - -* Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy` -* If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided. - -If you are asynchronously adding content which contains lazy images then you need to call the function -`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed. -But in general it should be handled automatically through a `MutationObserver` in the lazy loading function. - -### Animations - -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]. - -If you _do_ need to change layout (e.g. a sidebar that pushes main content over), prefer [FLIP][flip] to change expensive -properties once, and handle the actual animation with transforms. - -## Reducing Asset Footprint - -### Universal code - -Code that is contained within `main.js` and `commons/index.js` are loaded and -run on _all_ pages. **DO NOT ADD** anything to these files unless it is truly -needed _everywhere_. These bundles include ubiquitous libraries like `vue`, -`axios`, and `jQuery`, as well as code for the main navigation and sidebar. -Where possible we should aim to remove modules from these bundles to reduce our -code footprint. - -### Page-specific JavaScript - -Webpack has been configured to automatically generate entry point bundles based -on the file structure within `app/assets/javascripts/pages/*`. The directories -within the `pages` directory correspond to Rails controllers and actions. These -auto-generated bundles will be automatically included on the corresponding -pages. - -For example, if you were to visit [gitlab.com/gitlab-org/gitlab-ce/issues](https://gitlab.com/gitlab-org/gitlab-ce/issues), -you would be accessing the `app/controllers/projects/issues_controller.rb` -controller with the `index` action. If a corresponding file exists at -`pages/projects/issues/index/index.js`, it will be compiled into a webpack -bundle and included on the page. - -> **Note:** Previously we had encouraged the use of -> `content_for :page_specific_javascripts` within haml files, along with -> manually generated webpack bundles. However under this new system you should -> not ever need to manually add an entry point to the `webpack.config.js` file. -> -> **Tip:** -> If you are unsure what controller and action corresponds to a given page, you -> can find this out by inspecting `document.body.dataset.page` within your -> browser's developer console while on any page within gitlab. - -#### Important Considerations: - -- **Keep Entry Points Lite:** - Page-specific javascript entry points should be as lite as possible. These - files are exempt from unit tests, and should be used primarily for - instantiation and dependency injection of classes and methods that live in - modules outside of the entry point script. Just import, read the DOM, - instantiate, and nothing else. - -- **Entry Points May Be Asynchronous:** - _DO NOT ASSUME_ that the DOM has been fully loaded and available when an - entry point script is run. If you require that some code be run after the - DOM has loaded, you should attach an event handler to the `DOMContentLoaded` - event with: - - ```javascript - import initMyWidget from './my_widget'; - - document.addEventListener('DOMContentLoaded', () => { - initMyWidget(); - }); - ``` - -- **Supporting Module Placement:** - - If a class or a module is _specific to a particular route_, try to locate - it close to the entry point it will be used. For instance, if - `my_widget.js` is only imported within `pages/widget/show/index.js`, you - should place the module at `pages/widget/show/my_widget.js` and import it - with a relative path (e.g. `import initMyWidget from './my_widget';`). - - If a class or module is _used by multiple routes_, place it within a - shared directory at the closest common parent directory for the entry - points that import it. For example, if `my_widget.js` is imported within - both `pages/widget/show/index.js` and `pages/widget/run/index.js`, then - place the module at `pages/widget/shared/my_widget.js` and import it with - a relative path if possible (e.g. `../shared/my_widget`). - -- **Enterprise Edition Caveats:** - For GitLab Enterprise Edition, page-specific entry points will override their - Community Edition counterparts with the same name, so if - `ee/app/assets/javascripts/pages/foo/bar/index.js` exists, it will take - precedence over `app/assets/javascripts/pages/foo/bar/index.js`. If you want - to minimize duplicate code, you can import one entry point from the other. - This is not done automatically to allow for flexibility in overriding - functionality. - -### Code Splitting - -For any code that does not need to be run immediately upon page load, (e.g. -modals, dropdowns, and other behaviors that can be lazy-loaded), you can split -your module into asynchronous chunks with dynamic import statements. These -imports return a Promise which will be resolved once the script has loaded: - -```javascript -import(/* webpackChunkName: 'emoji' */ '~/emoji') - .then(/* do something */) - .catch(/* report error */) -``` - -Please try to use `webpackChunkName` when generating these dynamic imports as -it will provide a deterministic filename for the chunk which can then be cached -the browser across GitLab versions. - -More information is available in [webpack's code splitting documentation](https://webpack.js.org/guides/code-splitting/#dynamic-imports). - -### Minimizing page size - -A smaller page size means the page loads faster (especially important on mobile -and poor connections), the page is parsed more quickly by the browser, and less -data is used for users with capped data plans. - -General tips: - -- Don't add new fonts. -- Prefer font formats with better compression, e.g. WOFF2 is better than WOFF, which is better than TTF. -- Compress and minify assets wherever possible (For CSS/JS, Sprockets and webpack do this for us). -- 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] - -------- - -## Additional Resources - -- [WebPage Test][web-page-test] for testing site loading time and size. -- [Google PageSpeed Insights][pagespeed-insights] grades web pages and provides feedback to improve the page. -- [Profiling with Chrome DevTools][google-devtools-profiling] -- [Browser Diet][browser-diet] is a community-built guide that catalogues practical tips for improving web page performance. - - -[web-page-test]: http://www.webpagetest.org/ -[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/ -[google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en -[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/ diff --git a/doc/development/new_fe_guide/principles.md b/doc/development/fe_guide/principles.md index 0af5f506a91..0af5f506a91 100644 --- a/doc/development/new_fe_guide/principles.md +++ b/doc/development/fe_guide/principles.md diff --git a/doc/development/fe_guide/security.md b/doc/development/fe_guide/security.md deleted file mode 100644 index 19e72c1d368..00000000000 --- a/doc/development/fe_guide/security.md +++ /dev/null @@ -1,92 +0,0 @@ -# Security -### Resources - -[Mozilla’s HTTP Observatory CLI][observatory-cli] and the -[Qualys SSL Labs Server Test][qualys-ssl] are good resources for finding -potential problems and ensuring compliance with security best practices. - -<!-- Uncomment these sections when CSP/SRI are implemented. -### Content Security Policy (CSP) - -Content Security Policy is a web standard that intends to mitigate certain -forms of Cross-Site Scripting (XSS) as well as data injection. - -Content Security Policy rules should be taken into consideration when -implementing new features, especially those that may rely on connection with -external services. - -GitLab's CSP is used for the following: - -- Blocking plugins like Flash and Silverlight from running at all on our pages. -- Blocking the use of scripts and stylesheets downloaded from external sources. -- Upgrading `http` requests to `https` when possible. -- Preventing `iframe` elements from loading in most contexts. - -Some exceptions include: - -- Scripts from Google Analytics and Piwik if either is enabled. -- 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 -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] - -### Subresource Integrity (SRI) - -Subresource Integrity prevents malicious assets from being provided by a CDN by -guaranteeing that the asset downloaded is identical to the asset the server -is expecting. - -The Rails app generates a unique hash of the asset, which is used as the -asset's `integrity` attribute. The browser generates the hash of the asset -on-load and will reject the asset if the hashes do not match. - -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] - ---> - -### Including external resources - -External fonts, CSS, and JavaScript should never be used with the exception of -Google Analytics and Piwik - and only when the instance has enabled it. Assets -should always be hosted and served locally from the GitLab instance. Embedded -resources via `iframes` should never be used except in certain circumstances -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 -inline scripts in the future using Content Security Policy. - -While inline scripts can be useful, they're also a security concern. If -user-supplied content is unintentionally left un-sanitized, malicious users can -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/new_fe_guide/style/html.md b/doc/development/fe_guide/style/html.md index 2d5b7d048ab..2d5b7d048ab 100644 --- a/doc/development/new_fe_guide/style/html.md +++ b/doc/development/fe_guide/style/html.md diff --git a/doc/development/new_fe_guide/style/index.md b/doc/development/fe_guide/style/index.md index 335d9e66240..335d9e66240 100644 --- a/doc/development/new_fe_guide/style/index.md +++ b/doc/development/fe_guide/style/index.md diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/fe_guide/style/javascript.md index 922fd1e4ea4..922fd1e4ea4 100644 --- a/doc/development/new_fe_guide/style/javascript.md +++ b/doc/development/fe_guide/style/javascript.md diff --git a/doc/development/new_fe_guide/style/prettier.md b/doc/development/fe_guide/style/prettier.md index baaea67d38b..baaea67d38b 100644 --- a/doc/development/new_fe_guide/style/prettier.md +++ b/doc/development/fe_guide/style/prettier.md diff --git a/doc/development/new_fe_guide/style/scss.md b/doc/development/fe_guide/style/scss.md index 6f5e818d7db..6f5e818d7db 100644 --- a/doc/development/new_fe_guide/style/scss.md +++ b/doc/development/fe_guide/style/scss.md diff --git a/doc/development/new_fe_guide/style/vue.md b/doc/development/fe_guide/style/vue.md index fd9353e0d3f..fd9353e0d3f 100644 --- a/doc/development/new_fe_guide/style/vue.md +++ b/doc/development/fe_guide/style/vue.md diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md deleted file mode 100644 index 1e0529262ad..00000000000 --- a/doc/development/fe_guide/style_guide_js.md +++ /dev/null @@ -1,706 +0,0 @@ -# Style guides and linting - -See the relevant style guides for our guidelines and for information on linting: - -## JavaScript - -We defer to [Airbnb][airbnb-js-style-guide] on most style-related -conventions and enforce them with eslint. - -See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.eslintrc.yml) for specific rules and patterns. - -### Common - -#### ESlint - -1. **Never** disable eslint rules unless you have a good reason. -You may see a lot of legacy files with `/* eslint-disable some-rule, some-other-rule */` -at the top, but legacy files are a special case. Any time you develop a new feature or -refactor an existing one, you should abide by the eslint rules. - -1. **Never Ever EVER** disable eslint globally for a file - - ```javascript - // bad - /* eslint-disable */ - - // better - /* eslint-disable some-rule, some-other-rule */ - - // best - // nothing :) - ``` - -1. If you do need to disable a rule for a single violation, try to do it as locally as possible - - ```javascript - // bad - /* eslint-disable no-new */ - - import Foo from 'foo'; - - new Foo(); - - // better - import Foo from 'foo'; - - // eslint-disable-next-line no-new - new Foo(); - ``` - -1. There are few rules that we need to disable due to technical debt. Which are: - 1. [no-new][eslint-new] - 1. [class-methods-use-this][eslint-this] - -1. When they are needed _always_ place ESlint directive comment blocks on the first line of a script, -followed by any global declarations, then a blank newline prior to any imports or code. - - ```javascript - // bad - /* global Foo */ - /* eslint-disable no-new */ - import Bar from './bar'; - - // good - /* eslint-disable no-new */ - /* global Foo */ - - import Bar from './bar'; - ``` - -1. **Never** disable the `no-undef` rule. Declare globals with `/* global Foo */` instead. - -1. When declaring multiple globals, always use one `/* global [name] */` line per variable. - - ```javascript - // bad - /* globals Flash, Cookies, jQuery */ - - // good - /* global Flash */ - /* global Cookies */ - /* global jQuery */ - ``` - -1. Use up to 3 parameters for a function or class. If you need more accept an Object instead. - - ```javascript - // bad - fn(p1, p2, p3, p4) {} - - // good - fn(options) {} - ``` - -#### Modules, Imports, and Exports - -1. Use ES module syntax to import modules - ```javascript - // bad - const SomeClass = require('some_class'); - - // good - import SomeClass from 'some_class'; - - // bad - module.exports = SomeClass; - - // good - export default SomeClass; - ``` - - Import statements are following usual naming guidelines, for example object literals use camel case: - - ```javascript - // some_object file - export default { - key: 'value', - }; - - // bad - import ObjectLiteral from 'some_object'; - - // good - import objectLiteral from 'some_object'; - ``` - -1. Relative paths: when importing a module in the same directory, a child -directory, or an immediate parent directory prefer relative paths. When -importing a module which is two or more levels up, prefer either `~/` or `ee/`. - - In **app/assets/javascripts/my-feature/subdir**: - - ```javascript - // bad - import Foo from '~/my-feature/foo'; - import Bar from '~/my-feature/subdir/bar'; - import Bin from '~/my-feature/subdir/lib/bin'; - - // good - import Foo from '../foo'; - import Bar from './bar'; - import Bin from './lib/bin'; - ``` - - In **spec/javascripts**: - - ```javascript - // bad - import Foo from '../../app/assets/javascripts/my-feature/foo'; - - // good - import Foo from '~/my-feature/foo'; - ``` - - When referencing an **EE component**: - - ```javascript - // bad - import Foo from '../../../../../ee/app/assets/javascripts/my-feature/ee-foo'; - - // good - import Foo from 'ee/my-feature/foo'; - ``` - -1. Avoid using IIFE. Although we have a lot of examples of files which wrap their -contents in IIFEs (immediately-invoked function expressions), -this is no longer necessary after the transition from Sprockets to webpack. -Do not use them anymore and feel free to remove them when refactoring legacy code. - -1. Avoid adding to the global namespace. - ```javascript - // bad - window.MyClass = class { /* ... */ }; - - // good - export default class MyClass { /* ... */ } - ``` - -1. Side effects are forbidden in any script which contains exports - ```javascript - // bad - export default class MyClass { /* ... */ } - - document.addEventListener("DOMContentLoaded", function(event) { - new MyClass(); - } - ``` - -#### Data Mutation and Pure functions - -1. Strive to write many small pure functions, and minimize where mutations occur. - - ```javascript - // bad - const values = {foo: 1}; - - function impureFunction(items) { - const bar = 1; - - items.foo = items.a * bar + 2; - - return items.a; - } - - const c = impureFunction(values); - - // good - var values = {foo: 1}; - - function pureFunction (foo) { - var bar = 1; - - foo = foo * bar + 2; - - return foo; - } - - var c = pureFunction(values.foo); - ``` - -1. Avoid constructors with side-effects. - Although we aim for code without side-effects we need some side-effects for our code to run. - - If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.) - - ```javascript - // Bad - export class Foo { - constructor() { - this.init(); - } - init() { - document.addEventListener('click', this.handleCallback) - }, - handleCallback() { - - } - } - - // Good - export class Foo { - constructor() { - document.addEventListener() - } - handleCallback() { - } - } - ``` - - On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized outside of the constructor. - -1. Prefer `.map`, `.reduce` or `.filter` over `.forEach` - A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`, - `.reduce` or `.filter` - - ```javascript - const users = [ { name: 'Foo' }, { name: 'Bar' } ]; - - // bad - users.forEach((user, index) => { - user.id = index; - }); - - // good - const usersWithId = users.map((user, index) => { - return Object.assign({}, user, { id: index }); - }); - ``` - -#### Parse Strings into Numbers - -1. `parseInt()` is preferable over `Number()` or `+` - - ```javascript - // bad - +'10' // 10 - - // good - Number('10') // 10 - - // better - parseInt('10', 10); - ``` - -#### CSS classes used for JavaScript - -1. If the class is being used in Javascript it needs to be prepend with `js-` - - ```html - // bad - <button class="add-user"> - Add User - </button> - - // good - <button class="js-add-user"> - Add User - </button> - ``` - -### Vue.js - -#### `eslint-vue-plugin` - -We default to [eslint-vue-plugin][eslint-plugin-vue], with the `plugin:vue/recommended`. -Please check this [rules][eslint-plugin-vue-rules] for more documentation. - -#### Basic Rules - -1. The service has it's own file -1. The store has it's own file -1. Use a function in the bundle file to instantiate the Vue component: - - ```javascript - // bad - class { - init() { - new Component({}) - } - } - - // good - document.addEventListener('DOMContentLoaded', () => new Vue({ - el: '#element', - components: { - componentName - }, - render: createElement => createElement('component-name'), - })); - ``` - -1. Do not use a singleton for the service or the store - - ```javascript - // bad - class Store { - constructor() { - if (!this.prototype.singleton) { - // do something - } - } - } - - // good - class Store { - constructor() { - // do something - } - } - ``` -1. Use `.vue` for Vue templates. Do not use `%template` in HAML. - -#### Naming - -1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension ([#34371]). -1. **Reference Naming**: Use PascalCase for their instances: - - ```javascript - // bad - import cardBoard from 'cardBoard.vue' - - components: { - cardBoard, - }; - - // good - import CardBoard from 'cardBoard.vue' - - components: { - CardBoard, - }; - ``` - -1. **Props Naming:** Avoid using DOM component prop names. -1. **Props Naming:** Use kebab-case instead of camelCase to provide props in templates. - - ```javascript - // bad - <component class="btn"> - - // good - <component css-class="btn"> - - // bad - <component myProp="prop" /> - - // good - <component my-prop="prop" /> - ``` - -[#34371]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34371 - -#### Alignment - -1. Follow these alignment styles for the template method: - - 1. With more than one attribute, all attributes should be on a new line: - - ```javascript - // bad - <component v-if="bar" - param="baz" /> - - <button class="btn">Click me</button> - - // good - <component - v-if="bar" - param="baz" - /> - - <button class="btn"> - Click me - </button> - ``` - - 1. The tag can be inline if there is only one attribute: - - ```javascript - // good - <component bar="bar" /> - - // good - <component - bar="bar" - /> - - // bad - <component - bar="bar" /> - ``` - -#### Quotes - -1. Always use double quotes `"` inside templates and single quotes `'` for all other JS. - - ```javascript - // bad - template: ` - <button :class='style'>Button</button> - ` - - // good - template: ` - <button :class="style">Button</button> - ` - ``` - -#### Props - -1. Props should be declared as an object - ```javascript - // bad - props: ['foo'] - - // good - props: { - foo: { - type: String, - required: false, - default: 'bar' - } - } - ``` - -1. Required key should always be provided when declaring a prop - - ```javascript - // bad - props: { - foo: { - type: String, - } - } - - // good - props: { - foo: { - type: String, - required: false, - default: 'bar' - } - } - ``` - -1. Default key should be provided if the prop is not required. -_Note:_ There are some scenarios where we need to check for the existence of the property. -On those a default key should not be provided. - - ```javascript - // good - props: { - foo: { - type: String, - required: false, - } - } - - // good - props: { - foo: { - type: String, - required: false, - default: 'bar' - } - } - - // good - props: { - foo: { - type: String, - required: true - } - } - ``` - -#### Data - -1. `data` method should always be a function - - ```javascript - // bad - data: { - foo: 'foo' - } - - // good - data() { - return { - foo: 'foo' - }; - } - ``` - -#### Directives - -1. Shorthand `@` is preferable over `v-on` - - ```javascript - // bad - <component v-on:click="eventHandler"/> - - // good - <component @click="eventHandler"/> - ``` - -1. Shorthand `:` is preferable over `v-bind` - - ```javascript - // bad - <component v-bind:class="btn"/> - - // good - <component :class="btsn"/> - ``` - -#### Closing tags - -1. Prefer self closing component tags - - ```javascript - // bad - <component></component> - - // good - <component /> - ``` - -#### Ordering - -1. Tag order in `.vue` file - - ``` - <script> - // ... - </script> - - <template> - // ... - </template> - - // We don't use scoped styles but there are few instances of this - <style> - // ... - </style> - ``` - -1. Properties in a Vue Component: - Check [order of properties in components rule][vue-order]. - -#### `:key` - -When using `v-for` you need to provide a *unique* `:key` attribute for each item. - -1. If the elements of the array being iterated have an unique `id` it is advised to use it: - - ```html - <div - v-for="item in items" - :key="item.id" - > - <!-- content --> - </div> - ``` - -1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute - - ```html - <div - v-for="(item, index) in items" - :key="index" - > - <!-- content --> - </div> - ``` - -1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces. - - ```html - <template v-for="(item, index) in items"> - <span :key="`span-${index}`"></span> - <button :key="`button-${index}`"></button> - </template> - ``` - -1. When dealing with nested `v-for` use the same guidelines as above. - - ```html - <div - v-for="item in items" - :key="item.id" - > - <span - v-for="element in array" - :key="element.id" - > - <!-- content --> - </span> - </div> - ``` - -Useful links: - -1. [`key`](https://vuejs.org/v2/guide/list.html#key) -1. [Vue Style Guide: Keyed v-for](https://vuejs.org/v2/style-guide/#Keyed-v-for-essential ) - -#### Vue and Bootstrap - -1. Tooltips: Do not rely on `has-tooltip` class name for Vue components - - ```javascript - // bad - <span - class="has-tooltip" - title="Some tooltip text"> - Text - </span> - - // good - <span - v-tooltip - title="Some tooltip text"> - Text - </span> - ``` - -1. Tooltips: When using a tooltip, include the tooltip directive, `./app/assets/javascripts/vue_shared/directives/tooltip.js` - -1. Don't change `data-original-title`. - - ```javascript - // bad - <span data-original-title="tooltip text">Foo</span> - - // good - <span title="tooltip text">Foo</span> - - $('span').tooltip('_fixTitle'); - ``` - -### The Javascript/Vue Accord - -The goal of this accord is to make sure we are all on the same page. - -1. When writing Vue, you may not use jQuery in your application. - 1. If you need to grab data from the DOM, you may query the DOM 1 time while bootstrapping your application to grab data attributes using `dataset`. You can do this without jQuery. - 1. You may use a jQuery dependency in Vue.js following [this example from the docs](https://vuejs.org/v2/examples/select2.html). - 1. If an outside jQuery Event needs to be listen to inside the Vue application, you may use jQuery event listeners. - 1. We will avoid adding new jQuery events when they are not required. Instead of adding new jQuery events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit). -1. You may query the `window` object 1 time, while bootstrapping your application for application specific data (e.g. `scrollTo` is ok to access anytime). Do this access during the bootstrapping of your application. -1. You may have a temporary but immediate need to create technical debt by writing code that does not follow our standards, to be refactored later. Maintainers need to be ok with the tech debt in the first place. An issue should be created for that tech debt to evaluate it further and discuss. In the coming months you should fix that tech debt, with it's priority to be determined by maintainers. -1. When creating tech debt you must write the tests for that code before hand and those tests may not be rewritten. e.g. jQuery tests rewritten to Vue tests. -1. You may choose to use VueX as a centralized state management. If you choose not to use VueX, you must use the *store pattern* which can be found in the [Vue.js documentation](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch). -1. Once you have chosen a centralized state management solution you must use it for your entire application. i.e. Don't mix and match your state management solutions. - -## SCSS - -- [SCSS](style_guide_scss.md) - -[airbnb-js-style-guide]: https://github.com/airbnb/javascript -[eslintrc]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.eslintrc -[eslint-this]: http://eslint.org/docs/rules/class-methods-use-this -[eslint-new]: http://eslint.org/docs/rules/no-new -[eslint-plugin-vue]: https://github.com/vuejs/eslint-plugin-vue -[eslint-plugin-vue-rules]: https://github.com/vuejs/eslint-plugin-vue#bulb-rules -[vue-order]: https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/order-in-components.md diff --git a/doc/development/fe_guide/style_guide_scss.md b/doc/development/fe_guide/style_guide_scss.md deleted file mode 100644 index b09243598d5..00000000000 --- a/doc/development/fe_guide/style_guide_scss.md +++ /dev/null @@ -1,239 +0,0 @@ -# SCSS styleguide - -This style guide recommends best practices for SCSS to make styles easy to read, -easy to maintain, and performant for the end-user. - -## Rules - -### Naming - -Filenames should use `snake_case`. - -CSS classes should use the `lowercase-hyphenated` format rather than -`snake_case` or `camelCase`. - -```scss -// Bad -.class_name { - color: #fff; -} - -// Bad -.className { - color: #fff; -} - -// Good -.class-name { - color: #fff; -} -``` - -### Formatting - -You should always use a space before a brace, braces should be on the same -line, each property should each get its own line, and there should be a space -between the property and its value. - -```scss -// Bad -.container-item { - width: 100px; height: 100px; - margin-top: 0; -} - -// Bad -.container-item -{ - width: 100px; - height: 100px; - margin-top: 0; -} - -// Bad -.container-item{ - width:100px; - height:100px; - margin-top:0; -} - -// Good -.container-item { - width: 100px; - height: 100px; - margin-top: 0; -} -``` - -Note that there is an exception for single-line rulesets, although these are -not typically recommended. - -```scss -p { margin: 0; padding: 0; } -``` - -### Colors - -HEX (hexadecimal) colors should use shorthand where possible, and should use -lower case letters to differentiate between letters and numbers, e.g. `#E3E3E3` -vs. `#e3e3e3`. - -```scss -// Bad -p { - color: #ffffff; -} - -// Bad -p { - color: #FFFFFF; -} - -// Good -p { - color: #fff; -} -``` - -### Indentation - -Indentation should always use two spaces for each indentation level. - -```scss -// Bad, four spaces -p { - color: #f00; -} - -// Good -p { - color: #f00; -} -``` - -### Semicolons - -Always include semicolons after every property. When the stylesheets are -minified, the semicolons will be removed automatically. - -```scss -// Bad -.container-item { - width: 100px; - height: 100px -} - -// Good -.container-item { - width: 100px; - height: 100px; -} -``` - -### Shorthand - -The shorthand form should be used for properties that support it. - -```scss -// Bad -margin: 10px 15px 10px 15px; -padding: 10px 10px 10px 10px; - -// Good -margin: 10px 15px; -padding: 10px; -``` - -### Zero Units - -Omit length units on zero values, they're unnecessary and not including them -is slightly more performant. - -```scss -// Bad -.item-with-padding { - padding: 0px; -} - -// Good -.item-with-padding { - padding: 0; -} -``` - -### Selectors with a `js-` Prefix - -Do not use any selector prefixed with `js-` for styling purposes. These -selectors are intended for use only with JavaScript to allow for removal or -renaming without breaking styling. - -### IDs -Don't use ID selectors in CSS. - -```scss -// Bad -#my-element { - padding: 0; -} - -// Good -.my-element { - padding: 0; -} -``` - -### Variables - -Before adding a new variable for a color or a size, guarantee: - -- There isn't already one -- There isn't a similar one we can use instead. - -## Linting - -We use [SCSS Lint][scss-lint] to check for style guide conformity. It uses the -ruleset in `.scss-lint.yml`, which is located in the home directory of the -project. - -To check if any warnings will be produced by your changes, you can run `rake -scss_lint` in the GitLab directory. SCSS Lint will also run in GitLab CI to -catch any warnings. - -If the Rake task is throwing warnings you don't understand, SCSS Lint's -documentation includes [a full list of their linters][scss-lint-documentation]. - -### 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 -CSSComb globally (system-wide). Run it in the GitLab directory with -`csscomb app/assets/stylesheets` to automatically fix issues with CSS/SCSS. - -Note that this won't fix every problem, but it should fix a majority. - -### Ignoring issues - -If you want a line or set of lines to be ignored by the linter, you can use -`// scss-lint:disable RuleName` ([more info][disabling-linters]): - -```scss -// This lint rule is disabled because it is supported only in Chrome/Safari -// scss-lint:disable PropertySpelling -body { - text-decoration-skip: ink; -} -// scss-lint:enable PropertySpelling -``` - -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/ -[scss-lint]: https://github.com/brigade/scss-lint -[scss-lint-documentation]: https://github.com/brigade/scss-lint/blob/master/lib/scss_lint/linter/README.md -[disabling-linters]: https://github.com/brigade/scss-lint#disabling-linters-via-source diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md deleted file mode 100644 index 98e499b8c0f..00000000000 --- a/doc/development/fe_guide/testing.md +++ /dev/null @@ -1 +0,0 @@ -This document was moved to [../testing_guide/frontend_testing.md](../testing_guide/frontend_testing.md). diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/fe_guide/tips.md index 881ad1662ae..881ad1662ae 100644 --- a/doc/development/new_fe_guide/tips.md +++ b/doc/development/fe_guide/tips.md diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md deleted file mode 100644 index ccfd465531a..00000000000 --- a/doc/development/fe_guide/vue.md +++ /dev/null @@ -1,242 +0,0 @@ -# Vue - -To get started with Vue, read through [their documentation][vue-docs]. - -## Examples - -What is described in the following sections can be found in these examples: - -- web ide: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/ide/stores -- security products: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports -- registry: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/registry/stores - -## Vue architecture - -All new features built with Vue.js must follow a [Flux architecture][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]. - -### Components and Store - -In some features implemented with Vue.js, like the [issue board][issue-boards] -or [environments table][environments-table] -you can find a clear separation of concerns: - -``` -new_feature -├── components -│ └── component.vue -│ └── ... -├── store -│ └── new_feature_store.js -├── index.js -``` -_For consistency purposes, we recommend you to follow the same structure._ - -Let's look into each of them: - -### A `index.js` file - -This is the index file of your new feature. This is where the root Vue instance -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. - -Don't forget to follow [these steps][page_specific_javascript]. - -### Bootstrapping Gotchas -#### Providing data from HAML to JavaScript -While mounting a Vue application may be a need to provide data from Rails to JavaScript. -To do that, provide the data through `data` attributes in the HTML element and query them while mounting the application. - -_Note:_ You should only do this while initializing the application, because the mounted element will be replaced with Vue-generated DOM. - -The advantage of providing data from the DOM to the Vue instance through `props` in the `render` function -instead of querying the DOM inside the main vue component is that makes tests easier by avoiding the need to -create a fixture or an HTML element in the unit test. See the following example: - -```javascript -// haml -.js-vue-app{ data: { endpoint: 'foo' }} - -// index.js -document.addEventListener('DOMContentLoaded', () => new Vue({ - el: '.js-vue-app', - data() { - const dataset = this.$options.el.dataset; - return { - endpoint: dataset.endpoint, - }; - }, - render(createElement) { - return createElement('my-component', { - props: { - endpoint: this.isLoading, - }, - }); - }, -})); -``` - -#### Accessing the `gl` object -When we need to query the `gl` object for data that won't change during the application's life cyle, we should do it in the same place where we query the DOM. -By following this practice, we can avoid the need to mock the `gl` object, which will make tests easier. -It should be done while initializing our Vue instance, and the data should be provided as `props` to the main component: - -```javascript -document.addEventListener('DOMContentLoaded', () => new Vue({ - el: '.js-vue-app', - render(createElement) { - return createElement('my-component', { - props: { - username: gon.current_username, - }, - }); - }, -})); -``` - -### A folder for Components - -This folder holds all components that are specific of this new feature. -If you need to use or create a component that will probably be used somewhere -else, please refer to `vue_shared/components`. - -A good thumb rule to know when you should create a component is to think if -it will be reusable elsewhere. - -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] - -### A folder for the Store - -#### Vuex -Check this [page](vuex.md) for more details. - -## Style guide - -Please refer to the Vue section of our [style guide](style_guide_js.md#vue-js) -for best practices while writing your Vue components and templates. - -## Testing Vue Components - -Each Vue component has a unique output. This output is always present in the render function. - -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-on-tests) to mock data returned. - -Here's how we would test the Todo App above: - -```javascript -import Vue from 'vue'; -import axios from '~/lib/utils/axios_utils'; -import MockAdapter from 'axios-mock-adapter'; - -describe('Todos App', () => { - let vm; - let mock; - - beforeEach(() => { - // Create a mock adapter for stubbing axios API requests - mock = new MockAdapter(axios); - - const Component = Vue.extend(component); - - // Mount the Component - vm = new Component().$mount(); - }); - - afterEach(() => { - // Reset the mock adapter - mock.restore(); - // Destroy the mounted component - vm.$destroy(); - }); - - it('should render the loading state while the request is being made', () => { - expect(vm.$el.querySelector('i.fa-spin')).toBeDefined(); - }); - - 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', - }, - ]); - - 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 add a todos on button click', (done) => { - - // 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'); - - return [201, {}]; - }); - - vm.$el.querySelector('.js-add-todo').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(); - }); - }); -}); -``` - -### `mountComponent` helper -There is a helper in `spec/javascripts/helpers/vue_mount_component_helper.js` that allows you to mount a component with the given props: - -```javascript -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper' -import component from 'component.vue' - -const Component = Vue.extend(component); -const data = {prop: 'foo'}; -const vm = mountComponent(Component, data); -``` - -### Test the component's output -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: - -## Vue.js Expert Role -One should apply to be a Vue.js expert by opening an MR when the Merge Request's they create and review show: -- Deep understanding of Vue and Vuex reactivy -- 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) -- Knowledge about the existing Vue and Vuex applications and existing reusable components - - -[vue-docs]: http://vuejs.org/guide/index.html -[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards -[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments -[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#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 diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md deleted file mode 100644 index 0f57835fb87..00000000000 --- a/doc/development/fe_guide/vuex.md +++ /dev/null @@ -1,377 +0,0 @@ -# Vuex -To manage the state of an application you should use [Vuex][vuex-docs]. - -_Note:_ All of the below is explained in more detail in the official [Vuex documentation][vuex-docs]. - -## Separation of concerns -Vuex is composed of State, Getters, Mutations, Actions and Modules. - -When a user clicks on an action, we need to `dispatch` it. This action will `commit` a mutation that will change the state. -_Note:_ The action itself will not update the state, only a mutation should update the state. - -## File structure -When using Vuex at GitLab, separate this concerns into different files to improve readability: - -``` -└── store - ├── index.js # where we assemble modules and export the store - ├── actions.js # actions - ├── mutations.js # mutations - ├── getters.js # getters - ├── state.js # state - └── mutation_types.js # mutation types -``` -The following example shows an application that lists and adds users to the state. -(For a more complex example implementation take a look at the security applications store in [here](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports/store)) - -### `index.js` -This is the entry point for our store. You can use the following as a guide: - -```javascript -import Vue from 'vue'; -import Vuex from 'vuex'; -import * as actions from './actions'; -import * as getters from './getters'; -import mutations from './mutations'; -import state from './state'; - -Vue.use(Vuex); - -export const createStore = () => new Vuex.Store({ - actions, - getters, - mutations, - state, -}); -export default createStore(); -``` - -### `state.js` -The first thing you should do before writing any code is to design the state. - -Often we need to provide data from haml to our Vue application. Let's store it in the state for better access. - -```javascript - export default { - endpoint: null, - - isLoading: false, - error: null, - - isAddingUser: false, - errorAddingUser: false, - - users: [], - }; -``` - -#### Access `state` properties -You can use `mapState` to access state properties in the components. - -### `actions.js` -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. - -In this file, we will write the actions that will call the respective mutations: - -```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.REQUEST_USERS_ERROR, error); - - export const fetchUsers = ({ state, dispatch }) => { - dispatch('requestUsers'); - - axios.get(state.endpoint) - .then(({ data }) => dispatch('receiveUsersSuccess', data)) - .catch((error) => { - dispatch('receiveUsersError', 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'); - - axios.post(state.endpoint, user) - .then(({ data }) => dispatch('receiveAddUserSuccess', data)) - .catch((error) => dispatch('receiveAddUserError', 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: - - `PUT`: `createSomething` - - `POST`: `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: -```javascript -import { mapActions } from 'vuex'; - -{ - methods: { - ...mapActions([ - 'addUser', - ]), - onClickUser(user) { - this.addUser(user); - }, - }, -}; -``` - -### `mutations.js` -The mutations specify how the application state changes in response to actions sent to the store. -The only way to change state in a Vuex store should be by committing a mutation. - -**It's a good idea to think of the state before writing any code.** - -Remember that actions only describe that something happened, they don't describe how the application state changes. - -**Never commit a mutation directly from a component** - -```javascript - import * as types from './mutation_types'; - - export default { - [types.REQUEST_USERS](state) { - state.isLoading = true; - }, - [types.RECEIVE_USERS_SUCCESS](state, data) { - // Do any needed data transformation to the received payload here - state.users = data; - state.isLoading = false; - }, - [types.REQUEST_USERS_ERROR](state, error) { - state.isLoading = false; - }, - [types.REQUEST_ADD_USER](state, user) { - state.isAddingUser = true; - }, - [types.RECEIVE_ADD_USER_SUCCESS](state, user) { - state.isAddingUser = false; - state.users.push(user); - }, - [types.REQUEST_ADD_USER_ERROR](state, error) { - state.isAddingUser = true; - state.errorAddingUser = error; - }, - }; -``` - -### `getters.js` -Sometimes we may need to get derived state based on store state, like filtering for a specific prop. -Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods) -This can be done through the `getters`: - -```javascript -// get all the users with pets -export const getUsersWithPets = (state, getters) => { - return state.users.filter(user => user.pet !== undefined); -}; -``` - -To access a getter from a component, use the `mapGetters` helper: -```javascript -import { mapGetters } from 'vuex'; - -{ - computed: { - ...mapGetters([ - 'getUsersWithPets', - ]), - }, -}; -``` - -### `mutation_types.js` -From [vuex mutations docs][vuex-mutations]: -> It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application. - -```javascript -export const ADD_USER = 'ADD_USER'; -``` - -### How to include the store in your application -The store should be included in the main component of your application: -```javascript - // app.vue - import store from 'store'; // it will include the index.js file - - export default { - name: 'application', - store, - ... - }; -``` - -### Communicating with the Store -```javascript -<script> -import { mapActions, mapState, mapGetters } from 'vuex'; -import store from './store'; - -export default { - store, - computed: { - ...mapGetters([ - 'getUsersWithPets' - ]), - ...mapState([ - 'isLoading', - 'users', - 'error', - ]), - }, - methods: { - ...mapActions([ - 'fetchUsers', - 'addUser', - ]), - - onClickAddUser(data) { - this.addUser(data); - } - }, - - created() { - this.fetchUsers() - } -} -</script> -<template> - <ul> - <li v-if="isLoading"> - Loading... - </li> - <li v-else-if="error"> - {{ error }} - </li> - <template v-else> - <li - v-for="user in users" - :key="user.id" - > - {{ user }} - </li> - </template> - </ul> -</template> -``` - -### Vuex Gotchas - -1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency throughout the application. From Vuex docs: - - > why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action. - - ```javascript - // component.vue - - // bad - created() { - this.$store.commit('mutation'); - } - - // good - created() { - this.$store.dispatch('action'); - } - ``` -1. Use mutation types instead of hardcoding strings. It will be less error prone. -1. The State will be accessible in all components descending from the use where the store is instantiated. - -### Testing Vuex -#### Testing Vuex concerns -Refer to [vuex docs][vuex-testing] regarding testing Actions, Getters and Mutations. - -#### Testing components that need a store -Smaller components might use `store` properties to access the data. -In order to write unit tests for those components, we need to include the store and provide the correct state: - -```javascript -//component_spec.js -import Vue from 'vue'; -import { createStore } from './store'; -import component from './component.vue' - -describe('component', () => { - let store; - let vm; - let Component; - - beforeEach(() => { - Component = Vue.extend(issueActions); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should show a user', () => { - const user = { - name: 'Foo', - age: '30', - }; - - store = createStore(); - - // populate the store - store.dispatch('addUser', user); - - vm = new Component({ - store, - propsData: props, - }).$mount(); - }); -}); -``` - -#### Testing Vuex actions and getters -Because we're currently using [`babel-plugin-rewire`](https://github.com/speedskater/babel-plugin-rewire), you may encounter the following error when testing your Vuex actions and getters: -`[vuex] actions should be function or object with "handler" function` - -To prevent this error from happening, you need to export an empty function as `default`: -``` -// getters.js or actions.js - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; -``` - -[vuex-docs]: https://vuex.vuejs.org -[vuex-structure]: https://vuex.vuejs.org/en/structure.html -[vuex-mutations]: https://vuex.vuejs.org/en/mutations.html -[vuex-testing]: https://vuex.vuejs.org/en/testing.html diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md deleted file mode 100644 index bfcca9cec7b..00000000000 --- a/doc/development/new_fe_guide/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# Frontend Development Guidelines - -This guide contains all the information to successfully contribute to GitLab's frontend. -This is a living document, and we welcome contributions, feedback and suggestions. - -## [Principles](principles.md) - -Ensure that your frontend contribution starts off in the right direction. - -## [Initiatives](initiatives.md) - -High level overview of where we are going from a frontend perspective. - -## [Development](development/index.md) - -Guidance on topics related to development. - -## [Dependencies](dependencies.md) - -Learn about all the dependencies that make up our frontend, including some of our own custom built libraries. - -## [Modules](modules/index.md) - -Learn about all the internal JavaScript modules that make up our frontend. - -## [Style guides](style/index.md) - -Style guides to keep our code consistent. - -## [Tips](tips.md) - -Tips from our frontend team to develop more efficiently and effectively. |