diff --git a/.eslintrc.yml b/.eslintrc.yml
index f814bdc6434dee46089bd2482f3fb3d766a69b89..88fbf37780a1d91ed1c0184beaaf8e7473119a84 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -113,6 +113,8 @@ rules:
     - error
     - selector: ImportSpecifier[imported.name='GlSkeletonLoading']
       message: 'Migrate to GlSkeletonLoader, or import GlDeprecatedSkeletonLoading.'
+    - selector: ImportSpecifier[imported.name='GlSafeHtmlDirective']
+      message: 'Use directive at ~/vue_shared/directives/safe_html.js instead.'
   # See https://gitlab.com/gitlab-org/gitlab/-/issues/360551
   vue/multi-word-component-names: off
   unicorn/prefer-dom-node-dataset:
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue b/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
index 1408312d3e433df5b850814c023613f136c57130..44c6bc72705632e39cc66bbbec5d92c134915bd8 100644
--- a/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
+++ b/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlButton, GlTableLite, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButton, GlTableLite } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __ } from '~/locale';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 
@@ -12,7 +13,7 @@ export default {
     GlTableLite,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagsMixin()],
   i18n: {
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue
index f68666f8a0ca1b4b1f95469d75c75a80a7572a54..5bc29750635c1f43d4f8918e26b1d4b04006f08a 100644
--- a/app/assets/javascripts/badges/components/badge_form.vue
+++ b/app/assets/javascripts/badges/components/badge_form.vue
@@ -1,7 +1,8 @@
 <script>
-import { GlLoadingIcon, GlFormInput, GlFormGroup, GlButton, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlLoadingIcon, GlFormInput, GlFormGroup, GlButton } from '@gitlab/ui';
 import { escape, debounce } from 'lodash';
 import { mapActions, mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { createAlert, VARIANT_INFO } from '~/flash';
 import { s__, sprintf } from '~/locale';
 import createEmptyBadge from '../empty_badge';
@@ -19,7 +20,7 @@ export default {
     GlFormGroup,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     isEditing: {
diff --git a/app/assets/javascripts/batch_comments/components/draft_note.vue b/app/assets/javascripts/batch_comments/components/draft_note.vue
index e5408d0734afc45b5466d3437c15d4f3f4034d2f..794bf4cc51cd9111532c210694daa648937918cb 100644
--- a/app/assets/javascripts/batch_comments/components/draft_note.vue
+++ b/app/assets/javascripts/batch_comments/components/draft_note.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlButton, GlSafeHtmlDirective, GlBadge } from '@gitlab/ui';
+import { GlButton, GlBadge } from '@gitlab/ui';
 import { mapActions, mapGetters, mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import NoteableNote from '~/notes/components/noteable_note.vue';
 import PublishButton from './publish_button.vue';
@@ -13,7 +14,7 @@ export default {
     GlBadge,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagMixin()],
   props: {
diff --git a/app/assets/javascripts/cycle_analytics/components/path_navigation.vue b/app/assets/javascripts/cycle_analytics/components/path_navigation.vue
index 72a7659aac0aab69c53d78dc2737d9174be30bf8..ac41bc4917c5811f86f86573718ba236c0bcb129 100644
--- a/app/assets/javascripts/cycle_analytics/components/path_navigation.vue
+++ b/app/assets/javascripts/cycle_analytics/components/path_navigation.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlPath, GlPopover, GlSkeletonLoader, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlPath, GlPopover, GlSkeletonLoader } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import Tracking from '~/tracking';
 import { OVERVIEW_STAGE_ID } from '../constants';
 import FormattedStageCount from './formatted_stage_count.vue';
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
index e629f74ba020a46a534b76be8661882f9d2a3523..af4bf7eb14d0aac7bd245fe76ee72c7803b0f2e9 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
@@ -1,13 +1,7 @@
 <script>
-import {
-  GlAvatar,
-  GlAvatarLink,
-  GlButton,
-  GlLink,
-  GlSafeHtmlDirective,
-  GlTooltipDirective,
-} from '@gitlab/ui';
+import { GlAvatar, GlAvatarLink, GlButton, GlLink, GlTooltipDirective } from '@gitlab/ui';
 import { ApolloMutation } from 'vue-apollo';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
 import { __ } from '~/locale';
 import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
@@ -33,7 +27,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     note: {
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index 5a45797ed986225393c4db97e225ab7159d6b19f..1857ff557e6315aee95d26812bea5258531e86ef 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlButtonGroup, GlButton, GlTooltipDirective, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButtonGroup, GlButton, GlTooltipDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
 import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
@@ -32,7 +33,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagsMixin()],
   props: {
diff --git a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
index b2098b9e82d53dda6fbad321a4bb46e87be1afec..8fcbc4b5cce5d105c70dc797333f49058efb51b4 100644
--- a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlTooltipDirective, GlSafeHtmlDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui';
+import { GlTooltipDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui';
 import { mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { createAlert } from '~/flash';
 import { s__, sprintf } from '~/locale';
 import { UNFOLD_COUNT, INLINE_DIFF_LINES_KEY } from '../constants';
@@ -21,7 +22,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     file: {
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 8f041d1e670cddc8e470d8e6420b0bc59d4d2a7e..564f776edd223c15ae925d92300da2b0bd1472ad 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -1,13 +1,8 @@
 <script>
-import {
-  GlButton,
-  GlLoadingIcon,
-  GlSafeHtmlDirective as SafeHtml,
-  GlSprintf,
-  GlAlert,
-} from '@gitlab/ui';
+import { GlButton, GlLoadingIcon, GlSprintf, GlAlert } from '@gitlab/ui';
 import { escape } from 'lodash';
 import { mapActions, mapGetters, mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { IdState } from 'vendor/vue-virtual-scroller';
 import DiffContent from 'jh_else_ce/diffs/components/diff_content.vue';
 import { createAlert } from '~/flash';
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 91c3df39e32b8a710d667effafcc8aa33574acc3..dff61acdfba79c7c91bb1bcb23309243a56e6e3e 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -1,7 +1,6 @@
 <script>
 import {
   GlTooltipDirective,
-  GlSafeHtmlDirective,
   GlIcon,
   GlBadge,
   GlButton,
@@ -14,6 +13,7 @@ import {
 } from '@gitlab/ui';
 import { escape } from 'lodash';
 import { mapActions, mapGetters, mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { IdState } from 'vendor/vue-virtual-scroller';
 import { scrollToElement } from '~/lib/utils/common_utils';
 import { truncateSha } from '~/lib/utils/text_utility';
@@ -44,7 +44,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [IdState({ idProp: (vm) => vm.diffFile.file_hash }), glFeatureFlagsMixin()],
   i18n: {
diff --git a/app/assets/javascripts/diffs/components/diff_view.vue b/app/assets/javascripts/diffs/components/diff_view.vue
index 5ea118afe78a23892b308357374a0cf8b25e17d4..4a5a626af8dddf03d8a267c2474a2a72fdfdf88a 100644
--- a/app/assets/javascripts/diffs/components/diff_view.vue
+++ b/app/assets/javascripts/diffs/components/diff_view.vue
@@ -1,5 +1,4 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import { mapGetters, mapState, mapActions } from 'vuex';
 import { IdState } from 'vendor/vue-virtual-scroller';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -22,9 +21,6 @@ export default {
     DiffCommentCell,
     DraftNote,
   },
-  directives: {
-    SafeHtml,
-  },
   mixins: [
     draftCommentsMixin,
     IdState({ idProp: (vm) => vm.diffFile.file_hash }),
diff --git a/app/assets/javascripts/environments/components/deploy_board.vue b/app/assets/javascripts/environments/components/deploy_board.vue
index f22a0705b3dbba5c4785a8b99dfca8e97dba6933..31bc462f0b91edf05eda9369ca452d99f5d46153 100644
--- a/app/assets/javascripts/environments/components/deploy_board.vue
+++ b/app/assets/javascripts/environments/components/deploy_board.vue
@@ -15,10 +15,10 @@ import {
   GlLink,
   GlTooltip,
   GlTooltipDirective,
-  GlSafeHtmlDirective as SafeHtml,
   GlSprintf,
 } from '@gitlab/ui';
 import { isEmpty } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__, n__ } from '~/locale';
 import InstanceComponent from '~/vue_shared/components/deployment_instance.vue';
 import { STATUS_MAP, CANARY_STATUS } from '../constants';
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
index 34d01f21da24ca7a28658c870f8aea9b621b5928..6ddd982ebf112c3c349819df50287c3baf3cc64d 100644
--- a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
+++ b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlTooltip, GlSprintf, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlTooltip, GlSprintf, GlIcon } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
 import FileIcon from '~/vue_shared/components/file_icon.vue';
 
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_popover.vue b/app/assets/javascripts/feature_highlight/feature_highlight_popover.vue
index 79d7eb945698390c28bbd825c4f7f818e011bff9..1c6e6380e764d0716e0d85e5da2ddb5e2aa95524 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight_popover.vue
+++ b/app/assets/javascripts/feature_highlight/feature_highlight_popover.vue
@@ -1,12 +1,7 @@
 <script>
 import clusterPopover from '@gitlab/svgs/dist/illustrations/cluster_popover.svg';
-import {
-  GlPopover,
-  GlSprintf,
-  GlLink,
-  GlButton,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlPopover, GlSprintf, GlLink, GlButton } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __ } from '~/locale';
 import { POPOVER_TARGET_ID } from './constants';
 import { dismiss } from './feature_highlight_helper';
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
index 33ab1d5cd7fd60c9cf5d3efe5d86a13dd3cd6ac3..89b6885091c94509e057504b37d4089c8381f012 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlButton, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
 import { snakeCase } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import highlight from '~/lib/utils/highlight';
 import { truncateNamespace } from '~/lib/utils/text_utility';
 import { mapVuexModuleState } from '~/lib/utils/vuex_module_mappers';
@@ -15,7 +16,7 @@ export default {
     ProjectAvatar,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [trackingMixin],
   inject: ['vuexModule'],
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 961af800971b0a830bcd58781b3231b2d07607a8..3874d06da91c78f49dbc9d543e2042e171c63f3b 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -9,8 +9,8 @@ import {
   GlPopover,
   GlLink,
   GlTooltipDirective,
-  GlSafeHtmlDirective,
 } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { visitUrl } from '~/lib/utils/url_utility';
 import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
 import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
@@ -29,7 +29,7 @@ import ItemTypeIcon from './item_type_icon.vue';
 export default {
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   components: {
     GlAvatar,
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index 8fc0ce48e6104a88117d3de3e40bf30a185b8e24..bf5daf29b21bdeaaac424bed9a868f4ebd778099 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -4,7 +4,6 @@ import {
   GlOutsideDirective as Outside,
   GlIcon,
   GlToken,
-  GlSafeHtmlDirective as SafeHtml,
   GlTooltipDirective,
   GlResizeObserverDirective,
 } from '@gitlab/ui';
@@ -56,7 +55,7 @@ export default {
       false,
     ),
   },
-  directives: { SafeHtml, Outside, GlTooltip: GlTooltipDirective, GlResizeObserverDirective },
+  directives: { Outside, GlTooltip: GlTooltipDirective, GlResizeObserverDirective },
   components: {
     GlSearchBoxByType,
     HeaderSearchDefaultItems,
diff --git a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
index 025c48f355d110d706df9c7819bb3c599f05cf18..c85fb4f4158b26ef68ac7d22ec3282f3ea475d7e 100644
--- a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
+++ b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
@@ -6,9 +6,9 @@ import {
   GlAvatar,
   GlAlert,
   GlLoadingIcon,
-  GlSafeHtmlDirective as SafeHtml,
 } from '@gitlab/ui';
 import { mapState, mapGetters } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__ } from '~/locale';
 import highlight from '~/lib/utils/highlight';
 import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index d02dc67d933e3e793efe2140a6d6aaa8685ace47..ef3da57c240513b9abbd1c31f50265088c6ae8e8 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlModal, GlSafeHtmlDirective, GlButton, GlTooltipDirective } from '@gitlab/ui';
+import { GlModal, GlButton, GlTooltipDirective } from '@gitlab/ui';
 import { mapState, mapActions, mapGetters } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { n__ } from '~/locale';
 import { leftSidebarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants';
 import { createUnexpectedCommitError } from '../../lib/errors';
@@ -17,7 +18,7 @@ export default {
     GlButton,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
     GlTooltip: GlTooltipDirective,
   },
   data() {
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
index 5272c4310d8e955168d622586e57db2bfc8e8f85..dd343bc5f79a9400e11341d6182c4e70b61d38c7 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
@@ -1,6 +1,6 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import { mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   directives: {
diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue
index 67eedc6b37fe116f8fffe4f1f129699c457adacd..eba9bbcdf09f1310e992a5fe18b5cb2eb3ad8538 100644
--- a/app/assets/javascripts/ide/components/error_message.vue
+++ b/app/assets/javascripts/ide/components/error_message.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlAlert, GlLoadingIcon, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
 import { mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   components: {
@@ -8,7 +9,7 @@ export default {
     GlLoadingIcon,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     message: {
diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue
index 8d6a0b99e0c1c5ba6caddf176a1eb211f022cccb..9676233a44306003ba5c5c344393498f745d3644 100644
--- a/app/assets/javascripts/ide/components/jobs/detail.vue
+++ b/app/assets/javascripts/ide/components/jobs/detail.vue
@@ -1,7 +1,8 @@
 <script>
-import { GlTooltipDirective, GlButton, GlIcon, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlTooltipDirective, GlButton, GlIcon } from '@gitlab/ui';
 import { throttle } from 'lodash';
 import { mapActions, mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __ } from '~/locale';
 import JobDescription from './detail/description.vue';
 import ScrollButton from './detail/scroll_button.vue';
@@ -14,7 +15,7 @@ const scrollPositions = {
 export default {
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   components: {
     GlButton,
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 7f513afe82e1440983988a21def63c9b4ec5c07f..a270d9ef03c27bef2c21f2edb9b34fbb8b8ad877 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -1,15 +1,8 @@
 <script>
-import {
-  GlLoadingIcon,
-  GlIcon,
-  GlSafeHtmlDirective as SafeHtml,
-  GlTabs,
-  GlTab,
-  GlBadge,
-  GlAlert,
-} from '@gitlab/ui';
+import { GlLoadingIcon, GlIcon, GlTabs, GlTab, GlBadge, GlAlert } from '@gitlab/ui';
 import { escape } from 'lodash';
 import { mapActions, mapGetters, mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import IDEServices from '~/ide/services';
 import { sprintf, __ } from '~/locale';
 import CiIcon from '~/vue_shared/components/ci_icon.vue';
diff --git a/app/assets/javascripts/ide/components/terminal/empty_state.vue b/app/assets/javascripts/ide/components/terminal/empty_state.vue
index 623ba719b28a8671b8135dad60f87b89defe5331..fa93f6d42a5b7555e39c0c60c84fbdadcf51f79c 100644
--- a/app/assets/javascripts/ide/components/terminal/empty_state.vue
+++ b/app/assets/javascripts/ide/components/terminal/empty_state.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlLoadingIcon, GlButton, GlAlert, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   components: {
@@ -8,7 +9,7 @@ export default {
     GlAlert,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     isLoading: {
diff --git a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
index fe687ea9767d7907c49a3c7f181ebee119e1cdf8..4260a224ca8b6bab6fb77d610b712240ed9536c0 100644
--- a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
+++ b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
@@ -1,14 +1,8 @@
 <script>
-import {
-  GlFormGroup,
-  GlFormCheckbox,
-  GlFormInput,
-  GlFormSelect,
-  GlFormTextarea,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlFormGroup, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormTextarea } from '@gitlab/ui';
 import { capitalize, lowerCase, isEmpty } from 'lodash';
 import { mapGetters } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   name: 'DynamicField',
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index 4bf2b8d44689fcec31736322c6c2407d4f53da26..881752b018e742bf333f28195781cc4ce9cdb6dc 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -1,16 +1,10 @@
 <script>
-import {
-  GlAlert,
-  GlBadge,
-  GlButton,
-  GlModalDirective,
-  GlSafeHtmlDirective as SafeHtml,
-  GlForm,
-} from '@gitlab/ui';
+import { GlAlert, GlBadge, GlButton, GlModalDirective, GlForm } from '@gitlab/ui';
 import axios from 'axios';
 import * as Sentry from '@sentry/browser';
 import { mapState, mapActions, mapGetters } from 'vuex';
 import { s__ } from '~/locale';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import {
   I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
   I18N_DEFAULT_ERROR_MESSAGE,
diff --git a/app/assets/javascripts/issuable/components/related_issuable_item.vue b/app/assets/javascripts/issuable/components/related_issuable_item.vue
index 254248ef1d4862ab6ffbb7299ee64f08cdf43d98..fd55f05e955718062086fbd20f23be8fed2eea90 100644
--- a/app/assets/javascripts/issuable/components/related_issuable_item.vue
+++ b/app/assets/javascripts/issuable/components/related_issuable_item.vue
@@ -1,13 +1,7 @@
 <script>
 import '~/commons/bootstrap';
-import {
-  GlIcon,
-  GlLink,
-  GlTooltip,
-  GlTooltipDirective,
-  GlButton,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlIcon, GlLink, GlTooltip, GlTooltipDirective, GlButton } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import IssueDueDate from '~/boards/components/issue_due_date.vue';
 import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
 import { convertToGraphQLId } from '~/graphql_shared/utils';
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index 5c2a154362f3799349a7af6c13e251885aa7f021..e22f1abac7d69509ddc3ee93de8356a591fc280f 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -1,8 +1,9 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml, GlToast, GlTooltip, GlModalDirective } from '@gitlab/ui';
+import { GlToast, GlTooltip, GlModalDirective } from '@gitlab/ui';
 import $ from 'jquery';
 import Sortable from 'sortablejs';
 import Vue from 'vue';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
 import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
 import createFlash from '~/flash';
diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue
index cbf3c387fa3438815382fcc6df646ef240f28141..79a61880cf868b84096c10d48d7d1f0309de03ca 100644
--- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue
+++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlDropdown, GlDropdownItem, GlIcon, GlSafeHtmlDirective, GlSprintf } from '@gitlab/ui';
+import { GlDropdown, GlDropdownItem, GlIcon, GlSprintf } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { formatDate } from '~/lib/utils/datetime_utility';
 import { timelineItemI18n } from './constants';
 import { getEventIcon } from './utils';
@@ -14,7 +15,7 @@ export default {
     GlSprintf,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   inject: ['canUpdateTimelineEvent'],
   props: {
diff --git a/app/assets/javascripts/issues/show/components/title.vue b/app/assets/javascripts/issues/show/components/title.vue
index 307d9f9f69a86848b136976e71fccd1f3009fcb0..6978f730e1d3007bc15352fbed66b9bb994d5596 100644
--- a/app/assets/javascripts/issues/show/components/title.vue
+++ b/app/assets/javascripts/issues/show/components/title.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlButton, GlTooltipDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlButton, GlTooltipDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __ } from '~/locale';
 import eventHub from '../event_hub';
 import animateMixin from '../mixins/animate';
diff --git a/app/assets/javascripts/jobs/components/job/job_app.vue b/app/assets/javascripts/jobs/components/job/job_app.vue
index 81b65d175a7ff0c99274e33ce13e6fd8a2deb98f..e5fbf77be1e6aeee0819f11005ed8d9e16cac66e 100644
--- a/app/assets/javascripts/jobs/components/job/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job/job_app.vue
@@ -1,8 +1,9 @@
 <script>
-import { GlLoadingIcon, GlIcon, GlSafeHtmlDirective as SafeHtml, GlAlert } from '@gitlab/ui';
+import { GlLoadingIcon, GlIcon, GlAlert } from '@gitlab/ui';
 import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
 import { throttle, isEmpty } from 'lodash';
 import { mapGetters, mapState, mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
 import { __, sprintf } from '~/locale';
 import CiHeader from '~/vue_shared/components/header_ci_component.vue';
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
index 3788d8ab20c81cb61178e4f0a9ab9c0b838a33cd..ea91ccec5469d96a32f914b07c9ee11a1cf45062 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
@@ -1,10 +1,11 @@
 <script>
-import { GlModal, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlModal } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __ } from '~/locale';
 
 export default {
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   components: {
     GlModal,
diff --git a/app/assets/javascripts/members/components/avatars/user_avatar.vue b/app/assets/javascripts/members/components/avatars/user_avatar.vue
index ec59f0f681c066f48b94091673501de5fa90c606..4260ee14a1457fc2be2a30910574af97f21e6110 100644
--- a/app/assets/javascripts/members/components/avatars/user_avatar.vue
+++ b/app/assets/javascripts/members/components/avatars/user_avatar.vue
@@ -1,10 +1,6 @@
 <script>
-import {
-  GlAvatarLink,
-  GlAvatarLabeled,
-  GlBadge,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlAvatarLink, GlAvatarLabeled, GlBadge } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { generateBadges } from 'ee_else_ce/members/utils';
 import { glEmojiTag } from '~/emoji';
 import { __ } from '~/locale';
diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue
index 87eeb272659c8ac592b73ba7ca86536c1f65cdcb..6c431dc8af3df3c518c0c85a2ccea0da39af3f9f 100644
--- a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue
+++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue
@@ -1,6 +1,6 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import { mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import syntaxHighlight from '~/syntax_highlight';
 import { SYNTAX_HIGHLIGHT_CLASS } from '../constants';
 import utilsMixin from '../mixins/line_conflict_utils';
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue
index 2c59e7bfa2fa35d122ce35b61ed0efed66479cca..f8a097a3a0f9047e35c73ddb816ad61dc72bda79 100644
--- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue
@@ -1,6 +1,6 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import { mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import syntaxHighlight from '~/syntax_highlight';
 import { SYNTAX_HIGHLIGHT_CLASS } from '../constants';
 import utilsMixin from '../mixins/line_conflict_utils';
diff --git a/app/assets/javascripts/merge_requests/components/sticky_header.vue b/app/assets/javascripts/merge_requests/components/sticky_header.vue
index b7629ba001fb9f539c5fba973cd59dcafc9d3cdb..4a675cf75630955203f33fc0c136180e31c56280 100644
--- a/app/assets/javascripts/merge_requests/components/sticky_header.vue
+++ b/app/assets/javascripts/merge_requests/components/sticky_header.vue
@@ -1,12 +1,7 @@
 <script>
-import {
-  GlIntersectionObserver,
-  GlLink,
-  GlSprintf,
-  GlBadge,
-  GlSafeHtmlDirective,
-} from '@gitlab/ui';
+import { GlIntersectionObserver, GlLink, GlSprintf, GlBadge } from '@gitlab/ui';
 import { mapGetters, mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
 import { convertToGraphQLId } from '~/graphql_shared/utils';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -28,7 +23,7 @@ export default {
     ClipboardButton,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagsMixin()],
   inject: {
diff --git a/app/assets/javascripts/monitoring/components/charts/empty_chart.vue b/app/assets/javascripts/monitoring/components/charts/empty_chart.vue
index ae079da0b0b71d86a2b0b7fe132f6707aac0ff23..da4c92df711f520d7199cefd8f8e373f5e085fff 100644
--- a/app/assets/javascripts/monitoring/components/charts/empty_chart.vue
+++ b/app/assets/javascripts/monitoring/components/charts/empty_chart.vue
@@ -1,11 +1,11 @@
 <script>
 import chartEmptyStateIllustration from '@gitlab/svgs/dist/illustrations/chart-empty-state.svg';
-import { GlSafeHtmlDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { chartHeight } from '../../constants';
 
 export default {
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   data() {
     return {
diff --git a/app/assets/javascripts/monitoring/components/group_empty_state.vue b/app/assets/javascripts/monitoring/components/group_empty_state.vue
index 0365fc663317e259a071f143f897f2322fd2e113..a67770b93be2b2742f64d53f28b2d0bcf3269837 100644
--- a/app/assets/javascripts/monitoring/components/group_empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/group_empty_state.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlEmptyState, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlEmptyState } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __, sprintf } from '~/locale';
 import { metricStates } from '../constants';
 
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index 9aa6abd9d8cb69ffa5406f6527ab62a0816e0b5b..2caa93c3c93563eb9fdc64d7300e00db01a49493 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -1,7 +1,7 @@
 <script>
 import katex from 'katex';
 import { marked } from 'marked';
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { sanitize } from '~/lib/dompurify';
 import { hasContent, markdownConfig } from '~/lib/utils/text_utility';
 import Prompt from './prompt.vue';
diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue
index 5437a607e8a8ef6e6d4c780873fbc701157b2d70..74a5dd3806d57eeab4ba68a42453fa42310420eb 100644
--- a/app/assets/javascripts/notebook/cells/output/html.vue
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -1,14 +1,10 @@
 <script>
-import { GlSafeHtmlDirective } from '@gitlab/ui';
 import Prompt from '../prompt.vue';
 
 export default {
   components: {
     Prompt,
   },
-  directives: {
-    SafeHtml: GlSafeHtmlDirective,
-  },
   props: {
     count: {
       type: Number,
@@ -28,12 +24,6 @@ export default {
       return this.index === 0;
     },
   },
-  safeHtmlConfig: {
-    ADD_TAGS: ['use'], // to support icon SVGs
-    FORBID_TAGS: ['style'],
-    FORBID_ATTR: ['style'],
-    ALLOW_DATA_ATTR: false,
-  },
 };
 </script>
 
diff --git a/app/assets/javascripts/notebook/cells/output/latex.vue b/app/assets/javascripts/notebook/cells/output/latex.vue
index d0ed963b55d5444dd6588e8d475f485c963897d6..55f97fee3dc97d008826db6ccb1f59d5454e2fef 100644
--- a/app/assets/javascripts/notebook/cells/output/latex.vue
+++ b/app/assets/javascripts/notebook/cells/output/latex.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import 'mathjax/es5/tex-svg';
 import Prompt from '../prompt.vue';
 
diff --git a/app/assets/javascripts/notebook/cells/output/markdown.vue b/app/assets/javascripts/notebook/cells/output/markdown.vue
index 5da057dee724587f50866d86bfdb21a240b269db..ad74e28ac74559ef099566ed95d8846bff7bc477 100644
--- a/app/assets/javascripts/notebook/cells/output/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/output/markdown.vue
@@ -1,5 +1,4 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import Prompt from '../prompt.vue';
 import Markdown from '../markdown.vue';
 
@@ -9,9 +8,6 @@ export default {
     Prompt,
     Markdown,
   },
-  directives: {
-    SafeHtml,
-  },
   props: {
     count: {
       type: Number,
diff --git a/app/assets/javascripts/notes/components/diff_discussion_header.vue b/app/assets/javascripts/notes/components/diff_discussion_header.vue
index cf6474270a25cb235093d140001ed9e9f6e556cb..f949142d90a8d76620f3eae0cf1124c64d988d35 100644
--- a/app/assets/javascripts/notes/components/diff_discussion_header.vue
+++ b/app/assets/javascripts/notes/components/diff_discussion_header.vue
@@ -1,7 +1,8 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml, GlAvatar, GlAvatarLink } from '@gitlab/ui';
+import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
 import { escape } from 'lodash';
 import { mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { truncateSha } from '~/lib/utils/text_utility';
 import { s__, __, sprintf } from '~/locale';
 import NoteEditedText from './note_edited_text.vue';
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 3bdf8349a12b60826634b3d0e35bda2b2fcc62c1..aabdc1c99b6bc587cf0cbcb828db27f60f17917e 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlSkeletonLoader, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlSkeletonLoader } from '@gitlab/ui';
 import { mapState, mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
 import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
 import { getDiffMode } from '~/diffs/store/utils';
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index 82c125b79ce05ee7d5e12e5fec0a3c841788811f..7aa86864e03419d8957ded8a70ed46ed698a859b 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -1,9 +1,8 @@
 <script>
 import $ from 'jquery';
-import { GlSafeHtmlDirective } from '@gitlab/ui';
 import { escape } from 'lodash';
 import { mapActions, mapGetters, mapState } from 'vuex';
-
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __ } from '~/locale';
 import '~/behaviors/markdown/render_gfm';
 import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
@@ -22,7 +21,7 @@ export default {
     Suggestions,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [autosave],
   props: {
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 63c7010983e692c4b99ec44260a14d417a7fe829..36f7d720e483231d9c64a19a5f828c8d12e1b11a 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,17 +1,10 @@
 <script>
-import {
-  GlIcon,
-  GlBadge,
-  GlLoadingIcon,
-  GlTooltipDirective,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlIcon, GlBadge, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
 import { mapActions } from 'vuex';
 import { __, s__ } from '~/locale';
 import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
 
 export default {
-  safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
   components: {
     TimeAgoTooltip,
     GitlabTeamMemberBadge: () =>
@@ -21,7 +14,6 @@ export default {
     GlLoadingIcon,
   },
   directives: {
-    SafeHtml,
     GlTooltip: GlTooltipDirective,
   },
   props: {
diff --git a/app/assets/javascripts/notes/components/note_signed_out_widget.vue b/app/assets/javascripts/notes/components/note_signed_out_widget.vue
index 593933016e1ab41244378d8c7161a2b4ec0a6bbe..94636b3e47bde2bcc8de7b6b2ee8a3eebf6f9ab0 100644
--- a/app/assets/javascripts/notes/components/note_signed_out_widget.vue
+++ b/app/assets/javascripts/notes/components/note_signed_out_widget.vue
@@ -1,6 +1,6 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import { mapGetters } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __, sprintf } from '~/locale';
 
 export default {
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 8ce0c2f86484d4f0e91de80da9bf0f0f6802266b..3475a0204c4f56cd82b43cf0a02c94de9d90ba10 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -1,8 +1,9 @@
 <script>
-import { GlSprintf, GlSafeHtmlDirective as SafeHtml, GlAvatarLink, GlAvatar } from '@gitlab/ui';
+import { GlSprintf, GlAvatarLink, GlAvatar } from '@gitlab/ui';
 import $ from 'jquery';
 import { escape, isEmpty } from 'lodash';
 import { mapGetters, mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
 import { INLINE_DIFF_LINES_KEY } from '~/diffs/constants';
 import { createAlert } from '~/flash';
diff --git a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
index b06c804f3ca0b79ede2187fc87b29b515a5a9cc2..48241a213ef342c92ea96796ffb2d342aabb5550 100644
--- a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
+++ b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml, GlModal } from '@gitlab/ui';
+import { GlModal } from '@gitlab/ui';
 import { escape } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __, s__, sprintf } from '~/locale';
 
 export default {
diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue
index b72579276e8f42ed585e1ffdcb44491bd39228d0..1bb03679b09c79b1f981b88c904c1eb442d23ec8 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSkeletonLoader, GlSafeHtmlDirective, GlAlert } from '@gitlab/ui';
+import { GlSkeletonLoader, GlAlert } from '@gitlab/ui';
 import { createAlert } from '~/flash';
 import { __ } from '~/locale';
 import axios from '~/lib/utils/axios_utils';
@@ -11,9 +11,6 @@ export default {
     GlSkeletonLoader,
     GlAlert,
   },
-  directives: {
-    SafeHtml: GlSafeHtmlDirective,
-  },
   props: {
     getWikiContentUrl: {
       type: String,
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index a5fa85f1ed53fbac7ce12ef935b28781945a70d7..dbca8bc9be722e6061c11ec42f1acdbc9adc57a1 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { glEmojiTag } from '~/emoji';
 import { mergeUrlParams } from '~/lib/utils/url_utility';
 
@@ -15,7 +15,7 @@ export default {
     RequestSelector,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     store: {
diff --git a/app/assets/javascripts/performance_bar/components/request_warning.vue b/app/assets/javascripts/performance_bar/components/request_warning.vue
index 3ebd222029b53a0cb6ded1fe296dd95f7f9e421e..91e905d62e667ce3a43795605a58737b190e8cb1 100644
--- a/app/assets/javascripts/performance_bar/components/request_warning.vue
+++ b/app/assets/javascripts/performance_bar/components/request_warning.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlPopover, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlPopover } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { glEmojiTag } from '~/emoji';
 
 export default {
@@ -7,7 +8,7 @@ export default {
     GlPopover,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     htmlId: {
diff --git a/app/assets/javascripts/pipeline_new/components/legacy_pipeline_new_form.vue b/app/assets/javascripts/pipeline_new/components/legacy_pipeline_new_form.vue
index cd7cb7f839333ea85b3104f27107a170ad0235ca..c3d46d1954a019bb05ea80e93c212f955a8b1a19 100644
--- a/app/assets/javascripts/pipeline_new/components/legacy_pipeline_new_form.vue
+++ b/app/assets/javascripts/pipeline_new/components/legacy_pipeline_new_form.vue
@@ -12,11 +12,11 @@ import {
   GlLink,
   GlSprintf,
   GlLoadingIcon,
-  GlSafeHtmlDirective as SafeHtml,
 } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
 import { uniqueId } from 'lodash';
 import Vue from 'vue';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import axios from '~/lib/utils/axios_utils';
 import { backOff } from '~/lib/utils/common_utils';
 import httpStatusCodes from '~/lib/utils/http_status';
diff --git a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
index a9af1181027d45d49608d2d8e4d7d06dd20942b5..8f59a50df4dc64f381d3da4cd92e0dfa813dc955 100644
--- a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
+++ b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
@@ -12,11 +12,11 @@ import {
   GlLink,
   GlSprintf,
   GlLoadingIcon,
-  GlSafeHtmlDirective as SafeHtml,
 } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
 import { uniqueId } from 'lodash';
 import Vue from 'vue';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { redirectTo } from '~/lib/utils/url_utility';
 import { s__, __, n__ } from '~/locale';
 import { VARIABLE_TYPE, FILE_TYPE, CC_VALIDATION_REQUIRED_ERROR } from '../constants';
diff --git a/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue
index 18607bfae1cee6878ad1154011ecd94304c08b59..c56537f4039763ccbfb51fe97e15be75f6457496 100644
--- a/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue
+++ b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlButton, GlLink, GlSafeHtmlDirective, GlTableLite } from '@gitlab/ui';
+import { GlButton, GlLink, GlTableLite } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __, s__ } from '~/locale';
 import { createAlert } from '~/flash';
 import { redirectTo } from '~/lib/utils/url_utility';
@@ -17,7 +18,7 @@ export default {
     GlTableLite,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     failedJobs: {
diff --git a/app/assets/javascripts/popovers/components/popovers.vue b/app/assets/javascripts/popovers/components/popovers.vue
index a758503b56b76219081386c6a92842fa6774e563..7ec54231e651a11956281ad188ab28018c493d4d 100644
--- a/app/assets/javascripts/popovers/components/popovers.vue
+++ b/app/assets/javascripts/popovers/components/popovers.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlPopover, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlPopover } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 const newPopover = (element) => {
   const { content, html, placement, title, triggers = 'focus' } = element.dataset;
@@ -19,7 +20,7 @@ export default {
     GlPopover,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   data() {
     return {
diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue
index b038b78088f580e5dcf31a2c3bc674cd2241a722..51e62984715c7dec9601ecea202ae9de9f0edd3f 100644
--- a/app/assets/javascripts/profile/account/components/update_username.vue
+++ b/app/assets/javascripts/profile/account/components/update_username.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml, GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
+import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
 import { escape } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { createAlert, VARIANT_INFO } from '~/flash';
 import axios from '~/lib/utils/axios_utils';
 import { __, s__, sprintf } from '~/locale';
diff --git a/app/assets/javascripts/projects/new/components/app.vue b/app/assets/javascripts/projects/new/components/app.vue
index 59ca393fe920dcb3ccd90cbab4ed02cb2349cb74..3100029eb311d9564ec15fd70276f6224b87bac1 100644
--- a/app/assets/javascripts/projects/new/components/app.vue
+++ b/app/assets/javascripts/projects/new/components/app.vue
@@ -3,7 +3,7 @@ import createFromTemplateIllustration from '@gitlab/svgs/dist/illustrations/proj
 import blankProjectIllustration from '@gitlab/svgs/dist/illustrations/project-create-new-sm.svg';
 import importProjectIllustration from '@gitlab/svgs/dist/illustrations/project-import-sm.svg';
 import ciCdProjectIllustration from '@gitlab/svgs/dist/illustrations/project-run-CICD-pipelines-sm.svg';
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__ } from '~/locale';
 import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue';
 import NewProjectPushTipPopover from './new_project_push_tip_popover.vue';
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
index 71ff3e892b1215b8d6f24b131ed8ec5ef7f732c8..b79b3fa45732e3b377351c12994114b8b021f492 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlAlert, GlSprintf, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import axios from '~/lib/utils/axios_utils';
 import { helpPagePath } from '~/helpers/help_page_helper';
 import { __, sprintf } from '~/locale';
@@ -16,7 +17,7 @@ export default {
     ServiceDeskSetting,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   inject: {
     initialIsEnabled: {
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index b2bd405574fc025f6638412f7f9565f1f0ff635f..9c404d74acb2654c5dca34ec89a5c1b87e4d4191 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -1,7 +1,7 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import $ from 'jquery';
 import { isEmpty } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { scrollToElement } from '~/lib/utils/common_utils';
 import { slugify } from '~/lib/utils/text_utility';
 import { getLocationHash } from '~/lib/utils/url_utility';
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 05d6407786645d1521d4a9be063005755bbdf265..4d3c15215590eccc479a7e1b8689b816bd5ee2a8 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -1,12 +1,6 @@
 <script>
-import {
-  GlTooltipDirective,
-  GlLink,
-  GlButton,
-  GlButtonGroup,
-  GlLoadingIcon,
-  GlSafeHtmlDirective,
-} from '@gitlab/ui';
+import { GlTooltipDirective, GlLink, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import defaultAvatarUrl from 'images/no_avatar.png';
 import pathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
 import { sprintf, s__ } from '~/locale';
@@ -32,7 +26,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [getRefMixin],
   apollo: {
diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue
index 4935b8029f92871137516209a87a5ea5a9975c83..29890a54abe268e3e7982976a80410305d5306aa 100644
--- a/app/assets/javascripts/repository/components/preview/index.vue
+++ b/app/assets/javascripts/repository/components/preview/index.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlIcon, GlLink, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlIcon, GlLink, GlLoadingIcon } from '@gitlab/ui';
 import $ from 'jquery';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import '~/behaviors/markdown/render_gfm';
 import { handleLocationHash } from '~/lib/utils/common_utils';
 import readmeQuery from '../../queries/readme.query.graphql';
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index f3c5ace75fc5493152e1be52220f1b9767d17b13..e10ec07abc4a3dbdb21c84e1fdc53dcc80268245 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -7,10 +7,10 @@ import {
   GlLoadingIcon,
   GlIcon,
   GlHoverLoadDirective,
-  GlSafeHtmlDirective,
   GlIntersectionObserver,
 } from '@gitlab/ui';
 import { escapeRegExp } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql';
 import { escapeFileUrl } from '~/lib/utils/url_utility';
 import { TREE_PAGE_SIZE, ROW_APPEAR_DELAY } from '~/repository/constants';
@@ -35,7 +35,7 @@ export default {
   directives: {
     GlTooltip: GlTooltipDirective,
     GlHoverLoad: GlHoverLoadDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   apollo: {
     commit: {
diff --git a/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue b/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
index 701561423651fa971f3fd309fec03a321c8c57fb..c1e33df3c420b94c3e65f8b16e7ba3019792d34a 100644
--- a/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
+++ b/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlDropdownItem, GlAvatar, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlDropdownItem, GlAvatar } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import highlight from '~/lib/utils/highlight';
 import { truncateNamespace } from '~/lib/utils/text_utility';
 import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
diff --git a/app/assets/javascripts/security_configuration/components/training_provider_list.vue b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
index 0bcb2bb6720043bb7087c50fa0503af5ea4ada12..6dae8e50908f2792ab2718efd2652a42031e4295 100644
--- a/app/assets/javascripts/security_configuration/components/training_provider_list.vue
+++ b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
@@ -8,9 +8,9 @@ import {
   GlLink,
   GlSkeletonLoader,
   GlIcon,
-  GlSafeHtmlDirective,
 } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import Tracking from '~/tracking';
 import { __, s__ } from '~/locale';
 import {
@@ -54,7 +54,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [Tracking.mixin()],
   inject: ['projectFullPath'],
diff --git a/app/assets/javascripts/self_monitor/components/self_monitor_form.vue b/app/assets/javascripts/self_monitor/components/self_monitor_form.vue
index ffba3aac6818d2a4daa245c901844a0d6244b0a1..d9e969e227823bad38684116c7d154a73e2f8a74 100644
--- a/app/assets/javascripts/self_monitor/components/self_monitor_form.vue
+++ b/app/assets/javascripts/self_monitor/components/self_monitor_form.vue
@@ -1,15 +1,8 @@
 <script>
-import {
-  GlFormGroup,
-  GlButton,
-  GlModal,
-  GlToast,
-  GlToggle,
-  GlLink,
-  GlSafeHtmlDirective,
-} from '@gitlab/ui';
+import { GlFormGroup, GlButton, GlModal, GlToast, GlToggle, GlLink } from '@gitlab/ui';
 import Vue from 'vue';
 import { mapState, mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { helpPagePath } from '~/helpers/help_page_helper';
 import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
 import { visitUrl, getBaseURL } from '~/lib/utils/url_utility';
@@ -26,7 +19,7 @@ export default {
     GlLink,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   formLabels: {
     createProject: __('Self-monitoring'),
diff --git a/app/assets/javascripts/set_status_modal/set_status_form.vue b/app/assets/javascripts/set_status_modal/set_status_form.vue
index 86049a2b781e8ff6e2a612ba5faf828a81bcf258..dd27a12cbee275487e441333d6dfa690fc1fb515 100644
--- a/app/assets/javascripts/set_status_modal/set_status_form.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_form.vue
@@ -10,9 +10,9 @@ import {
   GlDropdownItem,
   GlSprintf,
   GlFormGroup,
-  GlSafeHtmlDirective,
 } from '@gitlab/ui';
 import $ from 'jquery';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
 import * as Emoji from '~/emoji';
 import { s__ } from '~/locale';
@@ -33,7 +33,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     defaultEmoji: {
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index 80158c55dbcfe18dff28d05739a6e17748da0077..5becc03646ec20002798e472937cee7694a91968 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlToast, GlTooltipDirective, GlSafeHtmlDirective, GlModal } from '@gitlab/ui';
+import { GlToast, GlTooltipDirective, GlModal } from '@gitlab/ui';
 import Vue from 'vue';
 import { createAlert } from '~/flash';
 import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
@@ -19,7 +19,6 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
   },
   mixins: [glFeatureFlagsMixin()],
   props: {
@@ -110,7 +109,6 @@ export default {
       this.availability = value;
     },
   },
-  safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
   actionPrimary: { text: s__('SetStatusModal|Set status') },
   actionSecondary: { text: s__('SetStatusModal|Remove status') },
 };
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
index 91c15061fb9c5c11dc2055d3deff606a726b6230..6cd9596e43f620d30c8aac5d1bd26c3cbb6062eb 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlButton, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { joinPaths } from '~/lib/utils/url_utility';
 import { sprintf, s__ } from '~/locale';
 
@@ -9,7 +10,7 @@ export default {
     GlButton,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   computed: {
     href() {
diff --git a/app/assets/javascripts/snippets/components/snippet_description_view.vue b/app/assets/javascripts/snippets/components/snippet_description_view.vue
index 737a131ce7c48735ac642ba0c3bba2d0f82816ea..ab2ff6e0ef83953b27e5ae5de4943643771346bd 100644
--- a/app/assets/javascripts/snippets/components/snippet_description_view.vue
+++ b/app/assets/javascripts/snippets/components/snippet_description_view.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import MarkdownFieldView from '~/vue_shared/components/markdown/field_view.vue';
 
 export default {
diff --git a/app/assets/javascripts/surveys/merge_request_experience/app.vue b/app/assets/javascripts/surveys/merge_request_experience/app.vue
index df114c27908f42083c9687d138f3f774326b36c6..6e90ad2e0fd1d2d890e0f3c77dd142c0f62f2677 100644
--- a/app/assets/javascripts/surveys/merge_request_experience/app.vue
+++ b/app/assets/javascripts/surveys/merge_request_experience/app.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlButton, GlSprintf, GlSafeHtmlDirective, GlTooltipDirective } from '@gitlab/ui';
+import { GlButton, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
 import gitlabLogo from '@gitlab/svgs/dist/illustrations/gitlab_logo.svg';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__, __ } from '~/locale';
 import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
 import SatisfactionRate from '~/surveys/components/satisfaction_rate.vue';
@@ -30,7 +31,7 @@ export default {
     SatisfactionRate,
   },
   directives: {
-    safeHtml: GlSafeHtmlDirective,
+    SafeHtml,
     tooltip: GlTooltipDirective,
   },
   mixins: [Tracking.mixin()],
diff --git a/app/assets/javascripts/terms/components/app.vue b/app/assets/javascripts/terms/components/app.vue
index a54a198faed70aafd810707d0022b01495032b31..be4ed9825a7be448f1dae865495e1e9c112e4c29 100644
--- a/app/assets/javascripts/terms/components/app.vue
+++ b/app/assets/javascripts/terms/components/app.vue
@@ -1,6 +1,7 @@
 <script>
 import $ from 'jquery';
-import { GlButton, GlIntersectionObserver, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlButton, GlIntersectionObserver } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 import { FLASH_TYPES, FLASH_CLOSED_EVENT } from '~/flash';
 import { isLoggedIn } from '~/lib/utils/common_utils';
diff --git a/app/assets/javascripts/tooltips/components/tooltips.vue b/app/assets/javascripts/tooltips/components/tooltips.vue
index 1ad18508294019aa1b458e5f3f578443a4f32519..a4dc783f1e4a91329d1e9badad558af02f8e8f3e 100644
--- a/app/assets/javascripts/tooltips/components/tooltips.vue
+++ b/app/assets/javascripts/tooltips/components/tooltips.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlTooltip, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlTooltip } from '@gitlab/ui';
 import { uniqueId } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 const getTooltipTitle = (element) => {
   return element.getAttribute('title') || element.dataset.title;
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
index 3d03dbd9db36b02b4f654e07b31ad0b44e89a12e..bf24c152baa8dd25c1f0855046bde61762da9ae1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -1,12 +1,7 @@
 <script>
-import {
-  GlButton,
-  GlLoadingIcon,
-  GlSafeHtmlDirective,
-  GlTooltipDirective,
-  GlIntersectionObserver,
-} from '@gitlab/ui';
+import { GlButton, GlLoadingIcon, GlTooltipDirective, GlIntersectionObserver } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
 import { sprintf, s__, __ } from '~/locale';
 import Poll from '~/lib/utils/poll';
@@ -40,7 +35,7 @@ export default {
     StateContainer,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
     GlTooltip: GlTooltipDirective,
   },
   data() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
index a10e5efa0e7e11b69ec23bd89f034b221122bcc7..fba16d3c1d68e7b965332b8ff685eb041a0a94ec 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlBadge, GlLink, GlSafeHtmlDirective, GlModalDirective } from '@gitlab/ui';
+import { GlBadge, GlLink, GlModalDirective } from '@gitlab/ui';
 import { isArray } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import Actions from '../action_buttons.vue';
 import StatusIcon from './status_icon.vue';
 import { generateText } from './utils';
@@ -14,7 +15,7 @@ export default {
     Actions,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
     GlModal: GlModalDirective,
   },
   props: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index 97c6de37054c5a4fda7149baf5ba349daa614a7e..6d1e8ab1f5cb615147502a39a97765c6b26be646 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -7,8 +7,8 @@ import {
   GlSprintf,
   GlTooltip,
   GlTooltipDirective,
-  GlSafeHtmlDirective,
 } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__, n__ } from '~/locale';
 import CiIcon from '~/vue_shared/components/ci_icon.vue';
 import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
@@ -33,7 +33,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     pipeline: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
index 870972156c555288e1681ec546bda7762cb9f10e..92f8351b0a0c06503eb46798764cf95299f22cf4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml, GlLink } from '@gitlab/ui';
+import { GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__, n__ } from '~/locale';
 
 export default {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
index 4902c9b45e85154076a9413c341e70fb24064bf6..850a4e2fd56dcb8f7e4d3ed1b5ea9440cebea46f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlButton, GlSprintf, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButton, GlSprintf, GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
 import api from '~/api';
 import { helpPagePath } from '~/helpers/help_page_helper';
@@ -12,7 +13,7 @@ export default {
     GlLink,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     mr: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
index 4d66c75719bac635f6de45371585efacf5bc12a8..067c29efc3d31fb9447e804a9297b04093ef9e62 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlBadge, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlBadge, GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import Actions from '../action_buttons.vue';
 import { generateText } from '../extensions/utils';
 import ContentRow from './widget_content_row.vue';
@@ -13,7 +14,7 @@ export default {
     ContentRow,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     data: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
index cea7fb8260a67ef12bea90cda42960560cd76081..81f0823e33366f620635f8e45c745041cd2affab 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
@@ -1,13 +1,8 @@
 <script>
-import {
-  GlButton,
-  GlLink,
-  GlTooltipDirective,
-  GlLoadingIcon,
-  GlSafeHtmlDirective,
-} from '@gitlab/ui';
+import { GlButton, GlLink, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
 import { normalizeHeaders } from '~/lib/utils/common_utils';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { sprintf, __ } from '~/locale';
 import Poll from '~/lib/utils/poll';
 import HelpPopover from '~/vue_shared/components/help_popover.vue';
@@ -35,7 +30,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     /**
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
index 1fd1e325863fd281477f491b3afb7099a36ba2ff..84633b12e32294557e01bbcc6e93dbb4699c110f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
@@ -1,7 +1,8 @@
 <script>
-import { GlSafeHtmlDirective, GlLink } from '@gitlab/ui';
+import { GlLink } from '@gitlab/ui';
 import { __ } from '~/locale';
 import HelpPopover from '~/vue_shared/components/help_popover.vue';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import ActionButtons from '../action_buttons.vue';
 import { EXTENSION_ICONS } from '../../constants';
 import { generateText } from '../extensions/utils';
@@ -15,7 +16,7 @@ export default {
     ActionButtons,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     level: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index b96bdcb38332e2bbe7ed0a2906d448fd218b50d5..4bdb1eb40b6d9cb54140fa0bb711262fe0fc2b4f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,10 +1,10 @@
 <script>
-import { GlSafeHtmlDirective } from '@gitlab/ui';
 import { isEmpty } from 'lodash';
 import {
   registerExtension,
   registeredExtensions,
 } from '~/vue_merge_request_widget/components/extensions';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import MrWidgetApprovals from 'ee_else_ce/vue_merge_request_widget/components/approvals/approvals.vue';
 import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service';
 import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
@@ -57,7 +57,7 @@ export default {
   // eslint-disable-next-line @gitlab/require-i18n-strings
   name: 'MRWidget',
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   components: {
     Loading,
diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue
index 96c2ffa929cfbd78395edda3d7b455c23abfbe4e..5fed66f88fa1e5ba4b1e57b6d61659f3333a57a6 100644
--- a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue
+++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue
@@ -9,9 +9,9 @@ import {
   GlTabs,
   GlTab,
   GlButton,
-  GlSafeHtmlDirective,
 } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
 import { fetchPolicies } from '~/lib/graphql';
 import { toggleContainerClasses } from '~/lib/utils/dom_utils';
@@ -41,7 +41,7 @@ export default {
     reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   severityLabels: SEVERITY_LEVELS,
   tabsConfig: [
diff --git a/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue b/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue
index 6b774b2a734060cc549da12f95922f54e3148f74..3c73f42b6b1ab765edc95aca97435c8a2f0db0a2 100644
--- a/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlIcon } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import NoteHeader from '~/notes/components/note_header.vue';
 
 export default {
@@ -8,7 +9,7 @@ export default {
     GlIcon,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     note: {
diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue
index f5d8811e83c5c96431ca90f3b9c9bc19834804b9..da4b21a2790cb70dbeeb16065efc4cbb49d183f2 100644
--- a/app/assets/javascripts/vue_shared/components/awards_list.vue
+++ b/app/assets/javascripts/vue_shared/components/awards_list.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlIcon, GlButton, GlTooltipDirective, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
 import { groupBy } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import EmojiPicker from '~/emoji/components/picker.vue';
 import { __, sprintf } from '~/locale';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -17,7 +18,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagsMixin()],
   props: {
diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue
index ed0eb9cc0b86b32ddd3aa0513c23f84e166de8cf..49181bb847da1082f8e2a5faefa6da293aba261f 100644
--- a/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { handleBlobRichViewer } from '~/blob/viewer';
 import MarkdownFieldView from '~/vue_shared/components/markdown/field_view.vue';
 import ViewerMixin from './mixins';
diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
index 0117c06c3d543b04a5f3a138553249d3dbb506ef..c7a76af7f740f27ebbd76c374ea6c1335a5b0f13 100644
--- a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlIcon } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { HIGHLIGHT_CLASS_NAME } from './constants';
 import ViewerMixin from './mixins';
 
@@ -9,7 +10,7 @@ export default {
     GlIcon,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [ViewerMixin],
   inject: ['blobHash'],
diff --git a/app/assets/javascripts/vue_shared/components/code_block_highlighted.vue b/app/assets/javascripts/vue_shared/components/code_block_highlighted.vue
index 65b08b608e85dd76ed3c2d05225e0c32d452ccca..352d03befc391f0e616f1e8253264f5fcbcda5ce 100644
--- a/app/assets/javascripts/vue_shared/components/code_block_highlighted.vue
+++ b/app/assets/javascripts/vue_shared/components/code_block_highlighted.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 import languageLoader from '~/content_editor/services/highlight_js_language_loader';
 import CodeBlock from './code_block.vue';
@@ -7,7 +7,7 @@ import CodeBlock from './code_block.vue';
 export default {
   name: 'CodeBlockHighlighted',
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   components: {
     CodeBlock,
diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
index 7a982bc035aa3e738ffcf34a3e68f27647531b4f..d0a634d8e54a1f20da5691c2c2e2a2847177fe53 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
@@ -1,12 +1,6 @@
 <script>
-import {
-  GlAlert,
-  GlModal,
-  GlFormGroup,
-  GlFormInput,
-  GlSafeHtmlDirective as SafeHtml,
-  GlSprintf,
-} from '@gitlab/ui';
+import { GlAlert, GlModal, GlFormGroup, GlFormInput, GlSprintf } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import {
   CONFIRM_DANGER_MODAL_BUTTON,
   CONFIRM_DANGER_MODAL_TITLE,
diff --git a/app/assets/javascripts/vue_shared/components/confirm_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_modal.vue
index 72504e5bc503968a95083c3e34653b2b5c38a80b..664c35787858e0f3e0930ed30bba46e44a434292 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/confirm_modal.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlModal, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlModal } from '@gitlab/ui';
 import { uniqueId } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import csrf from '~/lib/utils/csrf';
 import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from './confirm_modal_eventhub';
 import DomElementListener from './dom_element_listener.vue';
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
index 3ecfac10f9cd6009ae1563cbd5b698fdd65a51ab..a0379cf01ff22248963ecb482011e8167ef17211 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -1,8 +1,9 @@
 <script>
-import { GlSkeletonLoader, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlSkeletonLoader } from '@gitlab/ui';
 import $ from 'jquery';
 import '~/behaviors/markdown/render_gfm';
 import { forEach, escape } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import axios from '~/lib/utils/axios_utils';
 import { __ } from '~/locale';
 
diff --git a/app/assets/javascripts/vue_shared/components/dismissible_alert.vue b/app/assets/javascripts/vue_shared/components/dismissible_alert.vue
index 0621ec14c6c108f4b5cbb51ec6b14aed1ef99b09..8395bc897900ed4931ffe745e10d47b6ea511ad6 100644
--- a/app/assets/javascripts/vue_shared/components/dismissible_alert.vue
+++ b/app/assets/javascripts/vue_shared/components/dismissible_alert.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlAlert, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlAlert } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   name: 'DismissibleAlert',
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 96f7427dda1fe631578f8cf0e6e1a98636bf32c2..3c4ae08d2f715c20a39cfce6d6b28d584aa79bbf 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -1,12 +1,6 @@
 <script>
-import {
-  GlTooltipDirective,
-  GlButton,
-  GlSafeHtmlDirective,
-  GlAvatarLink,
-  GlAvatarLabeled,
-  GlTooltip,
-} from '@gitlab/ui';
+import { GlTooltipDirective, GlButton, GlAvatarLink, GlAvatarLabeled, GlTooltip } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils';
 import { glEmojiTag } from '~/emoji';
 import { __, sprintf } from '~/locale';
@@ -31,7 +25,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   EMOJI_REF: 'EMOJI_REF',
   props: {
diff --git a/app/assets/javascripts/vue_shared/components/help_popover.vue b/app/assets/javascripts/vue_shared/components/help_popover.vue
index f349aa78bacea97a804b2325b542d36ce1fce2b3..92d468cf970b505c9aa3a7dcd4e1fab8820c0fbe 100644
--- a/app/assets/javascripts/vue_shared/components/help_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/help_popover.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlButton, GlPopover, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButton, GlPopover } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 /**
  * Render a button with a question mark icon
@@ -12,7 +13,7 @@ export default {
     GlPopover,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     options: {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 657e4498b5365be8e6035d8a045df7ca4e1e5783..09f8ce6de6f9eab198b92d69e7356dfa9223d728 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -1,10 +1,11 @@
 <script>
-import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlIcon } from '@gitlab/ui';
 import $ from 'jquery';
 import '~/behaviors/markdown/render_gfm';
 import { debounce, unescape } from 'lodash';
 import { createAlert } from '~/flash';
 import GLForm from '~/gl_form';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import axios from '~/lib/utils/axios_utils';
 import { stripHtml } from '~/lib/utils/text_utility';
 import { __, sprintf } from '~/locale';
@@ -25,7 +26,7 @@ export default {
     Suggestions,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagsMixin()],
   props: {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
index a04f8616acbc53d789d39568d0639d7ba79a565c..0b598d3acafbaefc16ac0c80de6394b49765420e 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   name: 'SuggestionDiffRow',
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index 30d72332c900aa53a73311afdd25eeb7b17420be..c307601e670bc3f390b178efcaa8a2d1547a4c3a 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -1,6 +1,6 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import Vue from 'vue';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { createAlert } from '~/flash';
 import { __ } from '~/locale';
 import SuggestionDiff from './suggestion_diff.vue';
diff --git a/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue b/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue
index a4b509f86563b8b9d50d81b1d5877a42f2befc41..7d20e0ce10be70bbbbcc5d7aa6db2329b43cd632 100644
--- a/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml, GlDrawer, GlAlert, GlSkeletonLoader } from '@gitlab/ui';
+import { GlDrawer, GlAlert, GlSkeletonLoader } from '@gitlab/ui';
 import $ from 'jquery';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import '~/behaviors/markdown/render_gfm';
 import { s__ } from '~/locale';
 import { contentTop } from '~/lib/utils/common_utils';
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
index cf34a60c363fdbc64b79a553918abfceb8fe9c5e..748d6082abd0107ccb9a62a05cdbc6aece7035a6 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -16,8 +16,9 @@
  *   :note="{body: 'This is a note'}"
  *   />
  */
-import { GlSafeHtmlDirective as SafeHtml, GlAvatarLink, GlAvatar } from '@gitlab/ui';
+import { GlAvatarLink, GlAvatar } from '@gitlab/ui';
 import { mapGetters } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { renderMarkdown } from '~/notes/utils';
 import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
 
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index c5c8442344c307fdafa03a65e4fc10348d929d86..ed4867681348a95e68ee45082e0c452142952ddc 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -16,15 +16,10 @@
  *    }"
  *   />
  */
-import {
-  GlButton,
-  GlSkeletonLoader,
-  GlTooltipDirective,
-  GlIcon,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlButton, GlSkeletonLoader, GlTooltipDirective, GlIcon } from '@gitlab/ui';
 import $ from 'jquery';
 import { mapGetters, mapActions, mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import descriptionVersionHistoryMixin from 'ee_else_ce/notes/mixins/description_version_history';
 import '~/behaviors/markdown/render_gfm';
 import axios from '~/lib/utils/axios_utils';
diff --git a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue
index 63f5ddd0069af301553229c5b3f31e8529298ed7..ea043f866192cf4549c96f0b9bbd7718a24cb420 100644
--- a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue
@@ -1,12 +1,6 @@
 <script>
-import {
-  GlAlert,
-  GlBadge,
-  GlPagination,
-  GlTab,
-  GlTabs,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlAlert, GlBadge, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import Api from '~/api';
 import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
 import Tracking from '~/tracking';
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
index 66643ff40266990d6922a48f137a5aa36bdb3315..16bc8070dc137858d7caf05893b24edccf15121d 100644
--- a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
@@ -1,9 +1,10 @@
 <script>
-import { GlButton, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlButton, GlIcon } from '@gitlab/ui';
 import { isString } from 'lodash';
 import highlight from '~/lib/utils/highlight';
 import { truncateNamespace } from '~/lib/utils/text_utility';
 import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   name: 'ProjectListItem',
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
index a2d8b7cbd157e11d03a76f62ab2a5153b57e2279..428fa9f8279e04486383d757f0a790ed8ca94041 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlIntersectionObserver, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlIntersectionObserver } from '@gitlab/ui';
 import { scrollToElement } from '~/lib/utils/common_utils';
 import ChunkLine from './chunk_line.vue';
 
@@ -20,9 +20,6 @@ export default {
     ChunkLine,
     GlIntersectionObserver,
   },
-  directives: {
-    SafeHtml: GlSafeHtmlDirective,
-  },
   props: {
     isFirstChunk: {
       type: Boolean,
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
index 0bf19f83d865655d36b0a7b8ccccf21cdca447ee..ce6741f33b112e9c7b427e1afd91c641a96383f6 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
@@ -1,11 +1,11 @@
 <script>
-import { GlSafeHtmlDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import { getPageParamValue, getPageSearchString } from '~/blob/utils';
 
 export default {
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagMixin()],
   props: {
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
index f621a23734a5607115d0ef490367a3d6209892e5..0cfee93ce5d8621a3ee1f8fd8bbae5a6a8e31087 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon } from '@gitlab/ui';
 import LineHighlighter from '~/blob/line_highlighter';
 import eventHub from '~/notes/event_hub';
 import languageLoader from '~/content_editor/services/highlight_js_language_loader';
@@ -28,9 +28,6 @@ export default {
     GlLoadingIcon,
     Chunk,
   },
-  directives: {
-    SafeHtml: GlSafeHtmlDirective,
-  },
   mixins: [Tracking.mixin()],
   props: {
     blob: {
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index 80c1fcbacfae23fae9588279be1ba92ab467eed3..d06bc7b8f988cedf996fdf85fdf366fda9806e17 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -4,11 +4,11 @@ import {
   GlLink,
   GlSkeletonLoader,
   GlIcon,
-  GlSafeHtmlDirective,
   GlSprintf,
   GlButton,
   GlAvatarLabeled,
 } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { glEmojiTag } from '~/emoji';
 import { createAlert } from '~/flash';
 import { followUser, unfollowUser } from '~/rest_api';
@@ -44,7 +44,7 @@ export default {
     GlAvatarLabeled,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [Tracking.mixin()],
   props: {
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_description.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_description.vue
index d4e9120ff179f6d4ef59feb9e7a2543c0fa4fcac..9ced3826d191e70485b2eabef23615f25820076f 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_description.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_description.vue
@@ -1,6 +1,6 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import $ from 'jquery';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import '~/behaviors/markdown/render_gfm';
 
 export default {
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
index 35124bd15d2343d9770e600361d7b704b28a11d8..fd94245b7c95777ee52b2e0569e6eae350bae022 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
@@ -1,12 +1,6 @@
 <script>
-import {
-  GlIcon,
-  GlBadge,
-  GlButton,
-  GlIntersectionObserver,
-  GlTooltipDirective,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlIcon, GlBadge, GlButton, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __ } from '~/locale';
 import { IssuableStates } from '~/vue_shared/issuable/list/constants';
 
diff --git a/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue b/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue
index 5cd2018bb8c6292a362b215be5109909a55eca4b..b6a459f21e0b9c3b34797034970cc178d75d70f2 100644
--- a/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue
+++ b/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import Tracking from '~/tracking';
 
 export default {
diff --git a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
index 624ae7027d5834457b0ca2b360bac5adda5945d1..318adec23197d610dd03da24911ca8e62a35b96b 100644
--- a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
+++ b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlBreadcrumb, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlBreadcrumb, GlIcon } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import NewTopLevelGroupAlert from '~/groups/components/new_top_level_group_alert.vue';
 
 import LegacyContainer from './components/legacy_container.vue';
diff --git a/app/assets/javascripts/whats_new/components/feature.vue b/app/assets/javascripts/whats_new/components/feature.vue
index c954a86e593620ef4948b1f8046c5f4aed4d2498..044a6db6d938c61becd526f0dd1afda979d07bef 100644
--- a/app/assets/javascripts/whats_new/components/feature.vue
+++ b/app/assets/javascripts/whats_new/components/feature.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlBadge, GlIcon, GlLink, GlSafeHtmlDirective, GlButton } from '@gitlab/ui';
+import { GlBadge, GlIcon, GlLink, GlButton } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { dateInWords, isValidDate } from '~/lib/utils/datetime_utility';
 
 export default {
@@ -10,7 +11,7 @@ export default {
     GlButton,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     feature: {
diff --git a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue
index e6f8a301c5e1dee960d59042d3d546ffaa525846..4225509dd2c9adf367ef746e5e117f43614e741f 100644
--- a/app/assets/javascripts/work_items/components/work_item_description_rendered.vue
+++ b/app/assets/javascripts/work_items/components/work_item_description_rendered.vue
@@ -1,13 +1,14 @@
 <script>
-import { GlButton, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
 import $ from 'jquery';
 import '~/behaviors/markdown/render_gfm';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 const isCheckbox = (target) => target?.classList.contains('task-list-item-checkbox');
 
 export default {
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   components: {
     GlButton,
diff --git a/doc/development/fe_guide/security.md b/doc/development/fe_guide/security.md
index 4b7ce6d11e3c3cfaa8dfcd44689efb37795e01d7..d578449e5781087d2da20289cda50c48e7bd47c1 100644
--- a/doc/development/fe_guide/security.md
+++ b/doc/development/fe_guide/security.md
@@ -88,7 +88,7 @@ readability.
 
 If you need to output raw HTML, you should sanitize it.
 
-If you are using Vue, you can use the[`v-safe-html` directive](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/directives-safe-html-directive--default) from GitLab UI.
+If you are using Vue, you can use the[`v-safe-html` directive](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/vue_shared/directives/safe_html.js).
 
 For other use cases, wrap a preconfigured version of [`dompurify`](https://www.npmjs.com/package/dompurify)
 that also allows the icons to be rendered:
diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md
index c102e99720fd85dacc6804d7147aeb9513605759..e99926663dd67a3f3010079517e67e6452843520 100644
--- a/doc/development/secure_coding_guidelines.md
+++ b/doc/development/secure_coding_guidelines.md
@@ -410,7 +410,7 @@ References:
 #### XSS mitigation and prevention in JavaScript and Vue
 
 - When updating the content of an HTML element using JavaScript, mark user-controlled values as `textContent` or `nodeValue` instead of `innerHTML`.
-- Avoid using `v-html` with user-controlled data, use [`v-safe-html`](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/directives-safe-html-directive--default) instead.
+- Avoid using `v-html` with user-controlled data, use [`v-safe-html`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/vue_shared/directives/safe_html.js) instead.
 - Render unsafe or unsanitized content using [`dompurify`](fe_guide/security.md#sanitize-html-output).
 - Consider using [`gl-sprintf`](../../ee/development/i18n/externalization.md#interpolation) to interpolate translated strings securely.
 - Avoid `__()` with translations that contain user-controlled values.
diff --git a/ee/app/assets/javascripts/admin/subscriptions/show/components/subscription_breakdown.vue b/ee/app/assets/javascripts/admin/subscriptions/show/components/subscription_breakdown.vue
index 7cfcabc459d0d9a60e834a77167770712df5a5ef..9b8e51f9a5bc0c3b7c25092a9b21f3044fd99730 100644
--- a/ee/app/assets/javascripts/admin/subscriptions/show/components/subscription_breakdown.vue
+++ b/ee/app/assets/javascripts/admin/subscriptions/show/components/subscription_breakdown.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlButton, GlModalDirective, GlModal, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlButton, GlModalDirective, GlModal } from '@gitlab/ui';
 import { uniqueId } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { createAlert } from '~/flash';
 import { __, sprintf } from '~/locale';
 import { refreshCurrentPage } from '~/lib/utils/url_utility';
diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue
index eb0c37fa6c6ce97f29c6df89f963b808a287015f..6817dfc841ead1d923759d70afef251b13205cfb 100644
--- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue
+++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue
@@ -1,6 +1,7 @@
 <script>
 import { mapActions, mapGetters, mapState } from 'vuex';
-import { GlAlert, GlIcon, GlTooltip, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlAlert, GlIcon, GlTooltip } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { __ } from '~/locale';
 import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
 import { uniqById, generateFilterTextDescription } from '../utils';
@@ -20,7 +21,7 @@ export default {
     TasksByTypeFilters,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   computed: {
     ...mapState('typeOfWork', [
diff --git a/ee/app/assets/javascripts/analytics/repository_analytics/components/test_coverage_summary.vue b/ee/app/assets/javascripts/analytics/repository_analytics/components/test_coverage_summary.vue
index 4799f7b60b5c978a7dcfb996fc5967ffc7849495..d139607dfca1b3cc11f026a0c4b169844504ce05 100644
--- a/ee/app/assets/javascripts/analytics/repository_analytics/components/test_coverage_summary.vue
+++ b/ee/app/assets/javascripts/analytics/repository_analytics/components/test_coverage_summary.vue
@@ -1,13 +1,8 @@
 <script>
 import chartEmptyStateIllustration from '@gitlab/svgs/dist/illustrations/chart-empty-state.svg';
-import {
-  GlCard,
-  GlSprintf,
-  GlSkeletonLoader,
-  GlSafeHtmlDirective as SafeHtml,
-  GlPopover,
-} from '@gitlab/ui';
+import { GlCard, GlSprintf, GlSkeletonLoader, GlPopover } from '@gitlab/ui';
 import { GlSingleStat, GlAreaChart } from '@gitlab/ui/dist/charts';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import {
   formatDate,
   getTimeago,
diff --git a/ee/app/assets/javascripts/analytics/shared/components/groups_dropdown_filter.vue b/ee/app/assets/javascripts/analytics/shared/components/groups_dropdown_filter.vue
index 9112bdcb44fc74af29318c8ef242ba4fa26653cf..60789994463453c3941cf0502a5e9a30509fbbdc 100644
--- a/ee/app/assets/javascripts/analytics/shared/components/groups_dropdown_filter.vue
+++ b/ee/app/assets/javascripts/analytics/shared/components/groups_dropdown_filter.vue
@@ -7,9 +7,9 @@ import {
   GlDropdownSectionHeader,
   GlDropdownItem,
   GlSearchBoxByType,
-  GlSafeHtmlDirective as SafeHtml,
 } from '@gitlab/ui';
 import { debounce } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { filterBySearchTerm } from '~/analytics/shared/utils';
 import Api from '~/api';
 import { s__, __ } from '~/locale';
diff --git a/ee/app/assets/javascripts/approvals/components/mr_edit/app.vue b/ee/app/assets/javascripts/approvals/components/mr_edit/app.vue
index 3b9a4e4806a071d901f84d2b4802822ef56c53dd..fc1d500ebb60c5081f766e1781c45cc3754f3ef0 100644
--- a/ee/app/assets/javascripts/approvals/components/mr_edit/app.vue
+++ b/ee/app/assets/javascripts/approvals/components/mr_edit/app.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlSafeHtmlDirective, GlAccordion, GlAccordionItem, GlSprintf, GlLink } from '@gitlab/ui';
+import { GlAccordion, GlAccordionItem, GlSprintf, GlLink } from '@gitlab/ui';
 import { mapState } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { helpPagePath } from '~/helpers/help_page_helper';
 import { __, n__, sprintf, s__ } from '~/locale';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -19,7 +20,7 @@ export default {
     MrRulesHiddenInputs,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagsMixin()],
   computed: {
diff --git a/ee/app/assets/javascripts/audit_events/components/audit_events_stream.vue b/ee/app/assets/javascripts/audit_events/components/audit_events_stream.vue
index 0b6b5c30dd96dede468de2dbec80dd981cc8794f..948eb1cb0e4ce44dbae947560208176e8bf0deb1 100644
--- a/ee/app/assets/javascripts/audit_events/components/audit_events_stream.vue
+++ b/ee/app/assets/javascripts/audit_events/components/audit_events_stream.vue
@@ -1,11 +1,5 @@
 <script>
-import {
-  GlAlert,
-  GlButton,
-  GlLoadingIcon,
-  GlIcon,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlAlert, GlButton, GlLoadingIcon, GlIcon } from '@gitlab/ui';
 import { createAlert } from '~/flash';
 import {
   ADD_STREAM,
@@ -31,9 +25,6 @@ export default {
     StreamEmptyState,
     StreamItem,
   },
-  directives: {
-    SafeHtml,
-  },
   inject: ['groupPath'],
   data() {
     return {
diff --git a/ee/app/assets/javascripts/audit_events/components/stream/stream_empty_state.vue b/ee/app/assets/javascripts/audit_events/components/stream/stream_empty_state.vue
index 9232f33c8d9b0c515fa8ddaaffbe1034df480474..d9168e1d65c06b935d12394753132c49b6e62901 100644
--- a/ee/app/assets/javascripts/audit_events/components/stream/stream_empty_state.vue
+++ b/ee/app/assets/javascripts/audit_events/components/stream/stream_empty_state.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlButton, GlSafeHtmlDirective as SafeHtml, GlEmptyState } from '@gitlab/ui';
+import { GlButton, GlEmptyState } from '@gitlab/ui';
 import { ADD_STREAM, AUDIT_STREAMS_EMPTY_STATE_I18N } from '../../constants';
 
 export default {
@@ -7,9 +7,6 @@ export default {
     GlButton,
     GlEmptyState,
   },
-  directives: {
-    SafeHtml,
-  },
   inject: ['emptyStateSvgPath'],
   i18n: {
     ...AUDIT_STREAMS_EMPTY_STATE_I18N,
diff --git a/ee/app/assets/javascripts/audit_events/components/table_cells/html_table_cell.vue b/ee/app/assets/javascripts/audit_events/components/table_cells/html_table_cell.vue
index c1ca6c28092a33c1262800a64c4ab660e327dbe5..b52b2628230e52113ed60b063419fd7f1c03c7d6 100644
--- a/ee/app/assets/javascripts/audit_events/components/table_cells/html_table_cell.vue
+++ b/ee/app/assets/javascripts/audit_events/components/table_cells/html_table_cell.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   directives: {
diff --git a/ee/app/assets/javascripts/ci/runner/components/runner_maintenance_note_detail.vue b/ee/app/assets/javascripts/ci/runner/components/runner_maintenance_note_detail.vue
index def6ba66f3bf0e2895f63290ebdbf329f4087ffc..eea340c5eab66dd88767e92f16982fb9a74bf4cc 100644
--- a/ee/app/assets/javascripts/ci/runner/components/runner_maintenance_note_detail.vue
+++ b/ee/app/assets/javascripts/ci/runner/components/runner_maintenance_note_detail.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlSafeHtmlDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import RunnerDetail from '~/ci/runner/components/runner_detail.vue';
 
@@ -8,7 +8,7 @@ export default {
     RunnerDetail,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagMixin()],
   props: {
diff --git a/ee/app/assets/javascripts/external_issues_list/components/external_issues_list_root.vue b/ee/app/assets/javascripts/external_issues_list/components/external_issues_list_root.vue
index 7b310e94e3e3431b85dfdd8e3c8221c367d2aba9..a9e00f4f9f7b89a2e21fc5b3ea34fd210f86353a 100644
--- a/ee/app/assets/javascripts/external_issues_list/components/external_issues_list_root.vue
+++ b/ee/app/assets/javascripts/external_issues_list/components/external_issues_list_root.vue
@@ -1,13 +1,7 @@
 <script>
-import {
-  GlButton,
-  GlIcon,
-  GlLink,
-  GlSprintf,
-  GlSafeHtmlDirective as SafeHtml,
-  GlAlert,
-} from '@gitlab/ui';
+import { GlButton, GlIcon, GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
 import {
diff --git a/ee/app/assets/javascripts/external_issues_show/components/note.vue b/ee/app/assets/javascripts/external_issues_show/components/note.vue
index 43a53febb5eb5f2b76145a4de8baf5d1c9f3fa76..15ce88a62ba9f4324f91b7c6f9b402c75834e841 100644
--- a/ee/app/assets/javascripts/external_issues_show/components/note.vue
+++ b/ee/app/assets/javascripts/external_issues_show/components/note.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlAvatarLink, GlAvatar, GlLink, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlAvatarLink, GlAvatar, GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
 import TimelineIcon from '~/vue_shared/components/notes/timeline_icon.vue';
 import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
diff --git a/ee/app/assets/javascripts/iterations/components/iteration_report.vue b/ee/app/assets/javascripts/iterations/components/iteration_report.vue
index bbf56cbc4e59fddb7f9545e65c131a3537a3c6bc..ade0efd70f895e5d3d102976010a397e1cf140e8 100644
--- a/ee/app/assets/javascripts/iterations/components/iteration_report.vue
+++ b/ee/app/assets/javascripts/iterations/components/iteration_report.vue
@@ -7,8 +7,8 @@ import {
   GlIcon,
   GlLoadingIcon,
   GlModal,
-  GlSafeHtmlDirective,
 } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import BurnCharts from 'ee/burndown_chart/components/burn_charts.vue';
 import { TYPE_ITERATION } from '~/graphql_shared/constants';
 import { convertToGraphQLId } from '~/graphql_shared/utils';
@@ -37,7 +37,7 @@ export default {
     IterationTitle,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   apollo: {
     iteration: {
diff --git a/ee/app/assets/javascripts/license_compliance/components/pipeline_info.vue b/ee/app/assets/javascripts/license_compliance/components/pipeline_info.vue
index 503d6b967402c12de70726f30427534e43c1fd75..5d1495089b601cef204e4ea9022103da0aab363b 100644
--- a/ee/app/assets/javascripts/license_compliance/components/pipeline_info.vue
+++ b/ee/app/assets/javascripts/license_compliance/components/pipeline_info.vue
@@ -1,6 +1,6 @@
 <script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
 import { escape } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__, sprintf } from '~/locale';
 import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
 
diff --git a/ee/app/assets/javascripts/namespaces/verification/components/credit_card_verification.vue b/ee/app/assets/javascripts/namespaces/verification/components/credit_card_verification.vue
index 19882a4119194bcf9919f8d2a425a390bfb5269f..81240c20f58e8e31c314859238eab48c69032f83 100644
--- a/ee/app/assets/javascripts/namespaces/verification/components/credit_card_verification.vue
+++ b/ee/app/assets/javascripts/namespaces/verification/components/credit_card_verification.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlBreadcrumb, GlButton, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlBreadcrumb, GlButton } from '@gitlab/ui';
 import newGroupIllustration from '@gitlab/svgs/dist/illustrations/group-new.svg';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import CreateGroupDescriptionDetails from '~/pages/groups/new/components/create_group_description_details.vue';
 import Zuora from 'ee/billings/components/zuora.vue';
 import {
diff --git a/ee/app/assets/javascripts/on_demand_scans_form/components/on_demand_scans_form.vue b/ee/app/assets/javascripts/on_demand_scans_form/components/on_demand_scans_form.vue
index 4691aca45d082b85d88d79737966adc6bd786995..356522a5f9c0335515e207216449caa1a5e42511 100644
--- a/ee/app/assets/javascripts/on_demand_scans_form/components/on_demand_scans_form.vue
+++ b/ee/app/assets/javascripts/on_demand_scans_form/components/on_demand_scans_form.vue
@@ -9,10 +9,10 @@ import {
   GlFormTextarea,
   GlLink,
   GlSprintf,
-  GlSafeHtmlDirective,
   GlPopover,
 } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { SCAN_TYPE } from 'ee/security_configuration/dast_profiles/dast_scanner_profiles/constants';
 import { DAST_SITE_VALIDATION_STATUS } from 'ee/security_configuration/dast_site_validation/constants';
 import { initFormField } from 'ee/security_configuration/utils';
@@ -118,7 +118,7 @@ export default {
     GlPopover,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
     validation: validation(),
   },
   mixins: [glFeatureFlagMixin()],
diff --git a/ee/app/assets/javascripts/oncall_schedules/components/add_edit_schedule_form.vue b/ee/app/assets/javascripts/oncall_schedules/components/add_edit_schedule_form.vue
index 096cc02d1b3ad0083dcf1741636c1993bf977afc..b25e94df3e44291bacb4638875eef7bbcf4fafdc 100644
--- a/ee/app/assets/javascripts/oncall_schedules/components/add_edit_schedule_form.vue
+++ b/ee/app/assets/javascripts/oncall_schedules/components/add_edit_schedule_form.vue
@@ -7,9 +7,9 @@ import {
   GlDropdown,
   GlDropdownItem,
   GlSearchBoxByType,
-  GlSafeHtmlDirective as SafeHtml,
 } from '@gitlab/ui';
 import { isEqual, isEmpty } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__, __ } from '~/locale';
 import { getFormattedTimezone } from '../utils/common_utils';
 
diff --git a/ee/app/assets/javascripts/password/components/password_requirement_list.vue b/ee/app/assets/javascripts/password/components/password_requirement_list.vue
index 0555d567fac0df826e14944504d81b39e6307384..01fecdd2b290fb89f224b0c923246504ef0cc432 100644
--- a/ee/app/assets/javascripts/password/components/password_requirement_list.vue
+++ b/ee/app/assets/javascripts/password/components/password_requirement_list.vue
@@ -1,14 +1,11 @@
 <script>
-import { GlSafeHtmlDirective, GlIcon } from '@gitlab/ui';
+import { GlIcon } from '@gitlab/ui';
 import { PASSWORD_RULE_MAP } from '../constants';
 
 export default {
   components: {
     GlIcon,
   },
-  directives: {
-    SafeHtml: GlSafeHtmlDirective,
-  },
   props: {
     submitted: {
       type: Boolean,
diff --git a/ee/app/assets/javascripts/related_items_tree/components/tree_item_remove_modal.vue b/ee/app/assets/javascripts/related_items_tree/components/tree_item_remove_modal.vue
index 7b9c077699cd66a3aef2b38c732e4e96ba1a8fcd..89faac06491452891f6d5a06fce63bb4731503eb 100644
--- a/ee/app/assets/javascripts/related_items_tree/components/tree_item_remove_modal.vue
+++ b/ee/app/assets/javascripts/related_items_tree/components/tree_item_remove_modal.vue
@@ -1,7 +1,8 @@
 <script>
-import { GlModal, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlModal } from '@gitlab/ui';
 import { escape } from 'lodash';
 import { mapState, mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 import { sprintf, __ } from '~/locale';
 
@@ -13,7 +14,7 @@ export default {
     GlModal,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   computed: {
     ...mapState(['childrenFlags', 'parentItem', 'removeItemModalProps']),
diff --git a/ee/app/assets/javascripts/requirements/components/requirement_form.vue b/ee/app/assets/javascripts/requirements/components/requirement_form.vue
index 34e07f5980191dc58036128d91ba8bacd1126b53..7a8a0a08e5a5e99d2d4de064d2f771fa00cd2c05 100644
--- a/ee/app/assets/javascripts/requirements/components/requirement_form.vue
+++ b/ee/app/assets/javascripts/requirements/components/requirement_form.vue
@@ -1,12 +1,6 @@
 <script>
 import '~/behaviors/markdown/render_gfm';
-import {
-  GlDrawer,
-  GlButton,
-  GlFormCheckbox,
-  GlTooltipDirective,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlDrawer, GlButton, GlFormCheckbox, GlTooltipDirective } from '@gitlab/ui';
 import $ from 'jquery';
 import { isEmpty } from 'lodash';
 import IssuableBody from '~/vue_shared/issuable/show/components/issuable_body.vue';
@@ -37,7 +31,6 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml,
   },
   mixins: [RequirementMeta],
   inject: ['descriptionPreviewPath', 'descriptionHelpPath'],
diff --git a/ee/app/assets/javascripts/roadmap/components/epics_list_empty.vue b/ee/app/assets/javascripts/roadmap/components/epics_list_empty.vue
index 0b3127d6fcb12b459ebc848c08d071695bad126f..b75bab34b05a536ab5e65221fbf7b684008956c5 100644
--- a/ee/app/assets/javascripts/roadmap/components/epics_list_empty.vue
+++ b/ee/app/assets/javascripts/roadmap/components/epics_list_empty.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlSafeHtmlDirective, GlEmptyState } from '@gitlab/ui';
+import { GlEmptyState } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { dateInWords } from '~/lib/utils/datetime_utility';
 import { s__, sprintf } from '~/locale';
 
@@ -15,7 +16,7 @@ export default {
     GlEmptyState,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [CommonMixin],
   inject: ['newEpicPath', 'listEpicsPath', 'epicsDocsPath', 'canCreateEpic'],
diff --git a/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_profiles_list.vue b/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_profiles_list.vue
index 4bb4501794ea87cb0b092f783ae95c279f0f93d1..7d7dc65e08eee3a3c55b4f2f0cf80abc168057b7 100644
--- a/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_profiles_list.vue
+++ b/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_profiles_list.vue
@@ -5,13 +5,13 @@ import {
   GlModal,
   GlSkeletonLoader,
   GlTable,
-  GlSafeHtmlDirective,
   GlTooltipDirective,
   GlDropdown,
   GlDropdownItem,
   GlIcon,
 } from '@gitlab/ui';
 import { uniqueId } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { visitUrl } from '~/lib/utils/url_utility';
 import { s__ } from '~/locale';
 
@@ -27,7 +27,7 @@ export default {
     GlIcon,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
     GlTooltip: GlTooltipDirective,
   },
   props: {
diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/filters/project_filter.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/filters/project_filter.vue
index 62c9aa75b2e3a7110a2ed73c888cb0005663faad..4615c2c30d315ffdefa800305bc79a77091409f9 100644
--- a/ee/app/assets/javascripts/security_dashboard/components/shared/filters/project_filter.vue
+++ b/ee/app/assets/javascripts/security_dashboard/components/shared/filters/project_filter.vue
@@ -3,10 +3,10 @@ import {
   GlDropdownDivider,
   GlDropdownText,
   GlLoadingIcon,
-  GlSafeHtmlDirective as SafeHtml,
   GlIntersectionObserver,
 } from '@gitlab/ui';
 import { escapeRegExp, has, xorBy } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
 import createFlash from '~/flash';
 import { convertToGraphQLIds, getIdFromGraphQLId } from '~/graphql_shared/utils';
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_preview_human.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_preview_human.vue
index feb6cd467dbcbaa8ff8396f036c7353e38652562..421b365baf5fd91fa24a6eb6fde890058e3f7c98 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_preview_human.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_preview_human.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlAlert, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlAlert } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { PARSING_ERROR_MESSAGE } from './constants';
 
 export default {
@@ -10,7 +11,7 @@ export default {
     GlAlert,
   },
   directives: {
-    safeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     policyDescription: {
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_selection.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_selection.vue
index 8b22b2a6f04e6f7538c81844b81547bfdcaa803a..967fa6d8e7bfaf5b25cb98ec7072b6f4b5f75003 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_selection.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_selection.vue
@@ -1,8 +1,9 @@
 <script>
 import shieldCheckIllustration from '@gitlab/svgs/dist/illustrations/shield-check.svg';
 import magnifyingGlassIllustration from '@gitlab/svgs/dist/illustrations/magnifying-glass.svg';
-import { GlCard, GlButton, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlCard, GlButton } from '@gitlab/ui';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__, __ } from '~/locale';
 import { mergeUrlParams } from '~/lib/utils/url_utility';
 import { POLICY_TYPE_COMPONENT_OPTIONS } from '../constants';
@@ -36,7 +37,7 @@ export default {
     GlButton,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [glFeatureFlagsMixin()],
   inject: ['policiesPath'],
diff --git a/ee/app/assets/javascripts/sidebar/components/ancestors_tree/ancestors_tree.vue b/ee/app/assets/javascripts/sidebar/components/ancestors_tree/ancestors_tree.vue
index 5bb698542731466c60f301c7d6a206564d0b6254..0143a0fa893663295c4c0a3d437584795e9054bd 100644
--- a/ee/app/assets/javascripts/sidebar/components/ancestors_tree/ancestors_tree.vue
+++ b/ee/app/assets/javascripts/sidebar/components/ancestors_tree/ancestors_tree.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlLoadingIcon, GlLink, GlTooltip, GlIcon, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlLoadingIcon, GlLink, GlTooltip, GlIcon } from '@gitlab/ui';
 import { escape } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 import { __ } from '~/locale';
 
@@ -13,7 +14,7 @@ export default {
     GlTooltip,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     ancestors: {
diff --git a/ee/app/assets/javascripts/usage_quotas/seats/components/remove_billable_member_modal.vue b/ee/app/assets/javascripts/usage_quotas/seats/components/remove_billable_member_modal.vue
index bed504fc28793456817fb27556a017cee566d8b7..412d8750be6d26740a0b50ba768b53358b767091 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/components/remove_billable_member_modal.vue
+++ b/ee/app/assets/javascripts/usage_quotas/seats/components/remove_billable_member_modal.vue
@@ -1,11 +1,5 @@
 <script>
-import {
-  GlBadge,
-  GlFormInput,
-  GlModal,
-  GlSprintf,
-  GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
+import { GlBadge, GlFormInput, GlModal, GlSprintf } from '@gitlab/ui';
 import { mapActions, mapState } from 'vuex';
 import {
   REMOVE_BILLABLE_MEMBER_MODAL_ID,
@@ -23,9 +17,6 @@ export default {
     GlSprintf,
     GlBadge,
   },
-  directives: {
-    SafeHtml,
-  },
   data() {
     return {
       enteredMemberUsername: null,
diff --git a/ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 5f50cd7ac7180cd595d24818d758953aba0d467a..723807be0b745b7c44297060da5f3c917d8309a9 100644
--- a/ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlSprintf, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlSprintf, GlLink } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin';
 import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
 import { s__, __, sprintf } from '~/locale';
@@ -32,7 +33,7 @@ export default {
       import('ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue'),
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   extends: CEWidgetOptions,
   mixins: [reportsMixin],
diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue b/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue
index 4aa9d606970a7a405ca2c7a9395b7b129503f5d6..a377dcd7fbab1055be9cc151886b6b47e8986576 100644
--- a/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue
+++ b/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue
@@ -1,10 +1,10 @@
 <script>
-import { GlSafeHtmlDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__ } from '~/locale';
 
 export default {
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     solution: {
diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/components/vulnerability_details.vue b/ee/app/assets/javascripts/vue_shared/security_reports/components/vulnerability_details.vue
index f5b2d0db61ac6ace4c154293874b54d88f3e3a36..ae69d0b60783c17171a7a8485e6f1bfd0d7cb1ad 100644
--- a/ee/app/assets/javascripts/vue_shared/security_reports/components/vulnerability_details.vue
+++ b/ee/app/assets/javascripts/vue_shared/security_reports/components/vulnerability_details.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlFriendlyWrap, GlLink, GlBadge, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlFriendlyWrap, GlLink, GlBadge } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { REPORT_TYPES_ALL } from 'ee/security_dashboard/store/constants';
 import FalsePositiveAlert from 'ee/vulnerabilities/components/false_positive_alert.vue';
 import GenericReportSection from 'ee/vulnerabilities/components/generic_report/report_section.vue';
@@ -34,7 +35,7 @@ export default {
     VulnerabilityTraining,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: { vulnerability: { type: Object, required: true } },
   data() {
diff --git a/ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/markdown.vue b/ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/markdown.vue
index 302482d6160d8a4792d006bfdea3f6343fd78508..cb2431a221432ae342faae5316acd72d144f80b4 100644
--- a/ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/markdown.vue
+++ b/ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/markdown.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlSkeletonLoader, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlSkeletonLoader } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { getMarkdown } from '~/rest_api';
 
 export default {
diff --git a/ee/app/assets/javascripts/vulnerabilities/components/history_comment.vue b/ee/app/assets/javascripts/vulnerabilities/components/history_comment.vue
index 03cf487bb5534ae32ba830cc0a98b5dd289d60ea..0fb36cd531dfd0be080fe8079e15b050af4e2bee 100644
--- a/ee/app/assets/javascripts/vulnerabilities/components/history_comment.vue
+++ b/ee/app/assets/javascripts/vulnerabilities/components/history_comment.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlButton, GlSafeHtmlDirective as SafeHtml, GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import createNoteMutation from 'ee/security_dashboard/graphql/mutations/note_create.mutation.graphql';
 import destroyNoteMutation from 'ee/security_dashboard/graphql/mutations/note_destroy.mutation.graphql';
 import updateNoteMutation from 'ee/security_dashboard/graphql/mutations/note_update.mutation.graphql';
diff --git a/ee/app/assets/javascripts/vulnerabilities/components/issue_link.vue b/ee/app/assets/javascripts/vulnerabilities/components/issue_link.vue
index 7b901d58649a60027deb01e611cdf495aaf4bf9c..050592143186fa250a5d16660e5b4ab6515ef89c 100644
--- a/ee/app/assets/javascripts/vulnerabilities/components/issue_link.vue
+++ b/ee/app/assets/javascripts/vulnerabilities/components/issue_link.vue
@@ -1,6 +1,7 @@
 <script>
 import jiraLogo from '@gitlab/svgs/dist/illustrations/logos/jira.svg';
-import { GlIcon, GlLink, GlTooltipDirective, GlSafeHtmlDirective, GlSprintf } from '@gitlab/ui';
+import { GlIcon, GlLink, GlTooltipDirective, GlSprintf } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 
 export default {
   components: {
@@ -10,7 +11,7 @@ export default {
   },
   directives: {
     GlTooltip: GlTooltipDirective,
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   props: {
     issue: {
diff --git a/ee/app/assets/javascripts/vulnerabilities/components/related_jira_issues.vue b/ee/app/assets/javascripts/vulnerabilities/components/related_jira_issues.vue
index a52f2eeece9c7f739cece3506c40682424c33417..47e944320ad71f88a5ba76cac81b4f8481b40c31 100644
--- a/ee/app/assets/javascripts/vulnerabilities/components/related_jira_issues.vue
+++ b/ee/app/assets/javascripts/vulnerabilities/components/related_jira_issues.vue
@@ -1,15 +1,7 @@
 <script>
 import jiraLogo from '@gitlab/svgs/dist/illustrations/logos/jira.svg';
-import {
-  GlAlert,
-  GlCard,
-  GlIcon,
-  GlLink,
-  GlLoadingIcon,
-  GlButton,
-  GlSafeHtmlDirective as SafeHtml,
-  GlSprintf,
-} from '@gitlab/ui';
+import { GlAlert, GlCard, GlIcon, GlLink, GlLoadingIcon, GlButton, GlSprintf } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import CreateJiraIssue from 'ee/vue_shared/security_reports/components/create_jira_issue.vue';
 import axios from '~/lib/utils/axios_utils';
 import { s__ } from '~/locale';
diff --git a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue
index 4bb47a54de8924dc8daf9a4d9816c1504c0553b3..a80a06ddcc013965ec5227c251d706917f437779 100644
--- a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue
+++ b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue
@@ -1,5 +1,6 @@
 <script>
-import { GlFriendlyWrap, GlLink, GlSprintf, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlFriendlyWrap, GlLink, GlSprintf } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { bodyWithFallBack } from 'ee/vue_shared/security_reports/components/helpers';
 import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
 import convertReportType from 'ee/vue_shared/security_reports/store/utils/convert_report_type';
@@ -28,7 +29,7 @@ export default {
     VulnerabilityTraining,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   inject: {
     projectFullPath: {
diff --git a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_training.vue b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_training.vue
index 342a451f9757c64d6d9cb7db88d2286f31721405..d3f11702f20bb33a1b9d42f4be169ce95b52858c 100644
--- a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_training.vue
+++ b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_training.vue
@@ -1,6 +1,7 @@
 <script>
-import { GlLink, GlIcon, GlSkeletonLoader, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlLink, GlIcon, GlSkeletonLoader } from '@gitlab/ui';
 import * as Sentry from '@sentry/browser';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { s__, __ } from '~/locale';
 import securityTrainingProvidersQuery from '~/security_configuration/graphql/security_training_providers.query.graphql';
 import securityTrainingVulnerabilityQuery from '~/security_configuration/graphql/security_training_vulnerability.query.graphql';
@@ -30,7 +31,7 @@ export default {
     GlSkeletonLoader,
   },
   directives: {
-    SafeHtml: GlSafeHtmlDirective,
+    SafeHtml,
   },
   mixins: [Tracking.mixin()],
   props: {
diff --git a/spec/frontend/integrations/edit/components/dynamic_field_spec.js b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
index 5af0e2722857c19ff00d783cc42053768192d8de..68f65a3ef6e71ce7746afe1a311b93611e1b1db3 100644
--- a/spec/frontend/integrations/edit/components/dynamic_field_spec.js
+++ b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
@@ -204,7 +204,7 @@ describe('DynamicField', () => {
         });
 
         expect(findGlFormGroup().find('small').html()).toContain(
-          '[<code>1</code>  <a>3</a> <a href="foo">4</a>]',
+          '[<code>1</code>  <a>3</a> <a href="foo" target="_blank" rel="noopener noreferrer">4</a>',
         );
       });
     });
diff --git a/spec/frontend/popovers/components/popovers_spec.js b/spec/frontend/popovers/components/popovers_spec.js
index eba6b95214d7beca6a8e93b4694346b98239c3a9..1299e7277d13cbf4c2540df9ca35c1f264b9ca34 100644
--- a/spec/frontend/popovers/components/popovers_spec.js
+++ b/spec/frontend/popovers/components/popovers_spec.js
@@ -57,12 +57,13 @@ describe('popovers/components/popovers.vue', () => {
 
     describe('supports HTML content', () => {
       const svgIcon = '<svg><use xlink:href="icons.svg#test"></use></svg>';
+      const escapedSvgIcon = '<svg><use xlink:href=&quot;icons.svg#test&quot;></use></svg>';
 
       it.each`
         description                         | content                          | render
         ${'renders html content correctly'} | ${'<b>HTML</b>'}                 | ${'<b>HTML</b>'}
         ${'removes any unsafe content'}     | ${'<script>alert(XSS)</script>'} | ${''}
-        ${'renders svg icons correctly'}    | ${svgIcon}                       | ${svgIcon}
+        ${'renders svg icons correctly'}    | ${svgIcon}                       | ${escapedSvgIcon}
       `('$description', async ({ content, render }) => {
         await buildWrapper(createPopoverTarget({ content, html: true }));