Commit e3fc5c9e authored by Denys Mishunov's avatar Denys Mishunov

Defer loading non-essential components

There are multiple components that are not essential for
WebIDE's loading, yet they are loaded and bootstrapped
together with the essential ones. This commit fixes that
parent 8a3e6935
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import {
WEBIDE_MARK_APP_START,
......@@ -14,15 +13,8 @@ import {
import { performanceMarkAndMeasure } from '~/performance/utils';
import { modalTypes } from '../constants';
import eventHub from '../eventhub';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import NewModal from './new_dropdown/modal.vue';
import IdeSidebar from './ide_side_bar.vue';
import RepoTabs from './repo_tabs.vue';
import IdeStatusBar from './ide_status_bar.vue';
import RepoEditor from './repo_editor.vue';
import RightPane from './panes/right.vue';
import ErrorMessage from './error_message.vue';
import CommitEditorHeader from './commit_sidebar/editor_header.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { measurePerformance } from '../utils';
......@@ -43,19 +35,24 @@ eventHub.$on(WEBIDE_MEASURE_FILE_AFTER_INTERACTION, () =>
export default {
components: {
NewModal,
IdeSidebar,
RepoTabs,
IdeStatusBar,
RepoEditor,
FindFile,
ErrorMessage,
CommitEditorHeader,
GlButton,
GlLoadingIcon,
RightPane,
'error-message': () => import('./error_message.vue'),
'gl-button': () => import('@gitlab/ui/src/components/base/button/button.vue'),
'gl-loading-icon': () => import('@gitlab/ui/src/components/base/loading_icon/loading_icon.vue'),
'commit-editor-header': () => import('./commit_sidebar/editor_header.vue'),
'repo-tabs': () => import('./repo_tabs.vue'),
'ide-status-bar': () => import('./ide_status_bar.vue'),
'find-file': () => import('~/vue_shared/components/file_finder/index.vue'),
'right-pane': () => import('./panes/right.vue'),
'new-modal': () => import('./new_dropdown/modal.vue'),
},
mixins: [glFeatureFlagsMixin()],
data() {
return {
loadDeferred: false,
};
},
computed: {
...mapState([
'openFiles',
......@@ -107,6 +104,9 @@ export default {
createNewFile() {
this.$refs.newModal.open(modalTypes.blob);
},
loadDeferredComponents() {
this.loadDeferred = true;
},
},
};
</script>
......@@ -118,6 +118,7 @@ export default {
>
<error-message v-if="errorMessage" :message="errorMessage" />
<div class="ide-view flex-grow d-flex">
<template v-if="loadDeferred">
<find-file
v-show="fileFindVisible"
:files="allBlobs"
......@@ -126,11 +127,14 @@ export default {
@toggle="toggleFileFinder"
@click="openFile"
/>
<ide-sidebar />
</template>
<ide-sidebar @tree-ready="loadDeferredComponents" />
<div class="multi-file-edit-pane">
<template v-if="activeFile">
<template v-if="loadDeferred">
<commit-editor-header v-if="isCommitModeActive" :active-file="activeFile" />
<repo-tabs v-else :active-file="activeFile" :files="openFiles" :viewer="viewer" />
</template>
<repo-editor :file="activeFile" class="multi-file-edit-pane-content" />
</template>
<template v-else>
......@@ -177,9 +181,13 @@ export default {
</div>
</template>
</div>
<template v-if="loadDeferred">
<right-pane v-if="currentProjectId" />
</template>
</div>
<template v-if="loadDeferred">
<ide-status-bar />
<new-modal ref="newModal" />
</template>
</article>
</template>
......@@ -4,21 +4,19 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import IdeTree from './ide_tree.vue';
import ResizablePanel from './resizable_panel.vue';
import ActivityBar from './activity_bar.vue';
import RepoCommitSection from './repo_commit_section.vue';
import CommitForm from './commit_sidebar/form.vue';
import IdeReview from './ide_review.vue';
import IdeProjectHeader from './ide_project_header.vue';
import { SIDEBAR_INIT_WIDTH } from '../constants';
import { SIDEBAR_INIT_WIDTH, leftSidebarViews } from '../constants';
export default {
components: {
GlSkeletonLoading,
ResizablePanel,
ActivityBar,
RepoCommitSection,
IdeTree,
[leftSidebarViews.review.name]: () => import('./ide_review.vue'),
[leftSidebarViews.commit.name]: () => import('./repo_commit_section.vue'),
CommitForm,
IdeReview,
IdeProjectHeader,
},
computed: {
......@@ -49,7 +47,7 @@ export default {
<div class="multi-file-commit-panel-inner" data-testid="ide-side-bar-inner">
<div class="multi-file-commit-panel-inner-content">
<keep-alive>
<component :is="currentActivityView" />
<component :is="currentActivityView" @tree-ready="$emit('tree-ready')" />
</keep-alive>
</div>
<commit-form />
......
......@@ -51,7 +51,7 @@ export default {
</script>
<template>
<ide-tree-list>
<ide-tree-list @tree-ready="$emit('tree-ready')">
<template #header>
{{ __('Edit') }}
<div class="ide-tree-actions ml-auto d-flex" data-testid="ide-root-actions">
......
......@@ -32,6 +32,13 @@ export default {
return !this.currentTree || this.currentTree.loading;
},
},
watch: {
showLoading(newVal) {
if (!newVal) {
this.$emit('tree-ready');
}
},
},
beforeCreate() {
performanceMarkAndMeasure({ mark: WEBIDE_MARK_TREE_START });
},
......
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlSkeletonLoading } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import { createStore } from '~/ide/stores';
import IdeSidebar from '~/ide/components/ide_side_bar.vue';
import IdeTree from '~/ide/components/ide_tree.vue';
import RepoCommitSection from '~/ide/components/repo_commit_section.vue';
import IdeReview from '~/ide/components/ide_review.vue';
import { leftSidebarViews } from '~/ide/constants';
import { projectData } from '../mock_data';
......@@ -15,11 +17,12 @@ describe('IdeSidebar', () => {
let wrapper;
let store;
function createComponent() {
function createComponent({ view = leftSidebarViews.edit.name } = {}) {
store = createStore();
store.state.currentProjectId = 'abcproject';
store.state.projects.abcproject = projectData;
store.state.currentActivityView = view;
return mount(IdeSidebar, {
store,
......@@ -48,22 +51,46 @@ describe('IdeSidebar', () => {
expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3);
});
describe('activityBarComponent', () => {
it('renders tree component', () => {
describe('deferred rendering components', () => {
it('fetches components on demand', async () => {
wrapper = createComponent();
expect(wrapper.find(IdeTree).exists()).toBe(true);
});
expect(wrapper.find(IdeReview).exists()).toBe(false);
expect(wrapper.find(RepoCommitSection).exists()).toBe(false);
it('renders commit component', async () => {
wrapper = createComponent();
store.state.currentActivityView = leftSidebarViews.review.name;
await waitForPromises();
await wrapper.vm.$nextTick();
store.state.currentActivityView = leftSidebarViews.commit.name;
expect(wrapper.find(IdeTree).exists()).toBe(false);
expect(wrapper.find(IdeReview).exists()).toBe(true);
expect(wrapper.find(RepoCommitSection).exists()).toBe(false);
store.state.currentActivityView = leftSidebarViews.commit.name;
await waitForPromises();
await wrapper.vm.$nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(false);
expect(wrapper.find(IdeReview).exists()).toBe(false);
expect(wrapper.find(RepoCommitSection).exists()).toBe(true);
});
it.each`
view | tree | review | commit
${leftSidebarViews.edit.name} | ${true} | ${false} | ${false}
${leftSidebarViews.review.name} | ${false} | ${true} | ${false}
${leftSidebarViews.commit.name} | ${false} | ${false} | ${true}
`('renders correct panels for $view', async ({ view, tree, review, commit } = {}) => {
wrapper = createComponent({
view,
});
await waitForPromises();
await wrapper.vm.$nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(tree);
expect(wrapper.find(IdeReview).exists()).toBe(review);
expect(wrapper.find(RepoCommitSection).exists()).toBe(commit);
});
});
it('keeps the current activity view components alive', async () => {
......@@ -72,7 +99,7 @@ describe('IdeSidebar', () => {
const ideTreeComponent = wrapper.find(IdeTree).element;
store.state.currentActivityView = leftSidebarViews.commit.name;
await waitForPromises();
await wrapper.vm.$nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(false);
......@@ -80,6 +107,7 @@ describe('IdeSidebar', () => {
store.state.currentActivityView = leftSidebarViews.edit.name;
await waitForPromises();
await wrapper.vm.$nextTick();
// reference to the elements remains the same, meaning the components were kept alive
......
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createStore } from '~/ide/stores';
import ide from '~/ide/components/ide.vue';
import { file } from '../helpers';
......@@ -63,18 +64,17 @@ describe('ide component, non-empty repo', () => {
vm.$destroy();
});
it('shows error message when set', done => {
it('shows error message when set', async () => {
expect(vm.$el.querySelector('.gl-alert')).toBe(null);
vm.$store.state.errorMessage = {
text: 'error',
};
vm.$nextTick(() => {
expect(vm.$el.querySelector('.gl-alert')).not.toBe(null);
await waitForPromises();
await vm.$nextTick();
done();
});
expect(vm.$el.querySelector('.gl-alert')).not.toBe(null);
});
describe('onBeforeUnload', () => {
......
......@@ -8,12 +8,6 @@ exports[`WebIDE runs 1`] = `
<div
class="ide-view flex-grow d-flex"
>
<div
class="file-finder-overlay"
style="display: none;"
>
(jest: contents hidden)
</div>
<div
class="gl-relative multi-file-commit-panel flex-column"
style="width: 340px;"
......@@ -109,28 +103,12 @@ exports[`WebIDE runs 1`] = `
<h4>
Make and review changes in the browser with the Web IDE
</h4>
<div
class="gl-spinner-container"
>
<span
aria-label="Loading"
class="align-text-bottom gl-spinner gl-spinner-dark gl-spinner-md"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer
class="ide-status-bar"
>
<div
class="ide-status-list d-flex ml-auto"
>
</div>
</footer>
</article>
</div>
`;
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment