diff --git a/config/webpack.config.js b/config/webpack.config.js
index 64d82e714c453bda74f9310babfd3b895c8841c0..52cca8fa6dee997a0d1bc50003670834f3193130 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -42,9 +42,8 @@ const {
   WEBPACK_OUTPUT_PATH,
   WEBPACK_PUBLIC_PATH,
   SOURCEGRAPH_PUBLIC_PATH,
-  SOURCEGRAPH_OUTPUT_PATH,
-  GITLAB_WEB_IDE_OUTPUT_PATH,
   GITLAB_WEB_IDE_PUBLIC_PATH,
+  copyFilesPatterns,
 } = require('./webpack.constants');
 
 const createIncrementalWebpackCompiler = require('./helpers/incremental_webpack_compiler');
@@ -89,9 +88,6 @@ if (WEBPACK_REPORT) {
   NO_HASHED_CHUNKS = true;
 }
 
-const SOURCEGRAPH_PACKAGE = '@sourcegraph/code-host-integration';
-const GITLAB_WEB_IDE_PACKAGE = '@gitlab/web-ide';
-
 const devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map';
 
 let autoEntriesCount = 0;
@@ -711,34 +707,7 @@ module.exports = {
       }),
 
     new CopyWebpackPlugin({
-      patterns: [
-        {
-          from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/cmaps/'),
-          to: path.join(WEBPACK_OUTPUT_PATH, 'pdfjs/cmaps/'),
-        },
-        {
-          from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/legacy/build/pdf.worker.min.js'),
-          to: path.join(WEBPACK_OUTPUT_PATH, 'pdfjs/'),
-        },
-        {
-          from: path.join(ROOT_PATH, 'node_modules', SOURCEGRAPH_PACKAGE, '/'),
-          to: SOURCEGRAPH_OUTPUT_PATH,
-          globOptions: {
-            ignore: ['package.json'],
-          },
-        },
-        {
-          from: path.join(ROOT_PATH, 'node_modules', GITLAB_WEB_IDE_PACKAGE, 'dist', 'public'),
-          to: GITLAB_WEB_IDE_OUTPUT_PATH,
-        },
-        {
-          from: path.join(
-            ROOT_PATH,
-            'node_modules/@gitlab/visual-review-tools/dist/visual_review_toolbar.js',
-          ),
-          to: WEBPACK_OUTPUT_PATH,
-        },
-      ],
+      patterns: copyFilesPatterns,
     }),
 
     // compression can require a lot of compute time and is disabled in CI
diff --git a/config/webpack.constants.js b/config/webpack.constants.js
index 0eb2ac70c82e5ee600b52148eb42986a39a0e182..fd9dd40a4c2d6117f7c39a8acb30be2deb569f01 100644
--- a/config/webpack.constants.js
+++ b/config/webpack.constants.js
@@ -18,14 +18,45 @@ const GITLAB_WEB_IDE_PUBLIC_PATH = path.join(WEBPACK_PUBLIC_PATH, GITLAB_WEB_IDE
 const IS_EE = require('./helpers/is_ee_env');
 const IS_JH = require('./helpers/is_jh_env');
 
+const SOURCEGRAPH_PACKAGE = '@sourcegraph/code-host-integration';
+const GITLAB_WEB_IDE_PACKAGE = '@gitlab/web-ide';
+
+const copyFilesPatterns = [
+  {
+    from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/cmaps/'),
+    to: path.join(WEBPACK_OUTPUT_PATH, 'pdfjs/cmaps/'),
+  },
+  {
+    from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/legacy/build/pdf.worker.min.js'),
+    to: path.join(WEBPACK_OUTPUT_PATH, 'pdfjs/'),
+  },
+  {
+    from: path.join(ROOT_PATH, 'node_modules', SOURCEGRAPH_PACKAGE, '/'),
+    to: SOURCEGRAPH_OUTPUT_PATH,
+    globOptions: {
+      ignore: ['package.json'],
+    },
+  },
+  {
+    from: path.join(ROOT_PATH, 'node_modules', GITLAB_WEB_IDE_PACKAGE, 'dist', 'public'),
+    to: GITLAB_WEB_IDE_OUTPUT_PATH,
+  },
+  {
+    from: path.join(
+      ROOT_PATH,
+      'node_modules/@gitlab/visual-review-tools/dist/visual_review_toolbar.js',
+    ),
+    to: WEBPACK_OUTPUT_PATH,
+  },
+];
+
 module.exports = {
   IS_EE,
   IS_JH,
   ROOT_PATH,
   WEBPACK_OUTPUT_PATH,
   WEBPACK_PUBLIC_PATH,
-  SOURCEGRAPH_OUTPUT_PATH,
   SOURCEGRAPH_PUBLIC_PATH,
-  GITLAB_WEB_IDE_OUTPUT_PATH,
   GITLAB_WEB_IDE_PUBLIC_PATH,
+  copyFilesPatterns,
 };
diff --git a/package.json b/package.json
index 5a1f9288be09ef4cb9444abc5c438af1b314cda8..7cb261f2e7102e48d64abee7bfe10fa1c939e075 100644
--- a/package.json
+++ b/package.json
@@ -267,6 +267,7 @@
     "gettext-extractor": "^3.7.0",
     "gettext-extractor-vue": "^5.1.0",
     "glob": "^7.1.6",
+    "globby": "^11.0.1",
     "jest": "^28.1.3",
     "jest-canvas-mock": "^2.4.0",
     "jest-diff": "^28.1.3",
diff --git a/vite.config.js b/vite.config.js
index c74ef145b423db4b172ed6a1b8b467e5bae98d82..41344ca361a8518eb3304a14303a5206b050f878 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,4 +1,5 @@
 import { readFileSync } from 'node:fs';
+import { stat, mkdir, copyFile } from 'node:fs/promises';
 import path from 'node:path';
 
 import { defineConfig } from 'vite';
@@ -6,6 +7,7 @@ import vue from '@vitejs/plugin-vue2';
 import graphql from '@rollup/plugin-graphql';
 import RubyPlugin from 'vite-plugin-ruby';
 import chokidar from 'chokidar';
+import globby from 'globby';
 import { viteCommonjs } from '@originjs/vite-plugin-commonjs';
 import webpackConfig from './config/webpack.config';
 import {
@@ -13,6 +15,7 @@ import {
   IS_JH,
   SOURCEGRAPH_PUBLIC_PATH,
   GITLAB_WEB_IDE_PUBLIC_PATH,
+  copyFilesPatterns,
 } from './config/webpack.constants';
 /* eslint-disable import/extensions */
 import { viteCSSCompilerPlugin } from './scripts/frontend/lib/compile_css.mjs';
@@ -90,6 +93,76 @@ const autoRestartPlugin = {
   },
 };
 
+/**
+ * This is a simple-reimplementation of the copy-webpack-plugin
+ *
+ * it also uses the `globby` package under the hood, and _only_ allows for copying
+ * 1. absolute paths
+ * 2. files and directories.
+ */
+function viteCopyPlugin({ patterns }) {
+  return {
+    name: 'viteCopyPlugin',
+    async configureServer() {
+      console.warn('Start copying files...');
+      let count = 0;
+
+      const allTheFiles = patterns.map(async (patternEntry) => {
+        const { from, to, globOptions = {} } = patternEntry;
+
+        // By only supporting absolute paths we simplify
+        // the implementation a lot
+        if (!path.isAbsolute(from)) {
+          throw new Error(`'from' path is not absolute: ${path}`);
+        }
+        if (!path.isAbsolute(to)) {
+          throw new Error(`'to' path is not absolute: ${path}`);
+        }
+
+        let pattern = '';
+        let sourceRoot = '';
+        const fromStat = await stat(from);
+        if (fromStat.isDirectory()) {
+          sourceRoot = from;
+          pattern = path.join(from, '**/*');
+        } else if (fromStat.isFile()) {
+          sourceRoot = path.dirname(from);
+          pattern = from;
+        } else {
+          // No need to support globs, because we do not
+          // use them yet...
+          throw new Error('Our implementation does not support globs.');
+        }
+
+        globOptions.dot = globOptions.dot ?? true;
+
+        const paths = await globby(pattern, globOptions);
+
+        return paths.map((srcPath) => {
+          const targetPath = path.join(to, path.relative(sourceRoot, srcPath));
+          return { srcPath, targetPath };
+        });
+      });
+
+      const srcTargetMap = (await Promise.all(allTheFiles)).flat();
+
+      await Promise.all(
+        srcTargetMap.map(async ({ srcPath, targetPath }) => {
+          try {
+            await mkdir(path.dirname(targetPath), { recursive: true });
+            await copyFile(srcPath, targetPath);
+            count += 1;
+          } catch (e) {
+            console.warn(`Could not copy ${srcPath} => ${targetPath}`);
+          }
+        }),
+      );
+
+      console.warn(`Done copying ${count} files...`);
+    },
+  };
+}
+
 export default defineConfig({
   cacheDir: path.resolve(__dirname, 'tmp/cache/vite'),
   resolve: {
@@ -110,6 +183,9 @@ export default defineConfig({
   plugins: [
     viteCSSCompilerPlugin({ shouldWatch: viteGDKConfig.hmr !== null }),
     viteTailwindCompilerPlugin({ shouldWatch: viteGDKConfig.hmr !== null }),
+    viteCopyPlugin({
+      patterns: copyFilesPatterns,
+    }),
     viteGDKConfig.enabled ? autoRestartPlugin : null,
     fixedRubyPlugin,
     vue({