diff --git a/README.md b/README.md
index d7fcd12..53c1852 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,6 @@ yarn add @symfony/stimulus-bridge
 
 ## Usage
 
-This package relies on [webpack-virtual-modules](https://github.com/sysgears/webpack-virtual-modules)
-to build dynamic modules referencing vendor Stimulus controllers and styles.
-
 To use it, first configure Webpack Encore:
 
 ```javascript
@@ -38,11 +35,24 @@ Then use the package in your JavaScript code:
 // app.js (or bootstrap.js if you use the standard Symfony structure)
 
 import { startStimulusApp } from '@symfony/stimulus-bridge';
-import '@symfony/autoimport';
 
 export const app = startStimulusApp(require.context('./controllers', true, /\.(j|t)sx?$/));
 ```
 
+If you get this error:
+
+> ./assets/bootstrap.js contains a reference to the file @symfony/autoimport.
+> This file can not be found.
+
+Remove the following line in the mentioned file: it's not needed anymore:
+
+```diff
+// assets/bootstrap.js
+
+// ...
+- import '@symfony/autoimport';
+```
+
 ## Run tests
 
 ```sh
diff --git a/controllers.json b/controllers.json
new file mode 100644
index 0000000..0f4ab6b
--- /dev/null
+++ b/controllers.json
@@ -0,0 +1,3 @@
+{
+    "placeholder": true
+}
diff --git a/dist/index.js b/dist/index.js
index c2675f5..824e003 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -17,10 +17,12 @@ var _stimulus = require("stimulus");
 
 var _webpackHelpers = require("stimulus/webpack-helpers");
 
-var _controllers = _interopRequireDefault(require("@symfony/controllers"));
+var _controllers = _interopRequireDefault(require("./webpack/loader!@symfony/stimulus-bridge/controllers.json"));
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
 
+// The @symfony/stimulus-bridge/controllers.json should be changed
+// to point to the real controllers.json file via a Webpack alias
 function startStimulusApp(context) {
   var application = _stimulus.Application.start();
 
@@ -36,7 +38,7 @@ function startStimulusApp(context) {
 
     _controllers["default"][_controllerName].then(function (module) {
       // Normalize the controller name: remove the initial @ and use Stimulus format
-      _controllerName = _controllerName.substr(1).replace(/_/g, "-").replace(/\//g, "--");
+      _controllerName = _controllerName.substr(1).replace(/_/g, '-').replace(/\//g, '--');
       application.register(_controllerName, module["default"]);
     });
 
diff --git a/dist/webpack/create-autoimport-module.js b/dist/webpack/create-autoimport-module.js
deleted file mode 100644
index eb51edd..0000000
--- a/dist/webpack/create-autoimport-module.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-'use strict';
-
-module.exports = function createAutoimportVirtualModule(config) {
-  var file = '';
-
-  if ('undefined' === typeof config['controllers']) {
-    throw new Error('Your Stimulus configuration file (assets/controllers.json) lacks a "controllers" key.');
-  }
-
-  for (var packageName in config.controllers) {
-    for (var controllerName in config.controllers[packageName]) {
-      var controllerConfig = config.controllers[packageName][controllerName];
-
-      if (!controllerConfig.enabled || 'undefined' === typeof controllerConfig.autoimport) {
-        continue;
-      }
-
-      for (var autoimport in controllerConfig.autoimport) {
-        if (!controllerConfig.autoimport[autoimport]) {
-          continue;
-        }
-
-        file += "\nimport '" + autoimport + "';";
-      }
-    }
-  }
-
-  return file;
-};
\ No newline at end of file
diff --git a/dist/webpack/create-controllers-module.js b/dist/webpack/create-controllers-module.js
index f0d421b..6220d7e 100644
--- a/dist/webpack/create-controllers-module.js
+++ b/dist/webpack/create-controllers-module.js
@@ -8,8 +8,13 @@
  */
 'use strict';
 
-module.exports = function createControllersVirtualModule(config) {
-  var file = 'export default {';
+module.exports = function createControllersModule(config) {
+  var controllerContents = 'export default {';
+  var autoImportContents = '';
+
+  if ('undefined' !== typeof config['placeholder']) {
+    throw new Error('Your controllers.json file was not found. Be sure to add a Webpack alias from "@symfony/stimulus-bridge/controllers.json" to *your* controllers.json file.');
+  }
 
   if ('undefined' === typeof config['controllers']) {
     throw new Error('Your Stimulus configuration file (assets/controllers.json) lacks a "controllers" key.');
@@ -34,9 +39,15 @@ module.exports = function createControllersVirtualModule(config) {
 
       var controllerMain = packageName + '/' + controllerPackageConfig.main;
       var webpackMode = controllerUserConfig.webpackMode;
-      file += "\n  '" + controllerReference + '\': import(/* webpackMode: "' + webpackMode + '" */ \'' + controllerMain + "'),";
+      controllerContents += "\n  '" + controllerReference + '\': import(/* webpackMode: "' + webpackMode + '" */ \'' + controllerMain + "'),";
+
+      for (var autoimport in controllerUserConfig.autoimport || []) {
+        if (controllerUserConfig.autoimport[autoimport]) {
+          autoImportContents += "import '" + autoimport + "';\n";
+        }
+      }
     }
   }
 
-  return file + '\n};';
+  return "".concat(autoImportContents).concat(controllerContents, "\n};");
 };
\ No newline at end of file
diff --git a/dist/webpack/loader.js b/dist/webpack/loader.js
new file mode 100644
index 0000000..f23a364
--- /dev/null
+++ b/dist/webpack/loader.js
@@ -0,0 +1,28 @@
+"use strict";
+
+var LoaderDependency = require('webpack/lib/dependencies/LoaderDependency');
+
+var createControllersModule = require('./create-controllers-module');
+
+module.exports = function (source) {
+  var logger = this.getLogger('stimulus-bridge-loader');
+  /*
+   * The following code prevents the normal JSON loader from
+   * executing after our loader. This is a workaround from WebpackEncore.
+   */
+
+  var requiredType = 'javascript/auto';
+
+  var factory = this._compilation.dependencyFactories.get(LoaderDependency);
+
+  if (factory === undefined) {
+    throw new Error('Could not retrieve module factory for type LoaderDependency');
+  }
+
+  this._module.type = requiredType;
+  this._module.generator = factory.getGenerator(requiredType);
+  this._module.parser = factory.getParser(requiredType);
+  /* End workaround */
+
+  return createControllersModule(JSON.parse(source));
+};
\ No newline at end of file
diff --git a/dist/webpack/plugin.js b/dist/webpack/plugin.js
index 43d76c6..ca81b1b 100644
--- a/dist/webpack/plugin.js
+++ b/dist/webpack/plugin.js
@@ -8,15 +8,13 @@
  */
 'use strict';
 
-var VirtualModulesPlugin = require('webpack-virtual-modules');
+var webpack = require('webpack');
 
-var createAutoimportVirtualModule = require('./create-autoimport-module');
+module.exports = function createStimulusBridgePlugin(controllersJsonPath) {
+  return new webpack.NormalModuleReplacementPlugin(/stimulus-bridge\/controllers-placeholder\.json$/, function (resource) {
+    // controls how the import string will be parsed, includes loader
+    resource.request = "./webpack/loader!".concat(controllersJsonPath); // controls the physical file that will be read
 
-var createControllersVirtualModule = require('./create-controllers-module');
-
-module.exports = function createStimulusBridgePlugin(config) {
-  return new VirtualModulesPlugin({
-    'node_modules/@symfony/autoimport.js': createAutoimportVirtualModule(config),
-    'node_modules/@symfony/controllers.js': createControllersVirtualModule(config)
+    resource.createData.resource = controllersJsonPath;
   });
 };
\ No newline at end of file
diff --git a/package.json b/package.json
index 80784d8..dea4d65 100644
--- a/package.json
+++ b/package.json
@@ -16,15 +16,13 @@
         "stimulus": "^2.0"
     },
     "dependencies": {
-        "@babel/plugin-proposal-class-properties": "^7.12.1",
-        "webpack-virtual-modules": "^0.3.2"
+        "@babel/plugin-proposal-class-properties": "^7.12.1"
     },
     "devDependencies": {
         "@babel/cli": "^7.12.1",
         "@babel/core": "^7.12.3",
         "@babel/plugin-proposal-class-properties": "^7.12.1",
         "@babel/preset-env": "^7.12.7",
-        "@symfony/controllers": "file:test/fixtures/controllers",
         "@symfony/mock-module": "file:test/fixtures/module",
         "@symfony/stimulus-testing": "^1.0.0",
         "stimulus": "^2.0",
@@ -40,6 +38,7 @@
     "files": [
         "src/",
         "dist/",
-        "webpack-helper.js"
+        "webpack-helper.js",
+        "controllers.json"
     ]
 }
diff --git a/src/index.js b/src/index.js
index aa98858..a3a206b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -11,7 +11,10 @@
 
 import { Application } from 'stimulus';
 import { definitionsFromContext } from 'stimulus/webpack-helpers';
-import symfonyControllers from '@symfony/controllers';
+
+// The @symfony/stimulus-bridge/controllers.json should be changed
+// to point to the real controllers.json file via a Webpack alias
+import symfonyControllers from './webpack/loader!@symfony/stimulus-bridge/controllers.json';
 
 export function startStimulusApp(context) {
     const application = Application.start();
@@ -27,7 +30,7 @@ export function startStimulusApp(context) {
 
         symfonyControllers[controllerName].then((module) => {
             // Normalize the controller name: remove the initial @ and use Stimulus format
-            controllerName = controllerName.substr(1).replace(/_/g, "-").replace(/\//g, "--");
+            controllerName = controllerName.substr(1).replace(/_/g, '-').replace(/\//g, '--');
 
             application.register(controllerName, module.default);
         });
diff --git a/src/webpack/create-autoimport-module.js b/src/webpack/create-autoimport-module.js
deleted file mode 100644
index 637d2fd..0000000
--- a/src/webpack/create-autoimport-module.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-'use strict';
-
-module.exports = function createAutoimportVirtualModule(config) {
-    let file = '';
-
-    if ('undefined' === typeof config['controllers']) {
-        throw new Error('Your Stimulus configuration file (assets/controllers.json) lacks a "controllers" key.');
-    }
-
-    for (let packageName in config.controllers) {
-        for (let controllerName in config.controllers[packageName]) {
-            const controllerConfig = config.controllers[packageName][controllerName];
-
-            if (!controllerConfig.enabled || 'undefined' === typeof controllerConfig.autoimport) {
-                continue;
-            }
-
-            for (let autoimport in controllerConfig.autoimport) {
-                if (!controllerConfig.autoimport[autoimport]) {
-                    continue;
-                }
-
-                file += "\nimport '" + autoimport + "';";
-            }
-        }
-    }
-
-    return file;
-};
diff --git a/src/webpack/create-controllers-module.js b/src/webpack/create-controllers-module.js
index 45efc70..55eed1b 100644
--- a/src/webpack/create-controllers-module.js
+++ b/src/webpack/create-controllers-module.js
@@ -9,8 +9,15 @@
 
 'use strict';
 
-module.exports = function createControllersVirtualModule(config) {
-    let file = 'export default {';
+module.exports = function createControllersModule(config) {
+    let controllerContents = 'export default {';
+    let autoImportContents = '';
+
+    if ('undefined' !== typeof config['placeholder']) {
+        throw new Error(
+            'Your controllers.json file was not found. Be sure to add a Webpack alias from "@symfony/stimulus-bridge/controllers.json" to *your* controllers.json file.'
+        );
+    }
 
     if ('undefined' === typeof config['controllers']) {
         throw new Error('Your Stimulus configuration file (assets/controllers.json) lacks a "controllers" key.');
@@ -39,7 +46,7 @@ module.exports = function createControllersVirtualModule(config) {
             const controllerMain = packageName + '/' + controllerPackageConfig.main;
             const webpackMode = controllerUserConfig.webpackMode;
 
-            file +=
+            controllerContents +=
                 "\n  '" +
                 controllerReference +
                 '\': import(/* webpackMode: "' +
@@ -47,8 +54,14 @@ module.exports = function createControllersVirtualModule(config) {
                 '" */ \'' +
                 controllerMain +
                 "'),";
+
+            for (let autoimport in controllerUserConfig.autoimport || []) {
+                if (controllerUserConfig.autoimport[autoimport]) {
+                    autoImportContents += "import '" + autoimport + "';\n";
+                }
+            }
         }
     }
 
-    return file + '\n};';
+    return `${autoImportContents}${controllerContents}\n};`;
 };
diff --git a/src/webpack/loader.js b/src/webpack/loader.js
new file mode 100644
index 0000000..b13053d
--- /dev/null
+++ b/src/webpack/loader.js
@@ -0,0 +1,22 @@
+const LoaderDependency = require('webpack/lib/dependencies/LoaderDependency');
+const createControllersModule = require('./create-controllers-module');
+
+module.exports = function (source) {
+    const logger = this.getLogger('stimulus-bridge-loader');
+
+    /*
+     * The following code prevents the normal JSON loader from
+     * executing after our loader. This is a workaround from WebpackEncore.
+     */
+    const requiredType = 'javascript/auto';
+    const factory = this._compilation.dependencyFactories.get(LoaderDependency);
+    if (factory === undefined) {
+        throw new Error('Could not retrieve module factory for type LoaderDependency');
+    }
+    this._module.type = requiredType;
+    this._module.generator = factory.getGenerator(requiredType);
+    this._module.parser = factory.getParser(requiredType);
+    /* End workaround */
+
+    return createControllersModule(JSON.parse(source));
+};
diff --git a/src/webpack/plugin.js b/src/webpack/plugin.js
deleted file mode 100644
index 2a51a88..0000000
--- a/src/webpack/plugin.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-'use strict';
-
-const VirtualModulesPlugin = require('webpack-virtual-modules');
-const createAutoimportVirtualModule = require('./create-autoimport-module');
-const createControllersVirtualModule = require('./create-controllers-module');
-
-module.exports = function createStimulusBridgePlugin(config) {
-    return new VirtualModulesPlugin({
-        'node_modules/@symfony/autoimport.js': createAutoimportVirtualModule(config),
-        'node_modules/@symfony/controllers.js': createControllersVirtualModule(config),
-    });
-};
diff --git a/test/controllers.json b/test/controllers.json
new file mode 100644
index 0000000..a05d3e1
--- /dev/null
+++ b/test/controllers.json
@@ -0,0 +1,11 @@
+{
+    "controllers": {
+        "@symfony/mock-module": {
+            "mock": {
+                "webpackMode": "eager",
+                "enabled": true
+            }
+        }
+    },
+    "entrypoints": []
+}
diff --git a/test/fixtures/controllers/index.js b/test/fixtures/controllers/index.js
deleted file mode 100644
index 7be1f07..0000000
--- a/test/fixtures/controllers/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export default {
-    '@symfony/mock-module/mock-controller': import('@symfony/mock-module/dist/controller.js'),
-};
diff --git a/test/fixtures/controllers/package.json b/test/fixtures/controllers/package.json
deleted file mode 100644
index a06fa1a..0000000
--- a/test/fixtures/controllers/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "name": "@symfony/controllers",
-    "version": "1.0.0",
-    "main": "./index.js"
-}
diff --git a/test/index.test.js b/test/index.test.js
index 39a2f18..00132ce 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -19,6 +19,6 @@ describe('startStimulusApp', () => {
         await new Promise(setImmediate);
 
         expect(app.router.modules.length).toBe(1);
-        expect(app.router.modules[0].definition.identifier).toBe('symfony--mock-module--mock-controller');
+        expect(app.router.modules[0].definition.identifier).toBe('symfony--mock-module--mock');
     });
 });
diff --git a/test/webpack.config.js b/test/webpack.config.js
index 79c2bee..ae6a2cb 100644
--- a/test/webpack.config.js
+++ b/test/webpack.config.js
@@ -24,6 +24,11 @@ module.exports = {
     output: {
         filename: 'index.js',
         path: path.resolve(__dirname, 'dist'),
-        libraryTarget: 'commonjs2'
+        libraryTarget: 'commonjs2',
+    },
+    resolve: {
+        alias: {
+            '@symfony/stimulus-bridge/controllers.json': path.resolve(__dirname, './controllers.json'),
+        },
     },
 };
diff --git a/test/webpack/create-autoimport-module.test.js b/test/webpack/create-autoimport-module.test.js
deleted file mode 100644
index b548107..0000000
--- a/test/webpack/create-autoimport-module.test.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * This file is part of the Symfony Webpack Encore package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-'use strict';
-
-const createAutoimportVirtualModule = require('../../dist/webpack/create-autoimport-module');
-
-describe('createAutoimportVirtualModule', () => {
-    describe('empty.json', () => {
-        it('must return an empty file', () => {
-            const config = require('../fixtures/empty.json');
-            expect(createAutoimportVirtualModule(config)).toEqual('');
-        });
-    });
-
-    describe('disabled-controller.json', () => {
-        it('must return an empty file', () => {
-            const config = require('../fixtures/disabled-controller.json');
-            expect(createAutoimportVirtualModule(config)).toEqual('');
-        });
-    });
-
-    describe('disabled-autoimport.json', () => {
-        it('must return an empty file', () => {
-            const config = require('../fixtures/disabled-autoimport.json');
-            expect(createAutoimportVirtualModule(config)).toEqual('');
-        });
-    });
-
-    describe('eager-no-autoimport.json', () => {
-        it('must return an empty file', () => {
-            const config = require('../fixtures/eager-no-autoimport.json');
-            expect(createAutoimportVirtualModule(config)).toEqual('');
-        });
-    });
-
-    describe('lazy-autoimport.json', () => {
-        it('must return a file with the enabled controller', () => {
-            const config = require('../fixtures/lazy-autoimport.json');
-            expect(createAutoimportVirtualModule(config)).toEqual("\nimport '@symfony/mock-module/dist/style.css';");
-        });
-    });
-});
diff --git a/test/webpack/create-controllers-module.test.js b/test/webpack/create-controllers-module.test.js
index 6bb04b6..ba304be 100644
--- a/test/webpack/create-controllers-module.test.js
+++ b/test/webpack/create-controllers-module.test.js
@@ -9,36 +9,36 @@
 
 'use strict';
 
-const createControllersVirtualModule = require('../../dist/webpack/create-controllers-module');
+const createControllersModule = require('../../dist/webpack/create-controllers-module');
 
-describe('createControllersVirtualModule', () => {
+describe('createControllersModule', () => {
     describe('empty.json', () => {
         it('must return an empty file', () => {
             const config = require('../fixtures/empty.json');
-            expect(createControllersVirtualModule(config)).toEqual('export default {\n};');
+            expect(createControllersModule(config)).toEqual('export default {\n};');
         });
     });
 
     describe('disabled-controller.json', () => {
         it('must return an empty file', () => {
             const config = require('../fixtures/disabled-controller.json');
-            expect(createControllersVirtualModule(config)).toEqual('export default {\n};');
+            expect(createControllersModule(config)).toEqual('export default {\n};');
         });
     });
 
     describe('disabled-autoimport.json', () => {
-        it('must return an empty file', () => {
+        it('must return file with no autoimport', () => {
             const config = require('../fixtures/disabled-autoimport.json');
-            expect(createControllersVirtualModule(config)).toEqual(
+            expect(createControllersModule(config)).toEqual(
                 "export default {\n  '@symfony/mock-module/mock': import(/* webpackMode: \"lazy\" */ '@symfony/mock-module/dist/controller.js'),\n};"
             );
         });
     });
 
     describe('eager-no-autoimport.json', () => {
-        it('must return an empty file', () => {
+        it('must return file with no autoimport', () => {
             const config = require('../fixtures/eager-no-autoimport.json');
-            expect(createControllersVirtualModule(config)).toEqual(
+            expect(createControllersModule(config)).toEqual(
                 "export default {\n  '@symfony/mock-module/mock': import(/* webpackMode: \"eager\" */ '@symfony/mock-module/dist/controller.js'),\n};"
             );
         });
@@ -47,8 +47,8 @@ describe('createControllersVirtualModule', () => {
     describe('lazy-autoimport.json', () => {
         it('must return a file with the enabled controller', () => {
             const config = require('../fixtures/lazy-autoimport.json');
-            expect(createControllersVirtualModule(config)).toEqual(
-                "export default {\n  '@symfony/mock-module/mock': import(/* webpackMode: \"lazy\" */ '@symfony/mock-module/dist/controller.js'),\n};"
+            expect(createControllersModule(config)).toEqual(
+                "import '@symfony/mock-module/dist/style.css';\nexport default {\n  '@symfony/mock-module/mock': import(/* webpackMode: \"lazy\" */ '@symfony/mock-module/dist/controller.js'),\n};"
             );
         });
     });
diff --git a/test/webpack/plugin.test.js b/test/webpack/plugin.test.js
deleted file mode 100644
index e8c8a14..0000000
--- a/test/webpack/plugin.test.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * This file is part of the Symfony Webpack Encore package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-'use strict';
-
-const createStimulusBridgePlugin = require('../../dist/webpack/plugin');
-const VirtualModulesPlugin = require('webpack-virtual-modules');
-
-describe('createStimulusBridgePlugin', () => {
-    it('must return created VirtualModule plugin', () => {
-        const config = require('../fixtures/empty.json');
-        expect(createStimulusBridgePlugin(config)).toBeInstanceOf(VirtualModulesPlugin);
-    });
-});