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 - * - * 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 - * - * 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 - * - * 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 - * - * 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 - * - * 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); - }); -});