Skip to content

Commit a720035

Browse files
committed
feature #14 Replacing virtual-modules with a loader for controller.json (weaverryan)
This PR was squashed before being merged into the main branch. Discussion ---------- Replacing virtual-modules with a loader for controller.json Hi! The motivation behind this was a few things: 1) the wepback-virtual-modules plugin emits a worrying warning in Webpack 5 and, in general, doesn't seem to be super-actively developed. 2) sokra says that a loader is the correct solution: webpack/webpack#11074 (comment) 3) Since loaders are native/normal, this works well with `watch` mode and should work just fine with Webpack 5 persistent build caching. This also includes a tricky NormalModuleReplacementPlugin() so that we can continue to handling the parsing of the user's controller.json inside this library (instead of putting the import + loader code in their app). This will require a partner PR in webpack-encore (symfony/webpack-encore#888). The only line users will need to change in their app is to remove `import '@symfony/autoimport';` from their `assets/bootstrap.js` file. Cheers! Commits ------- fb5aaf7 Replacing virtual-modules with a loader for controller.json
2 parents c727a28 + fb5aaf7 commit a720035

21 files changed

+145
-213
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ yarn add @symfony/stimulus-bridge
2020

2121
## Usage
2222

23-
This package relies on [webpack-virtual-modules](https://github.com/sysgears/webpack-virtual-modules)
24-
to build dynamic modules referencing vendor Stimulus controllers and styles.
25-
2623
To use it, first configure Webpack Encore:
2724

2825
```javascript
@@ -38,11 +35,24 @@ Then use the package in your JavaScript code:
3835
// app.js (or bootstrap.js if you use the standard Symfony structure)
3936

4037
import { startStimulusApp } from '@symfony/stimulus-bridge';
41-
import '@symfony/autoimport';
4238

4339
export const app = startStimulusApp(require.context('./controllers', true, /\.(j|t)sx?$/));
4440
```
4541

42+
If you get this error:
43+
44+
> ./assets/bootstrap.js contains a reference to the file @symfony/autoimport.
45+
> This file can not be found.
46+
47+
Remove the following line in the mentioned file: it's not needed anymore:
48+
49+
```diff
50+
// assets/bootstrap.js
51+
52+
// ...
53+
- import '@symfony/autoimport';
54+
```
55+
4656
## Run tests
4757

4858
```sh

controllers.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"placeholder": true
3+
}

dist/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ var _stimulus = require("stimulus");
1717

1818
var _webpackHelpers = require("stimulus/webpack-helpers");
1919

20-
var _controllers = _interopRequireDefault(require("@symfony/controllers"));
20+
var _controllers = _interopRequireDefault(require("./webpack/loader!@symfony/stimulus-bridge/controllers.json"));
2121

2222
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
2323

24+
// The @symfony/stimulus-bridge/controllers.json should be changed
25+
// to point to the real controllers.json file via a Webpack alias
2426
function startStimulusApp(context) {
2527
var application = _stimulus.Application.start();
2628

@@ -36,7 +38,7 @@ function startStimulusApp(context) {
3638

3739
_controllers["default"][_controllerName].then(function (module) {
3840
// Normalize the controller name: remove the initial @ and use Stimulus format
39-
_controllerName = _controllerName.substr(1).replace(/_/g, "-").replace(/\//g, "--");
41+
_controllerName = _controllerName.substr(1).replace(/_/g, '-').replace(/\//g, '--');
4042
application.register(_controllerName, module["default"]);
4143
});
4244

dist/webpack/create-autoimport-module.js

Lines changed: 0 additions & 37 deletions
This file was deleted.

dist/webpack/create-controllers-module.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@
88
*/
99
'use strict';
1010

11-
module.exports = function createControllersVirtualModule(config) {
12-
var file = 'export default {';
11+
module.exports = function createControllersModule(config) {
12+
var controllerContents = 'export default {';
13+
var autoImportContents = '';
14+
15+
if ('undefined' !== typeof config['placeholder']) {
16+
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.');
17+
}
1318

1419
if ('undefined' === typeof config['controllers']) {
1520
throw new Error('Your Stimulus configuration file (assets/controllers.json) lacks a "controllers" key.');
@@ -34,9 +39,15 @@ module.exports = function createControllersVirtualModule(config) {
3439

3540
var controllerMain = packageName + '/' + controllerPackageConfig.main;
3641
var webpackMode = controllerUserConfig.webpackMode;
37-
file += "\n '" + controllerReference + '\': import(/* webpackMode: "' + webpackMode + '" */ \'' + controllerMain + "'),";
42+
controllerContents += "\n '" + controllerReference + '\': import(/* webpackMode: "' + webpackMode + '" */ \'' + controllerMain + "'),";
43+
44+
for (var autoimport in controllerUserConfig.autoimport || []) {
45+
if (controllerUserConfig.autoimport[autoimport]) {
46+
autoImportContents += "import '" + autoimport + "';\n";
47+
}
48+
}
3849
}
3950
}
4051

41-
return file + '\n};';
52+
return "".concat(autoImportContents).concat(controllerContents, "\n};");
4253
};

dist/webpack/loader.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use strict";
2+
3+
var LoaderDependency = require('webpack/lib/dependencies/LoaderDependency');
4+
5+
var createControllersModule = require('./create-controllers-module');
6+
7+
module.exports = function (source) {
8+
var logger = this.getLogger('stimulus-bridge-loader');
9+
/*
10+
* The following code prevents the normal JSON loader from
11+
* executing after our loader. This is a workaround from WebpackEncore.
12+
*/
13+
14+
var requiredType = 'javascript/auto';
15+
16+
var factory = this._compilation.dependencyFactories.get(LoaderDependency);
17+
18+
if (factory === undefined) {
19+
throw new Error('Could not retrieve module factory for type LoaderDependency');
20+
}
21+
22+
this._module.type = requiredType;
23+
this._module.generator = factory.getGenerator(requiredType);
24+
this._module.parser = factory.getParser(requiredType);
25+
/* End workaround */
26+
27+
return createControllersModule(JSON.parse(source));
28+
};

dist/webpack/plugin.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@
88
*/
99
'use strict';
1010

11-
var VirtualModulesPlugin = require('webpack-virtual-modules');
11+
var webpack = require('webpack');
1212

13-
var createAutoimportVirtualModule = require('./create-autoimport-module');
13+
module.exports = function createStimulusBridgePlugin(controllersJsonPath) {
14+
return new webpack.NormalModuleReplacementPlugin(/stimulus-bridge\/controllers-placeholder\.json$/, function (resource) {
15+
// controls how the import string will be parsed, includes loader
16+
resource.request = "./webpack/loader!".concat(controllersJsonPath); // controls the physical file that will be read
1417

15-
var createControllersVirtualModule = require('./create-controllers-module');
16-
17-
module.exports = function createStimulusBridgePlugin(config) {
18-
return new VirtualModulesPlugin({
19-
'node_modules/@symfony/autoimport.js': createAutoimportVirtualModule(config),
20-
'node_modules/@symfony/controllers.js': createControllersVirtualModule(config)
18+
resource.createData.resource = controllersJsonPath;
2119
});
2220
};

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616
"stimulus": "^2.0"
1717
},
1818
"dependencies": {
19-
"@babel/plugin-proposal-class-properties": "^7.12.1",
20-
"webpack-virtual-modules": "^0.3.2"
19+
"@babel/plugin-proposal-class-properties": "^7.12.1"
2120
},
2221
"devDependencies": {
2322
"@babel/cli": "^7.12.1",
2423
"@babel/core": "^7.12.3",
2524
"@babel/plugin-proposal-class-properties": "^7.12.1",
2625
"@babel/preset-env": "^7.12.7",
27-
"@symfony/controllers": "file:test/fixtures/controllers",
2826
"@symfony/mock-module": "file:test/fixtures/module",
2927
"@symfony/stimulus-testing": "^1.0.0",
3028
"stimulus": "^2.0",
@@ -40,6 +38,7 @@
4038
"files": [
4139
"src/",
4240
"dist/",
43-
"webpack-helper.js"
41+
"webpack-helper.js",
42+
"controllers.json"
4443
]
4544
}

src/index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
import { Application } from 'stimulus';
1313
import { definitionsFromContext } from 'stimulus/webpack-helpers';
14-
import symfonyControllers from '@symfony/controllers';
14+
15+
// The @symfony/stimulus-bridge/controllers.json should be changed
16+
// to point to the real controllers.json file via a Webpack alias
17+
import symfonyControllers from './webpack/loader!@symfony/stimulus-bridge/controllers.json';
1518

1619
export function startStimulusApp(context) {
1720
const application = Application.start();
@@ -27,7 +30,7 @@ export function startStimulusApp(context) {
2730

2831
symfonyControllers[controllerName].then((module) => {
2932
// Normalize the controller name: remove the initial @ and use Stimulus format
30-
controllerName = controllerName.substr(1).replace(/_/g, "-").replace(/\//g, "--");
33+
controllerName = controllerName.substr(1).replace(/_/g, '-').replace(/\//g, '--');
3134

3235
application.register(controllerName, module.default);
3336
});

src/webpack/create-autoimport-module.js

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/webpack/create-controllers-module.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@
99

1010
'use strict';
1111

12-
module.exports = function createControllersVirtualModule(config) {
13-
let file = 'export default {';
12+
module.exports = function createControllersModule(config) {
13+
let controllerContents = 'export default {';
14+
let autoImportContents = '';
15+
16+
if ('undefined' !== typeof config['placeholder']) {
17+
throw new Error(
18+
'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.'
19+
);
20+
}
1421

1522
if ('undefined' === typeof config['controllers']) {
1623
throw new Error('Your Stimulus configuration file (assets/controllers.json) lacks a "controllers" key.');
@@ -39,16 +46,22 @@ module.exports = function createControllersVirtualModule(config) {
3946
const controllerMain = packageName + '/' + controllerPackageConfig.main;
4047
const webpackMode = controllerUserConfig.webpackMode;
4148

42-
file +=
49+
controllerContents +=
4350
"\n '" +
4451
controllerReference +
4552
'\': import(/* webpackMode: "' +
4653
webpackMode +
4754
'" */ \'' +
4855
controllerMain +
4956
"'),";
57+
58+
for (let autoimport in controllerUserConfig.autoimport || []) {
59+
if (controllerUserConfig.autoimport[autoimport]) {
60+
autoImportContents += "import '" + autoimport + "';\n";
61+
}
62+
}
5063
}
5164
}
5265

53-
return file + '\n};';
66+
return `${autoImportContents}${controllerContents}\n};`;
5467
};

src/webpack/loader.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const LoaderDependency = require('webpack/lib/dependencies/LoaderDependency');
2+
const createControllersModule = require('./create-controllers-module');
3+
4+
module.exports = function (source) {
5+
const logger = this.getLogger('stimulus-bridge-loader');
6+
7+
/*
8+
* The following code prevents the normal JSON loader from
9+
* executing after our loader. This is a workaround from WebpackEncore.
10+
*/
11+
const requiredType = 'javascript/auto';
12+
const factory = this._compilation.dependencyFactories.get(LoaderDependency);
13+
if (factory === undefined) {
14+
throw new Error('Could not retrieve module factory for type LoaderDependency');
15+
}
16+
this._module.type = requiredType;
17+
this._module.generator = factory.getGenerator(requiredType);
18+
this._module.parser = factory.getParser(requiredType);
19+
/* End workaround */
20+
21+
return createControllersModule(JSON.parse(source));
22+
};

src/webpack/plugin.js

Lines changed: 0 additions & 21 deletions
This file was deleted.

test/controllers.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"controllers": {
3+
"@symfony/mock-module": {
4+
"mock": {
5+
"webpackMode": "eager",
6+
"enabled": true
7+
}
8+
}
9+
},
10+
"entrypoints": []
11+
}

test/fixtures/controllers/index.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/fixtures/controllers/package.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/index.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ describe('startStimulusApp', () => {
1919
await new Promise(setImmediate);
2020

2121
expect(app.router.modules.length).toBe(1);
22-
expect(app.router.modules[0].definition.identifier).toBe('symfony--mock-module--mock-controller');
22+
expect(app.router.modules[0].definition.identifier).toBe('symfony--mock-module--mock');
2323
});
2424
});

0 commit comments

Comments
 (0)